Class: JPSClient::Auth
- Inherits:
-
Object
- Object
- JPSClient::Auth
- Defined in:
- lib/jpsclient/auth/auth.rb
Instance Attribute Summary collapse
-
#access_token ⇒ Object
readonly
Returns the value of attribute access_token.
-
#lark_user_id ⇒ Object
readonly
Returns the value of attribute lark_user_id.
-
#permissions ⇒ Object
readonly
Returns the value of attribute permissions.
-
#tenant_manager ⇒ Object
readonly
Returns the value of attribute tenant_manager.
-
#user_id ⇒ Object
readonly
Returns the value of attribute user_id.
-
#username ⇒ Object
readonly
Returns the value of attribute username.
Instance Method Summary collapse
-
#authorize ⇒ Object
启动授权流程(保留兼容性).
-
#authorize_and_login ⇒ Object
完整的授权和登录流程.
-
#exchange_code_for_token(code) ⇒ Object
使用授权码换取 token.
-
#get_token_data ⇒ Object
获取 token 数据(供 Client 使用).
-
#get_username ⇒ Object
获取用户名(登录后可用).
-
#initialize(config = nil) ⇒ Auth
constructor
A new instance of Auth.
-
#login ⇒ Object
主登录入口.
Constructor Details
#initialize(config = nil) ⇒ Auth
Returns a new instance of Auth.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/jpsclient/auth/auth.rb', line 69 def initialize(config = nil) # 只接受 LoginConfig 对象或默认配置 @config = config || LoginConfig.new # 从配置对象获取值 @client_id = @config.client_id @feishu_auth_url = @config.feishu_auth_url @redirect_uri = @config.redirect_uri @api_endpoint = @config.api_endpoint @state = @config.state @server_port = @config.server_port @scope_list = [ 'offline_access', 'task:task:write', 'task:section:write', 'task:custom_field:write', 'task:tasklist:write', 'drive:drive', 'wiki:wiki', 'docx:document', 'bitable:app', 'contact:user.employee_id:readonly', 'docs:document.content:read', 'im:chat', 'base:app:copy', 'base:record:update', 'task:comment:write', 'task:comment', 'task:attachment:write', 'vc:room:readonly', 'vc:meeting:readonly' ] @access_token = nil @username = nil @user_id = nil @permissions = nil @lark_user_id = nil @tenant_manager = false # 调试模式,通过环境变量控制 @verbose = ENV['PINDO_DEBUG'] == 'true' end |
Instance Attribute Details
#access_token ⇒ Object (readonly)
Returns the value of attribute access_token.
66 67 68 |
# File 'lib/jpsclient/auth/auth.rb', line 66 def access_token @access_token end |
#lark_user_id ⇒ Object (readonly)
Returns the value of attribute lark_user_id.
67 68 69 |
# File 'lib/jpsclient/auth/auth.rb', line 67 def lark_user_id @lark_user_id end |
#permissions ⇒ Object (readonly)
Returns the value of attribute permissions.
67 68 69 |
# File 'lib/jpsclient/auth/auth.rb', line 67 def @permissions end |
#tenant_manager ⇒ Object (readonly)
Returns the value of attribute tenant_manager.
67 68 69 |
# File 'lib/jpsclient/auth/auth.rb', line 67 def tenant_manager @tenant_manager end |
#user_id ⇒ Object (readonly)
Returns the value of attribute user_id.
67 68 69 |
# File 'lib/jpsclient/auth/auth.rb', line 67 def user_id @user_id end |
#username ⇒ Object (readonly)
Returns the value of attribute username.
66 67 68 |
# File 'lib/jpsclient/auth/auth.rb', line 66 def username @username end |
Instance Method Details
#authorize ⇒ Object
启动授权流程(保留兼容性)
126 127 128 |
# File 'lib/jpsclient/auth/auth.rb', line 126 def login end |
#authorize_and_login ⇒ Object
完整的授权和登录流程
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/jpsclient/auth/auth.rb', line 150 def # 构建授权 URL = puts "正在打开浏览器进行飞书 OAuth 授权..." puts "\n授权 URL(如自动打开失败,请手动复制下面的链接到浏览器):" puts "=" * 80 puts puts "=" * 80 puts "" # 在浏览器中打开授权 URL open_browser() # 启动本地服务器处理回调 code = start_callback_server # 如果自动获取失败,提示用户手动输入 if code.nil? loop do puts "\n自动获取授权码失败,请选择:" puts "1. 输入授权码 (直接复制 'code=' 后面的内容)" puts "2. 输入完整回调 URL" puts "3. 重新打开授权网页" puts "4. 退出" print "> " begin choice = STDIN.gets&.chomp # 处理 Ctrl+C 中断 if choice.nil? puts "\n用户中断操作" return :user_cancelled end case choice when "1" puts "请输入授权码:" print "> " code_input = STDIN.gets&.chomp if code_input.nil? puts "用户中断操作" return :user_cancelled elsif !code_input.empty? code = code_input break else puts "授权码不能为空,请重新选择" end when "2" puts "请输入完整回调 URL:" print "> " url_input = STDIN.gets&.chomp if url_input.nil? puts "用户中断操作" return :user_cancelled elsif url_input.start_with?("http") # 尝试从 URL 中提取 code begin uri = URI(url_input) query_params = URI.decode_www_form(uri.query || '').to_h code = query_params['code'] if code puts "✓ 从 URL 中成功提取授权码" break else puts "✗ URL 中没有找到授权码,请重新选择" end rescue => e puts "✗ 无法从 URL 中提取授权码: #{e.}" end else puts "✗ 无效的 URL 格式,请重新选择" end when "3" # 重新打开授权网页 puts "正在重新打开授权网页..." open_browser() # 重新启动服务器尝试获取授权码 code = start_callback_server if code break end # 如果还是失败,继续循环让用户选择 when "4" puts "已退出授权流程" return :user_cancelled else puts "无效的选择,请输入 1-4 之间的数字" end rescue Interrupt puts "\n\n用户中断操作" return :user_cancelled end end end if code if exchange_code_for_token(code) puts "✓ JPS 登录成功!用户名: #{@username}" return true end return false else puts "✗ 授权失败" return false end end |
#exchange_code_for_token(code) ⇒ Object
使用授权码换取 token
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/jpsclient/auth/auth.rb', line 260 def exchange_code_for_token(code) begin puts "🔄 获取授权码成功: #{code[0..10]}..." puts "🔄 正在换取访问令牌..." request_data = { 'code' => code, 'redirectUri' => @redirect_uri, 'scope' => @scope_list.join(' ') } uri = URI(@api_endpoint) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = (uri.scheme == 'https') # 配置 SSL 以兼容 OpenSSL 3.x(避免 CRL 检查失败) if http.use_ssl? http.verify_mode = OpenSSL::SSL::VERIFY_PEER # 创建 SSL 上下文并禁用 CRL 检查 http.cert_store = OpenSSL::X509::Store.new http.cert_store.set_default_paths # verify_flags = 0 表示不检查 CRL http.cert_store.flags = 0 end request = Net::HTTP::Post.new(uri) request['Content-Type'] = 'application/json' request.body = request_data.to_json puts "正在请求 JPS API: #{@api_endpoint}" if @verbose response = http.request(request) puts "API 响应状态码: #{response.code}" if @verbose if response.body result = JSON.parse(response.body) puts "API 响应: #{result.inspect}" if @verbose if result['meta'] && result['meta']['code'] == 0 && result['data'] data = result['data'] # 兼容 snake_case 和 camelCase 两种响应格式 @access_token = data['token'] if data['token'] @username = data['username'] if data['username'] @user_id = data['user_id'] || data['userId'] @permissions = data['permissions'] @lark_user_id = data['lark_user_id'] || data['larkUserId'] @tenant_manager = data['tenant_manager'] || data['tenantManager'] || false return true if @access_token else error_msg = result['meta'] && result['meta']['message'] ? result['meta']['message'] : '未知错误' puts "JPS 登录失败: #{error_msg}" end else puts "API 返回空响应" if @verbose end return false rescue => e puts "请求 JPS API 失败: #{e.class} - #{e.}" return false end end |
#get_token_data ⇒ Object
获取 token 数据(供 Client 使用)
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/jpsclient/auth/auth.rb', line 136 def get_token_data return nil unless @access_token { 'token' => @access_token, 'username' => @username, 'user_id' => @user_id, 'permissions' => @permissions, 'lark_user_id' => @lark_user_id, 'tenant_manager' => @tenant_manager } end |
#get_username ⇒ Object
获取用户名(登录后可用)
131 132 133 |
# File 'lib/jpsclient/auth/auth.rb', line 131 def get_username @username end |
#login ⇒ Object
主登录入口
115 116 117 118 119 120 121 122 123 |
# File 'lib/jpsclient/auth/auth.rb', line 115 def login puts "🔐 需要登录 JPS..." result = # 如果用户主动取消,返回特殊标识 return :user_cancelled if result == :user_cancelled return result end |