From 9547720aa4468fe250ace05b4c2f52201163688f Mon Sep 17 00:00:00 2001 From: Mark Wylde Date: Tue, 10 Mar 2026 18:29:10 +0000 Subject: [PATCH 1/2] Fix OTP bypass in password change identity auth --- .../api/src/controllers/user/passwordAuth.ts | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/packages/api/src/controllers/user/passwordAuth.ts b/packages/api/src/controllers/user/passwordAuth.ts index a3b19f4..10479f8 100644 --- a/packages/api/src/controllers/user/passwordAuth.ts +++ b/packages/api/src/controllers/user/passwordAuth.ts @@ -1,6 +1,5 @@ import type { IncomingMessage } from "node:http"; -import { createRemoteJWKSet, jwtVerify } from "jose"; -import { UnauthorizedError, ValidationError } from "../../errors.ts"; +import { ValidationError } from "../../errors.ts"; import { requireSession } from "../../services/sessions.ts"; import type { Context } from "../../types.ts"; @@ -8,29 +7,9 @@ export async function requirePasswordChangeIdentity( context: Context, request: IncomingMessage ): Promise<{ sub: string; email: string }> { - try { - const session = await requireSession(context, request, false); - if (!session.sub || !session.email) { - throw new ValidationError("Invalid user session"); - } - return { sub: session.sub, email: session.email }; - } catch { - const authHeader = request.headers.authorization || ""; - const match = /^Bearer\s+(.+)$/.exec(authHeader); - if (!match?.[1]) { - throw new UnauthorizedError("No session cookie"); - } - try { - const jwks = createRemoteJWKSet(new URL(`${context.config.issuer}/.well-known/jwks.json`)); - const verified = await jwtVerify(match[1], jwks, { issuer: context.config.issuer }); - const sub = typeof verified.payload.sub === "string" ? verified.payload.sub : ""; - const email = typeof verified.payload.email === "string" ? verified.payload.email : ""; - if (!sub || !email) { - throw new UnauthorizedError("Invalid bearer token"); - } - return { sub, email }; - } catch { - throw new UnauthorizedError("Invalid bearer token"); - } + const session = await requireSession(context, request, false); + if (!session.sub || !session.email) { + throw new ValidationError("Invalid user session"); } + return { sub: session.sub, email: session.email }; } From e1c9b89e6cc7913d68ce159b57cedaedb8717b46 Mon Sep 17 00:00:00 2001 From: Mark Wylde Date: Tue, 10 Mar 2026 21:19:10 +0000 Subject: [PATCH 2/2] Restore bearer-token fallback for password change identity --- .../api/src/controllers/user/passwordAuth.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/api/src/controllers/user/passwordAuth.ts b/packages/api/src/controllers/user/passwordAuth.ts index 10479f8..a3b19f4 100644 --- a/packages/api/src/controllers/user/passwordAuth.ts +++ b/packages/api/src/controllers/user/passwordAuth.ts @@ -1,5 +1,6 @@ import type { IncomingMessage } from "node:http"; -import { ValidationError } from "../../errors.ts"; +import { createRemoteJWKSet, jwtVerify } from "jose"; +import { UnauthorizedError, ValidationError } from "../../errors.ts"; import { requireSession } from "../../services/sessions.ts"; import type { Context } from "../../types.ts"; @@ -7,9 +8,29 @@ export async function requirePasswordChangeIdentity( context: Context, request: IncomingMessage ): Promise<{ sub: string; email: string }> { - const session = await requireSession(context, request, false); - if (!session.sub || !session.email) { - throw new ValidationError("Invalid user session"); + try { + const session = await requireSession(context, request, false); + if (!session.sub || !session.email) { + throw new ValidationError("Invalid user session"); + } + return { sub: session.sub, email: session.email }; + } catch { + const authHeader = request.headers.authorization || ""; + const match = /^Bearer\s+(.+)$/.exec(authHeader); + if (!match?.[1]) { + throw new UnauthorizedError("No session cookie"); + } + try { + const jwks = createRemoteJWKSet(new URL(`${context.config.issuer}/.well-known/jwks.json`)); + const verified = await jwtVerify(match[1], jwks, { issuer: context.config.issuer }); + const sub = typeof verified.payload.sub === "string" ? verified.payload.sub : ""; + const email = typeof verified.payload.email === "string" ? verified.payload.email : ""; + if (!sub || !email) { + throw new UnauthorizedError("Invalid bearer token"); + } + return { sub, email }; + } catch { + throw new UnauthorizedError("Invalid bearer token"); + } } - return { sub: session.sub, email: session.email }; }