Files
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

124 lines
3.5 KiB
TypeScript

import { describe, expect, it, beforeEach, vi } from 'vitest';
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
vi.hoisted(() => {
process.env.JWT_SECRET = 'test-jwt-secret-please-change-in-production';
});
// Generate mock RSA key pair for testing
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
});
const testKid = 'kid_test';
const mockGetSecretByKey = vi.fn().mockImplementation((key: string) => {
if (key === 'JWT_PRIVATE_KEY') return privateKey;
if (key === 'JWT_PUBLIC_KEY') return publicKey;
if (key === 'JWT_KEY_ID') return testKid;
return null;
});
vi.mock('../../src/services/secrets/secret.service.js', () => ({
SecretService: {
getInstance: () => ({
getSecretByKey: mockGetSecretByKey,
initializeJwtKeyPair: vi.fn().mockResolvedValue({
privateKey,
publicKey,
kid: testKid,
}),
}),
},
}));
import { TokenManager } from '../../src/infra/security/token.manager.js';
describe('TokenManager JWKS & RS256 Support', () => {
let tokenManager: TokenManager;
beforeEach(async () => {
vi.clearAllMocks();
tokenManager = TokenManager.getInstance();
// Ensure keys are loaded in token manager
await tokenManager.ensureKeysLoaded();
});
it('generates RS256 access tokens containing a kid header', async () => {
const payload = {
sub: 'test-user-id',
email: 'test@example.com',
role: 'authenticated' as const,
};
const token = tokenManager.generateAccessToken(payload);
expect(token).toBeDefined();
// Decode the token header to inspect the kid and algorithm
const decoded = jwt.decode(token, { complete: true }) as unknown as {
header: { alg: string; kid: string };
};
expect(decoded).not.toBeNull();
expect(decoded.header.alg).toBe('RS256');
expect(decoded.header.kid).toBe(testKid);
});
it('verifies the RS256 access token successfully', async () => {
const payload = {
sub: 'test-user-id',
email: 'test@example.com',
role: 'authenticated' as const,
};
const token = tokenManager.generateAccessToken(payload);
const verified = tokenManager.verifyToken(token);
expect(verified.sub).toBe(payload.sub);
expect(verified.email).toBe(payload.email);
expect(verified.role).toBe(payload.role);
});
it('falls back to verifying HS256 tokens signed with the shared secret', async () => {
const payload = {
sub: 'legacy-user-id',
email: 'legacy@example.com',
role: 'authenticated' as const,
};
const legacyToken = jwt.sign(
payload,
process.env.JWT_SECRET || 'dev-secret-please-change-in-production',
{
algorithm: 'HS256',
expiresIn: '15m',
}
);
const verified = tokenManager.verifyToken(legacyToken);
expect(verified.sub).toBe(payload.sub);
expect(verified.email).toBe(payload.email);
expect(verified.role).toBe(payload.role);
});
it('exports the public key as a valid JWK Set (JWKS)', async () => {
const jwks = await tokenManager.getJwks();
expect(jwks).toBeDefined();
expect(jwks.keys).toBeDefined();
expect(jwks.keys.length).toBe(1);
const key = jwks.keys[0];
expect(key.kty).toBe('RSA');
expect(key.alg).toBe('RS256');
expect(key.use).toBe('sig');
expect(key.kid).toBe(testKid);
expect(key.n).toBeDefined();
expect(key.e).toBeDefined();
});
});