Class: JPSClient::Token

Inherits:
Object
  • Object
show all
Defined in:
lib/jpsclient/auth/token.rb

Overview

Token 管理类

负责登录态的本地存储、加载和清除,与 jps-cli / nbskills / 桌面端三端共享同一份加密凭证 (~/.jps_auth_info,测试服 ~/.jps_cli/test_auth_info)。

存储格式(跨端硬契约,见 OIDC 接入文档 §5.3): camelCase 紧凑 JSON → AES-128-ECB + PKCS7 → Base64 → 原子写文本文件(0600)

本地过期以 tokenExpireTimestamp(毫秒)预判,省一次后端 401 往返;最终失效以后端 20001 为权威。

Constant Summary collapse

CAMEL_TO_ATTR =

明文 JSON 字段(camelCase)↔ 内部属性(snake)。顺序仅利于人眼比对,与 jps-cli 一致。

{
  'token'                => :token,
  'tokenExpireTimestamp' => :token_expire_timestamp,
  'username'             => :username,
  'userId'               => :user_id,
  'permissions'          => :permissions,
  'larkUserId'           => :lark_user_id,
  'tenantManager'        => :tenant_manager,
  'avatar'               => :avatar
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil) ⇒ Token

Returns a new instance of Token.



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/jpsclient/auth/token.rb', line 38

def initialize(config = nil)
  @config = config
  @aes_key = config.aes_key if config

  # 凭证文件路径为跨端硬契约,统一由 Endpoints 解析(忽略 config 的 token_dir/token_file_name)
  @token_file = Endpoints.auth_file

  reset_fields

  @verbose = JPSClient.debug?
end

Instance Attribute Details

#avatarObject (readonly)

Returns the value of attribute avatar.



24
25
26
# File 'lib/jpsclient/auth/token.rb', line 24

def avatar
  @avatar
end

#lark_user_idObject (readonly)

Returns the value of attribute lark_user_id.



23
24
25
# File 'lib/jpsclient/auth/token.rb', line 23

def lark_user_id
  @lark_user_id
end

#permissionsObject (readonly)

Returns the value of attribute permissions.



23
24
25
# File 'lib/jpsclient/auth/token.rb', line 23

def permissions
  @permissions
end

#tenant_managerObject (readonly)

Returns the value of attribute tenant_manager.



23
24
25
# File 'lib/jpsclient/auth/token.rb', line 23

def tenant_manager
  @tenant_manager
end

#tokenObject (readonly)

Returns the value of attribute token.



22
23
24
# File 'lib/jpsclient/auth/token.rb', line 22

def token
  @token
end

#token_expire_timestampObject (readonly)

Returns the value of attribute token_expire_timestamp.



24
25
26
# File 'lib/jpsclient/auth/token.rb', line 24

def token_expire_timestamp
  @token_expire_timestamp
end

#user_idObject (readonly)

Returns the value of attribute user_id.



23
24
25
# File 'lib/jpsclient/auth/token.rb', line 23

def user_id
  @user_id
end

#usernameObject (readonly)

Returns the value of attribute username.



22
23
24
# File 'lib/jpsclient/auth/token.rb', line 22

def username
  @username
end

Instance Method Details

#clearObject

清除内存与磁盘上的 token



117
118
119
120
121
# File 'lib/jpsclient/auth/token.rb', line 117

def clear
  reset_fields
  FileUtils.rm_f(@token_file) if File.exist?(@token_file)
  puts "✓ Token 已清除" if @verbose
end

#loadObject

加载 token;成功且未过期返回 true,否则 false



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/jpsclient/auth/token.rb', line 51

def load
  return false unless File.exist?(@token_file)

  begin
    content = File.read(@token_file).strip
    return false if content.empty?

    token_data = decrypt_content(content)
    apply_fields(token_data)

    # token + user_id 齐全才算有效登录。~/.jps_auth_info 为三端共享,load 失败只重置内存、
    # 不删文件(删除仅保留给显式 clear / force-login),避免误删他端有效登录态
    unless valid_login?
      puts "Token 文件缺少必要字段,需要重新登录" if @verbose
      reset_fields
      return false
    end

    # 本地过期预判:直接当未登录,省一次后端 401 往返
    if token_expired?
      puts "Token 已过期(tokenExpireTimestamp),需要重新登录" if @verbose
      reset_fields
      return false
    end

    return true
  rescue => e
    puts "读取 token 失败: #{e.message}" if @verbose
    reset_fields
  end

  false
end

#loaded?Boolean

token 是否已加载

Returns:

  • (Boolean)


112
113
114
# File 'lib/jpsclient/auth/token.rb', line 112

def loaded?
  !@token.nil? && !@token.empty?
end

#save(token_data) ⇒ Object

保存 token。token_data 为内部 snake 键的 Hash(见 Auth#get_token_data)



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/jpsclient/auth/token.rb', line 86

def save(token_data)
  return false unless token_data.is_a?(Hash) && (token_data['token'] || token_data[:token])

  # ~/.jps_auth_info 为三端共享的加密凭证:缺少 AES 密钥时拒绝写入,绝不明文落盘
  # (明文会泄露 SESSION,且会让 jps-cli / 桌面端把该文件判为损坏)
  unless @aes_key
    puts "✗ 缺少 AES 密钥,拒绝写入共享凭证 #{@token_file}(需配置 aes_key)"
    return false
  end

  apply_fields(token_data)

  FileUtils.mkdir_p(File.dirname(@token_file))

  plaintext = JSON.generate(to_camel_hash)
  content = AES.new(@aes_key).encrypt(plaintext)

  atomic_write(@token_file, content)
  puts "✓ Token 已保存到 #{@token_file}" if @verbose
  true
rescue => e
  puts "保存 token 失败: #{e.message}"
  false
end

#to_hObject

转换为内部 snake 键 Hash(供 Client 使用)



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/jpsclient/auth/token.rb', line 124

def to_h
  return nil unless @token

  {
    'token'                  => @token,
    'username'               => @username,
    'user_id'                => @user_id,
    'permissions'            => @permissions,
    'lark_user_id'           => @lark_user_id,
    'tenant_manager'         => @tenant_manager,
    'avatar'                 => @avatar,
    'token_expire_timestamp' => @token_expire_timestamp
  }
end