// Auteur : Simon-Pierre Boucher — contact@spboucher.ai import crypto from "crypto"; // KA ID — registre des plateformes autorisées à déléguer leur connexion // au hub (« Se connecter avec KA »). Chaque client a son secret partagé // (env) et une liste blanche de redirect_uri. export const SSO_CLIENTS: Record< string, { label: string; redirectPrefixes: string[]; secretEnv: string } > = { "lou-ka": { label: "Lou·Ka", redirectPrefixes: ["https://www.lou-ka.com/", "http://localhost:8095/"], secretEnv: "KA_SSO_SECRET_LOU_KA", }, "immo-ka": { label: "Immo·Ka", redirectPrefixes: ["https://www.immo-ka.com/"], secretEnv: "KA_SSO_SECRET_IMMO_KA", }, "vrai-prix": { label: "Vrai-Prix", redirectPrefixes: ["https://www.vrai-prix.com/"], secretEnv: "KA_SSO_SECRET_VRAI_PRIX", }, valoplex: { label: "ValoPlex", redirectPrefixes: ["https://www.valoplex.com/"], secretEnv: "KA_SSO_SECRET_VALOPLEX", }, "auto-ka": { label: "Auto·Ka", redirectPrefixes: ["https://www.auto-ka.com/"], secretEnv: "KA_SSO_SECRET_AUTO_KA", }, "fabri-ka": { label: "Fabri·Ka", redirectPrefixes: ["https://www.fabri-ka.com/"], secretEnv: "KA_SSO_SECRET_FABRI_KA", }, "food-ka": { label: "Food·Ka", redirectPrefixes: ["https://www.food-ka.com/"], secretEnv: "KA_SSO_SECRET_FOOD_KA", }, "ora-ka": { label: "Ora·Ka", redirectPrefixes: ["https://www.ora-ka.com/"], secretEnv: "KA_SSO_SECRET_ORA_KA", }, }; /** * Vérifie une requête serveur-à-serveur d'une plateforme : * sig = HMAC-SHA256(secret_client, `${client_id}.${ka_id}.${ts}`), ts ±5 min. * Retourne le client si valide, sinon null. */ export function verifyClientSig( clientId: string, kaId: string, ts: string, sig: string, ): { label: string } | null { const client = SSO_CLIENTS[clientId]; const secret = client ? process.env[client.secretEnv] : undefined; if (!client || !secret) return null; if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return null; const expected = crypto .createHmac("sha256", secret) .update(`${clientId}.${kaId}.${ts}`) .digest("hex"); const a = Buffer.from(sig, "hex"); const b = Buffer.from(expected, "hex"); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null; return client; }