@faststore/core 3.100.2 → 3.100.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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +47 -47
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +2 -2
- package/.next/server/pages/api/fs/password-protection/unlock.js +1 -0
- package/.next/server/pages/en-US/404.html +2 -2
- package/.next/server/pages/en-US/500.html +2 -2
- package/.next/server/pages/en-US/checkout.html +2 -2
- package/.next/server/pages/en-US/login.html +2 -2
- package/.next/server/pages/en-US/{fs-auth-login.html → password-protection.html} +2 -2
- package/.next/server/pages/en-US/s.html +2 -2
- package/.next/server/pages/en-US.html +2 -2
- package/.next/server/pages-manifest.json +1 -1
- package/.next/server/src/middleware.js +33 -33
- package/.next/server/src/middleware.js.map +1 -1
- package/.next/static/0OKcQtIHO8diwBsczup15/_buildManifest.js +1 -0
- package/.next/static/chunks/main-a2d1cfe626f2fef1.js +1 -0
- package/.next/static/chunks/pages/password-protection-8dece47ea041d372.js +1 -0
- package/.next/static/chunks/{webpack-95c583fdc5f7d468.js → webpack-25c67512e1907efd.js} +1 -1
- package/.next/static/css/033b9397a0c0cf71.css +1 -0
- package/.next/trace +156 -156
- package/.turbo/turbo-build.log +18 -18
- package/.turbo/turbo-test.log +8 -8
- package/CHANGELOG.md +6 -0
- package/package.json +2 -2
- package/src/middleware.ts +14 -13
- package/src/pages/api/fs/{auth/login.ts → password-protection/unlock.ts} +4 -4
- package/src/pages/{fs-auth-login → password-protection}/index.tsx +10 -7
- package/src/pages/{fs-auth-login/fs-auth-login.module.scss → password-protection/password-protection.module.scss} +1 -1
- package/src/server/{authentication-service.ts → password-protection-service.ts} +22 -18
- package/src/utils/{loginResponse.ts → unlockResponse.ts} +7 -2
- package/test/server/{authentication-service.test.ts → password-protection-service.test.ts} +71 -59
- package/.next/server/pages/api/fs/auth/login.js +0 -1
- package/.next/static/chunks/main-083a618181bb5fbb.js +0 -1
- package/.next/static/chunks/pages/fs-auth-login-ba2d1b3ef0ef0415.js +0 -1
- package/.next/static/css/3bd4edd425711792.css +0 -1
- package/.next/static/qnFvdN4P3Em9yV6nJ1gX8/_buildManifest.js +0 -1
- /package/.next/server/pages/api/fs/{auth/login.js.nft.json → password-protection/unlock.js.nft.json} +0 -0
- /package/.next/server/pages/{fs-auth-login.js.nft.json → password-protection.js.nft.json} +0 -0
- /package/.next/static/{qnFvdN4P3Em9yV6nJ1gX8 → 0OKcQtIHO8diwBsczup15}/_ssgManifest.js +0 -0
|
@@ -2,9 +2,9 @@ import { decodeJwt, errors, importSPKI, jwtVerify } from 'jose'
|
|
|
2
2
|
import { NextRequest } from 'next/server'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
PasswordProtectionService,
|
|
6
6
|
resetPasswordProtectionPublicKeyCacheForTests,
|
|
7
|
-
} from '../../src/server/
|
|
7
|
+
} from '../../src/server/password-protection-service'
|
|
8
8
|
|
|
9
9
|
function setNodeEnv(value: string | undefined): void {
|
|
10
10
|
const env = process.env as Record<string, string | undefined>
|
|
@@ -62,7 +62,7 @@ function previewRequest(
|
|
|
62
62
|
})
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
describe('
|
|
65
|
+
describe('PasswordProtectionService', () => {
|
|
66
66
|
const originalNodeEnv = process.env.NODE_ENV
|
|
67
67
|
const originalCustomDomains = process.env.CUSTOM_DOMAINS_PROTECTION_ENABLED
|
|
68
68
|
|
|
@@ -83,8 +83,10 @@ describe('AuthenticationService', () => {
|
|
|
83
83
|
it('skips protection in development', async () => {
|
|
84
84
|
setNodeEnv('development')
|
|
85
85
|
|
|
86
|
-
const service = new
|
|
87
|
-
const { response } = await service.
|
|
86
|
+
const service = new PasswordProtectionService()
|
|
87
|
+
const { response } = await service.checkStoreProtection(
|
|
88
|
+
previewRequest('/p')
|
|
89
|
+
)
|
|
88
90
|
|
|
89
91
|
expect(response.status).toBe(200)
|
|
90
92
|
expect(global.fetch).not.toHaveBeenCalled()
|
|
@@ -99,13 +101,13 @@ describe('AuthenticationService', () => {
|
|
|
99
101
|
}),
|
|
100
102
|
})
|
|
101
103
|
|
|
102
|
-
const service = new
|
|
103
|
-
const { response } = await service.
|
|
104
|
+
const service = new PasswordProtectionService()
|
|
105
|
+
const { response } = await service.checkStoreProtection(
|
|
104
106
|
previewRequest('/products')
|
|
105
107
|
)
|
|
106
108
|
|
|
107
109
|
expect(response.status).toBe(200)
|
|
108
|
-
expect(response.cookies.get('
|
|
110
|
+
expect(response.cookies.get('__fs_password_protection')?.value).toBe(
|
|
109
111
|
'neg-cache-token'
|
|
110
112
|
)
|
|
111
113
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
@@ -118,7 +120,7 @@ describe('AuthenticationService', () => {
|
|
|
118
120
|
)
|
|
119
121
|
})
|
|
120
122
|
|
|
121
|
-
it('redirects to
|
|
123
|
+
it('redirects to unlock page when protected and host matches DEFAULT_DOMAINS scope', async () => {
|
|
122
124
|
;(global.fetch as jest.Mock).mockResolvedValue({
|
|
123
125
|
ok: true,
|
|
124
126
|
json: async () => ({
|
|
@@ -128,14 +130,14 @@ describe('AuthenticationService', () => {
|
|
|
128
130
|
}),
|
|
129
131
|
})
|
|
130
132
|
|
|
131
|
-
const service = new
|
|
132
|
-
const { response } = await service.
|
|
133
|
+
const service = new PasswordProtectionService()
|
|
134
|
+
const { response } = await service.checkStoreProtection(
|
|
133
135
|
previewRequest('/secret')
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
expect(response.status).toBe(307)
|
|
137
139
|
const location = response.headers.get('location') ?? ''
|
|
138
|
-
expect(location).toContain('/
|
|
140
|
+
expect(location).toContain('/password-protection')
|
|
139
141
|
expect(location).toContain('returnTo=%2Fsecret')
|
|
140
142
|
})
|
|
141
143
|
|
|
@@ -148,13 +150,15 @@ describe('AuthenticationService', () => {
|
|
|
148
150
|
}),
|
|
149
151
|
})
|
|
150
152
|
|
|
151
|
-
const service = new
|
|
152
|
-
const { response } = await service.
|
|
153
|
+
const service = new PasswordProtectionService()
|
|
154
|
+
const { response } = await service.checkStoreProtection(
|
|
155
|
+
previewRequest('/p')
|
|
156
|
+
)
|
|
153
157
|
|
|
154
158
|
expect(response.status).toBe(200)
|
|
155
159
|
})
|
|
156
160
|
|
|
157
|
-
it('redirects to
|
|
161
|
+
it('redirects to unlock page when protected for ALL_DOMAINS on custom host', async () => {
|
|
158
162
|
process.env.CUSTOM_DOMAINS_PROTECTION_ENABLED = 'true'
|
|
159
163
|
;(global.fetch as jest.Mock).mockResolvedValue({
|
|
160
164
|
ok: true,
|
|
@@ -164,8 +168,8 @@ describe('AuthenticationService', () => {
|
|
|
164
168
|
}),
|
|
165
169
|
})
|
|
166
170
|
|
|
167
|
-
const service = new
|
|
168
|
-
const { response } = await service.
|
|
171
|
+
const service = new PasswordProtectionService()
|
|
172
|
+
const { response } = await service.checkStoreProtection(
|
|
169
173
|
new NextRequest('https://shop.example.com/checkout', {
|
|
170
174
|
headers: { host: 'shop.example.com' },
|
|
171
175
|
})
|
|
@@ -190,10 +194,10 @@ describe('AuthenticationService', () => {
|
|
|
190
194
|
protectedHeader: { alg: 'RS256' },
|
|
191
195
|
} as unknown as Awaited<ReturnType<typeof jwtVerify>>)
|
|
192
196
|
|
|
193
|
-
const service = new
|
|
194
|
-
const { response } = await service.
|
|
197
|
+
const service = new PasswordProtectionService()
|
|
198
|
+
const { response } = await service.checkStoreProtection(
|
|
195
199
|
previewRequest('/account', {
|
|
196
|
-
headers: { cookie: '
|
|
200
|
+
headers: { cookie: '__fs_password_protection=valid' },
|
|
197
201
|
})
|
|
198
202
|
)
|
|
199
203
|
|
|
@@ -220,12 +224,12 @@ describe('AuthenticationService', () => {
|
|
|
220
224
|
protectedHeader: { alg: 'RS256' },
|
|
221
225
|
} as unknown as Awaited<ReturnType<typeof jwtVerify>>)
|
|
222
226
|
|
|
223
|
-
const service = new
|
|
224
|
-
const { response } = await service.
|
|
227
|
+
const service = new PasswordProtectionService()
|
|
228
|
+
const { response } = await service.checkStoreProtection(
|
|
225
229
|
previewRequest('/p', {
|
|
226
230
|
headers: {
|
|
227
231
|
host: 'preview.vtex.app',
|
|
228
|
-
cookie: '
|
|
232
|
+
cookie: '__fs_password_protection=abc',
|
|
229
233
|
},
|
|
230
234
|
})
|
|
231
235
|
)
|
|
@@ -254,11 +258,11 @@ describe('AuthenticationService', () => {
|
|
|
254
258
|
}),
|
|
255
259
|
})
|
|
256
260
|
|
|
257
|
-
const service = new
|
|
258
|
-
const { response } = await service.
|
|
261
|
+
const service = new PasswordProtectionService()
|
|
262
|
+
const { response } = await service.checkStoreProtection(
|
|
259
263
|
previewRequest('/x', {
|
|
260
264
|
headers: {
|
|
261
|
-
cookie: '
|
|
265
|
+
cookie: '__fs_password_protection=bad',
|
|
262
266
|
},
|
|
263
267
|
})
|
|
264
268
|
)
|
|
@@ -280,15 +284,17 @@ describe('AuthenticationService', () => {
|
|
|
280
284
|
}),
|
|
281
285
|
})
|
|
282
286
|
|
|
283
|
-
const service = new
|
|
284
|
-
const { response } = await service.
|
|
287
|
+
const service = new PasswordProtectionService()
|
|
288
|
+
const { response } = await service.checkStoreProtection(
|
|
285
289
|
previewRequest('/p', {
|
|
286
|
-
headers: { cookie: '
|
|
290
|
+
headers: { cookie: '__fs_password_protection=garbage' },
|
|
287
291
|
})
|
|
288
292
|
)
|
|
289
293
|
|
|
290
294
|
expect(response.status).toBe(200)
|
|
291
|
-
expect(response.cookies.get('
|
|
295
|
+
expect(response.cookies.get('__fs_password_protection')?.value).toBe(
|
|
296
|
+
'after-bad-jwt'
|
|
297
|
+
)
|
|
292
298
|
expect(jwtVerifyMock).toHaveBeenCalled()
|
|
293
299
|
expect(global.fetch).toHaveBeenCalledTimes(2)
|
|
294
300
|
})
|
|
@@ -316,18 +322,20 @@ describe('AuthenticationService', () => {
|
|
|
316
322
|
}),
|
|
317
323
|
})
|
|
318
324
|
|
|
319
|
-
const service = new
|
|
320
|
-
const { response } = await service.
|
|
325
|
+
const service = new PasswordProtectionService()
|
|
326
|
+
const { response } = await service.checkStoreProtection(
|
|
321
327
|
previewRequest('/p', {
|
|
322
|
-
headers: { cookie: '
|
|
328
|
+
headers: { cookie: '__fs_password_protection=old' },
|
|
323
329
|
})
|
|
324
330
|
)
|
|
325
331
|
|
|
326
332
|
expect(response.status).toBe(200)
|
|
327
|
-
expect(response.cookies.get('
|
|
333
|
+
expect(response.cookies.get('__fs_password_protection')?.value).toBe(
|
|
334
|
+
'renewed-token'
|
|
335
|
+
)
|
|
328
336
|
})
|
|
329
337
|
|
|
330
|
-
it('redirects to
|
|
338
|
+
it('redirects to unlock page when JWT is expired and renew response is not ok', async () => {
|
|
331
339
|
;(global.fetch as jest.Mock).mockResolvedValueOnce({
|
|
332
340
|
ok: true,
|
|
333
341
|
json: async () => ({ publicKey: 'test-pem' }),
|
|
@@ -345,17 +353,17 @@ describe('AuthenticationService', () => {
|
|
|
345
353
|
status: 502,
|
|
346
354
|
})
|
|
347
355
|
|
|
348
|
-
const service = new
|
|
349
|
-
const { response } = await service.
|
|
356
|
+
const service = new PasswordProtectionService()
|
|
357
|
+
const { response } = await service.checkStoreProtection(
|
|
350
358
|
previewRequest('/p', {
|
|
351
|
-
headers: { cookie: '
|
|
359
|
+
headers: { cookie: '__fs_password_protection=expired' },
|
|
352
360
|
})
|
|
353
361
|
)
|
|
354
362
|
|
|
355
363
|
expect(response.status).toBe(307)
|
|
356
364
|
})
|
|
357
365
|
|
|
358
|
-
it('redirects to
|
|
366
|
+
it('redirects to unlock page when JWT is expired and renew body is not valid', async () => {
|
|
359
367
|
;(global.fetch as jest.Mock).mockResolvedValueOnce({
|
|
360
368
|
ok: true,
|
|
361
369
|
json: async () => ({ publicKey: 'test-pem' }),
|
|
@@ -373,10 +381,10 @@ describe('AuthenticationService', () => {
|
|
|
373
381
|
json: async () => ({ valid: false }),
|
|
374
382
|
})
|
|
375
383
|
|
|
376
|
-
const service = new
|
|
377
|
-
const { response } = await service.
|
|
384
|
+
const service = new PasswordProtectionService()
|
|
385
|
+
const { response } = await service.checkStoreProtection(
|
|
378
386
|
previewRequest('/p', {
|
|
379
|
-
headers: { cookie: '
|
|
387
|
+
headers: { cookie: '__fs_password_protection=expired' },
|
|
380
388
|
})
|
|
381
389
|
)
|
|
382
390
|
|
|
@@ -398,61 +406,65 @@ describe('AuthenticationService', () => {
|
|
|
398
406
|
} as ReturnType<typeof decodeJwt>)
|
|
399
407
|
;(global.fetch as jest.Mock).mockRejectedValueOnce(new Error('renew down'))
|
|
400
408
|
|
|
401
|
-
const service = new
|
|
402
|
-
const { response } = await service.
|
|
409
|
+
const service = new PasswordProtectionService()
|
|
410
|
+
const { response } = await service.checkStoreProtection(
|
|
403
411
|
previewRequest('/p', {
|
|
404
|
-
headers: { cookie: '
|
|
412
|
+
headers: { cookie: '__fs_password_protection=old' },
|
|
405
413
|
})
|
|
406
414
|
)
|
|
407
415
|
|
|
408
416
|
expect(response.status).toBe(200)
|
|
409
417
|
})
|
|
410
418
|
|
|
411
|
-
it('fail-closes to
|
|
419
|
+
it('fail-closes to unlock page on default domain when status request fails', async () => {
|
|
412
420
|
;(global.fetch as jest.Mock).mockRejectedValue(new Error('network'))
|
|
413
421
|
|
|
414
|
-
const service = new
|
|
415
|
-
const { response } = await service.
|
|
422
|
+
const service = new PasswordProtectionService()
|
|
423
|
+
const { response } = await service.checkStoreProtection(
|
|
424
|
+
previewRequest('/p')
|
|
425
|
+
)
|
|
416
426
|
|
|
417
427
|
expect(response.status).toBe(307)
|
|
418
|
-
expect(response.headers.get('location')).toContain('/
|
|
428
|
+
expect(response.headers.get('location')).toContain('/password-protection')
|
|
419
429
|
})
|
|
420
430
|
|
|
421
|
-
it('fail-closes to
|
|
431
|
+
it('fail-closes to unlock page when status endpoint returns a non-ok HTTP status', async () => {
|
|
422
432
|
;(global.fetch as jest.Mock).mockResolvedValue({
|
|
423
433
|
ok: false,
|
|
424
434
|
status: 503,
|
|
425
435
|
})
|
|
426
436
|
|
|
427
|
-
const service = new
|
|
428
|
-
const { response } = await service.
|
|
437
|
+
const service = new PasswordProtectionService()
|
|
438
|
+
const { response } = await service.checkStoreProtection(
|
|
439
|
+
previewRequest('/p')
|
|
440
|
+
)
|
|
429
441
|
|
|
430
442
|
expect(response.status).toBe(307)
|
|
431
|
-
expect(response.headers.get('location')).toContain('/
|
|
443
|
+
expect(response.headers.get('location')).toContain('/password-protection')
|
|
432
444
|
})
|
|
433
445
|
|
|
434
|
-
it('fail-closes to
|
|
446
|
+
it('fail-closes to unlock page on custom domain when status request fails', async () => {
|
|
435
447
|
process.env.CUSTOM_DOMAINS_PROTECTION_ENABLED = 'true'
|
|
436
448
|
;(global.fetch as jest.Mock).mockRejectedValue(new Error('network'))
|
|
437
449
|
|
|
438
|
-
const service = new
|
|
450
|
+
const service = new PasswordProtectionService()
|
|
439
451
|
const request = new NextRequest('https://shop.example.com/page', {
|
|
440
452
|
headers: { host: 'shop.example.com' },
|
|
441
453
|
})
|
|
442
454
|
|
|
443
|
-
const { response } = await service.
|
|
455
|
+
const { response } = await service.checkStoreProtection(request)
|
|
444
456
|
|
|
445
457
|
expect(response.status).toBe(307)
|
|
446
|
-
expect(response.headers.get('location')).toContain('/
|
|
458
|
+
expect(response.headers.get('location')).toContain('/password-protection')
|
|
447
459
|
})
|
|
448
460
|
|
|
449
461
|
it('does not enforce protection on custom domain when env gate is off', async () => {
|
|
450
|
-
const service = new
|
|
462
|
+
const service = new PasswordProtectionService()
|
|
451
463
|
const request = new NextRequest('https://shop.example.com/page', {
|
|
452
464
|
headers: { host: 'shop.example.com' },
|
|
453
465
|
})
|
|
454
466
|
|
|
455
|
-
const { response } = await service.
|
|
467
|
+
const { response } = await service.checkStoreProtection(request)
|
|
456
468
|
|
|
457
469
|
expect(response.status).toBe(200)
|
|
458
470
|
expect(global.fetch).not.toHaveBeenCalled()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(()=>{var e={};e.id=1453,e.ids=[1453],e.modules={56330:e=>{e.exports=require("deepmerge")},20145:e=>{e.exports=require("next/dist/compiled/next-server/pages-api.runtime.prod.js")},92824:e=>{e.exports=require("next/server")},99369:e=>{e.exports=import("jose")},87179:(e,t,s)=>{s.a(e,async(e,r)=>{try{s.r(t),s.d(t,{default:()=>l});var a=s(40870),n=s.n(a),o=s(91930),i=s(99391),u=e([i]);i=(u.then?(await u)():u)[0];let isSafeReturnToPath=e=>e.startsWith("/")&&!e.startsWith("//"),isWebOpsSessionPayload=e=>!("object"!=typeof e||null===e||Array.isArray(e))&&"boolean"==typeof e.valid&&(void 0===e.token||"string"==typeof e.token),handler=async(e,t)=>{if("POST"!==e.method){t.status(405).end();return}let s=n().api.storeId;try{let{password:r}=e.body??{};if(!r||"string"!=typeof r){t.status(400).json({success:!1,error:"Password is required"});return}let a=await fetch((0,o.BW)(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({storeId:s,password:r}),signal:AbortSignal.timeout(o.nn.defaultMs)});if(!a.ok){if(401===a.status||403===a.status){t.status(401).json({success:!1,error:"Invalid password"});return}t.status(503).json({success:!1,error:"Service temporarily unavailable"});return}let n=await a.json();if(!isWebOpsSessionPayload(n)){t.status(500).json({success:!1,error:"Internal server error"});return}if(n.valid&&n.token){let s="string"==typeof e.query.returnTo?e.query.returnTo:"/",r=isSafeReturnToPath(s)?s:"/";t.setHeader("Set-Cookie",[`${i.B$}=${n.token}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=${i.Ax}`]),t.status(200).json({success:!0,redirectUrl:r})}else t.status(401).json({success:!1,error:"Invalid password"})}catch{t.status(503).json({success:!1,error:"Service temporarily unavailable"})}},l=handler;r()}catch(e){r(e)}})},99391:(e,t,s)=>{s.a(e,async(e,r)=>{try{s.d(t,{Ax:()=>i,B$:()=>o}),s(92824);var a=s(99369),n=e([a]);a=(n.then?(await n)():n)[0];let o="__fs_auth_token",i=600;r()}catch(e){r(e)}})},91930:(e,t,s)=>{function sessionUrl(){return new URL("/api/v1/password-protection/session",function(){let e=process.env.WEBOPS_API_URL?.trim()??"",t=e.replace(/\/+$/,""),s=t.length>0?t:"https://faststore.vtex.com";return/^https?:\/\//i.test(s)?s:`https://${s}`}())}s.d(t,{BW:()=>sessionUrl,nn:()=>r});let r={publicKeyMs:5e3,defaultMs:1e4}},36117:(e,t,s)=>{s.a(e,async(e,r)=>{try{s.r(t),s.d(t,{config:()=>c,default:()=>l,routeModule:()=>d});var a=s(59820),n=s(44708),o=s(64312),i=s(87179),u=e([i]);i=(u.then?(await u)():u)[0];let l=(0,o.l)(i,"default"),c=(0,o.l)(i,"config"),d=new a.PagesAPIRouteModule({definition:{kind:n.x.PAGES_API,page:"/api/fs/auth/login",pathname:"/api/fs/auth/login",bundlePath:"",filename:""},userland:i});r()}catch(e){r(e)}})}};var t=require("../../../../webpack-api-runtime.js");t.C(e);var __webpack_exec__=e=>t(t.s=e),s=t.X(0,[2249,870],()=>__webpack_exec__(36117));module.exports=s})();
|