import { tap, expect } from '@git.zone/tstest/tapbundle'; import { OidcAccessToken } from '../ts/reception/classes.oidcaccesstoken.js'; import { OidcAuthorizationCode } from '../ts/reception/classes.oidcauthorizationcode.js'; import { OidcManager } from '../ts/reception/classes.oidcmanager.js'; import { OidcRefreshToken } from '../ts/reception/classes.oidcrefreshtoken.js'; import { OidcUserConsent } from '../ts/reception/classes.oidcuserconsent.js'; const createTestOidcManager = () => { const oidcManager = new OidcManager({ db: { smartdataDb: {} }, typedrouter: { addTypedRouter: () => undefined }, options: { baseUrl: 'https://idp.example' }, } as any); void oidcManager.stop(); return oidcManager; }; tap.test('stores authorization codes as hashes and marks them used', async () => { const authCode = new OidcAuthorizationCode(); authCode.id = 'oidc-auth-code'; authCode.data.codeHash = OidcAuthorizationCode.hashCode('plain-auth-code'); let saveCount = 0; (authCode as OidcAuthorizationCode & { save: () => Promise }).save = async () => { saveCount++; }; expect(authCode.matchesCode('plain-auth-code')).toBeTrue(); expect(authCode.matchesCode('wrong-code')).toBeFalse(); await authCode.markUsed(); expect(authCode.data.used).toBeTrue(); expect(saveCount).toEqual(1); }); tap.test('stores access tokens without plaintext persistence', async () => { const accessToken = new OidcAccessToken(); accessToken.id = 'oidc-access-token'; accessToken.data.tokenHash = OidcAccessToken.hashToken('plain-access-token'); accessToken.data.expiresAt = Date.now() + 60_000; expect(accessToken.matchesToken('plain-access-token')).toBeTrue(); expect(accessToken.matchesToken('different-access-token')).toBeFalse(); expect(accessToken.isExpired()).toBeFalse(); }); tap.test('revokes persisted refresh tokens', async () => { const refreshToken = new OidcRefreshToken(); refreshToken.id = 'oidc-refresh-token'; refreshToken.data.tokenHash = OidcRefreshToken.hashToken('plain-refresh-token'); refreshToken.data.expiresAt = Date.now() + 60_000; let saveCount = 0; (refreshToken as OidcRefreshToken & { save: () => Promise }).save = async () => { saveCount++; }; expect(refreshToken.matchesToken('plain-refresh-token')).toBeTrue(); expect(refreshToken.data.revoked).toBeFalse(); await refreshToken.revoke(); expect(refreshToken.data.revoked).toBeTrue(); expect(saveCount).toEqual(1); }); tap.test('merges user consent scopes without duplicates', async () => { const consent = new OidcUserConsent(); consent.id = 'oidc-consent'; consent.data.userId = 'user-1'; consent.data.clientId = 'client-1'; consent.data.scopes = ['openid']; let saveCount = 0; (consent as OidcUserConsent & { save: () => Promise }).save = async () => { saveCount++; }; await consent.grantScopes(['openid', 'email', 'profile']); expect(consent.data.scopes.sort()).toEqual(['email', 'openid', 'profile']); expect(consent.data.grantedAt).toBeGreaterThan(0); expect(consent.data.updatedAt).toBeGreaterThan(0); expect(saveCount).toEqual(1); }); tap.test('builds an OAuth redirect URL after successful authorization completion', async () => { const oidcManager = createTestOidcManager(); (oidcManager as any).findAppByClientId = async () => ({ data: { name: 'Example App', appUrl: 'https://app.example', logoUrl: 'https://app.example/logo.png', oauthCredentials: { clientId: 'client-1', redirectUris: ['https://app.example/callback'], allowedScopes: ['openid', 'profile', 'email'], }, }, }); (oidcManager as any).generateAuthorizationCode = async () => 'generated-auth-code'; (oidcManager as any).getUserConsent = async () => ({ data: { scopes: ['openid', 'profile', 'email'], }, }); (oidcManager as any).upsertUserConsent = async () => undefined; const result = await oidcManager.completeAuthorizationForUser('user-1', { clientId: 'client-1', redirectUri: 'https://app.example/callback', scope: 'openid profile email', state: 'xyz-state', codeChallenge: 'challenge', codeChallengeMethod: 'S256', nonce: 'nonce-1', consentApproved: true, }); expect(result.code).toEqual('generated-auth-code'); expect(result.redirectUrl).toEqual( 'https://app.example/callback?code=generated-auth-code&state=xyz-state' ); await oidcManager.stop(); }); tap.test('prepares OAuth consent when scopes are not yet granted', async () => { const oidcManager = createTestOidcManager(); (oidcManager as any).findAppByClientId = async () => ({ data: { name: 'Example App', appUrl: 'https://app.example', logoUrl: 'https://app.example/logo.png', oauthCredentials: { clientId: 'client-1', redirectUris: ['https://app.example/callback'], allowedScopes: ['openid', 'profile', 'email'], }, }, }); (oidcManager as any).getUserConsent = async () => ({ data: { scopes: ['openid'], }, }); const result = await oidcManager.prepareAuthorizationForUser('user-1', { clientId: 'client-1', redirectUri: 'https://app.example/callback', scope: 'openid profile email', state: 'xyz-state', prompt: undefined, codeChallenge: undefined, codeChallengeMethod: undefined, nonce: undefined, }); expect(result.status).toEqual('consent_required'); expect(result.requestedScopes.sort()).toEqual(['email', 'openid', 'profile']); expect(result.grantedScopes).toEqual(['openid']); await oidcManager.stop(); }); tap.test('prepares OAuth authorization as ready when consent already exists', async () => { const oidcManager = createTestOidcManager(); (oidcManager as any).findAppByClientId = async () => ({ data: { name: 'Example App', appUrl: 'https://app.example', logoUrl: 'https://app.example/logo.png', oauthCredentials: { clientId: 'client-1', redirectUris: ['https://app.example/callback'], allowedScopes: ['openid', 'profile', 'email'], }, }, }); (oidcManager as any).getUserConsent = async () => ({ data: { scopes: ['openid', 'profile', 'email'], }, }); const result = await oidcManager.prepareAuthorizationForUser('user-1', { clientId: 'client-1', redirectUri: 'https://app.example/callback', scope: 'openid profile email', state: 'xyz-state', prompt: undefined, codeChallenge: undefined, codeChallengeMethod: undefined, nonce: undefined, }); expect(result.status).toEqual('ready'); await oidcManager.stop(); }); export default tap.start();