43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import { SignJWT, exportJWK, generateKeyPair } from 'jose';
|
|
import type { GenerateKeyPairResult } from 'jose';
|
|
|
|
export interface TestIdentity {
|
|
sub?: string;
|
|
username?: string;
|
|
roles?: string[];
|
|
groups?: string[];
|
|
azp?: string;
|
|
aud?: string | string[];
|
|
expiresIn?: number; // 秒;负数=已过期
|
|
}
|
|
|
|
let keys: GenerateKeyPairResult | null = null;
|
|
|
|
/** 生成本地密钥对并写入 KEYCLOAK_TEST_JWKS(必须先于首次 verify 调用) */
|
|
export async function setupTestJwks(): Promise<void> {
|
|
if (keys) return;
|
|
keys = await generateKeyPair('RS256');
|
|
const jwk = await exportJWK(keys.publicKey);
|
|
process.env.KEYCLOAK_TEST_JWKS = JSON.stringify({ keys: [{ ...jwk, alg: 'RS256', kid: 'test-key' }] });
|
|
}
|
|
|
|
export async function makeToken(id: TestIdentity = {}): Promise<string> {
|
|
await setupTestJwks();
|
|
const issuer = `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`;
|
|
const clientId = process.env.KEYCLOAK_CLIENT_ID ?? '';
|
|
if (!keys) throw new Error('test keys not ready');
|
|
return new SignJWT({
|
|
preferred_username: id.username ?? 'tester',
|
|
realm_access: { roles: id.roles ?? ['user'] },
|
|
groups: id.groups ?? [],
|
|
azp: id.azp ?? 'web-portal',
|
|
})
|
|
.setProtectedHeader({ alg: 'RS256', kid: 'test-key' })
|
|
.setSubject(id.sub ?? 'test-sub-1')
|
|
.setIssuer(issuer)
|
|
.setAudience(id.aud ?? clientId)
|
|
.setIssuedAt()
|
|
.setExpirationTime(Math.floor(Date.now() / 1000) + (id.expiresIn ?? 300))
|
|
.sign(keys.privateKey);
|
|
}
|