feat: 更新JWKS token创建逻辑,支持refresh token选项
This commit is contained in:
@@ -46,6 +46,7 @@ type TokenOptions = {
|
|||||||
host?: string; // 主机信息
|
host?: string; // 主机信息
|
||||||
wx?: any;
|
wx?: any;
|
||||||
loginWith?: string; // 登录方式,如 'cli', 'web', 'plugin' 等
|
loginWith?: string; // 登录方式,如 'cli', 'web', 'plugin' 等
|
||||||
|
hasRefreshToken?: boolean; // 是否需要 refresh token,默认为 false
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 用户模型,使用 Drizzle ORM
|
* 用户模型,使用 Drizzle ORM
|
||||||
@@ -85,15 +86,15 @@ export class User {
|
|||||||
/**
|
/**
|
||||||
* 创建JWKS token的通用方法
|
* 创建JWKS token的通用方法
|
||||||
*/
|
*/
|
||||||
static async createJwksTokenResponse(user: { id: string; username: string }, opts: { expire?: number, save?: boolean } = {}) {
|
static async createJwksTokenResponse(user: { id: string; username: string }, opts: { expire?: number, hasRefreshToken?: boolean } = {}) {
|
||||||
const expiresIn = opts?.expire ?? JWKS_TOKEN_EXPIRY;
|
const expiresIn = opts?.expire ?? JWKS_TOKEN_EXPIRY;
|
||||||
const save = opts?.save ?? true;
|
const hasRefreshToken = opts?.hasRefreshToken ?? true;
|
||||||
const accessToken = await jwksManager.sign({
|
const accessToken = await jwksManager.sign({
|
||||||
sub: 'user:' + user.id,
|
sub: 'user:' + user.id,
|
||||||
name: user.username,
|
name: user.username,
|
||||||
exp: Math.floor(Date.now() / 1000) + expiresIn,
|
exp: Math.floor(Date.now() / 1000) + expiresIn,
|
||||||
});
|
});
|
||||||
if (save) {
|
if (hasRefreshToken) {
|
||||||
await oauth.setJwksToken(accessToken, { id: user.id, expire: expiresIn });
|
await oauth.setJwksToken(accessToken, { id: user.id, expire: expiresIn });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +114,7 @@ export class User {
|
|||||||
|
|
||||||
async createToken(uid?: string, loginType?: 'default' | 'plugin' | 'month' | 'season' | 'year' | 'week' | 'jwks', opts: TokenOptions = {}) {
|
async createToken(uid?: string, loginType?: 'default' | 'plugin' | 'month' | 'season' | 'year' | 'week' | 'jwks', opts: TokenOptions = {}) {
|
||||||
const { id, username, type } = this;
|
const { id, username, type } = this;
|
||||||
|
const hasRefreshToken = opts.hasRefreshToken ?? true;
|
||||||
const oauthUser: OauthUser = {
|
const oauthUser: OauthUser = {
|
||||||
id,
|
id,
|
||||||
username,
|
username,
|
||||||
@@ -126,7 +128,7 @@ export class User {
|
|||||||
if (loginType === 'jwks') {
|
if (loginType === 'jwks') {
|
||||||
return await User.createJwksTokenResponse(this, opts);
|
return await User.createJwksTokenResponse(this, opts);
|
||||||
}
|
}
|
||||||
const token = await oauth.generateToken(oauthUser, { type: loginType, hasRefreshToken: true, ...opts });
|
const token = await oauth.generateToken(oauthUser, { type: loginType, hasRefreshToken, ...opts });
|
||||||
return {
|
return {
|
||||||
type: 'default',
|
type: 'default',
|
||||||
...token,
|
...token,
|
||||||
@@ -140,6 +142,17 @@ export class User {
|
|||||||
static async verifyToken(token: string) {
|
static async verifyToken(token: string) {
|
||||||
return await UserSecret.verifyToken(token);
|
return await UserSecret.verifyToken(token);
|
||||||
}
|
}
|
||||||
|
static async checkJwksValid(token: string) {
|
||||||
|
const verified = await User.verifyToken(token);
|
||||||
|
let isValid = false;
|
||||||
|
if (verified) {
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
const jwksToken = await oauth.getJwksToken(token);
|
||||||
|
if (!isValid && !jwksToken) {
|
||||||
|
throw new CustomError('Invalid refresh token');
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 刷新token
|
* 刷新token
|
||||||
* @param refreshToken
|
* @param refreshToken
|
||||||
@@ -150,10 +163,7 @@ export class User {
|
|||||||
let jwsRefreshToken = accessToken || refreshToken;
|
let jwsRefreshToken = accessToken || refreshToken;
|
||||||
if (oauth.getTokenType(jwsRefreshToken) === 'jwks') {
|
if (oauth.getTokenType(jwsRefreshToken) === 'jwks') {
|
||||||
// 可能是 jwks token
|
// 可能是 jwks token
|
||||||
const jwksToken = await oauth.getJwksToken(jwsRefreshToken);
|
await User.checkJwksValid(jwsRefreshToken);
|
||||||
if (!jwksToken) {
|
|
||||||
throw new CustomError('Invalid refresh token');
|
|
||||||
}
|
|
||||||
const decoded = await jwksManager.decode(jwsRefreshToken);
|
const decoded = await jwksManager.decode(jwsRefreshToken);
|
||||||
return await User.createJwksTokenResponse({
|
return await User.createJwksTokenResponse({
|
||||||
id: decoded.sub.replace('user:', ''),
|
id: decoded.sub.replace('user:', ''),
|
||||||
@@ -212,10 +222,7 @@ export class User {
|
|||||||
static async resetToken(refreshToken: string, expand?: Record<string, any>) {
|
static async resetToken(refreshToken: string, expand?: Record<string, any>) {
|
||||||
if (oauth.getTokenType(refreshToken) === 'jwks') {
|
if (oauth.getTokenType(refreshToken) === 'jwks') {
|
||||||
// 可能是 jwks token
|
// 可能是 jwks token
|
||||||
const jwksToken = await oauth.getJwksToken(refreshToken);
|
await User.checkJwksValid(refreshToken);
|
||||||
if (!jwksToken) {
|
|
||||||
throw new CustomError('Invalid refresh token');
|
|
||||||
}
|
|
||||||
const decoded = await jwksManager.decode(refreshToken);
|
const decoded = await jwksManager.decode(refreshToken);
|
||||||
return await User.createJwksTokenResponse({
|
return await User.createJwksTokenResponse({
|
||||||
id: decoded.sub.replace('user:', ''),
|
id: decoded.sub.replace('user:', ''),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const N5Proxy = async (req: IncomingMessage, res: ServerResponse, opts?:
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const user = await User.findByPk(userId);
|
const user = await User.findByPk(userId);
|
||||||
const token = await User.createJwksTokenResponse({ id: userId, username: user?.username || '' }, { save: false });
|
const token = await User.createJwksTokenResponse({ id: userId, username: user?.username || '' }, { hasRefreshToken: false });
|
||||||
const urlObj = new URL(link);
|
const urlObj = new URL(link);
|
||||||
urlObj.searchParams.set('token', token.accessToken);
|
urlObj.searchParams.set('token', token.accessToken);
|
||||||
const resultLink = await fetch(urlObj.toString(), { method: 'GET' }).then(res => res.json())
|
const resultLink = await fetch(urlObj.toString(), { method: 'GET' }).then(res => res.json())
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ app.route({
|
|||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
args: {
|
args: {
|
||||||
loginType: z.enum(['jwks']).optional(),
|
loginType: z.enum(['jwks']).optional().describe('登录类型,默认为jwks'),
|
||||||
|
hasRefreshToken: z.boolean().optional().describe('是否需要refresh token,默认为false'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const user = await UserModel.getUserByToken(ctx.query.token);
|
const user = await UserModel.getUserByToken(ctx.query.token);
|
||||||
const loginType = ctx.query?.loginType ?? 'jwks';
|
const loginType = ctx.query?.loginType ?? 'jwks';
|
||||||
|
const hasRefreshToken = ctx.query?.hasRefreshToken ?? false;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
ctx.throw(404, 'user not found');
|
ctx.throw(404, 'user not found');
|
||||||
}
|
}
|
||||||
@@ -28,6 +30,7 @@ app.route({
|
|||||||
}
|
}
|
||||||
const value = await user.createToken(null, loginType, {
|
const value = await user.createToken(null, loginType, {
|
||||||
expire: expire, // 24小时过期
|
expire: expire, // 24小时过期
|
||||||
|
hasRefreshToken: hasRefreshToken,
|
||||||
})
|
})
|
||||||
ctx.body = value
|
ctx.body = value
|
||||||
}).addTo(app)
|
}).addTo(app)
|
||||||
Reference in New Issue
Block a user