mediaDrop/tests/oidc.test.ts
Nicolas fe8bd26bb7 Initial commit: mediaDrop — serveur de dépôt de fichiers médias
Serveur TypeScript (Fastify 5) permettant de déposer des fichiers médias
dans des dossiers nommés, via une page web de glisser-déposer.

- Authentification OpenID Connect gérée côté serveur (Authorization Code +
  PKCE, client confidentiel, openid-client) ; session via cookie chiffré
  (@fastify/secure-session).
- Configuration dans un fichier JSON (schéma fourni) ; l'environnement ne
  porte que le pointeur MEDIADROP_CONFIG.
- Cibles de dépôt nommées : liste { name, label, dir }, sélection par
  ?target=, première cible par défaut.
- Upload multipart en streaming : nom de fichier dérivé côté serveur (pas
  de path traversal), liste blanche de types MIME, plafond de taille.
- TypeScript strict, ESLint + Prettier sans warning, 46 tests Vitest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 23:06:53 +02:00

78 lines
2.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { UnauthorizedError } from '../src/errors.js';
import { createOidcClient, type OidcClient } from '../src/oidc.js';
import { makeConfig, startStubIdp, type StubIdp } from './helpers.js';
let idp: StubIdp;
let oidc: OidcClient;
const REDIRECT_URI = 'http://localhost:3000/auth/callback';
const CODE_VERIFIER = 'a'.repeat(64);
beforeAll(async () => {
idp = await startStubIdp();
oidc = await createOidcClient(
makeConfig({
oidc: {
issuer: idp.issuer,
clientId: idp.clientId,
clientSecret: idp.clientSecret,
scopes: ['openid', 'profile', 'email'],
},
}),
);
});
afterAll(async () => {
await idp.close();
});
describe('createOidcClient', () => {
it('découvre un issuer en http (stub) et expose le scope', () => {
expect(oidc.scope).toBe('openid profile email');
});
it('construit une URL d’autorisation PKCE', () => {
const url = oidc.authorizationUrl({
redirectUri: REDIRECT_URI,
state: 's1',
nonce: 'n1',
codeChallenge: 'challenge',
});
expect(url.origin).toBe(idp.issuer);
expect(url.searchParams.get('response_type')).toBe('code');
expect(url.searchParams.get('state')).toBe('s1');
expect(url.searchParams.get('code_challenge_method')).toBe('S256');
expect(url.searchParams.get('redirect_uri')).toBe(REDIRECT_URI);
});
it('échange le code et renvoie l’identité', async () => {
idp.setNextIdToken({ nonce: 'n2', sub: 'zoe', name: 'Zoé' });
const user = await oidc.handleCallback({
currentUrl: new URL(`${REDIRECT_URI}?code=abc&state=s2`),
state: 's2',
nonce: 'n2',
codeVerifier: CODE_VERIFIER,
});
expect(user).toEqual({ sub: 'zoe', name: 'Zoé' });
});
it('rejette un nonce qui ne correspond pas', async () => {
idp.setNextIdToken({ nonce: 'autre', sub: 'zoe' });
await expect(
oidc.handleCallback({
currentUrl: new URL(`${REDIRECT_URI}?code=abc&state=s3`),
state: 's3',
nonce: 'n3',
codeVerifier: CODE_VERIFIER,
}),
).rejects.toBeInstanceOf(UnauthorizedError);
});
it('expose une URL de déconnexion RP-initiated', () => {
const url = oidc.endSessionUrl('http://localhost:3000/');
expect(url?.origin).toBe(idp.issuer);
expect(url?.searchParams.get('post_logout_redirect_uri')).toBe('http://localhost:3000/');
});
});