From 4f3c0e6433793a6ea6ec80aea1af4baf30a9a685 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Sat, 14 Mar 2026 06:40:43 +0100 Subject: [PATCH] feat: SuperOAuth token introspection middleware + /api/profile route --- backend/src/index.ts | 7 +++ backend/src/middleware/auth.middleware.ts | 69 +++++++++++++++++++++++ docker-compose.yml | 1 + 3 files changed, 77 insertions(+) create mode 100644 backend/src/middleware/auth.middleware.ts diff --git a/backend/src/index.ts b/backend/src/index.ts index 2ff113a..14e9589 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,6 +3,7 @@ import express from "express"; import cors from "cors"; import dotenv from "dotenv"; import { AppDataSource } from "./config/data-source"; +import { requireAuth, AuthenticatedRequest } from "./middleware/auth.middleware"; dotenv.config(); @@ -16,6 +17,12 @@ app.get("/api/health", (_req, res) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); }); +// Route protégée — valide l'intégration SuperOAuth end-to-end +app.get("/api/profile", requireAuth, (req, res) => { + const { user } = req as AuthenticatedRequest; + res.json({ success: true, data: { user } }); +}); + AppDataSource.initialize() .then(() => { console.log("Database connected"); diff --git a/backend/src/middleware/auth.middleware.ts b/backend/src/middleware/auth.middleware.ts new file mode 100644 index 0000000..cdd48c5 --- /dev/null +++ b/backend/src/middleware/auth.middleware.ts @@ -0,0 +1,69 @@ +import { Request, Response, NextFunction } from "express"; + +export interface AuthenticatedUser { + id: string; + email: string | null; + nickname: string; + isActive: boolean; + linkedProviders: string[]; +} + +export interface AuthenticatedRequest extends Request { + user: AuthenticatedUser; +} + +/** + * Middleware d'authentification — Token Introspection via SuperOAuth + * + * Valide le Bearer token auprès de SuperOAuth (POST /api/auth/token/validate). + * Aucun secret JWT partagé — SuperOAuth garde le contrôle total. + * + * Flow : + * 1. Extraire le Bearer token du header Authorization + * 2. Appeler SuperOAuth /api/auth/token/validate + * 3. Si valid → attacher req.user et continuer + * 4. Sinon → 401 + */ +export const requireAuth = async ( + req: Request, + res: Response, + next: NextFunction +): Promise => { + const token = req.headers.authorization?.split(" ")[1]; + + if (!token) { + res.status(401).json({ success: false, error: "UNAUTHORIZED", message: "Access token required" }); + return; + } + + const superOAuthUrl = process.env.SUPER_OAUTH_URL; + if (!superOAuthUrl) { + console.error("SUPER_OAUTH_URL not configured"); + res.status(500).json({ success: false, error: "INTERNAL_ERROR", message: "Auth service not configured" }); + return; + } + + try { + const response = await fetch(`${superOAuthUrl}/api/auth/token/validate`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token }), + }); + + const data = await response.json() as { + success: boolean; + data?: { valid: boolean; user?: AuthenticatedUser }; + error?: string; + }; + + if (!response.ok || !data.data?.valid || !data.data.user) { + res.status(401).json({ success: false, error: data.error ?? "INVALID_TOKEN", message: "Invalid or expired token" }); + return; + } + + (req as AuthenticatedRequest).user = data.data.user; + next(); + } catch { + res.status(500).json({ success: false, error: "AUTH_SERVICE_UNAVAILABLE", message: "Authentication service unreachable" }); + } +}; diff --git a/docker-compose.yml b/docker-compose.yml index c82e4ed..553317f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: DB_USER: ${DB_USER:-originsdigital} DB_PASSWORD: ${DB_PASSWORD} JWT_SECRET: ${JWT_SECRET} + SUPER_OAUTH_URL: ${SUPER_OAUTH_URL:-https://superoauth.tetardtek.com} depends_on: mysql: condition: service_healthy