@liorian/oauth-kit 0.4.0-alpha.754ee48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @liorian/oauth-kit
2
+
3
+ Client OAuth/SSO **côté serveur** pour les backends tiers (ex. `liorian-api-connect`,
4
+ `liorian-api-store`, applications externes).
5
+
6
+ Il permet de **vérifier et valider une session utilisateur** auprès de
7
+ `liorian-api-core` — appareil + session + consentement — et de récupérer les
8
+ informations de l'utilisateur (profil, e-mail, organisations, **rôles et
9
+ permissions**) **uniquement dans la limite de l'accord donné par l'utilisateur
10
+ lors de la connexion SSO** (scopes).
11
+
12
+ Le backend tiers ne possède que sa **clé API applicative** (clé publique + secret).
13
+ Il n'a jamais accès aux secrets, aux données critiques ni à la base de données de
14
+ `liorian-api-core` : **toute la sécurité est exécutée par `liorian-api-core`**.
15
+
16
+ ## Pourquoi ce package ?
17
+
18
+ - `liorian-api-core` concentre toutes les vérifications : signature HMAC de la clé
19
+ API, validité du jeton JWT, validation de l'appareil (`UserDevice`), validation de
20
+ la session persistée (`Session`), statut de l'utilisateur, et **consentement
21
+ utilisateur** (scopes).
22
+ - Le backend tiers ne reçoit **aucune donnée non couverte par le consentement** :
23
+ pas de hash de mot de passe, pas de jetons, pas de clés, pas d'`auditId`, pas
24
+ d'informations internes.
25
+ - Le backend tiers ne partage **aucun secret critique** (JWT_SECRET, clé de
26
+ chiffrement, secrets de session).
27
+
28
+ ## Workflow
29
+
30
+ ```
31
+ Frontend (app tierce) Backend tiers liorian-api-core
32
+ │ │ │
33
+ │ 1. login SSO (Autorisation Code + PKCE) │
34
+ │─────────────────────────────LIORIAN AUTH──────────────────▶│
35
+ │◀─────────────────── access_token (JWT) ──────────────────────│
36
+ │ │
37
+ │ 2. Bearer token envoyé au backend tiers │
38
+ │─────────────────────────────▶│ │
39
+ │ │ 3. verifySession(token) │
40
+ │ │ + clé API + signature HMAC │
41
+ │ │──────────────────────────────▶│
42
+ │ │ 4. valide l'appareil (UserDevice) │
43
+ │ │ 5. valide la session (Session) │
44
+ │ │ 6. vérifie le consentement │
45
+ │ │ 7. renvoie utilisateur + rôles │
46
+ │ │ + permissions (scopes) │
47
+ │ │◀───────────────────────────────│
48
+ ```
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ bun add @liorian/oauth-kit # (workspace monorepo : bun add @liorian/oauth-kit -w ... )
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ ```ts
59
+ import {LiorianOAuthClient} from '@liorian/oauth-kit';
60
+
61
+ const oauth = new LiorianOAuthClient({
62
+ baseUrl: 'https://api.liorian.local/api', // ou LIORIAN_OAUTH_API_URL
63
+ apiKey: 'aaak-application-<hash>-sen', // ou LIORIAN_OAUTH_API_KEY
64
+ apiSecret: '<secret applicatif>', // ou LIORIAN_OAUTH_API_SECRET
65
+ clientId: 'mon-application', // ou LIORIAN_OAUTH_CLIENT_ID
66
+ });
67
+ ```
68
+
69
+ Variables d'environnement supportées :
70
+
71
+ | Variable | Rôle |
72
+ | --- | --- |
73
+ | `LIORIAN_OAUTH_API_URL` | URL de base de `liorian-api-core` (défaut `https://localhost:5711/api`) |
74
+ | `LIORIAN_OAUTH_API_KEY` | Clé API publique de l'application (`aaak-...-sen`) |
75
+ | `LIORIAN_OAUTH_API_SECRET` | Secret de la clé API (signature HMAC) |
76
+ | `LIORIAN_OAUTH_CLIENT_ID` | Identifiant OAuth de l'application |
77
+ | `LIORIAN_OAUTH_AUTH_HOST` | Hôte de l'IdP Liorian Auth pour `buildAuthorizationUrl` |
78
+
79
+ ## Vérifier une session
80
+
81
+ ```ts
82
+ import {OAuthVerificationError} from '@liorian/oauth-kit';
83
+
84
+ async function handler(req) {
85
+ const token = req.headers.authorization?.replace(/^Bearer\s+/i, '');
86
+ try {
87
+ const result = await oauth.verifySession(token, {
88
+ userAgent: req.headers['user-agent'],
89
+ });
90
+ // result.user, result.roles, result.permissions, result.organizations…
91
+ } catch (e) {
92
+ if (e instanceof OAuthVerificationError) {
93
+ // 401 : session invalide / pas de consentement / clé API refusée
94
+ throw e;
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Exemple de réponse
101
+
102
+ ```json
103
+ {
104
+ "authenticated": true,
105
+ "scopes": ["openid", "profile", "email", "roles", "permissions"],
106
+ "user": {
107
+ "sub": "…",
108
+ "username": "…",
109
+ "name": {"firstname": "…", "lastname": "…"},
110
+ "email": "…"
111
+ },
112
+ "organizations": [{"id": "…", "name": "…", "isFounder": true, "role": "ROOT"}],
113
+ "roles": [{"id": "…", "name": "ROOT", "level": 99.99, "organizationId": "…"}],
114
+ "permissions": {"Customer": {"create": true, "read": true, "update": true, "delete": true}},
115
+ "device": {"id": "…", "userAgent": "…", "expiresAt": "…"},
116
+ "session": {"expiresAt": "…", "lastActiveAt": "…"}
117
+ }
118
+ ```
119
+
120
+ ## Sécurité
121
+
122
+ - **Authentification de l'application** : challenge horodaté + signature HMAC-SHA256
123
+ (anti-rejeu, comparaison à temps constant côté API). Le secret ne circule jamais.
124
+ - **Validation de session complète** : JWT vérifié, `Session` persistée active et
125
+ non expirée, correspondance d'appareil (User-Agent), `UserDevice` non expiré,
126
+ utilisateur actif.
127
+ - **Consentement utilisateur obligatoire** : seuls les scopes accordés lors du SSO
128
+ (et autorisés pour l'application) sont renvoyés. Aucune donnée sensible
129
+ (mots de passe, jetons, internalités) n'est exposée.
130
+ - Le backend tiers ne reçoit **jamais** le `JWT_SECRET` ni les secrets de
131
+ `liorian-api-core`.
132
+
133
+ ## Licence
134
+
135
+ MIT
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@liorian/oauth-kit",
3
+ "version": "0.4.0-alpha.754ee48",
4
+ "description": "Client OAuth/SSO côté serveur pour les backends tiers.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./src/index.ts",
8
+ "module": "./src/index.ts",
9
+ "types": "./src/index.ts",
10
+ "exports": {
11
+ ".": "./src/index.ts",
12
+ "./*": "./src/*"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "dependencies": {},
22
+ "devDependencies": {
23
+ "@types/bun": "^1.1.0",
24
+ "typescript": "^6.0.3"
25
+ }
26
+ }
@@ -0,0 +1,94 @@
1
+ import {DEFAULT_SCOPES} from '../domain/oauth-scope.enum';
2
+ import {OAuthClientError} from '../domain/oauth-exception';
3
+ import {signatureService} from '../infrastructure/crypto/signature.service';
4
+ import {HttpClient} from '../infrastructure/http/http-client';
5
+ import type {AuthorizationUrlParams, LiorianOAuthOptions, VerifySessionOptions} from '../types/config.type';
6
+ import type {OAuthVerifyRequest, OAuthVerificationResult} from '../types/verification.types';
7
+
8
+ export const DEFAULT_OAUTH_API_URL = 'https://localhost:5711/api';
9
+ export const DEFAULT_OAUTH_TIMEOUT_MS = 15000;
10
+
11
+ /**
12
+ * Client SSO/OAuth de `@liorian/oauth-kit` à utiliser CÔTÉ SERVEUR par un backend
13
+ * tiers (liorian-api-connect, liorian-api-store, applications externes…).
14
+ *
15
+ * Le backend tiers possède uniquement sa clé API applicative (clé publique +
16
+ * secret) : il ne détient jamais les secrets ni les données sensibles de
17
+ * `liorian-api-core`. Toute la sécurité (validation de l'appareil, de la
18
+ * session, du consentement utilisateur) est exécutée par `liorian-api-core`.
19
+ */
20
+ export class LiorianOAuthClient {
21
+ private readonly baseUrl: string;
22
+ private readonly apiKey: string;
23
+ private readonly apiSecret: string;
24
+ private readonly clientId: string;
25
+ private readonly timeoutMs: number;
26
+ private readonly http: HttpClient;
27
+
28
+ constructor(options: LiorianOAuthOptions) {
29
+ this.baseUrl = (options.baseUrl ?? process.env['LIORIAN_OAUTH_API_URL'] ?? DEFAULT_OAUTH_API_URL).replace(/\/$/, '');
30
+ this.apiKey = options.apiKey ?? process.env['LIORIAN_OAUTH_API_KEY'] ?? '';
31
+ this.apiSecret = options.apiSecret ?? process.env['LIORIAN_OAUTH_API_SECRET'] ?? '';
32
+ this.clientId = options.clientId ?? process.env['LIORIAN_OAUTH_CLIENT_ID'] ?? '';
33
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_OAUTH_TIMEOUT_MS;
34
+ this.http = new HttpClient(this.baseUrl, this.timeoutMs);
35
+
36
+ if (!this.apiKey || !this.apiSecret) {
37
+ throw new OAuthClientError(
38
+ '`@liorian/oauth-kit` requiert `apiKey` et `apiSecret` (ou les variables ' +
39
+ 'd’environnement LIORIAN_OAUTH_API_KEY / LIORIAN_OAUTH_API_SECRET).',
40
+ );
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Vérifie et valide la session d'un utilisateur connecté via le SSO Liorian.
46
+ *
47
+ * Appelle `POST /oauth/verify` de liorian-api-core qui :
48
+ * 1. authentifie l'application tierce (clé API + signature HMAC du challenge) ;
49
+ * 2. valide l'appareil (UserDevice) et la session persistée (Session) ;
50
+ * 3. vérifie que l'utilisateur a consenti à partager ses données avec cette
51
+ * application ;
52
+ * 4. ne renvoie que les données couvertes par le consentement (scopes).
53
+ *
54
+ * @param token Jeton de session de l'utilisateur (Bearer JWT émis au SSO).
55
+ */
56
+ async verifySession(token: string, opts: VerifySessionOptions = {}): Promise<OAuthVerificationResult> {
57
+ if (!token) {
58
+ throw new OAuthClientError('Le jeton de session à vérifier est requis.');
59
+ }
60
+
61
+ const challenge = signatureService.generateChallenge();
62
+ const signature = signatureService.sign(this.apiSecret, challenge);
63
+
64
+ const request: OAuthVerifyRequest = {
65
+ public_key: this.apiKey,
66
+ challenge,
67
+ signature,
68
+ token,
69
+ ...(opts.userAgent ? {user_agent: opts.userAgent} : {}),
70
+ ...(opts.ip ? {ip: opts.ip} : {}),
71
+ };
72
+
73
+ return this.http.post<OAuthVerificationResult>('/oauth/verify', request);
74
+ }
75
+
76
+ /**
77
+ * Construit l'URL d'autorisation SSO à destination du navigateur (frontend).
78
+ * L'application frontend redirige l'utilisateur vers l'IdP Liorian Auth.
79
+ */
80
+ buildAuthorizationUrl(params: AuthorizationUrlParams): string {
81
+ const base = process.env['LIORIAN_OAUTH_AUTH_HOST'] ?? process.env['LIORIAN_AUTH_HOST'] ?? 'https://localhost:5050';
82
+ const url = new URL('/auth/sign-in', base.replace(/\/$/, ''));
83
+ url.searchParams.set('response_type', params.responseType ?? 'code');
84
+ url.searchParams.set('client_id', params.clientId);
85
+ url.searchParams.set('redirect_uri', params.redirectUri);
86
+ url.searchParams.set('scope', params.scope ?? DEFAULT_SCOPES);
87
+ url.searchParams.set('state', params.state ?? '');
88
+ if (params.codeChallenge) {
89
+ url.searchParams.set('code_challenge', params.codeChallenge);
90
+ url.searchParams.set('code_challenge_method', params.codeChallengeMethod ?? 'S256');
91
+ }
92
+ return url.toString();
93
+ }
94
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Erreur technique du client `@liorian/oauth-kit` (réseau, timeout, réponse inattendue).
3
+ */
4
+ export class OAuthClientError extends Error {
5
+ constructor(message: string, options?: {cause?: unknown}) {
6
+ super(message, options);
7
+ this.name = 'OAuthClientError';
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Erreur métier renvoyée par liorian-api-core lors de la vérification
13
+ * d'une session (clé API invalide, session expirée, consentement révoqué…).
14
+ */
15
+ export class OAuthVerificationError extends Error {
16
+ /** Statut HTTP renvoyé par l'API. */
17
+ readonly status: number;
18
+ /** Code d'erreur optionnel renvoyé par l'API. */
19
+ readonly code?: string;
20
+
21
+ constructor(message: string, status = 401, code?: string) {
22
+ super(message);
23
+ this.name = 'OAuthVerificationError';
24
+ this.status = status;
25
+ this.code = code;
26
+ }
27
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Scopes OAuth (SSO Liorian) bien connus.
3
+ * Un backend tiers ne reçoit que les données couvertes par les scopes
4
+ * accordés par l'utilisateur lors de la connexion SSO (consentement).
5
+ */
6
+ export enum OAuthScope {
7
+ /** Identifiant unique de session (OIDC `sub`). */
8
+ OPENID = 'openid',
9
+ /** Nom d'utilisateur, nom complet et avatar. */
10
+ PROFILE = 'profile',
11
+ /** Adresse e-mail principale. */
12
+ EMAIL = 'email',
13
+ /** Organisations auxquelles l'utilisateur appartient. */
14
+ ORGANIZATIONS = 'organizations',
15
+ /** Rôles de l'utilisateur (par organisation). */
16
+ ROLES = 'roles',
17
+ /** Permissions agrégées de l'utilisateur (par organisation). */
18
+ PERMISSIONS = 'permissions',
19
+ }
20
+
21
+ /** Scopes par défaut demandés lors d'une connexion SSO standard. */
22
+ export const DEFAULT_SCOPES = `${OAuthScope.OPENID} ${OAuthScope.PROFILE} ${OAuthScope.EMAIL}`;
23
+
24
+ /** Durée de validité maximale acceptée pour le challenge de signature (anti-rejeu). */
25
+ export const CHALLENGE_TTL_MS = 2 * 60 * 1000;
26
+
27
+ /** Séparateur utilisé entre l'horodatage et le nonce dans un challenge. */
28
+ export const CHALLENGE_SEPARATOR = '.';
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ export {
2
+ LiorianOAuthClient,
3
+ DEFAULT_OAUTH_API_URL,
4
+ DEFAULT_OAUTH_TIMEOUT_MS,
5
+ } from './application/liorian-oauth.client';
6
+ export {OAuthScope, DEFAULT_SCOPES, CHALLENGE_TTL_MS, CHALLENGE_SEPARATOR} from './domain/oauth-scope.enum';
7
+ export {OAuthClientError, OAuthVerificationError} from './domain/oauth-exception';
8
+ export {SignatureService, signatureService} from './infrastructure/crypto/signature.service';
9
+ export {HttpClient} from './infrastructure/http/http-client';
10
+ export type {
11
+ LiorianOAuthOptions,
12
+ AuthorizationUrlParams,
13
+ VerifySessionOptions,
14
+ } from './types/config.type';
15
+ export type {SessionTokenPayload} from './types/session-token.type';
16
+ export type {
17
+ OAuthVerifyRequest,
18
+ OAuthVerificationResult,
19
+ OAuthVerifiedUser,
20
+ OAuthVerifiedOrganization,
21
+ OAuthVerifiedRole,
22
+ OAuthPermissionCapability,
23
+ OAuthVerifiedDevice,
24
+ OAuthVerifiedSession,
25
+ } from './types/verification.types';
@@ -0,0 +1,64 @@
1
+ import {createHmac, randomBytes, timingSafeEqual} from 'node:crypto';
2
+ import {CHALLENGE_SEPARATOR, CHALLENGE_TTL_MS} from '../../domain/oauth-scope.enum';
3
+
4
+ /**
5
+ * Service de signature HMAC-SHA256 utilisé pour authentifier les appels du
6
+ * backend tiers auprès de liorian-api-core.
7
+ *
8
+ * Le challenge est un nonce horodaté (`<timestamp>. <nonce>`). Sa signature
9
+ * avec le secret applicatif prouve la possession de la clé API sans jamais
10
+ * transmettre le secret, et sa vérification temporelle limite le rejeu.
11
+ */
12
+ export class SignatureService {
13
+ /**
14
+ * Génère un challenge frais : `<timestamp>.<nonce base64url>`.
15
+ */
16
+ generateChallenge(now: number = Date.now()): string {
17
+ const nonce = randomBytes(24).toString('base64url');
18
+ return `${now}${CHALLENGE_SEPARATOR}${nonce}`;
19
+ }
20
+
21
+ /**
22
+ * Signe le challenge avec le secret applicatif (HMAC-SHA256, base64).
23
+ * Algorithme identique à `CryptoService.hmacBase64` de `@liorian/api-resources`.
24
+ */
25
+ sign(secret: string, challenge: string): string {
26
+ return createHmac('sha256', secret).update(challenge).digest('base64');
27
+ }
28
+
29
+ /**
30
+ * Vérifie qu'un challenge est encore frais (anti-rejeu).
31
+ * Un challenge volé reste valable uniquement pendant `CHALLENGE_TTL_MS`.
32
+ */
33
+ isFresh(challenge: string, now: number = Date.now(), ttlMs: number = CHALLENGE_TTL_MS): boolean {
34
+ const separatorIndex = challenge.indexOf(CHALLENGE_SEPARATOR);
35
+ if (separatorIndex <= 0) {
36
+ return false;
37
+ }
38
+ const timestamp = Number(challenge.slice(0, separatorIndex));
39
+ if (!Number.isFinite(timestamp)) {
40
+ return false;
41
+ }
42
+ const delta = now - timestamp;
43
+ return delta >= 0 && delta <= ttlMs;
44
+ }
45
+
46
+ /**
47
+ * Compare deux signatures à temps constant (anti attaques par mesure du temps).
48
+ */
49
+ safeEqual(expected: string, actual: string): boolean {
50
+ if (expected.length !== actual.length) {
51
+ return false;
52
+ }
53
+ try {
54
+ return timingSafeEqual(
55
+ Buffer.from(expected, 'utf8'),
56
+ Buffer.from(actual, 'utf8'),
57
+ );
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ }
63
+
64
+ export const signatureService = new SignatureService();
@@ -0,0 +1,47 @@
1
+ import {OAuthClientError, OAuthVerificationError} from '../../domain/oauth-exception';
2
+ import type {RaitonErrorPayload} from '../../types/config.type';
3
+
4
+ /**
5
+ * Client HTTP minimal (fetch natif) vers liorian-api-core.
6
+ * Gère le décodage de `RaitonResponses` (`{message, data, status}`) et lève
7
+ * des erreurs typées.
8
+ */
9
+ export class HttpClient {
10
+ constructor(
11
+ private readonly baseUrl: string,
12
+ private readonly timeoutMs: number = 15000,
13
+ ) {}
14
+
15
+ async post<T>(path: string, body: unknown): Promise<T> {
16
+ const controller = new AbortController();
17
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
18
+
19
+ try {
20
+ const response = await fetch(`${this.baseUrl}${path}`, {
21
+ method: 'POST',
22
+ headers: {'content-type': 'application/json'},
23
+ body: JSON.stringify(body),
24
+ signal: controller.signal,
25
+ });
26
+
27
+ const payload = await response.json().catch(() => null) as (RaitonErrorPayload & {status?: number}) | null;
28
+
29
+ if (!response.ok) {
30
+ const message = payload?.message || `Erreur HTTP ${response.status}`;
31
+ throw new OAuthVerificationError(message, response.status, payload?.code);
32
+ }
33
+
34
+ return (payload?.data ?? payload) as T;
35
+ } catch (error) {
36
+ if (error instanceof OAuthVerificationError) {
37
+ throw error;
38
+ }
39
+ if (error instanceof Error && error.name === 'AbortError') {
40
+ throw new OAuthClientError(`Délai d'attente dépassé pour ${path}`, {cause: error});
41
+ }
42
+ throw new OAuthClientError(`Appel réseau échoué pour ${path}`, {cause: error});
43
+ } finally {
44
+ clearTimeout(timer);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,56 @@
1
+ import {OAuthScope} from "../domain/oauth-scope.enum";
2
+
3
+ /** Options de configuration du client `@liorian/oauth-kit`. */
4
+ export interface LiorianOAuthOptions {
5
+ /**
6
+ * URL de base de liorian-api-core (suffixe `/api` inclus).
7
+ * Défaut : `https://localhost:5711/api`.
8
+ */
9
+ baseUrl?: string;
10
+ /**
11
+ * Clé API publique de l'application enregistrée dans liorian-api-core
12
+ * (format `aaak-${type}-${hash}-sen`).
13
+ */
14
+ apiKey?: string;
15
+ /**
16
+ * Secret associé à la clé API (jamais exposé côté client/frontend).
17
+ * Utilisé uniquement pour signer le challenge HMAC.
18
+ */
19
+ apiSecret?: string;
20
+ /**
21
+ * Identifiant OAuth de l'application (`clientId`). Sert à construire
22
+ * l'URL d'autorisation destinée au navigateur.
23
+ */
24
+ clientId?: string;
25
+ /** Timeout des appels HTTP (millisecondes). Défaut : 15000. */
26
+ timeoutMs?: number;
27
+ }
28
+
29
+ /** Paramètres de construction de l'URL d'autorisation SSO (frontend). */
30
+ export interface AuthorizationUrlParams {
31
+ clientId: string;
32
+ redirectUri: string;
33
+ scope?: string;
34
+ state?: string;
35
+ codeChallenge?: string;
36
+ codeChallengeMethod?: 'S256' | 'plain';
37
+ responseType?: 'code';
38
+ }
39
+
40
+ /** Forme du message d'erreur standardisé de RaitonResponses. */
41
+ export interface RaitonErrorPayload {
42
+ message?: string;
43
+ code?: string;
44
+ data?: unknown;
45
+ }
46
+
47
+ export interface VerifySessionOptions {
48
+ /**
49
+ * User-Agent observé par le backend tiers au moment de la requête
50
+ * utilisateur. Permet à liorian-api-core de faire correspondre
51
+ * l'empreinte de l'appareil attendue avec la session.
52
+ */
53
+ userAgent?: string;
54
+ /** Adresse IP observée par le backend tiers (journalisable par l'API). */
55
+ ip?: string;
56
+ }
@@ -0,0 +1,13 @@
1
+ /** Claims portés par le jeton de session JWT émis par liorian-api-core au sign-in SSO. */
2
+ export interface SessionTokenPayload {
3
+ id: string;
4
+ username?: string;
5
+ email?: string;
6
+ device?: string;
7
+ userAgent?: string;
8
+ sub?: string;
9
+ iss?: string;
10
+ aud?: string;
11
+ iat?: number;
12
+ exp?: number;
13
+ }
@@ -0,0 +1,93 @@
1
+ import {OAuthScope} from "../domain/oauth-scope.enum";
2
+
3
+ /** Corps de la requête envoyé à `POST /oauth/verify` de liorian-api-core. */
4
+ export interface OAuthVerifyRequest {
5
+ /** Clé API publique de l'application tierce (format `aaak-...-sen`). */
6
+ public_key: string;
7
+ /** Challenge nonce horodaté (`<ts>.<nonce>`) signé en HMAC-SHA256. */
8
+ challenge: string;
9
+ /** Signature HMAC-SHA256 (base64) du challenge avec le secret applicatif. */
10
+ signature: string;
11
+ /** Jeton de session de l'utilisateur (Bearer JWT émis au SSO). */
12
+ token: string;
13
+ /** User-Agent observé par le backend tiers (validation de l'appareil). */
14
+ user_agent?: string;
15
+ /** Adresse IP observée par le backend tiers. */
16
+ ip?: string;
17
+ }
18
+
19
+ /** Identité minimale de l'utilisateur, filtrée par les scopes accordés. */
20
+ export interface OAuthVerifiedUser {
21
+ /** Identifiant unique (OIDC `sub`), accordé par le scope `openid`. */
22
+ sub: string;
23
+ /** Alias technique, renvoyé uniquement si le scope `profile` est accordé. */
24
+ username?: string;
25
+ /** Nom complet (prénom + nom), accordé par le scope `profile`. */
26
+ name?: {
27
+ firstname?: string;
28
+ lastname?: string;
29
+ fullname?: string;
30
+ };
31
+ /** Adresse e-mail, renvoyée uniquement si le scope `email` est accordé. */
32
+ email?: string;
33
+ /** Avatar public, renvoyé uniquement si le scope `profile` est accordé. */
34
+ avatar?: {
35
+ id?: string;
36
+ filename?: string;
37
+ type?: string;
38
+ label?: string;
39
+ isPublic?: boolean;
40
+ createdAt?: string;
41
+ };
42
+ }
43
+
44
+ /** Organisation membre, renvoyée uniquement si le scope `organizations` est accordé. */
45
+ export interface OAuthVerifiedOrganization {
46
+ id?: string;
47
+ name?: string;
48
+ isFounder?: boolean;
49
+ /** Nom du rôle dominant dans l'organisation si le scope `roles` est aussi accordé. */
50
+ role?: string;
51
+ }
52
+
53
+ /** Rôle de l'utilisateur dans une organisation (scope `roles`). */
54
+ export interface OAuthVerifiedRole {
55
+ id?: string;
56
+ name: string;
57
+ level?: number;
58
+ organizationId?: string;
59
+ }
60
+
61
+ /** Capacité CRUD d'un domaine (scope `permissions`). */
62
+ export interface OAuthPermissionCapability {
63
+ create: boolean;
64
+ read: boolean;
65
+ update: boolean;
66
+ delete: boolean;
67
+ }
68
+
69
+ /** Informations sur l'appareil validé (jamais le jeton associé). */
70
+ export interface OAuthVerifiedDevice {
71
+ id?: string;
72
+ userAgent?: string;
73
+ expiresAt?: string;
74
+ }
75
+
76
+ /** Informations sur la session validée (jamais le jeton associé). */
77
+ export interface OAuthVerifiedSession {
78
+ expiresAt?: string;
79
+ lastActiveAt?: string;
80
+ }
81
+
82
+ /** Résultat de la vérification d'une session par liorian-api-core. */
83
+ export interface OAuthVerificationResult {
84
+ authenticated: true;
85
+ /** Scopes effectivement accordés par le consentement (intersection client × consentement). */
86
+ scopes: OAuthScope[];
87
+ user: OAuthVerifiedUser;
88
+ organizations: OAuthVerifiedOrganization[];
89
+ roles: OAuthVerifiedRole[];
90
+ permissions: Record<string, OAuthPermissionCapability>;
91
+ device?: OAuthVerifiedDevice;
92
+ session?: OAuthVerifiedSession;
93
+ }