@faststore/core 4.9.3-dev.1 → 4.10.0-dev.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/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +136 -137
- package/CHANGELOG.md +18 -0
- package/cms/faststore/components/cms_component__bannernewsletter.jsonc +1 -1
- package/cms/faststore/components/cms_component__newsletter.jsonc +1 -1
- package/index.ts +3 -0
- package/package.json +6 -6
- package/src/components/ui/RichText/RichText.tsx +2 -0
- package/src/components/ui/RichText/RichTextImageNode.ts +163 -0
- package/src/pages/api/graphql.ts +23 -19
- package/src/utils/trustedForwardedHost.ts +50 -0
- package/test/components/ui/RichText/RichText.browser.test.tsx +321 -0
- package/test/utils/trustedForwardedHost.test.ts +105 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
singleForwardedHost,
|
|
5
|
+
isHostAllowed,
|
|
6
|
+
removeCookieDomain,
|
|
7
|
+
} from '../../src/utils/trustedForwardedHost'
|
|
8
|
+
|
|
9
|
+
const ALLOW_LIST = ['localhost', '.vtex.app', '.localhost']
|
|
10
|
+
|
|
11
|
+
describe('isHostAllowed', () => {
|
|
12
|
+
it.each([
|
|
13
|
+
'localhost',
|
|
14
|
+
'store.vtex.app',
|
|
15
|
+
'sfj-1--acc.preview.vtex.app',
|
|
16
|
+
'a.localhost',
|
|
17
|
+
])('allows %s', (host) => {
|
|
18
|
+
expect(isHostAllowed(host, ALLOW_LIST)).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it.each([
|
|
22
|
+
'evil-localhost',
|
|
23
|
+
'notlocalhost',
|
|
24
|
+
'evil-vtex.app',
|
|
25
|
+
'vtex.app.evil.com',
|
|
26
|
+
'evil.com',
|
|
27
|
+
])('rejects %s', (host) => {
|
|
28
|
+
expect(isHostAllowed(host, ALLOW_LIST)).toBe(false)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('isHostAllowed normalization', () => {
|
|
33
|
+
it('is case-insensitive', () => {
|
|
34
|
+
expect(isHostAllowed('Store.VTEX.app', ALLOW_LIST)).toBe(true)
|
|
35
|
+
expect(isHostAllowed('LOCALHOST', ALLOW_LIST)).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it.each([
|
|
39
|
+
'x;max-age=0;y.vtex.app',
|
|
40
|
+
'a b.vtex.app',
|
|
41
|
+
'x"y.vtex.app',
|
|
42
|
+
'x=y.vtex.app',
|
|
43
|
+
'x,y.vtex.app',
|
|
44
|
+
'x\r\nSet-Cookie: evil=1.vtex.app',
|
|
45
|
+
'x\ny.vtex.app',
|
|
46
|
+
'x\ry.vtex.app',
|
|
47
|
+
])('rejects %s, which would inject cookie attributes', (host) => {
|
|
48
|
+
expect(isHostAllowed(host, ALLOW_LIST)).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('accepts punycode hostnames', () => {
|
|
52
|
+
expect(isHostAllowed('xn--lja-bma.vtex.app', ALLOW_LIST)).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('expects a hostname: a port is not stripped here', () => {
|
|
56
|
+
expect(isHostAllowed('store.vtex.app:8080', ALLOW_LIST)).toBe(false)
|
|
57
|
+
expect(isHostAllowed('evil.com:vtex.app', ALLOW_LIST)).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('singleForwardedHost', () => {
|
|
62
|
+
it('returns the value when there is exactly one', () => {
|
|
63
|
+
expect(singleForwardedHost('a.vtex.app')).toBe('a.vtex.app')
|
|
64
|
+
expect(singleForwardedHost(' a.vtex.app ')).toBe('a.vtex.app')
|
|
65
|
+
expect(singleForwardedHost(['a.vtex.app'])).toBe('a.vtex.app')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('returns undefined when missing or empty', () => {
|
|
69
|
+
expect(singleForwardedHost(undefined)).toBeUndefined()
|
|
70
|
+
expect(singleForwardedHost('')).toBeUndefined()
|
|
71
|
+
expect(singleForwardedHost([])).toBeUndefined()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('ignores multi-value headers instead of picking one', () => {
|
|
75
|
+
expect(singleForwardedHost('a.vtex.app, evil.com')).toBeUndefined()
|
|
76
|
+
expect(singleForwardedHost('evil.com, a.vtex.app')).toBeUndefined()
|
|
77
|
+
expect(singleForwardedHost(['a.vtex.app', 'evil.com'])).toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('removeCookieDomain', () => {
|
|
82
|
+
it('drops the Domain attribute and keeps the rest', () => {
|
|
83
|
+
expect(
|
|
84
|
+
removeCookieDomain(
|
|
85
|
+
'checkout.vtex.com=__ofid=abc; expires=Sun, 04 Apr 2027 18:31:26 GMT; domain=acc.vtexcommercestable.com.br; path=/; secure; samesite=lax; httponly'
|
|
86
|
+
)
|
|
87
|
+
).toBe(
|
|
88
|
+
'checkout.vtex.com=__ofid=abc; expires=Sun, 04 Apr 2027 18:31:26 GMT; path=/; secure; samesite=lax; httponly'
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('is case-insensitive and removes repeated Domain attributes', () => {
|
|
93
|
+
expect(removeCookieDomain('a=1; Domain=x.com; DOMAIN=y.com; Path=/')).toBe(
|
|
94
|
+
'a=1; Path=/'
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('does not touch a cookie whose name is domain', () => {
|
|
99
|
+
expect(removeCookieDomain('domain=1; path=/')).toBe('domain=1; path=/')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('keeps cookies without a Domain attribute unchanged', () => {
|
|
103
|
+
expect(removeCookieDomain('a=1; path=/')).toBe('a=1; path=/')
|
|
104
|
+
})
|
|
105
|
+
})
|