feat: 添加刷新令牌支持
* 服务端: 添加对应的接口, 对原有令牌系统稍有修改, 添加了令牌类型 * 客户端: 自动刷新访问令牌, 登录时顺带获取刷新令牌
This commit is contained in:
@@ -51,15 +51,33 @@ class Client {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, (err: Error, res: ApiCallbackMessage) => {
|
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, async (err: Error, res: ApiCallbackMessage) => {
|
||||||
if (err) return resolve({
|
if (err) return resolve({
|
||||||
code: -1,
|
code: -1,
|
||||||
msg: err.message.indexOf("timed out") != -1 ? "請求超時" : err.message,
|
msg: err.message.indexOf("timed out") != -1 ? "請求超時" : err.message,
|
||||||
})
|
})
|
||||||
resolve(res)
|
if (res.code == 401) {
|
||||||
|
const token = await this.refreshAccessToken()
|
||||||
|
if (token) {
|
||||||
|
data.access_token = token
|
||||||
|
data.apply()
|
||||||
|
resolve(await this.invoke(method, {
|
||||||
|
...args,
|
||||||
|
token
|
||||||
|
}, timeout))
|
||||||
|
} else
|
||||||
|
resolve(res)
|
||||||
|
} else
|
||||||
|
resolve(res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
static async refreshAccessToken() {
|
||||||
|
const re = await this.invoke("User.refreshAccessToken", {
|
||||||
|
refresh_token: data.refresh_token
|
||||||
|
})
|
||||||
|
return re.data?.access_token
|
||||||
|
}
|
||||||
static async auth(token: string, timeout: number = 5000) {
|
static async auth(token: string, timeout: number = 5000) {
|
||||||
const re = await this.invoke("User.auth", {
|
const re = await this.invoke("User.auth", {
|
||||||
access_token: token
|
access_token: token
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default function LoginDialog({
|
|||||||
if (checkApiSuccessOrSncakbar(re, "登录失败")) return
|
if (checkApiSuccessOrSncakbar(re, "登录失败")) return
|
||||||
|
|
||||||
data.access_token = re.data!.access_token as string
|
data.access_token = re.data!.access_token as string
|
||||||
|
data.refresh_token = re.data!.refresh_token as string
|
||||||
data.apply()
|
data.apply()
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import TokenType from "./TokenType.ts"
|
||||||
|
|
||||||
export default interface Token {
|
export default interface Token {
|
||||||
author: string
|
author: string
|
||||||
auth: string
|
auth: string
|
||||||
made_time: number
|
made_time: number
|
||||||
expired_time: number
|
expired_time: number
|
||||||
device_id: string
|
device_id: string
|
||||||
|
type: TokenType
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import config from "../config.ts"
|
|||||||
import User from "../data/User.ts"
|
import User from "../data/User.ts"
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import Token from "./Token.ts"
|
import Token from "./Token.ts"
|
||||||
|
import TokenType from "./TokenType.ts"
|
||||||
|
|
||||||
function normalizeKey(key: string, keyLength = 32) {
|
function normalizeKey(key: string, keyLength = 32) {
|
||||||
const hash = crypto.createHash('sha256')
|
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())
|
const time = (time_ || Date.now())
|
||||||
return this.encode({
|
return this.encode({
|
||||||
author: user.bean.id,
|
author: user.bean.id,
|
||||||
auth: this.makeAuth(user),
|
auth: this.makeAuth(user),
|
||||||
made_time: time,
|
made_time: time,
|
||||||
expired_time: time + (1 * 1000 * 60 * 60 * 24),
|
expired_time: time + (type == 'access_token' ? (1000 * 60 * 60 * 2) : (40 * 1000 * 60 * 60 * 24)),
|
||||||
device_id: device_id
|
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)
|
* 嚴格檢驗令牌: 時間, 用戶, (設備 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.expired_time < Date.now()) return false
|
||||||
if (!token.author || !User.findById(token.author)) return false
|
if (!token.author || !User.findById(token.author)) return false
|
||||||
if (deviceId != null)
|
if (deviceId != null)
|
||||||
if (token.device_id != deviceId)
|
if (token.device_id != deviceId)
|
||||||
return false
|
return false
|
||||||
|
if (token.type != type)
|
||||||
|
return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
server/api/TokenType.ts
Normal file
3
server/api/TokenType.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
type TokenType = "access_token" | "refresh_token"
|
||||||
|
|
||||||
|
export default TokenType
|
||||||
@@ -57,6 +57,49 @@ export default class UserApi extends BaseApi {
|
|||||||
throw e
|
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 }) => {
|
this.registerEvent("User.login", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['account', 'password'])) return {
|
if (this.checkArgsMissing(args, ['account', 'password'])) return {
|
||||||
@@ -78,7 +121,8 @@ export default class UserApi extends BaseApi {
|
|||||||
msg: "成功",
|
msg: "成功",
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
access_token: TokenManager.make(user, null, deviceId)
|
refresh_token: TokenManager.make(user, null, deviceId, 'refresh_token'),
|
||||||
|
access_token: TokenManager.make(user, null, deviceId),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user