feat: 添加刷新令牌支持

* 服务端: 添加对应的接口, 对原有令牌系统稍有修改, 添加了令牌类型
* 客户端: 自动刷新访问令牌, 登录时顺带获取刷新令牌
This commit is contained in:
CrescentLeaf
2025-10-06 17:13:23 +08:00
parent dced175d7a
commit 85477fe46e
6 changed files with 80 additions and 20 deletions

View File

@@ -1,7 +1,10 @@
import TokenType from "./TokenType.ts"
export default interface Token {
author: string
auth: string
made_time: number
expired_time: number
device_id: string
type: TokenType
}

View File

@@ -3,6 +3,7 @@ import config from "../config.ts"
import User from "../data/User.ts"
import crypto from 'node:crypto'
import Token from "./Token.ts"
import TokenType from "./TokenType.ts"
function normalizeKey(key: string, keyLength = 32) {
const hash = crypto.createHash('sha256')
@@ -31,38 +32,28 @@ export default class TokenManager {
}
}
static make(user: User, time_: number | null | undefined, device_id: string) {
static make(user: User, time_: number | null | undefined, device_id: string, type: TokenType = "access_token") {
const time = (time_ || Date.now())
return this.encode({
author: user.bean.id,
auth: this.makeAuth(user),
made_time: time,
expired_time: time + (1 * 1000 * 60 * 60 * 24),
device_id: device_id
expired_time: time + (type == 'access_token' ? (1000 * 60 * 60 * 2) : (40 * 1000 * 60 * 60 * 24)),
device_id: device_id,
type
})
}
/**
* 獲取新令牌
* 注意: 只驗證用戶, 不驗證令牌有效性!
*/
static makeNewer(user: User, token: string) {
if (this.check(user, token))
return this.make(user, Date.now() + (1 * 1000 * 60 * 60 * 24), this.decode(token).device_id)
}
static check(user: User, token: string) {
const tk = this.decode(token)
return this.makeAuth(user) == tk.auth
}
/**
* 嚴格檢驗令牌: 時間, 用戶, (設備 ID)
*/
static checkToken(token: Token, deviceId?: string) {
static checkToken(token: Token, deviceId?: string, type: TokenType = 'access_token') {
if (token.expired_time < Date.now()) return false
if (!token.author || !User.findById(token.author)) return false
if (deviceId != null)
if (token.device_id != deviceId)
return false
if (token.type != type)
return false
return true
}
}

3
server/api/TokenType.ts Normal file
View File

@@ -0,0 +1,3 @@
type TokenType = "access_token" | "refresh_token"
export default TokenType

View File

@@ -57,6 +57,49 @@ export default class UserApi extends BaseApi {
throw e
}
})
// 刷新访问令牌
this.registerEvent("User.refreshAccessToken", (args, clientInfo) => {
if (this.checkArgsMissing(args, ['refresh_token'])) return {
msg: "参数缺失",
code: 400,
}
const { deviceId } = clientInfo
try {
const refresh_token = TokenManager.decode(args.refresh_token as string)
if (refresh_token.expired_time < Date.now()) return {
msg: "登录令牌失效",
code: 401,
}
if (!refresh_token.author || !User.findById(refresh_token.author)) return {
msg: "账号不存在",
code: 401,
}
if (refresh_token.device_id != deviceId) return {
msg: "验证失败",
code: 401,
}
const user = User.findById(refresh_token.author) as User
return {
msg: "成功",
code: 200,
data: {
access_token: TokenManager.make(user, null, deviceId)
}
}
} catch (e) {
const err = e as Error
if (err.message.indexOf("JSON") != -1)
return {
msg: "无效的用户令牌",
code: 401,
}
else
throw e
}
})
// 登錄
this.registerEvent("User.login", (args, { deviceId }) => {
if (this.checkArgsMissing(args, ['account', 'password'])) return {
@@ -78,7 +121,8 @@ export default class UserApi extends BaseApi {
msg: "成功",
code: 200,
data: {
access_token: TokenManager.make(user, null, deviceId)
refresh_token: TokenManager.make(user, null, deviceId, 'refresh_token'),
access_token: TokenManager.make(user, null, deviceId),
},
}