From d8b2a8fe6fa565e312f1bb0d400ddecf2522e5c6 Mon Sep 17 00:00:00 2001 From: Manus Date: Mon, 17 Aug 2026 20:25:16 +0000 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Correction=20compl=C3=A9mentaire?= =?UTF-8?q?=20:=20normalisation=20stricte=20des=20valeurs=20OAuth=20de=20l?= =?UTF-8?q?oginMethod=20avant=20insertion=20DB,=20afin=20d=E2=80=99=C3=A9v?= =?UTF-8?q?iter=20les=20erreurs=20de=20troncature=20caus=C3=A9es=20par=20l?= =?UTF-8?q?es=20identifiants=20de=20plateforme=20externes.=20Ajout=20de=20?= =?UTF-8?q?tests=20associ=C3=A9s=20et=20validation=20build=20compl=C3=A8te?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/_core/oauth.ts | 3 ++- server/_core/sdk.ts | 3 ++- server/authMethod.test.ts | 19 +++++++++++++++++++ server/authMethod.ts | 23 +++++++++++++++++++++++ todo.md | 1 + 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 server/authMethod.test.ts create mode 100644 server/authMethod.ts diff --git a/server/_core/oauth.ts b/server/_core/oauth.ts index 67e9af2..1745f95 100644 --- a/server/_core/oauth.ts +++ b/server/_core/oauth.ts @@ -1,6 +1,7 @@ import { COOKIE_NAME, ONE_YEAR_MS } from "@shared/const"; import type { Express, Request, Response } from "express"; import * as db from "../db"; +import { normalizeLoginMethod } from "../authMethod"; import { getSessionCookieOptions } from "./cookies"; import { sdk } from "./sdk"; @@ -32,7 +33,7 @@ export function registerOAuthRoutes(app: Express) { openId: userInfo.openId, name: userInfo.name || null, email: userInfo.email ?? "unknown@example.com", - loginMethod: (userInfo.loginMethod ?? userInfo.platform ?? "manus") as "manus" | "local" | "azure-ad", + loginMethod: normalizeLoginMethod(userInfo.loginMethod ?? userInfo.platform), lastSignedIn: new Date(), }); diff --git a/server/_core/sdk.ts b/server/_core/sdk.ts index eae78f6..f3f2a3a 100644 --- a/server/_core/sdk.ts +++ b/server/_core/sdk.ts @@ -2,6 +2,7 @@ import { AXIOS_TIMEOUT_MS, COOKIE_NAME, ONE_YEAR_MS } from "@shared/const"; import { ForbiddenError } from "@shared/_core/errors"; import axios, { type AxiosInstance } from "axios"; import { parse as parseCookieHeader } from "cookie"; +import { normalizeLoginMethod } from "../authMethod"; import type { Request } from "express"; import { SignJWT, jwtVerify } from "jose"; import type { User } from "../../drizzle/schema"; @@ -296,7 +297,7 @@ class SDKServer { openId: userInfo.openId, name: userInfo.name || null, email: userInfo.email ?? "unknown@example.com", - loginMethod: (userInfo.loginMethod ?? userInfo.platform ?? "manus") as "manus" | "local" | "azure-ad", + loginMethod: normalizeLoginMethod(userInfo.loginMethod ?? userInfo.platform), lastSignedIn: signedInAt, }); user = await db.getUserByOpenId(userInfo.openId); diff --git a/server/authMethod.test.ts b/server/authMethod.test.ts new file mode 100644 index 0000000..ef3d5ff --- /dev/null +++ b/server/authMethod.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest"; +import { normalizeLoginMethod } from "./authMethod"; + +describe("normalizeLoginMethod", () => { + it("conserve les valeurs de l’enum DB", () => { + expect(normalizeLoginMethod("local")).toBe("local"); + expect(normalizeLoginMethod("azure-ad")).toBe("azure-ad"); + }); + + it("convertit les identifiants de fournisseur Microsoft", () => { + expect(normalizeLoginMethod("Microsoft OAuth")).toBe("azure-ad"); + expect(normalizeLoginMethod("AZURE_ENTRA")).toBe("azure-ad"); + }); + + it("utilise Manus pour toute valeur inconnue au lieu d’échouer en base", () => { + expect(normalizeLoginMethod("platform-v2")).toBe("manus"); + expect(normalizeLoginMethod(undefined)).toBe("manus"); + }); +}); diff --git a/server/authMethod.ts b/server/authMethod.ts new file mode 100644 index 0000000..9cc7a45 --- /dev/null +++ b/server/authMethod.ts @@ -0,0 +1,23 @@ +/** Values accepted by the `users.loginMethod` database enum. */ +export type LoginMethod = "manus" | "local" | "azure-ad"; + +const LOGIN_METHODS = new Set(["manus", "local", "azure-ad"]); + +/** + * Maps provider-specific identifiers to the limited database enum. + * OAuth identity payloads are external input and must never be persisted verbatim. + */ +export function normalizeLoginMethod(value: unknown): LoginMethod { + if (typeof value !== "string") return "manus"; + + const normalized = value.trim().toLowerCase(); + if (LOGIN_METHODS.has(normalized as LoginMethod)) { + return normalized as LoginMethod; + } + + if (normalized.includes("azure") || normalized.includes("microsoft")) { + return "azure-ad"; + } + + return "manus"; +} diff --git a/todo.md b/todo.md index 1e91fed..37593e2 100644 --- a/todo.md +++ b/todo.md @@ -701,3 +701,4 @@ - [x] Renforcer les contrôles d’accès et la gestion d’erreur des endpoints critiques - [x] Documenter les modules métier, les invariants et les décisions techniques critiques - [x] Ajouter des tests de non-régression ciblés et vérifier build, types et tests +- [x] Normaliser les valeurs OAuth de loginMethod avant écriture en base