spb/groupe-ka Public
Groupe KA — site du holding + KA ID (compte unique & SSO des 7 plateformes). Next.js 16, SQLite, Google & Apple login.
TypeScript 93.9%
CSS 6%
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2import crypto from "crypto";3// KA ID — registre des plateformes autorisées à déléguer leur connexion4// au hub (« Se connecter avec KA »). Chaque client a son secret partagé5// (env) et une liste blanche de redirect_uri.6export const SSO_CLIENTS: Record<7 string,8 { label: string; redirectPrefixes: string[]; secretEnv: string }9> = {10 "lou-ka": {11 label: "Lou·Ka",12 redirectPrefixes: ["https://www.lou-ka.com/", "http://localhost:8095/"],13 secretEnv: "KA_SSO_SECRET_LOU_KA",14 },15 "immo-ka": {16 label: "Immo·Ka",17 redirectPrefixes: ["https://www.immo-ka.com/"],18 secretEnv: "KA_SSO_SECRET_IMMO_KA",19 },20 "vrai-prix": {21 label: "Vrai-Prix",22 redirectPrefixes: ["https://www.vrai-prix.com/"],23 secretEnv: "KA_SSO_SECRET_VRAI_PRIX",24 },25 valoplex: {26 label: "ValoPlex",27 redirectPrefixes: ["https://www.valoplex.com/"],28 secretEnv: "KA_SSO_SECRET_VALOPLEX",29 },30 "auto-ka": {31 label: "Auto·Ka",32 redirectPrefixes: ["https://www.auto-ka.com/"],33 secretEnv: "KA_SSO_SECRET_AUTO_KA",34 },35 "fabri-ka": {36 label: "Fabri·Ka",37 redirectPrefixes: ["https://www.fabri-ka.com/"],38 secretEnv: "KA_SSO_SECRET_FABRI_KA",39 },40 "food-ka": {41 label: "Food·Ka",42 redirectPrefixes: ["https://www.food-ka.com/"],43 secretEnv: "KA_SSO_SECRET_FOOD_KA",44 },45 "ora-ka": {46 label: "Ora·Ka",47 redirectPrefixes: ["https://www.ora-ka.com/"],48 secretEnv: "KA_SSO_SECRET_ORA_KA",49 },50};5152/**53 * Vérifie une requête serveur-à-serveur d'une plateforme :54 * sig = HMAC-SHA256(secret_client, `${client_id}.${ka_id}.${ts}`), ts ±5 min.55 * Retourne le client si valide, sinon null.56 */57export function verifyClientSig(58 clientId: string,59 kaId: string,60 ts: string,61 sig: string,62): { label: string } | null {63 const client = SSO_CLIENTS[clientId];64 const secret = client ? process.env[client.secretEnv] : undefined;65 if (!client || !secret) return null;66 if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300)67 return null;68 const expected = crypto69 .createHmac("sha256", secret)70 .update(`${clientId}.${kaId}.${ts}`)71 .digest("hex");72 const a = Buffer.from(sig, "hex");73 const b = Buffer.from(expected, "hex");74 if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null;75 return client;76}77