@fonderie/auth 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +54 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +55 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1704,7 +1704,10 @@ function authController(store, config, bus) {
|
|
|
1704
1704
|
passwordHash,
|
|
1705
1705
|
row.userId
|
|
1706
1706
|
]),
|
|
1707
|
-
tx.query(`DELETE FROM fonderie_password_resets WHERE user_id = $1`, [row.userId])
|
|
1707
|
+
tx.query(`DELETE FROM fonderie_password_resets WHERE user_id = $1`, [row.userId]),
|
|
1708
|
+
// Revoke every session: the reset exists because the account may
|
|
1709
|
+
// be compromised — a stolen session must not survive it.
|
|
1710
|
+
tx.query(`DELETE FROM fonderie_sessions WHERE user_id = $1`, [row.userId])
|
|
1708
1711
|
]);
|
|
1709
1712
|
});
|
|
1710
1713
|
return setApiResponse3(HTTP3.OK, "PASSWORD_RESET_SUCCESSFUL", "Password reset successfully.");
|
|
@@ -2225,7 +2228,8 @@ function userController(store, config, bus) {
|
|
|
2225
2228
|
}
|
|
2226
2229
|
|
|
2227
2230
|
// src/controllers/oauth.controller.ts
|
|
2228
|
-
import {
|
|
2231
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2232
|
+
import { setApiResponse as setApiResponse5, HTTP as HTTP5, constantTimeEqual as constantTimeEqual2 } from "@fonderie/core";
|
|
2229
2233
|
function oauthController(store, config) {
|
|
2230
2234
|
const users = new UserModel(store);
|
|
2231
2235
|
const sessions = new SessionModel(store);
|
|
@@ -2240,18 +2244,30 @@ function oauthController(store, config) {
|
|
|
2240
2244
|
"Google OAuth not configured"
|
|
2241
2245
|
);
|
|
2242
2246
|
}
|
|
2247
|
+
const state = randomBytes3(16).toString("hex");
|
|
2243
2248
|
const params = new URLSearchParams({
|
|
2244
2249
|
client_id: google.clientId,
|
|
2245
2250
|
redirect_uri: google.redirectUri,
|
|
2246
2251
|
response_type: "code",
|
|
2247
|
-
scope: "openid email profile"
|
|
2252
|
+
scope: "openid email profile",
|
|
2253
|
+
state
|
|
2248
2254
|
});
|
|
2249
2255
|
const url = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2256
|
+
const secure = config.secureCookies ?? process.env["NODE_ENV"] === "production" ? "; Secure" : "";
|
|
2257
|
+
return Response.json(
|
|
2258
|
+
{
|
|
2259
|
+
reason: "GOOGLE_AUTH_URL",
|
|
2260
|
+
explanation: "Redirect the user to the returned URL to begin Google OAuth.",
|
|
2261
|
+
result: { url }
|
|
2262
|
+
},
|
|
2263
|
+
{
|
|
2264
|
+
status: 200,
|
|
2265
|
+
// SameSite=Lax (not Strict): the cookie must be sent on the
|
|
2266
|
+
// top-level cross-site redirect back from Google.
|
|
2267
|
+
headers: cookieHeaders([
|
|
2268
|
+
`oauth_state=${state}; HttpOnly; SameSite=Lax; Path=/; Max-Age=600${secure}`
|
|
2269
|
+
])
|
|
2270
|
+
}
|
|
2255
2271
|
);
|
|
2256
2272
|
},
|
|
2257
2273
|
googleCallback: async (ctx) => {
|
|
@@ -2268,6 +2284,16 @@ function oauthController(store, config) {
|
|
|
2268
2284
|
if (!code) {
|
|
2269
2285
|
return setApiResponse5(HTTP5.BAD_REQUEST, "INVALID_PARAMETER", "Missing code");
|
|
2270
2286
|
}
|
|
2287
|
+
const returnedState = url.searchParams.get("state") ?? "";
|
|
2288
|
+
const cookieHeader = ctx.request.headers.get("cookie") ?? "";
|
|
2289
|
+
const expectedState = cookieHeader.match(/(?:^|;\s*)oauth_state=([^;]+)/)?.[1] ?? "";
|
|
2290
|
+
if (!returnedState || !expectedState || !constantTimeEqual2(Buffer.from(returnedState), Buffer.from(expectedState))) {
|
|
2291
|
+
return setApiResponse5(
|
|
2292
|
+
HTTP5.BAD_REQUEST,
|
|
2293
|
+
"GOOGLE_AUTH_FAILED",
|
|
2294
|
+
"OAuth state mismatch \u2014 restart the sign-in flow"
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2271
2297
|
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
2272
2298
|
method: "POST",
|
|
2273
2299
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
@@ -2290,9 +2316,25 @@ function oauthController(store, config) {
|
|
|
2290
2316
|
const payload = JSON.parse(
|
|
2291
2317
|
Buffer.from(tokenData.id_token.split(".")[1] ?? "", "base64url").toString()
|
|
2292
2318
|
);
|
|
2319
|
+
if (payload.aud !== google.clientId) {
|
|
2320
|
+
return setApiResponse5(HTTP5.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "OAuth token audience mismatch");
|
|
2321
|
+
}
|
|
2322
|
+
if (payload.iss !== "https://accounts.google.com" && payload.iss !== "accounts.google.com") {
|
|
2323
|
+
return setApiResponse5(HTTP5.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "OAuth token issuer mismatch");
|
|
2324
|
+
}
|
|
2325
|
+
if (typeof payload.exp !== "number" || payload.exp * 1e3 < Date.now()) {
|
|
2326
|
+
return setApiResponse5(HTTP5.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "OAuth token expired");
|
|
2327
|
+
}
|
|
2293
2328
|
if (!payload.email) {
|
|
2294
2329
|
return setApiResponse5(HTTP5.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "No email in OAuth response");
|
|
2295
2330
|
}
|
|
2331
|
+
if (payload.email_verified !== true) {
|
|
2332
|
+
return setApiResponse5(
|
|
2333
|
+
HTTP5.BAD_REQUEST,
|
|
2334
|
+
"GOOGLE_AUTH_FAILED",
|
|
2335
|
+
"Google account email is not verified"
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2296
2338
|
const normalizedEmail = normalizeEmailSafe(payload.email);
|
|
2297
2339
|
if (!normalizedEmail) {
|
|
2298
2340
|
return setApiResponse5(HTTP5.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "Invalid email in OAuth response");
|
|
@@ -2336,7 +2378,11 @@ function oauthController(store, config) {
|
|
|
2336
2378
|
},
|
|
2337
2379
|
{
|
|
2338
2380
|
status: 200,
|
|
2339
|
-
headers: cookieHeaders(
|
|
2381
|
+
headers: cookieHeaders([
|
|
2382
|
+
...tokenPairCookies(accessToken, refreshToken, config),
|
|
2383
|
+
// One-time value — clear it once the flow completes.
|
|
2384
|
+
"oauth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0"
|
|
2385
|
+
])
|
|
2340
2386
|
}
|
|
2341
2387
|
);
|
|
2342
2388
|
}
|