fix(auth): accidentally logged out due to WebSocket authorization. (#6342)

* fix(auth): improve logging by including full context and error details

* fix(auth): enhance logging with error handling for token renewal

* feat(auth): init checkToken

* feat(auth): implement checkToken method with detailed token status and user information

* fix(auth): update check method to handle expired tokens and improve token renewal process
This commit is contained in:
Sheldon Guo 2025-03-04 06:54:01 +08:00 committed by GitHub
parent a52352719b
commit f2c3f54109
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 15 deletions

View File

@ -83,6 +83,14 @@ export abstract class Auth implements IAuth {
// The abstract methods are required to be implemented by all authentications. // The abstract methods are required to be implemented by all authentications.
abstract check(): Promise<Model>; abstract check(): Promise<Model>;
abstract checkToken(): Promise<{
tokenStatus: 'valid' | 'expired' | 'invalid';
user: Awaited<ReturnType<Auth['check']>>;
jti?: string;
temp: any;
roleName?: any;
signInTime?: number;
}>;
// The following methods are mainly designed for user authentications. // The following methods are mainly designed for user authentications.
async signIn(): Promise<any> {} async signIn(): Promise<any> {}

View File

@ -70,10 +70,16 @@ export class BaseAuth extends Auth {
return /^[^@.<>"'/]{1,50}$/.test(username); return /^[^@.<>"'/]{1,50}$/.test(username);
} }
async check(): ReturnType<Auth['check']> { async checkToken(): Promise<{
const token = this.ctx.getBearerToken(); tokenStatus: 'valid' | 'expired' | 'invalid';
user: Awaited<ReturnType<Auth['check']>>;
jti?: string;
temp: any;
roleName?: any;
signInTime?: number;
}> {
const cache = this.ctx.cache as Cache; const cache = this.ctx.cache as Cache;
const token = this.ctx.getBearerToken();
if (!token) { if (!token) {
this.ctx.throw(401, { this.ctx.throw(401, {
message: this.ctx.t('Unauthenticated. Please sign in to continue.', { ns: localeNamespace }), message: this.ctx.t('Unauthenticated. Please sign in to continue.', { ns: localeNamespace }),
@ -127,7 +133,7 @@ export class BaseAuth extends Auth {
// api token check first // api token check first
if (!temp) { if (!temp) {
if (tokenStatus === 'valid') { if (tokenStatus === 'valid') {
return user; return { tokenStatus, user, temp };
} else { } else {
this.ctx.throw(401, { this.ctx.throw(401, {
message: this.ctx.t('Your session has expired. Please sign in again.', { ns: localeNamespace }), message: this.ctx.t('Your session has expired. Please sign in again.', { ns: localeNamespace }),
@ -164,11 +170,41 @@ export class BaseAuth extends Auth {
}); });
} }
this.ctx.logger.info('token renewing', {
method: 'auth.check',
url: this.ctx.originalUrl,
currentJti: jti,
});
const isStreamRequest = this.ctx?.req?.headers?.accept === 'text/event-stream';
if (isStreamRequest) {
this.ctx.throw(401, {
message: 'Stream api not allow renew token.',
code: AuthErrorCode.SKIP_TOKEN_RENEW,
});
}
if (!jti) {
this.ctx.throw(401, {
message: this.ctx.t('Your session has expired. Please sign in again.', { ns: localeNamespace }),
code: AuthErrorCode.INVALID_TOKEN,
});
}
return { tokenStatus, user, jti, signInTime, temp };
}
return { tokenStatus, user, jti, signInTime, temp };
}
async check(): ReturnType<Auth['check']> {
const { tokenStatus, user, jti, temp, signInTime, roleName } = await this.checkToken();
if (tokenStatus === 'expired') {
const tokenPolicy = await this.tokenController.getConfig();
try { try {
this.ctx.logger.info('token renewing', { this.ctx.logger.info('token renewing', {
method: 'auth.check', method: 'auth.check',
url: this.ctx.originalUrl, jti,
headers: JSON.stringify(this.ctx?.req?.headers),
}); });
const isStreamRequest = this.ctx?.req?.headers?.accept === 'text/event-stream'; const isStreamRequest = this.ctx?.req?.headers?.accept === 'text/event-stream';
@ -187,24 +223,23 @@ export class BaseAuth extends Auth {
} }
const renewedResult = await this.tokenController.renew(jti); const renewedResult = await this.tokenController.renew(jti);
this.ctx.logger.info('token renewed', { this.ctx.logger.info('token renewed', {
method: 'auth.check', method: 'auth.check',
url: this.ctx.originalUrl, oldJti: jti,
headers: JSON.stringify(this.ctx?.req?.headers), newJti: renewedResult.jti,
}); });
const expiresIn = Math.floor(tokenPolicy.tokenExpirationTime / 1000); const expiresIn = Math.floor(tokenPolicy.tokenExpirationTime / 1000);
const newToken = this.jwt.sign( const newToken = this.jwt.sign(
{ userId, roleName, temp, signInTime, iat: Math.floor(renewedResult.issuedTime / 1000) }, { userId: user.id, roleName, temp, signInTime, iat: Math.floor(renewedResult.issuedTime / 1000) },
{ jwtid: renewedResult.jti, expiresIn }, { jwtid: renewedResult.jti, expiresIn },
); );
this.ctx.res.setHeader('x-new-token', newToken); this.ctx.res.setHeader('x-new-token', newToken);
return user;
} catch (err) { } catch (err) {
this.ctx.logger.info('token renew failed', { this.ctx.logger.error('token renew failed', {
method: 'auth.check', method: 'auth.check',
url: this.ctx.originalUrl, jti,
err,
headers: JSON.stringify(this.ctx?.req?.headers),
}); });
const options = const options =
err instanceof AuthError err instanceof AuthError

View File

@ -154,11 +154,15 @@ export class PluginAuthServer extends Plugin {
cache: this.app.cache, cache: this.app.cache,
logger: this.app.logger, logger: this.app.logger,
log: this.app.log, log: this.app.log,
throw: (...args) => {
throw new Error(...args);
},
t: this.app.i18n.t,
} as any); } as any);
let user: Model; let user: Model;
try { try {
user = await auth.check(); user = (await auth.checkToken()).user;
} catch (error) { } catch (error) {
if (!user) { if (!user) {
this.app.logger.error(error); this.app.logger.error(error);