mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
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:
parent
a52352719b
commit
f2c3f54109
@ -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> {}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user