mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
0b8ddd7dcb
DAY-SVC-048 and DAY-DEL-011 pinned the old reorder, which handed a day without a date the first slot's date; they now expect it to keep none and the day it passes to keep its own. OIDC-SSRF-003 reads oidc.service.ts as text and missed the token call once Prettier put a trailing comma after its hop budget of 0; the pattern takes the comma.
50 lines
2.2 KiB
TypeScript
50 lines
2.2 KiB
TypeScript
/**
|
|
* The four OIDC provider calls go through the SSRF guard.
|
|
*
|
|
* Three of them are worse than they look: `discover` fetches a URL an admin
|
|
* configured, but the token, userinfo and JWKS URLs come out of the RESPONSE to
|
|
* that first call. They are chosen by the other end, not by anyone here, and
|
|
* until now all four went to a bare `fetch`.
|
|
*
|
|
* Deliberately a separate file. oidc.service.test.ts stubs global fetch and
|
|
* mocks the guard away so it can test OIDC logic; that is the right trade there
|
|
* and it is also exactly why the guard needs pinning somewhere it is not mocked.
|
|
*
|
|
* safeFetchAdminConfigured rather than safeFetch: a self-hoster running Authentik
|
|
* on http://localhost:9000 must keep working, so loopback and LAN stay allowed
|
|
* while link-local and the cloud-metadata range never are.
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
const SOURCE = readFileSync(join(__dirname, '../../../src/nest/oidc/oidc.service.ts'), 'utf8');
|
|
|
|
describe('OIDC outbound calls', () => {
|
|
it('OIDC-SSRF-001: no bare fetch( is left in the service', () => {
|
|
// A grep, and that is the point: the failure this prevents is somebody adding
|
|
// a fifth provider call next year with the same `await fetch(...)` the other
|
|
// four used to have. A behavioural test would only cover the four that exist.
|
|
const bare = SOURCE.split('\n')
|
|
.map((line, i) => ({ line: line.trim(), n: i + 1 }))
|
|
.filter(({ line }) => /\bawait fetch\(|[^.\w]fetch\(/.test(line) && !line.startsWith('*'));
|
|
|
|
expect(bare.map((b) => `${b.n}: ${b.line.slice(0, 70)}`)).toEqual([]);
|
|
});
|
|
|
|
it('OIDC-SSRF-002: all four calls name the guard', () => {
|
|
const guarded = SOURCE.match(/safeFetchAdminConfigured\(/g) ?? [];
|
|
|
|
// discover, exchangeCodeForToken, getUserInfo, fetchJwks.
|
|
expect(guarded).toHaveLength(4);
|
|
});
|
|
|
|
it('OIDC-SSRF-003: the token exchange follows no redirect at all', () => {
|
|
// A redirect there would hand client_secret to a second host, and the
|
|
// platform default is to follow. The third argument is the hop budget.
|
|
const tokenCall = /safeFetchAdminConfigured\(\s*doc\.token_endpoint[\s\S]*?\}\s*,\s*0\s*,?\s*\)/.test(SOURCE);
|
|
|
|
expect(tokenCall).toBe(true);
|
|
});
|
|
});
|