@awesome-lang-auth/node 1.10.1 → 1.10.3
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/README.detailed.md +21 -5
- package/dist/interfaces/session-store.interface.d.ts +5 -0
- package/dist/interfaces/session-store.interface.d.ts.map +1 -1
- package/dist/models/auth-config.model.d.ts +16 -1
- package/dist/models/auth-config.model.d.ts.map +1 -1
- package/dist/models/session.model.d.ts +2 -0
- package/dist/models/session.model.d.ts.map +1 -1
- package/dist/router/admin.router.d.ts.map +1 -1
- package/dist/router/admin.router.js +47 -23
- package/dist/router/admin.router.js.map +1 -1
- package/dist/router/auth.router.d.ts.map +1 -1
- package/dist/router/auth.router.js +215 -23
- package/dist/router/auth.router.js.map +1 -1
- package/dist/router/openapi.d.ts.map +1 -1
- package/dist/router/openapi.js +15 -3
- package/dist/router/openapi.js.map +1 -1
- package/dist/services/token.service.d.ts.map +1 -1
- package/dist/services/token.service.js +9 -2
- package/dist/services/token.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.resolveApiPrefix = resolveApiPrefix;
|
|
4
37
|
exports.buildUiLink = buildUiLink;
|
|
5
38
|
exports.createAuthRouter = createAuthRouter;
|
|
6
39
|
const express_1 = require("express");
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
7
41
|
const token_service_1 = require("../services/token.service");
|
|
8
42
|
const password_service_1 = require("../services/password.service");
|
|
9
43
|
const mailer_service_1 = require("../services/mailer.service");
|
|
@@ -146,6 +180,55 @@ function decodeOAuthStateOrigin(state) {
|
|
|
146
180
|
catch { /* plain nonce or invalid base64 — ignore */ }
|
|
147
181
|
return null;
|
|
148
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Extracts the random nonce from a structured or plain OAuth state parameter.
|
|
185
|
+
* Returns `null` when state is undefined or empty.
|
|
186
|
+
*/
|
|
187
|
+
function decodeOAuthStateNonce(state) {
|
|
188
|
+
if (!state)
|
|
189
|
+
return null;
|
|
190
|
+
try {
|
|
191
|
+
const parsed = JSON.parse(Buffer.from(state, 'base64url').toString());
|
|
192
|
+
if (isOAuthState(parsed))
|
|
193
|
+
return parsed.n;
|
|
194
|
+
}
|
|
195
|
+
catch { /* plain nonce or invalid base64 */ }
|
|
196
|
+
return typeof state === 'string' && state.trim() ? state.trim() : null;
|
|
197
|
+
}
|
|
198
|
+
function oauthCallbackPath(authUrl, fallback) {
|
|
199
|
+
try {
|
|
200
|
+
const r = new URL(authUrl).searchParams.get('redirect_uri');
|
|
201
|
+
if (r)
|
|
202
|
+
return new URL(r).pathname;
|
|
203
|
+
}
|
|
204
|
+
catch { /* custom URL */ }
|
|
205
|
+
return fallback;
|
|
206
|
+
}
|
|
207
|
+
function oauthStateCookieOpts(path, config, req) {
|
|
208
|
+
return {
|
|
209
|
+
httpOnly: true,
|
|
210
|
+
sameSite: 'lax',
|
|
211
|
+
secure: config.cookieOptions?.secure ?? (req.secure || req.headers['x-forwarded-proto'] === 'https'),
|
|
212
|
+
path,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function verifyOAuthState(req, res, cookieName, path, config) {
|
|
216
|
+
const cookieVal = req.cookies?.[cookieName]
|
|
217
|
+
?? req.cookies?.['oauth_state']
|
|
218
|
+
?? tokenService.extractTokenFromCookie(req, cookieName)
|
|
219
|
+
?? tokenService.extractTokenFromCookie(req, 'oauth_state');
|
|
220
|
+
res.clearCookie(cookieName, oauthStateCookieOpts(path, config, req));
|
|
221
|
+
res.clearCookie(cookieName, { path: '/' });
|
|
222
|
+
res.clearCookie('oauth_state', oauthStateCookieOpts(path, config, req));
|
|
223
|
+
res.clearCookie('oauth_state', { path: '/' });
|
|
224
|
+
const rawState = req.query['state'];
|
|
225
|
+
const stateNonce = decodeOAuthStateNonce(typeof rawState === 'string' ? rawState : undefined);
|
|
226
|
+
const a = Buffer.from(cookieVal ?? '');
|
|
227
|
+
const b = Buffer.from(stateNonce ?? '');
|
|
228
|
+
if (!cookieVal || !stateNonce || a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
|
|
229
|
+
throw new errors_1.AuthError('Invalid OAuth state', 'INVALID_OAUTH_STATE', 400);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
149
232
|
/**
|
|
150
233
|
* Resolves the post-OAuth redirect target from the encoded state.
|
|
151
234
|
* Falls back to `getDefaultSiteUrl` when the state carries no origin or the
|
|
@@ -242,23 +325,47 @@ function sendTokens(req, res, tokens, config) {
|
|
|
242
325
|
async function issueTokens(req, res, user, config, options, userStore, redirectTo, oldSid) {
|
|
243
326
|
const payload = buildPayload(user, config);
|
|
244
327
|
const refreshExpiryMs = parseExpiryMs(config.refreshTokenExpiresIn);
|
|
328
|
+
let createdSession;
|
|
245
329
|
if (options.sessionStore) {
|
|
330
|
+
const isSingleSession = config.session?.singleSessionPerUser ?? config.session?.singleSession ?? false;
|
|
331
|
+
if (isSingleSession && !oldSid) {
|
|
332
|
+
await options.sessionStore.revokeAllSessionsForUser(user.id, user.tenantId).catch(() => { });
|
|
333
|
+
}
|
|
334
|
+
// Revoke the previous session before creating new one (token rotation)
|
|
335
|
+
// Store failures during /refresh must reject (500) so old tokens cannot be replayed
|
|
336
|
+
if (oldSid) {
|
|
337
|
+
await options.sessionStore.revokeSession(oldSid);
|
|
338
|
+
}
|
|
246
339
|
const session = await options.sessionStore.createSession({
|
|
247
340
|
userId: user.id,
|
|
341
|
+
tenantId: user.tenantId,
|
|
248
342
|
userAgent: req.headers['user-agent'],
|
|
249
343
|
ipAddress: req.ip || req.socket.remoteAddress,
|
|
250
344
|
expiresAt: new Date(Date.now() + refreshExpiryMs),
|
|
251
345
|
createdAt: new Date(),
|
|
252
346
|
});
|
|
253
347
|
payload.sid = session.sessionHandle;
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
await options.sessionStore.revokeSession(oldSid).catch(() => { });
|
|
257
|
-
}
|
|
348
|
+
// Save session in local variable for updating hash below
|
|
349
|
+
createdSession = session;
|
|
258
350
|
}
|
|
259
351
|
const tokens = tokenService.generateTokenPair(payload, config);
|
|
260
352
|
const refreshExpiry = new Date(Date.now() + refreshExpiryMs);
|
|
261
|
-
|
|
353
|
+
// Only write refresh token to user record when no session store is configured (single-device fallback)
|
|
354
|
+
if (!options.sessionStore) {
|
|
355
|
+
await userStore.updateRefreshToken(user.id, tokens.refreshToken, refreshExpiry);
|
|
356
|
+
}
|
|
357
|
+
if (options.sessionStore && payload.sid) {
|
|
358
|
+
const refreshTokenHash = crypto.createHash('sha256').update(tokens.refreshToken).digest('hex');
|
|
359
|
+
if (createdSession) {
|
|
360
|
+
createdSession.refreshTokenHash = refreshTokenHash;
|
|
361
|
+
if (!createdSession.data)
|
|
362
|
+
createdSession.data = {};
|
|
363
|
+
createdSession.data.refreshTokenHash = refreshTokenHash;
|
|
364
|
+
}
|
|
365
|
+
if (options.sessionStore.updateSessionRefreshTokenHash) {
|
|
366
|
+
await options.sessionStore.updateSessionRefreshTokenHash(payload.sid, refreshTokenHash).catch(() => { });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
262
369
|
if (redirectTo) {
|
|
263
370
|
tokenService.setTokenCookies(res, tokens, config);
|
|
264
371
|
res.redirect(redirectTo);
|
|
@@ -270,6 +377,9 @@ async function issueTokens(req, res, user, config, options, userStore, redirectT
|
|
|
270
377
|
}
|
|
271
378
|
function createAuthRouter(userStore, config, options = {}) {
|
|
272
379
|
const router = (0, express_1.Router)();
|
|
380
|
+
if (options.sessionStore && config.refreshTokenSecret === config.accessTokenSecret) {
|
|
381
|
+
throw new Error('refreshTokenSecret must differ from accessTokenSecret when a sessionStore is configured');
|
|
382
|
+
}
|
|
273
383
|
// Parameterize refresh token path by default based on apiPrefix
|
|
274
384
|
config.cookieOptions = {
|
|
275
385
|
...config.cookieOptions,
|
|
@@ -472,13 +582,23 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
472
582
|
if (typeof bodyRefreshToken === 'string' && bodyRefreshToken) {
|
|
473
583
|
try {
|
|
474
584
|
const payload = tokenService.verifyRefreshToken(bodyRefreshToken, config);
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (
|
|
478
|
-
|
|
585
|
+
if (options.sessionStore && payload.sid) {
|
|
586
|
+
const session = await options.sessionStore.getSession(payload.sid);
|
|
587
|
+
if (session) {
|
|
588
|
+
const expectedHash = session.refreshTokenHash ?? session.data?.refreshTokenHash;
|
|
589
|
+
if (!expectedHash || crypto.createHash('sha256').update(bodyRefreshToken).digest('hex') === expectedHash) {
|
|
590
|
+
await options.sessionStore.revokeSession(payload.sid).catch(() => { });
|
|
591
|
+
if (!req.user)
|
|
592
|
+
req.user = payload;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
const user = await userStore.findById(payload.sub);
|
|
598
|
+
if (user && user.refreshToken === bodyRefreshToken) {
|
|
599
|
+
if (!req.user)
|
|
600
|
+
req.user = payload;
|
|
479
601
|
}
|
|
480
|
-
if (!req.user)
|
|
481
|
-
req.user = payload;
|
|
482
602
|
}
|
|
483
603
|
}
|
|
484
604
|
catch {
|
|
@@ -491,7 +611,7 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
491
611
|
const userId = req.user?.sub;
|
|
492
612
|
const sessionId = req.user?.sid;
|
|
493
613
|
if (req.user?.sub) {
|
|
494
|
-
await userStore.updateRefreshToken(req.user.sub, null, null);
|
|
614
|
+
await userStore.updateRefreshToken(req.user.sub, null, null).catch(() => { });
|
|
495
615
|
}
|
|
496
616
|
tokenService.clearTokenCookies(res, config);
|
|
497
617
|
(0, router_events_1.publishRequestEvent)(eventBus, auth_event_names_1.AuthEventNames.AUTH_LOGOUT, req, {
|
|
@@ -518,20 +638,39 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
518
638
|
return;
|
|
519
639
|
}
|
|
520
640
|
const payload = tokenService.verifyRefreshToken(refreshToken, config);
|
|
521
|
-
// Real-time Session Validation
|
|
522
|
-
|
|
523
|
-
if (options.sessionStore && payload.sid && checkOn !== 'none') {
|
|
641
|
+
// Real-time Session Validation (always checked on /refresh, regardless of checkOn)
|
|
642
|
+
if (options.sessionStore && payload.sid) {
|
|
524
643
|
const session = await options.sessionStore.getSession(payload.sid);
|
|
525
644
|
if (!session) {
|
|
526
645
|
res.status(401).json({ error: 'Session has been revoked', code: 'SESSION_REVOKED' });
|
|
527
646
|
return;
|
|
528
647
|
}
|
|
648
|
+
if (session.userId !== payload.sub) {
|
|
649
|
+
res.status(401).json({ error: 'Invalid refresh token' });
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
// Per-session refresh token validation (multi-device support)
|
|
653
|
+
const expectedHash = session.refreshTokenHash ?? session.data?.refreshTokenHash;
|
|
654
|
+
if (expectedHash) {
|
|
655
|
+
const presentedHash = crypto.createHash('sha256').update(refreshToken).digest('hex');
|
|
656
|
+
if (presentedHash !== expectedHash) {
|
|
657
|
+
res.status(401).json({ error: 'Invalid refresh token' });
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
529
661
|
}
|
|
530
662
|
const user = await userStore.findById(payload.sub);
|
|
531
|
-
if (!user
|
|
663
|
+
if (!user) {
|
|
532
664
|
res.status(401).json({ error: 'Invalid refresh token' });
|
|
533
665
|
return;
|
|
534
666
|
}
|
|
667
|
+
// If no session store is used or payload has no sid, validate against user.refreshToken
|
|
668
|
+
if (!options.sessionStore || !payload.sid) {
|
|
669
|
+
if (user.refreshToken !== refreshToken) {
|
|
670
|
+
res.status(401).json({ error: 'Invalid refresh token' });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
535
674
|
const { sessionId } = await issueTokens(req, res, user, config, options, userStore, /* redirectTo */ undefined, payload.sid);
|
|
536
675
|
(0, router_events_1.publishRequestEvent)(eventBus, auth_event_names_1.AuthEventNames.SESSION_ROTATED, req, {
|
|
537
676
|
userId: user.id,
|
|
@@ -1244,10 +1383,24 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
1244
1383
|
const returnPath = typeof req.query['return_path'] === 'string' ? req.query['return_path'] : undefined;
|
|
1245
1384
|
const state = resolved ? encodeOAuthState(nonce, resolved, returnPath) : nonce;
|
|
1246
1385
|
const url = googleStrategy.getAuthorizationUrl(state);
|
|
1386
|
+
const callbackPath = oauthCallbackPath(url, `${req.baseUrl}/oauth/google/callback`);
|
|
1387
|
+
res.cookie('oauth_nonce_google', nonce, {
|
|
1388
|
+
...oauthStateCookieOpts(callbackPath, config, req),
|
|
1389
|
+
maxAge: 10 * 60 * 1000,
|
|
1390
|
+
});
|
|
1391
|
+
res.cookie('oauth_nonce_google', nonce, {
|
|
1392
|
+
httpOnly: true,
|
|
1393
|
+
sameSite: 'lax',
|
|
1394
|
+
secure: config.cookieOptions?.secure ?? (req.secure || req.headers['x-forwarded-proto'] === 'https'),
|
|
1395
|
+
maxAge: 10 * 60 * 1000,
|
|
1396
|
+
path: '/',
|
|
1397
|
+
});
|
|
1247
1398
|
res.redirect(url);
|
|
1248
1399
|
});
|
|
1249
1400
|
router.get('/oauth/google/callback', ...rl, async (req, res) => {
|
|
1250
1401
|
try {
|
|
1402
|
+
const callbackPath = oauthCallbackPath(googleStrategy.getAuthorizationUrl(''), `${req.baseUrl}${req.path}`);
|
|
1403
|
+
verifyOAuthState(req, res, 'oauth_nonce_google', callbackPath, config);
|
|
1251
1404
|
const { code, state } = req.query;
|
|
1252
1405
|
const redirectTo = resolveOAuthRedirect(state, config, allowedOrigins);
|
|
1253
1406
|
const user = await googleStrategy.handleCallback(code, state);
|
|
@@ -1268,14 +1421,17 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
1268
1421
|
(0, router_events_1.publishRequestEvent)(eventBus, auth_event_names_1.AuthEventNames.AUTH_OAUTH_CONFLICT, req, {
|
|
1269
1422
|
data: (0, router_events_1.oauthConflictEventData)('google', err.data),
|
|
1270
1423
|
});
|
|
1271
|
-
const
|
|
1424
|
+
const rawOrigin = decodeOAuthStateOrigin(req.query.state);
|
|
1425
|
+
const conflictOrigin = (rawOrigin && (allowedOrigins.length === 0 || allowedOrigins.includes(rawOrigin)))
|
|
1426
|
+
? rawOrigin
|
|
1427
|
+
: getDefaultSiteUrl(config);
|
|
1272
1428
|
const { email, providerAccountId } = (err.data ?? {});
|
|
1273
1429
|
if (options.pendingLinkStore && email && providerAccountId) {
|
|
1274
1430
|
await options.pendingLinkStore.stash(email, 'google', providerAccountId).catch((e) => { console.error('[node-auth] pendingLinkStore.stash error (google):', e); });
|
|
1275
1431
|
}
|
|
1276
1432
|
const emailParam = email ? `&email=${encodeURIComponent(email)}` : '';
|
|
1277
1433
|
const paramStr = `?provider=google&code=OAUTH_ACCOUNT_CONFLICT${emailParam}`;
|
|
1278
|
-
res.redirect(buildUiLink(
|
|
1434
|
+
res.redirect(buildUiLink(conflictOrigin, `/account-conflict${paramStr}`, config, options));
|
|
1279
1435
|
return;
|
|
1280
1436
|
}
|
|
1281
1437
|
handleError(res, err);
|
|
@@ -1295,10 +1451,24 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
1295
1451
|
const returnPath = typeof req.query['return_path'] === 'string' ? req.query['return_path'] : undefined;
|
|
1296
1452
|
const state = resolved ? encodeOAuthState(nonce, resolved, returnPath) : nonce;
|
|
1297
1453
|
const url = githubStrategy.getAuthorizationUrl(state);
|
|
1454
|
+
const callbackPath = oauthCallbackPath(url, `${req.baseUrl}/oauth/github/callback`);
|
|
1455
|
+
res.cookie('oauth_nonce_github', nonce, {
|
|
1456
|
+
...oauthStateCookieOpts(callbackPath, config, req),
|
|
1457
|
+
maxAge: 10 * 60 * 1000,
|
|
1458
|
+
});
|
|
1459
|
+
res.cookie('oauth_nonce_github', nonce, {
|
|
1460
|
+
httpOnly: true,
|
|
1461
|
+
sameSite: 'lax',
|
|
1462
|
+
secure: config.cookieOptions?.secure ?? (req.secure || req.headers['x-forwarded-proto'] === 'https'),
|
|
1463
|
+
maxAge: 10 * 60 * 1000,
|
|
1464
|
+
path: '/',
|
|
1465
|
+
});
|
|
1298
1466
|
res.redirect(url);
|
|
1299
1467
|
});
|
|
1300
1468
|
router.get('/oauth/github/callback', ...rl, async (req, res) => {
|
|
1301
1469
|
try {
|
|
1470
|
+
const callbackPath = oauthCallbackPath(githubStrategy.getAuthorizationUrl(''), `${req.baseUrl}${req.path}`);
|
|
1471
|
+
verifyOAuthState(req, res, 'oauth_nonce_github', callbackPath, config);
|
|
1302
1472
|
const { code, state } = req.query;
|
|
1303
1473
|
const redirectTo = resolveOAuthRedirect(state, config, allowedOrigins);
|
|
1304
1474
|
const user = await githubStrategy.handleCallback(code, state);
|
|
@@ -1319,14 +1489,17 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
1319
1489
|
(0, router_events_1.publishRequestEvent)(eventBus, auth_event_names_1.AuthEventNames.AUTH_OAUTH_CONFLICT, req, {
|
|
1320
1490
|
data: (0, router_events_1.oauthConflictEventData)('github', err.data),
|
|
1321
1491
|
});
|
|
1322
|
-
const
|
|
1492
|
+
const rawOrigin = decodeOAuthStateOrigin(req.query.state);
|
|
1493
|
+
const conflictOrigin = (rawOrigin && (allowedOrigins.length === 0 || allowedOrigins.includes(rawOrigin)))
|
|
1494
|
+
? rawOrigin
|
|
1495
|
+
: getDefaultSiteUrl(config);
|
|
1323
1496
|
const { email, providerAccountId } = (err.data ?? {});
|
|
1324
1497
|
if (options.pendingLinkStore && email && providerAccountId) {
|
|
1325
1498
|
await options.pendingLinkStore.stash(email, 'github', providerAccountId).catch((e) => { console.error('[node-auth] pendingLinkStore.stash error (github):', e); });
|
|
1326
1499
|
}
|
|
1327
1500
|
const emailParam = email ? `&email=${encodeURIComponent(email)}` : '';
|
|
1328
1501
|
const paramStr = `?provider=github&code=OAUTH_ACCOUNT_CONFLICT${emailParam}`;
|
|
1329
|
-
res.redirect(buildUiLink(
|
|
1502
|
+
res.redirect(buildUiLink(conflictOrigin, `/account-conflict${paramStr}`, config, options));
|
|
1330
1503
|
return;
|
|
1331
1504
|
}
|
|
1332
1505
|
handleError(res, err);
|
|
@@ -1346,10 +1519,26 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
1346
1519
|
const resolved = resolveSiteUrl(req, config, allowedOrigins);
|
|
1347
1520
|
const returnPath = typeof req.query['return_path'] === 'string' ? req.query['return_path'] : undefined;
|
|
1348
1521
|
const state = resolved ? encodeOAuthState(nonce, resolved, returnPath) : nonce;
|
|
1349
|
-
|
|
1522
|
+
const url = s.getAuthorizationUrl(state);
|
|
1523
|
+
const callbackPath = oauthCallbackPath(url, `${req.baseUrl}/oauth/${s.name}/callback`);
|
|
1524
|
+
res.cookie(`oauth_nonce_${s.name}`, nonce, {
|
|
1525
|
+
...oauthStateCookieOpts(callbackPath, config, req),
|
|
1526
|
+
maxAge: 10 * 60 * 1000,
|
|
1527
|
+
});
|
|
1528
|
+
res.cookie(`oauth_nonce_${s.name}`, nonce, {
|
|
1529
|
+
httpOnly: true,
|
|
1530
|
+
sameSite: 'lax',
|
|
1531
|
+
secure: config.cookieOptions?.secure ?? (req.secure || req.headers['x-forwarded-proto'] === 'https'),
|
|
1532
|
+
maxAge: 10 * 60 * 1000,
|
|
1533
|
+
path: '/',
|
|
1534
|
+
});
|
|
1535
|
+
res.redirect(url);
|
|
1350
1536
|
});
|
|
1351
1537
|
router.get(`/oauth/${s.name}/callback`, ...rl, async (req, res) => {
|
|
1352
1538
|
try {
|
|
1539
|
+
const cookieName = `oauth_nonce_${s.name}`;
|
|
1540
|
+
const callbackPath = oauthCallbackPath(s.getAuthorizationUrl(''), `${req.baseUrl}${req.path}`);
|
|
1541
|
+
verifyOAuthState(req, res, cookieName, callbackPath, config);
|
|
1353
1542
|
const { code, state } = req.query;
|
|
1354
1543
|
const redirectTo = resolveOAuthRedirect(state, config, allowedOrigins);
|
|
1355
1544
|
const user = await s.handleCallback(code, state);
|
|
@@ -1370,14 +1559,17 @@ function createAuthRouter(userStore, config, options = {}) {
|
|
|
1370
1559
|
(0, router_events_1.publishRequestEvent)(eventBus, auth_event_names_1.AuthEventNames.AUTH_OAUTH_CONFLICT, req, {
|
|
1371
1560
|
data: (0, router_events_1.oauthConflictEventData)(s.name, err.data),
|
|
1372
1561
|
});
|
|
1373
|
-
const
|
|
1562
|
+
const rawOrigin = decodeOAuthStateOrigin(req.query.state);
|
|
1563
|
+
const conflictOrigin = (rawOrigin && (allowedOrigins.length === 0 || allowedOrigins.includes(rawOrigin)))
|
|
1564
|
+
? rawOrigin
|
|
1565
|
+
: getDefaultSiteUrl(config);
|
|
1374
1566
|
const { email, providerAccountId } = (err.data ?? {});
|
|
1375
1567
|
if (options.pendingLinkStore && email && providerAccountId) {
|
|
1376
1568
|
await options.pendingLinkStore.stash(email, s.name, providerAccountId).catch((e) => { console.error(`[node-auth] pendingLinkStore.stash error (${s.name}):`, e); });
|
|
1377
1569
|
}
|
|
1378
1570
|
const emailParam = email ? `&email=${encodeURIComponent(email)}` : '';
|
|
1379
1571
|
const paramStr = `?provider=${encodeURIComponent(s.name)}&code=OAUTH_ACCOUNT_CONFLICT${emailParam}`;
|
|
1380
|
-
res.redirect(buildUiLink(
|
|
1572
|
+
res.redirect(buildUiLink(conflictOrigin, `/account-conflict${paramStr}`, config, options));
|
|
1381
1573
|
return;
|
|
1382
1574
|
}
|
|
1383
1575
|
handleError(res, err);
|