@akinon/next 2.0.104-rc.0 → 2.0.104-rc.1
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/CHANGELOG.md +8 -0
- package/api/auth.ts +97 -20
- package/data/client/user.ts +6 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 2.0.104-rc.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1595d45: ZERO-4866: Fix register getting stuck with no error after successful signup
|
|
8
|
+
|
|
9
|
+
The login/register credentials provider threw an uncaught SyntaxError whenever the backend returned a non-JSON body (e.g. an HTML error page on a 500/502), surfacing NextAuth's opaque "Configuration" error instead of a usable one; it also only ever read one Set-Cookie response header, missing the osessionid/csrftoken cookie depending on header order, and dropped the session entirely after register/OTP since those flows upgrade the existing anonymous osessionid in place instead of rotating it. getCaptcha also threw when the backend had no reCAPTCHA site key configured. All four are fixed: JSON parsing failures throw a clean auth error, every Set-Cookie header is read, the pre-request session id is used as a fallback after register/OTP, and a missing site key now falls back to an empty string.
|
|
10
|
+
|
|
3
11
|
## 2.0.104-rc.0
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/api/auth.ts
CHANGED
|
@@ -225,12 +225,31 @@ const getDefaultAuthConfig = () => {
|
|
|
225
225
|
}
|
|
226
226
|
);
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
let body: {
|
|
229
229
|
key: string;
|
|
230
230
|
non_field_errors: string[];
|
|
231
231
|
redirect_url: string;
|
|
232
232
|
};
|
|
233
233
|
|
|
234
|
+
try {
|
|
235
|
+
body = await request.json();
|
|
236
|
+
} catch {
|
|
237
|
+
// Backend returned a non-JSON body (e.g. an HTML error page on
|
|
238
|
+
// a 500/502) — surface a clean auth error instead of letting
|
|
239
|
+
// the SyntaxError bubble up as an opaque "Configuration" error
|
|
240
|
+
// (ZERO-4866).
|
|
241
|
+
logger.warn(
|
|
242
|
+
`Login/Register response was not valid JSON (status ${request.status})`,
|
|
243
|
+
{ userIp }
|
|
244
|
+
);
|
|
245
|
+
throwAuthError([
|
|
246
|
+
{
|
|
247
|
+
type: 'non_field_errors',
|
|
248
|
+
data: ['Something went wrong. Please try again later.']
|
|
249
|
+
}
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
|
|
234
253
|
return { request, body };
|
|
235
254
|
};
|
|
236
255
|
|
|
@@ -274,17 +293,32 @@ const getDefaultAuthConfig = () => {
|
|
|
274
293
|
|
|
275
294
|
let sessionId = '';
|
|
276
295
|
let rotatedCsrfToken = '';
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
// Headers.get('set-cookie') only ever returns ONE of potentially
|
|
297
|
+
// several Set-Cookie response headers (csrftoken, osessionid,
|
|
298
|
+
// sessionid, ...) — getSetCookie() (when available) returns all of
|
|
299
|
+
// them, so the osessionid/csrftoken cookies aren't missed
|
|
300
|
+
// depending on header order (ZERO-4866).
|
|
301
|
+
const setCookieHeaders =
|
|
302
|
+
typeof apiRequest.headers.getSetCookie === 'function'
|
|
303
|
+
? apiRequest.headers.getSetCookie()
|
|
304
|
+
: [apiRequest.headers.get('set-cookie')].filter(Boolean);
|
|
305
|
+
|
|
306
|
+
for (const cookieHeader of setCookieHeaders) {
|
|
307
|
+
if (!sessionId) {
|
|
308
|
+
const sessionMatch = cookieHeader?.match(/osessionid=\w+/)?.[0];
|
|
309
|
+
if (sessionMatch) {
|
|
310
|
+
sessionId = sessionMatch.replace('osessionid=', '');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (!rotatedCsrfToken) {
|
|
314
|
+
const csrfMatch = cookieHeader?.match(/csrftoken=[^;,\s]+/)?.[0];
|
|
315
|
+
if (csrfMatch) {
|
|
316
|
+
rotatedCsrfToken = csrfMatch.replace('csrftoken=', '');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
287
320
|
|
|
321
|
+
if (sessionId) {
|
|
288
322
|
logger.debug(`Login/Register session id: ${sessionId}`);
|
|
289
323
|
} else {
|
|
290
324
|
logger.warn('No set-cookie header found in response');
|
|
@@ -341,8 +375,14 @@ const getDefaultAuthConfig = () => {
|
|
|
341
375
|
}
|
|
342
376
|
}
|
|
343
377
|
|
|
378
|
+
// Register/OTP responses don't rotate the session cookie — the
|
|
379
|
+
// backend upgrades the shopper's existing anonymous osessionid in
|
|
380
|
+
// place instead of issuing a new one, so no Set-Cookie is present
|
|
381
|
+
// here. Falling back to the pre-request session id keeps this
|
|
382
|
+
// working for those flows (login always rotates it, so sessionId
|
|
383
|
+
// still wins there) (ZERO-4866).
|
|
344
384
|
const currentUser = await getCurrentUser(
|
|
345
|
-
sessionId,
|
|
385
|
+
sessionId || existingSessionId || '',
|
|
346
386
|
cookieStore.get('pz-currency')?.value ?? ''
|
|
347
387
|
);
|
|
348
388
|
return currentUser;
|
|
@@ -531,12 +571,31 @@ const defaultNextAuthOptionsV4 = (req: any, res: any) => {
|
|
|
531
571
|
}
|
|
532
572
|
);
|
|
533
573
|
|
|
534
|
-
|
|
574
|
+
let body: {
|
|
535
575
|
key: string;
|
|
536
576
|
non_field_errors: string[];
|
|
537
577
|
redirect_url: string;
|
|
538
578
|
};
|
|
539
579
|
|
|
580
|
+
try {
|
|
581
|
+
body = await request.json();
|
|
582
|
+
} catch {
|
|
583
|
+
// Backend returned a non-JSON body (e.g. an HTML error page on
|
|
584
|
+
// a 500/502) — surface a clean auth error instead of letting
|
|
585
|
+
// the SyntaxError bubble up as an opaque "Configuration" error
|
|
586
|
+
// (ZERO-4866).
|
|
587
|
+
logger.warn(
|
|
588
|
+
`Login/Register response was not valid JSON (status ${request.status})`,
|
|
589
|
+
{ userIp }
|
|
590
|
+
);
|
|
591
|
+
throwAuthError([
|
|
592
|
+
{
|
|
593
|
+
type: 'non_field_errors',
|
|
594
|
+
data: ['Something went wrong. Please try again later.']
|
|
595
|
+
}
|
|
596
|
+
]);
|
|
597
|
+
}
|
|
598
|
+
|
|
540
599
|
return { request, body };
|
|
541
600
|
};
|
|
542
601
|
|
|
@@ -579,13 +638,25 @@ const defaultNextAuthOptionsV4 = (req: any, res: any) => {
|
|
|
579
638
|
logger.debug(`Login/Register response: ${JSON.stringify(response)}`);
|
|
580
639
|
|
|
581
640
|
let sessionId = '';
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
641
|
+
// Headers.get('set-cookie') only ever returns ONE of potentially
|
|
642
|
+
// several Set-Cookie response headers (csrftoken, osessionid,
|
|
643
|
+
// sessionid, ...) — getSetCookie() (when available) returns all of
|
|
644
|
+
// them, so the actual osessionid cookie isn't missed depending on
|
|
645
|
+
// header order (ZERO-4866).
|
|
646
|
+
const setCookieHeaders =
|
|
647
|
+
typeof apiRequest.headers.getSetCookie === 'function'
|
|
648
|
+
? apiRequest.headers.getSetCookie()
|
|
649
|
+
: [apiRequest.headers.get('set-cookie')].filter(Boolean);
|
|
650
|
+
|
|
651
|
+
for (const cookieHeader of setCookieHeaders) {
|
|
652
|
+
const match = cookieHeader?.match(/osessionid=\w+/)?.[0];
|
|
653
|
+
if (match) {
|
|
654
|
+
sessionId = match.replace('osessionid=', '');
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
588
658
|
|
|
659
|
+
if (sessionId) {
|
|
589
660
|
logger.debug(`Login/Register session id: ${sessionId}`);
|
|
590
661
|
} else {
|
|
591
662
|
logger.warn('No set-cookie header found in response');
|
|
@@ -625,8 +696,14 @@ const defaultNextAuthOptionsV4 = (req: any, res: any) => {
|
|
|
625
696
|
}
|
|
626
697
|
}
|
|
627
698
|
|
|
699
|
+
// Register/OTP responses don't rotate the session cookie — the
|
|
700
|
+
// backend upgrades the shopper's existing anonymous osessionid in
|
|
701
|
+
// place instead of issuing a new one, so no Set-Cookie is present
|
|
702
|
+
// here. Falling back to the pre-request session id keeps this
|
|
703
|
+
// working for those flows (login always rotates it, so sessionId
|
|
704
|
+
// still wins there) (ZERO-4866).
|
|
628
705
|
const currentUser = await getCurrentUser(
|
|
629
|
-
sessionId,
|
|
706
|
+
sessionId || req.cookies['osessionid'] || '',
|
|
630
707
|
req.cookies['pz-currency'] ?? ''
|
|
631
708
|
);
|
|
632
709
|
return currentUser;
|
package/data/client/user.ts
CHANGED
|
@@ -22,7 +22,12 @@ const userApi = api.injectEndpoints({
|
|
|
22
22
|
getCaptcha: build.query<GetCaptchaResponse, void>({
|
|
23
23
|
query: () => buildClientRequestUrl(user.captcha),
|
|
24
24
|
transformResponse: (response: { html: string }) => {
|
|
25
|
-
|
|
25
|
+
// Falls back to '' instead of throwing when the backend has no
|
|
26
|
+
// reCAPTCHA site key configured (data-sitekey="") — an empty
|
|
27
|
+
// siteKey is handled by the caller, whereas a thrown error here
|
|
28
|
+
// isn't (ZERO-4866).
|
|
29
|
+
const siteKey =
|
|
30
|
+
response.html.match(/data-sitekey="([^"]+)"/i)?.[1] || '';
|
|
26
31
|
|
|
27
32
|
const csrfTokenMatch = response.html.match(
|
|
28
33
|
/name=['|"]csrfmiddlewaretoken['|"] value=['|"][^'"]+/gi
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "2.0.104-rc.
|
|
4
|
+
"version": "2.0.104-rc.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"set-cookie-parser": "2.6.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@akinon/eslint-plugin-projectzero": "2.0.104-rc.
|
|
39
|
+
"@akinon/eslint-plugin-projectzero": "2.0.104-rc.1",
|
|
40
40
|
"@babel/core": "7.26.10",
|
|
41
41
|
"@babel/preset-env": "7.26.9",
|
|
42
42
|
"@babel/preset-typescript": "7.27.0",
|