fix: implement renewed JTI cache duration and update related tests

This commit is contained in:
Sheldon Guo 2025-02-14 07:36:28 +08:00
parent ddff4e9b7c
commit 0e169da245
3 changed files with 46 additions and 11 deletions

View File

@ -11,3 +11,4 @@ export const tokenPolicyRecordKey = 'token-policy-config';
export const tokenPolicyCacheKey = 'auth:' + tokenPolicyRecordKey;
export const tokenPolicyCollectionName = 'tokenControlConfig';
export const issuedTokensCollectionName = 'issuedTokens';
export const RENEWED_JTI_CACHE_MS = 10000;

View File

@ -7,10 +7,11 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { AuthErrorCode, BaseAuth } from '@nocobase/auth';
import { BaseAuth } from '@nocobase/auth';
import { Database, Model } from '@nocobase/database';
import { MockServer, createMockServer } from '@nocobase/test';
import { AuthErrorType } from '@nocobase/auth';
import { RENEWED_JTI_CACHE_MS } from '../../constants';
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
@ -161,19 +162,47 @@ describe('auth', () => {
expect(checkedUser.id).toEqual(user.id);
});
it('when call renew token with same jti multiple times, only one resolved', async () => {
it('when call renew token with same jti multiple times within 10s, the result is same', async () => {
const tokenInfo = await auth.tokenController.add({ userId: 1 });
const renewTasks = Array(15)
.fill(null)
.map(() => auth.tokenController.renew(tokenInfo.jti));
const allSettled = await Promise.allSettled(renewTasks);
const successTasks = allSettled.filter((result) => result.status === 'fulfilled');
expect(successTasks).toHaveLength(1);
const failedTasks = allSettled.filter(
(result) => result.status === 'rejected' && result.reason.code === AuthErrorCode.TOKEN_RENEW_FAILED,
);
expect(failedTasks).toHaveLength(14);
const results = await Promise.all(renewTasks);
expect(
results.every((result) => result.jti === results[0].jti && result.issuedTime === results[0].issuedTime),
).toBe(true);
});
it('after JTI is renewed for 10s, any further renewal should fail.', async () => {
const tokenInfo = await auth.tokenController.add({ userId: 1 });
await auth.tokenController.renew(tokenInfo.jti);
const renewedIntervals = [
RENEWED_JTI_CACHE_MS - 1000,
RENEWED_JTI_CACHE_MS - 2000,
RENEWED_JTI_CACHE_MS + 1000,
RENEWED_JTI_CACHE_MS + 2000,
];
const renewTasks = renewedIntervals.map(async (interval) => {
try {
await sleep(interval);
const result = await auth.tokenController.renew(tokenInfo.jti);
return [interval, 'resolved', result];
} catch (e) {
return [interval, 'rejected', e];
}
});
const results = await Promise.all(renewTasks);
expect(
results.every(([interval, status, result]) => {
if (interval < RENEWED_JTI_CACHE_MS) {
return status === 'resolved';
} else {
return status === 'rejected';
}
}),
).toBe(true);
});
it('use token policy tokenExpirationTime as token expirein', async () => {
const config = await auth.tokenController.getConfig();
const { token } = await auth.signIn();

View File

@ -21,7 +21,12 @@ import { randomUUID } from 'crypto';
import ms from 'ms';
import Application from '@nocobase/server';
import Database, { Repository } from '@nocobase/database';
import { issuedTokensCollectionName, tokenPolicyCollectionName, tokenPolicyRecordKey } from '../constants';
import {
issuedTokensCollectionName,
tokenPolicyCollectionName,
tokenPolicyRecordKey,
RENEWED_JTI_CACHE_MS,
} from '../constants';
type TokenControlService = ITokenControlService;
@ -119,7 +124,7 @@ export class TokenController implements TokenControlService {
);
if (count === 1) {
await this.cache.set(`jti-renewed-cahce:${jti}`, { jti: newId, issuedTime }, 20000);
await this.cache.set(`jti-renewed-cahce:${jti}`, { jti: newId, issuedTime }, RENEWED_JTI_CACHE_MS);
this.logger.info('jti renewed', { oldJti: jti, newJti: newId, issuedTime });
return { jti: newId, issuedTime };
} else {