feat(auth): add abuse protection for login and OIDC flows with consent-based authorization handling

This commit is contained in:
2026-04-20 09:46:13 +00:00
parent 21f5abb49b
commit 29a21fd3b3
36 changed files with 1129 additions and 84 deletions
+132
View File
@@ -2,9 +2,20 @@ 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';
@@ -73,4 +84,125 @@ tap.test('merges user consent scopes without duplicates', async () => {
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();