payload-plugin-newsletter 0.3.2 → 0.4.5
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 +44 -1
- package/CLAUDE.md +31 -19
- package/dist/client.cjs +899 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +52 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.js +867 -0
- package/dist/client.js.map +1 -0
- package/dist/components.cjs +899 -0
- package/dist/components.cjs.map +1 -0
- package/dist/components.d.cts +4 -0
- package/dist/components.d.ts +4 -0
- package/dist/components.js +867 -0
- package/dist/components.js.map +1 -0
- package/dist/index.cjs +2004 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1967 -0
- package/dist/index.js.map +1 -0
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/{types/index.d.ts → types.d.cts} +19 -17
- package/dist/types.d.ts +350 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -25
- package/dist/.tsbuildinfo +0 -1
- package/dist/collections/NewsletterSettings.d.ts +0 -4
- package/dist/collections/NewsletterSettings.d.ts.map +0 -1
- package/dist/collections/Subscribers.d.ts +0 -4
- package/dist/collections/Subscribers.d.ts.map +0 -1
- package/dist/components/MagicLinkVerify.d.ts +0 -27
- package/dist/components/MagicLinkVerify.d.ts.map +0 -1
- package/dist/components/NewsletterForm.d.ts +0 -5
- package/dist/components/NewsletterForm.d.ts.map +0 -1
- package/dist/components/PreferencesForm.d.ts +0 -5
- package/dist/components/PreferencesForm.d.ts.map +0 -1
- package/dist/components/index.d.ts +0 -5
- package/dist/components/index.d.ts.map +0 -1
- package/dist/endpoints/index.d.ts +0 -4
- package/dist/endpoints/index.d.ts.map +0 -1
- package/dist/endpoints/preferences.d.ts +0 -5
- package/dist/endpoints/preferences.d.ts.map +0 -1
- package/dist/endpoints/subscribe.d.ts +0 -4
- package/dist/endpoints/subscribe.d.ts.map +0 -1
- package/dist/endpoints/unsubscribe.d.ts +0 -4
- package/dist/endpoints/unsubscribe.d.ts.map +0 -1
- package/dist/endpoints/verify-magic-link.d.ts +0 -4
- package/dist/endpoints/verify-magic-link.d.ts.map +0 -1
- package/dist/exports/client.d.ts +0 -6
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/components.d.ts +0 -2
- package/dist/exports/components.d.ts.map +0 -1
- package/dist/exports/types.d.ts +0 -2
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/fields/newsletterScheduling.d.ts +0 -4
- package/dist/fields/newsletterScheduling.d.ts.map +0 -1
- package/dist/hooks/useNewsletterAuth.d.ts +0 -16
- package/dist/hooks/useNewsletterAuth.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/providers/broadcast.d.ts +0 -19
- package/dist/providers/broadcast.d.ts.map +0 -1
- package/dist/providers/index.d.ts +0 -23
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/resend.d.ts +0 -20
- package/dist/providers/resend.d.ts.map +0 -1
- package/dist/providers/types.d.ts +0 -46
- package/dist/providers/types.d.ts.map +0 -1
- package/dist/src/__tests__/fixtures/newsletter-settings.js +0 -41
- package/dist/src/__tests__/fixtures/newsletter-settings.js.map +0 -1
- package/dist/src/__tests__/fixtures/subscribers.js +0 -70
- package/dist/src/__tests__/fixtures/subscribers.js.map +0 -1
- package/dist/src/__tests__/integration/collections/subscriber-hooks.test.js +0 -356
- package/dist/src/__tests__/integration/collections/subscriber-hooks.test.js.map +0 -1
- package/dist/src/__tests__/integration/endpoints/preferences.test.js +0 -266
- package/dist/src/__tests__/integration/endpoints/preferences.test.js.map +0 -1
- package/dist/src/__tests__/integration/endpoints/subscribe.test.js +0 -280
- package/dist/src/__tests__/integration/endpoints/subscribe.test.js.map +0 -1
- package/dist/src/__tests__/integration/endpoints/unsubscribe.test.js +0 -187
- package/dist/src/__tests__/integration/endpoints/unsubscribe.test.js.map +0 -1
- package/dist/src/__tests__/integration/endpoints/verify-magic-link.test.js +0 -188
- package/dist/src/__tests__/integration/endpoints/verify-magic-link.test.js.map +0 -1
- package/dist/src/__tests__/mocks/email-providers.js +0 -153
- package/dist/src/__tests__/mocks/email-providers.js.map +0 -1
- package/dist/src/__tests__/mocks/payload.js +0 -244
- package/dist/src/__tests__/mocks/payload.js.map +0 -1
- package/dist/src/__tests__/security/csrf-protection.test.js +0 -309
- package/dist/src/__tests__/security/csrf-protection.test.js.map +0 -1
- package/dist/src/__tests__/security/settings-access.test.js +0 -204
- package/dist/src/__tests__/security/settings-access.test.js.map +0 -1
- package/dist/src/__tests__/security/subscriber-access.test.js +0 -210
- package/dist/src/__tests__/security/subscriber-access.test.js.map +0 -1
- package/dist/src/__tests__/security/xss-prevention.test.js +0 -305
- package/dist/src/__tests__/security/xss-prevention.test.js.map +0 -1
- package/dist/src/__tests__/setup/integration.setup.js +0 -38
- package/dist/src/__tests__/setup/integration.setup.js.map +0 -1
- package/dist/src/__tests__/setup/unit.setup.js +0 -41
- package/dist/src/__tests__/setup/unit.setup.js.map +0 -1
- package/dist/src/__tests__/unit/utils/access.test.js +0 -116
- package/dist/src/__tests__/unit/utils/access.test.js.map +0 -1
- package/dist/src/__tests__/unit/utils/jwt.test.js +0 -238
- package/dist/src/__tests__/unit/utils/jwt.test.js.map +0 -1
- package/dist/src/collections/NewsletterSettings.js +0 -390
- package/dist/src/collections/NewsletterSettings.js.map +0 -1
- package/dist/src/collections/Subscribers.js +0 -309
- package/dist/src/collections/Subscribers.js.map +0 -1
- package/dist/src/components/MagicLinkVerify.js +0 -180
- package/dist/src/components/MagicLinkVerify.js.map +0 -1
- package/dist/src/components/NewsletterForm.js +0 -326
- package/dist/src/components/NewsletterForm.js.map +0 -1
- package/dist/src/components/PreferencesForm.js +0 -524
- package/dist/src/components/PreferencesForm.js.map +0 -1
- package/dist/src/components/index.js +0 -5
- package/dist/src/components/index.js.map +0 -1
- package/dist/src/endpoints/index.js +0 -17
- package/dist/src/endpoints/index.js.map +0 -1
- package/dist/src/endpoints/preferences.js +0 -136
- package/dist/src/endpoints/preferences.js.map +0 -1
- package/dist/src/endpoints/subscribe.js +0 -151
- package/dist/src/endpoints/subscribe.js.map +0 -1
- package/dist/src/endpoints/unsubscribe.js +0 -105
- package/dist/src/endpoints/unsubscribe.js.map +0 -1
- package/dist/src/endpoints/verify-magic-link.js +0 -103
- package/dist/src/endpoints/verify-magic-link.js.map +0 -1
- package/dist/src/exports/client.js +0 -7
- package/dist/src/exports/client.js.map +0 -1
- package/dist/src/exports/components.js +0 -6
- package/dist/src/exports/components.js.map +0 -1
- package/dist/src/exports/types.js +0 -3
- package/dist/src/exports/types.js.map +0 -1
- package/dist/src/fields/newsletterScheduling.js +0 -195
- package/dist/src/fields/newsletterScheduling.js.map +0 -1
- package/dist/src/hooks/useNewsletterAuth.js +0 -112
- package/dist/src/hooks/useNewsletterAuth.js.map +0 -1
- package/dist/src/index.js +0 -130
- package/dist/src/index.js.map +0 -1
- package/dist/src/providers/broadcast.js +0 -158
- package/dist/src/providers/broadcast.js.map +0 -1
- package/dist/src/providers/index.js +0 -63
- package/dist/src/providers/index.js.map +0 -1
- package/dist/src/providers/resend.js +0 -122
- package/dist/src/providers/resend.js.map +0 -1
- package/dist/src/providers/types.js +0 -12
- package/dist/src/providers/types.js.map +0 -1
- package/dist/src/templates/BaseTemplate.js +0 -105
- package/dist/src/templates/BaseTemplate.js.map +0 -1
- package/dist/src/templates/MagicLinkTemplate.js +0 -178
- package/dist/src/templates/MagicLinkTemplate.js.map +0 -1
- package/dist/src/templates/NewsletterTemplate.js +0 -150
- package/dist/src/templates/NewsletterTemplate.js.map +0 -1
- package/dist/src/templates/WelcomeTemplate.js +0 -192
- package/dist/src/templates/WelcomeTemplate.js.map +0 -1
- package/dist/src/templates/index.js +0 -6
- package/dist/src/templates/index.js.map +0 -1
- package/dist/src/types/index.js +0 -3
- package/dist/src/types/index.js.map +0 -1
- package/dist/src/utils/access.js +0 -80
- package/dist/src/utils/access.js.map +0 -1
- package/dist/src/utils/jwt.js +0 -91
- package/dist/src/utils/jwt.js.map +0 -1
- package/dist/src/utils/validation.js +0 -74
- package/dist/src/utils/validation.js.map +0 -1
- package/dist/templates/BaseTemplate.d.ts +0 -45
- package/dist/templates/BaseTemplate.d.ts.map +0 -1
- package/dist/templates/MagicLinkTemplate.d.ts +0 -67
- package/dist/templates/MagicLinkTemplate.d.ts.map +0 -1
- package/dist/templates/NewsletterTemplate.d.ts +0 -112
- package/dist/templates/NewsletterTemplate.d.ts.map +0 -1
- package/dist/templates/WelcomeTemplate.d.ts +0 -55
- package/dist/templates/WelcomeTemplate.d.ts.map +0 -1
- package/dist/templates/index.d.ts +0 -7
- package/dist/templates/index.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/access.d.ts +0 -15
- package/dist/utils/access.d.ts.map +0 -1
- package/dist/utils/jwt.d.ts +0 -32
- package/dist/utils/jwt.d.ts.map +0 -1
- package/dist/utils/validation.d.ts +0 -25
- package/dist/utils/validation.d.ts.map +0 -1
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { adminOnly, adminOrSelf } from '../../utils/access';
|
|
3
|
-
import { createMockUser, createMockAdminUser } from '../mocks/payload';
|
|
4
|
-
describe('Subscriber Access Control Security', ()=>{
|
|
5
|
-
let mockReq;
|
|
6
|
-
const mockConfig = {};
|
|
7
|
-
beforeEach(()=>{
|
|
8
|
-
mockReq = {
|
|
9
|
-
user: null
|
|
10
|
-
};
|
|
11
|
-
});
|
|
12
|
-
describe('adminOnly', ()=>{
|
|
13
|
-
it('should deny access to unauthenticated users', ()=>{
|
|
14
|
-
const access = adminOnly(mockConfig);
|
|
15
|
-
expect(access({
|
|
16
|
-
req: mockReq
|
|
17
|
-
})).toBe(false);
|
|
18
|
-
});
|
|
19
|
-
it('should deny access to regular users', ()=>{
|
|
20
|
-
mockReq.user = createMockUser();
|
|
21
|
-
const access = adminOnly(mockConfig);
|
|
22
|
-
expect(access({
|
|
23
|
-
req: mockReq
|
|
24
|
-
})).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
it('should allow access to admin users', ()=>{
|
|
27
|
-
mockReq.user = createMockAdminUser();
|
|
28
|
-
const access = adminOnly(mockConfig);
|
|
29
|
-
expect(access({
|
|
30
|
-
req: mockReq
|
|
31
|
-
})).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
it('should respect custom admin function', ()=>{
|
|
34
|
-
const customConfig = {
|
|
35
|
-
access: {
|
|
36
|
-
isAdmin: (user)=>user?.customRole === 'manager'
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
// Regular admin should be denied
|
|
40
|
-
mockReq.user = createMockAdminUser();
|
|
41
|
-
const access = adminOnly(customConfig);
|
|
42
|
-
expect(access({
|
|
43
|
-
req: mockReq
|
|
44
|
-
})).toBe(false);
|
|
45
|
-
// Custom role should be allowed
|
|
46
|
-
mockReq.user = createMockUser({
|
|
47
|
-
customRole: 'manager'
|
|
48
|
-
});
|
|
49
|
-
expect(access({
|
|
50
|
-
req: mockReq
|
|
51
|
-
})).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe('adminOrSelf', ()=>{
|
|
55
|
-
it('should deny access to unauthenticated users', ()=>{
|
|
56
|
-
const access = adminOrSelf(mockConfig);
|
|
57
|
-
expect(access({
|
|
58
|
-
req: mockReq,
|
|
59
|
-
id: 'sub-123'
|
|
60
|
-
})).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
it('should allow admin users to access any subscriber', ()=>{
|
|
63
|
-
mockReq.user = createMockAdminUser();
|
|
64
|
-
const access = adminOrSelf(mockConfig);
|
|
65
|
-
// Admin can access any subscriber
|
|
66
|
-
expect(access({
|
|
67
|
-
req: mockReq,
|
|
68
|
-
id: 'sub-123'
|
|
69
|
-
})).toBe(true);
|
|
70
|
-
expect(access({
|
|
71
|
-
req: mockReq,
|
|
72
|
-
id: 'sub-456'
|
|
73
|
-
})).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
it('should allow synthetic users to access their own data', ()=>{
|
|
76
|
-
// Synthetic user (from magic link)
|
|
77
|
-
mockReq.user = {
|
|
78
|
-
id: 'sub-123',
|
|
79
|
-
email: 'test@example.com',
|
|
80
|
-
collection: 'subscribers'
|
|
81
|
-
};
|
|
82
|
-
const access = adminOrSelf(mockConfig);
|
|
83
|
-
// Can access own data
|
|
84
|
-
expect(access({
|
|
85
|
-
req: mockReq,
|
|
86
|
-
id: 'sub-123'
|
|
87
|
-
})).toBe(true);
|
|
88
|
-
// Cannot access other subscribers
|
|
89
|
-
expect(access({
|
|
90
|
-
req: mockReq,
|
|
91
|
-
id: 'sub-456'
|
|
92
|
-
})).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
it('should deny regular users access to subscriber data', ()=>{
|
|
95
|
-
mockReq.user = createMockUser({
|
|
96
|
-
id: 'user-123'
|
|
97
|
-
});
|
|
98
|
-
const access = adminOrSelf(mockConfig);
|
|
99
|
-
// Regular users cannot access any subscriber data
|
|
100
|
-
expect(access({
|
|
101
|
-
req: mockReq,
|
|
102
|
-
id: 'sub-123'
|
|
103
|
-
})).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
it('should handle where queries for list operations', ()=>{
|
|
106
|
-
const access = adminOrSelf(mockConfig);
|
|
107
|
-
// Unauthenticated - no access
|
|
108
|
-
expect(access({
|
|
109
|
-
req: mockReq
|
|
110
|
-
})).toEqual({
|
|
111
|
-
id: {
|
|
112
|
-
equals: 'unauthorized-no-access'
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
// Admin - full access
|
|
116
|
-
mockReq.user = createMockAdminUser();
|
|
117
|
-
expect(access({
|
|
118
|
-
req: mockReq
|
|
119
|
-
})).toBe(true);
|
|
120
|
-
// Synthetic user - scoped to self
|
|
121
|
-
mockReq.user = {
|
|
122
|
-
id: 'sub-123',
|
|
123
|
-
email: 'test@example.com',
|
|
124
|
-
collection: 'subscribers'
|
|
125
|
-
};
|
|
126
|
-
expect(access({
|
|
127
|
-
req: mockReq
|
|
128
|
-
})).toEqual({
|
|
129
|
-
id: {
|
|
130
|
-
equals: 'sub-123'
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
// Regular user - no access
|
|
134
|
-
mockReq.user = createMockUser();
|
|
135
|
-
expect(access({
|
|
136
|
-
req: mockReq
|
|
137
|
-
})).toEqual({
|
|
138
|
-
id: {
|
|
139
|
-
equals: 'unauthorized-no-access'
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
it('should prevent data leakage through query manipulation', ()=>{
|
|
144
|
-
// Synthetic user trying to access other data
|
|
145
|
-
mockReq.user = {
|
|
146
|
-
id: 'sub-123',
|
|
147
|
-
email: 'attacker@example.com',
|
|
148
|
-
collection: 'subscribers'
|
|
149
|
-
};
|
|
150
|
-
const access = adminOrSelf(mockConfig);
|
|
151
|
-
// Direct ID access is properly restricted
|
|
152
|
-
expect(access({
|
|
153
|
-
req: mockReq,
|
|
154
|
-
id: 'sub-456'
|
|
155
|
-
})).toBe(false);
|
|
156
|
-
// List queries are properly scoped
|
|
157
|
-
const whereClause = access({
|
|
158
|
-
req: mockReq
|
|
159
|
-
});
|
|
160
|
-
expect(whereClause).toEqual({
|
|
161
|
-
id: {
|
|
162
|
-
equals: 'sub-123'
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe('Cross-subscriber data isolation', ()=>{
|
|
168
|
-
it('should prevent subscribers from accessing each other\'s data', ()=>{
|
|
169
|
-
const access = adminOrSelf(mockConfig);
|
|
170
|
-
// Subscriber A
|
|
171
|
-
const subscriberA = {
|
|
172
|
-
id: 'sub-a',
|
|
173
|
-
email: 'a@example.com',
|
|
174
|
-
collection: 'subscribers'
|
|
175
|
-
};
|
|
176
|
-
// Subscriber B trying to access Subscriber A's data
|
|
177
|
-
mockReq.user = {
|
|
178
|
-
id: 'sub-b',
|
|
179
|
-
email: 'b@example.com',
|
|
180
|
-
collection: 'subscribers'
|
|
181
|
-
};
|
|
182
|
-
expect(access({
|
|
183
|
-
req: mockReq,
|
|
184
|
-
id: 'sub-a'
|
|
185
|
-
})).toBe(false);
|
|
186
|
-
});
|
|
187
|
-
it('should prevent forged synthetic users', ()=>{
|
|
188
|
-
const access = adminOrSelf(mockConfig);
|
|
189
|
-
// Attacker trying to forge a synthetic user
|
|
190
|
-
mockReq.user = {
|
|
191
|
-
id: 'sub-target',
|
|
192
|
-
email: 'attacker@evil.com',
|
|
193
|
-
collection: 'subscribers'
|
|
194
|
-
};
|
|
195
|
-
// Even if they claim the right ID, the magic link system
|
|
196
|
-
// should have validated this is the correct user
|
|
197
|
-
expect(access({
|
|
198
|
-
req: mockReq,
|
|
199
|
-
id: 'sub-target'
|
|
200
|
-
})).toBe(true); // Access control trusts the auth layer
|
|
201
|
-
// But they still can't access other subscribers
|
|
202
|
-
expect(access({
|
|
203
|
-
req: mockReq,
|
|
204
|
-
id: 'sub-other'
|
|
205
|
-
})).toBe(false);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
//# sourceMappingURL=subscriber-access.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/__tests__/security/subscriber-access.test.ts"],"sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest'\nimport { adminOnly, adminOrSelf } from '../../utils/access'\nimport type { PayloadRequest } from 'payload'\nimport type { NewsletterPluginConfig } from '../../types'\nimport { createMockUser, createMockAdminUser } from '../mocks/payload'\n\ndescribe('Subscriber Access Control Security', () => {\n let mockReq: Partial<PayloadRequest>\n const mockConfig: NewsletterPluginConfig = {}\n\n beforeEach(() => {\n mockReq = {\n user: null,\n }\n })\n\n describe('adminOnly', () => {\n it('should deny access to unauthenticated users', () => {\n const access = adminOnly(mockConfig)\n expect(access({ req: mockReq as PayloadRequest })).toBe(false)\n })\n\n it('should deny access to regular users', () => {\n mockReq.user = createMockUser()\n const access = adminOnly(mockConfig)\n expect(access({ req: mockReq as PayloadRequest })).toBe(false)\n })\n\n it('should allow access to admin users', () => {\n mockReq.user = createMockAdminUser()\n const access = adminOnly(mockConfig)\n expect(access({ req: mockReq as PayloadRequest })).toBe(true)\n })\n\n it('should respect custom admin function', () => {\n const customConfig: NewsletterPluginConfig = {\n access: {\n isAdmin: (user) => user?.customRole === 'manager',\n },\n }\n\n // Regular admin should be denied\n mockReq.user = createMockAdminUser()\n const access = adminOnly(customConfig)\n expect(access({ req: mockReq as PayloadRequest })).toBe(false)\n\n // Custom role should be allowed\n mockReq.user = createMockUser({ customRole: 'manager' })\n expect(access({ req: mockReq as PayloadRequest })).toBe(true)\n })\n })\n\n describe('adminOrSelf', () => {\n it('should deny access to unauthenticated users', () => {\n const access = adminOrSelf(mockConfig)\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-123',\n })).toBe(false)\n })\n\n it('should allow admin users to access any subscriber', () => {\n mockReq.user = createMockAdminUser()\n const access = adminOrSelf(mockConfig)\n \n // Admin can access any subscriber\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-123',\n })).toBe(true)\n \n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-456',\n })).toBe(true)\n })\n\n it('should allow synthetic users to access their own data', () => {\n // Synthetic user (from magic link)\n mockReq.user = {\n id: 'sub-123',\n email: 'test@example.com',\n collection: 'subscribers',\n }\n \n const access = adminOrSelf(mockConfig)\n \n // Can access own data\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-123',\n })).toBe(true)\n \n // Cannot access other subscribers\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-456',\n })).toBe(false)\n })\n\n it('should deny regular users access to subscriber data', () => {\n mockReq.user = createMockUser({ id: 'user-123' })\n const access = adminOrSelf(mockConfig)\n \n // Regular users cannot access any subscriber data\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-123',\n })).toBe(false)\n })\n\n it('should handle where queries for list operations', () => {\n const access = adminOrSelf(mockConfig)\n \n // Unauthenticated - no access\n expect(access({ req: mockReq as PayloadRequest })).toEqual({\n id: { equals: 'unauthorized-no-access' },\n })\n \n // Admin - full access\n mockReq.user = createMockAdminUser()\n expect(access({ req: mockReq as PayloadRequest })).toBe(true)\n \n // Synthetic user - scoped to self\n mockReq.user = {\n id: 'sub-123',\n email: 'test@example.com',\n collection: 'subscribers',\n }\n expect(access({ req: mockReq as PayloadRequest })).toEqual({\n id: { equals: 'sub-123' },\n })\n \n // Regular user - no access\n mockReq.user = createMockUser()\n expect(access({ req: mockReq as PayloadRequest })).toEqual({\n id: { equals: 'unauthorized-no-access' },\n })\n })\n\n it('should prevent data leakage through query manipulation', () => {\n // Synthetic user trying to access other data\n mockReq.user = {\n id: 'sub-123',\n email: 'attacker@example.com',\n collection: 'subscribers',\n }\n \n const access = adminOrSelf(mockConfig)\n \n // Direct ID access is properly restricted\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-456', // Trying to access another subscriber\n })).toBe(false)\n \n // List queries are properly scoped\n const whereClause = access({ req: mockReq as PayloadRequest })\n expect(whereClause).toEqual({\n id: { equals: 'sub-123' },\n })\n })\n })\n\n describe('Cross-subscriber data isolation', () => {\n it('should prevent subscribers from accessing each other\\'s data', () => {\n const access = adminOrSelf(mockConfig)\n \n // Subscriber A\n const subscriberA = {\n id: 'sub-a',\n email: 'a@example.com',\n collection: 'subscribers',\n }\n \n // Subscriber B trying to access Subscriber A's data\n mockReq.user = {\n id: 'sub-b',\n email: 'b@example.com',\n collection: 'subscribers',\n }\n \n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-a',\n })).toBe(false)\n })\n\n it('should prevent forged synthetic users', () => {\n const access = adminOrSelf(mockConfig)\n \n // Attacker trying to forge a synthetic user\n mockReq.user = {\n id: 'sub-target',\n email: 'attacker@evil.com',\n collection: 'subscribers', // Claiming to be a subscriber\n // But this should be validated by the magic link system\n }\n \n // Even if they claim the right ID, the magic link system\n // should have validated this is the correct user\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-target',\n })).toBe(true) // Access control trusts the auth layer\n \n // But they still can't access other subscribers\n expect(access({ \n req: mockReq as PayloadRequest,\n id: 'sub-other',\n })).toBe(false)\n })\n })\n})"],"names":["describe","it","expect","beforeEach","adminOnly","adminOrSelf","createMockUser","createMockAdminUser","mockReq","mockConfig","user","access","req","toBe","customConfig","isAdmin","customRole","id","email","collection","toEqual","equals","whereClause","subscriberA"],"mappings":"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"}
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { createPayloadRequestMock, seedCollection, clearCollections } from '../mocks/payload';
|
|
3
|
-
import { mockNewsletterSettings } from '../fixtures/newsletter-settings';
|
|
4
|
-
describe('XSS Prevention', ()=>{
|
|
5
|
-
let mockReq;
|
|
6
|
-
const config = {};
|
|
7
|
-
beforeEach(()=>{
|
|
8
|
-
clearCollections();
|
|
9
|
-
seedCollection('newsletter-settings', [
|
|
10
|
-
mockNewsletterSettings
|
|
11
|
-
]);
|
|
12
|
-
const payloadMock = createPayloadRequestMock();
|
|
13
|
-
mockReq = {
|
|
14
|
-
payload: payloadMock.payload,
|
|
15
|
-
body: {}
|
|
16
|
-
};
|
|
17
|
-
vi.clearAllMocks();
|
|
18
|
-
});
|
|
19
|
-
describe('Input Sanitization', ()=>{
|
|
20
|
-
it('should sanitize subscriber name field', async ()=>{
|
|
21
|
-
const maliciousNames = [
|
|
22
|
-
'<script>alert("xss")</script>John',
|
|
23
|
-
'John<img src=x onerror=alert("xss")>',
|
|
24
|
-
'John<iframe src="javascript:alert(\'xss\')"></iframe>',
|
|
25
|
-
'<svg onload=alert("xss")>John</svg>',
|
|
26
|
-
'John<body onload=alert("xss")>'
|
|
27
|
-
];
|
|
28
|
-
for (const maliciousName of maliciousNames){
|
|
29
|
-
const result = await mockReq.payload.create({
|
|
30
|
-
collection: 'subscribers',
|
|
31
|
-
data: {
|
|
32
|
-
email: `test${Date.now()}@example.com`,
|
|
33
|
-
name: maliciousName
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
// Name should be sanitized (implementation dependent)
|
|
37
|
-
expect(result.name).not.toContain('<script>');
|
|
38
|
-
expect(result.name).not.toContain('alert(');
|
|
39
|
-
expect(result.name).not.toContain('onerror=');
|
|
40
|
-
expect(result.name).not.toContain('javascript:');
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
it('should not allow HTML in email addresses', async ()=>{
|
|
44
|
-
const maliciousEmails = [
|
|
45
|
-
'user<script>alert("xss")</script>@example.com',
|
|
46
|
-
'user@example.com<img src=x onerror=alert("xss")>',
|
|
47
|
-
'<user@example.com>'
|
|
48
|
-
];
|
|
49
|
-
for (const maliciousEmail of maliciousEmails){
|
|
50
|
-
try {
|
|
51
|
-
await mockReq.payload.create({
|
|
52
|
-
collection: 'subscribers',
|
|
53
|
-
data: {
|
|
54
|
-
email: maliciousEmail,
|
|
55
|
-
name: 'Test User'
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
} catch (error) {
|
|
59
|
-
// Should fail validation
|
|
60
|
-
expect(error.message).toContain('Invalid email');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
it('should sanitize custom fields', async ()=>{
|
|
65
|
-
const result = await mockReq.payload.create({
|
|
66
|
-
collection: 'subscribers',
|
|
67
|
-
data: {
|
|
68
|
-
email: 'test@example.com',
|
|
69
|
-
name: 'Test User',
|
|
70
|
-
customField: '<script>alert("xss")</script>Custom Value'
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
if (result.customField) {
|
|
74
|
-
expect(result.customField).not.toContain('<script>');
|
|
75
|
-
expect(result.customField).not.toContain('alert(');
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
describe('Template Injection Prevention', ()=>{
|
|
80
|
-
it('should prevent template injection in email subjects', async ()=>{
|
|
81
|
-
const maliciousSubjects = [
|
|
82
|
-
'{{process.env.JWT_SECRET}}',
|
|
83
|
-
'${process.env.JWT_SECRET}',
|
|
84
|
-
'<%= process.env.JWT_SECRET %>',
|
|
85
|
-
'#{process.env.JWT_SECRET}'
|
|
86
|
-
];
|
|
87
|
-
for (const subject of maliciousSubjects){
|
|
88
|
-
const settings = await mockReq.payload.update({
|
|
89
|
-
collection: 'newsletter-settings',
|
|
90
|
-
id: 'settings-1',
|
|
91
|
-
data: {
|
|
92
|
-
emailTemplates: {
|
|
93
|
-
welcome: {
|
|
94
|
-
subject: subject
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
// Subject should be treated as literal string, not evaluated
|
|
100
|
-
expect(settings.emailTemplates.welcome.subject).toBe(subject);
|
|
101
|
-
// When used, should not expose secrets
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
it('should escape user data in email templates', ()=>{
|
|
105
|
-
// Template rendering function (example)
|
|
106
|
-
const renderTemplate = (template, data)=>{
|
|
107
|
-
// Should escape HTML entities
|
|
108
|
-
const escaped = {};
|
|
109
|
-
for (const [key, value] of Object.entries(data)){
|
|
110
|
-
if (typeof value === 'string') {
|
|
111
|
-
escaped[key] = value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
112
|
-
} else {
|
|
113
|
-
escaped[key] = value;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Simple template replacement
|
|
117
|
-
return template.replace(/\{\{(\w+)\}\}/g, (match, key)=>escaped[key] || '');
|
|
118
|
-
};
|
|
119
|
-
const template = '<p>Hello {{name}}, welcome to {{newsletter}}!</p>';
|
|
120
|
-
const maliciousData = {
|
|
121
|
-
name: '<script>alert("xss")</script>',
|
|
122
|
-
newsletter: 'Test Newsletter<img src=x onerror=alert("xss")>'
|
|
123
|
-
};
|
|
124
|
-
const rendered = renderTemplate(template, maliciousData);
|
|
125
|
-
expect(rendered).not.toContain('<script>');
|
|
126
|
-
expect(rendered).not.toContain('<img src=x onerror=');
|
|
127
|
-
expect(rendered).toContain('<script>');
|
|
128
|
-
expect(rendered).toContain('<img');
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
describe('Content Security Policy', ()=>{
|
|
132
|
-
it('should set appropriate CSP headers for admin UI', ()=>{
|
|
133
|
-
// Mock response headers
|
|
134
|
-
const headers = {};
|
|
135
|
-
// CSP middleware (example)
|
|
136
|
-
const setCSPHeaders = (res)=>{
|
|
137
|
-
res.setHeader('Content-Security-Policy', [
|
|
138
|
-
"default-src 'self'",
|
|
139
|
-
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
|
140
|
-
"style-src 'self' 'unsafe-inline'",
|
|
141
|
-
"img-src 'self' data: https:",
|
|
142
|
-
"font-src 'self'",
|
|
143
|
-
"connect-src 'self'",
|
|
144
|
-
"frame-ancestors 'none'",
|
|
145
|
-
"base-uri 'self'",
|
|
146
|
-
"form-action 'self'"
|
|
147
|
-
].join('; '));
|
|
148
|
-
};
|
|
149
|
-
const mockRes = {
|
|
150
|
-
setHeader: (name, value)=>{
|
|
151
|
-
headers[name] = value;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
setCSPHeaders(mockRes);
|
|
155
|
-
expect(headers['Content-Security-Policy']).toContain("default-src 'self'");
|
|
156
|
-
expect(headers['Content-Security-Policy']).toContain("frame-ancestors 'none'");
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
describe('JSON Injection Prevention', ()=>{
|
|
160
|
-
it('should prevent JSON injection in API responses', async ()=>{
|
|
161
|
-
const maliciousData = {
|
|
162
|
-
email: 'test@example.com',
|
|
163
|
-
name: 'Test", "isAdmin": true, "name": "Hacked'
|
|
164
|
-
};
|
|
165
|
-
const result = await mockReq.payload.create({
|
|
166
|
-
collection: 'subscribers',
|
|
167
|
-
data: maliciousData
|
|
168
|
-
});
|
|
169
|
-
// The name should be stored as a string, not parsed as JSON
|
|
170
|
-
expect(result.name).toBe(maliciousData.name);
|
|
171
|
-
expect(result.isAdmin).toBeUndefined();
|
|
172
|
-
});
|
|
173
|
-
it('should properly escape JSON in responses', ()=>{
|
|
174
|
-
const escapeJSON = (data)=>{
|
|
175
|
-
return JSON.stringify(data).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
176
|
-
};
|
|
177
|
-
const data = {
|
|
178
|
-
name: 'Test\u2028User\u2029',
|
|
179
|
-
email: 'test@example.com'
|
|
180
|
-
};
|
|
181
|
-
const escaped = escapeJSON(data);
|
|
182
|
-
expect(escaped).not.toContain('\u2028');
|
|
183
|
-
expect(escaped).not.toContain('\u2029');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe('URL Injection Prevention', ()=>{
|
|
187
|
-
it('should validate redirect URLs', ()=>{
|
|
188
|
-
const validateRedirectUrl = (url, allowedHosts)=>{
|
|
189
|
-
try {
|
|
190
|
-
const parsed = new URL(url);
|
|
191
|
-
return allowedHosts.includes(parsed.host);
|
|
192
|
-
} catch {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
const allowedHosts = [
|
|
197
|
-
'example.com',
|
|
198
|
-
'app.example.com'
|
|
199
|
-
];
|
|
200
|
-
// Valid URLs
|
|
201
|
-
expect(validateRedirectUrl('https://example.com/preferences', allowedHosts)).toBe(true);
|
|
202
|
-
expect(validateRedirectUrl('https://app.example.com/unsubscribe', allowedHosts)).toBe(true);
|
|
203
|
-
// Invalid URLs
|
|
204
|
-
expect(validateRedirectUrl('https://evil.com/phishing', allowedHosts)).toBe(false);
|
|
205
|
-
expect(validateRedirectUrl('javascript:alert("xss")', allowedHosts)).toBe(false);
|
|
206
|
-
expect(validateRedirectUrl('data:text/html,<script>alert("xss")</script>', allowedHosts)).toBe(false);
|
|
207
|
-
});
|
|
208
|
-
it('should sanitize magic link URLs', ()=>{
|
|
209
|
-
const generateMagicLink = (baseUrl, token)=>{
|
|
210
|
-
// Validate base URL
|
|
211
|
-
try {
|
|
212
|
-
const url = new URL(baseUrl);
|
|
213
|
-
if (![
|
|
214
|
-
'http:',
|
|
215
|
-
'https:'
|
|
216
|
-
].includes(url.protocol)) {
|
|
217
|
-
throw new Error('Invalid protocol');
|
|
218
|
-
}
|
|
219
|
-
// Encode token to prevent injection
|
|
220
|
-
url.searchParams.set('token', encodeURIComponent(token));
|
|
221
|
-
return url.toString();
|
|
222
|
-
} catch {
|
|
223
|
-
throw new Error('Invalid base URL');
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
// Valid usage
|
|
227
|
-
const link = generateMagicLink('https://example.com/verify', 'abc123');
|
|
228
|
-
expect(link).toBe('https://example.com/verify?token=abc123');
|
|
229
|
-
// Token with special characters
|
|
230
|
-
const maliciousToken = '"><script>alert("xss")</script>';
|
|
231
|
-
const safeLink = generateMagicLink('https://example.com/verify', maliciousToken);
|
|
232
|
-
expect(safeLink).not.toContain('<script>');
|
|
233
|
-
// Verify the token is properly encoded (double-encoding is actually safer)
|
|
234
|
-
expect(safeLink).toContain('%253Cscript%253E');
|
|
235
|
-
expect(safeLink).toContain('%2522');
|
|
236
|
-
// Invalid base URLs
|
|
237
|
-
expect(()=>generateMagicLink('javascript:alert("xss")', 'token')).toThrow();
|
|
238
|
-
expect(()=>generateMagicLink('data:text/html,test', 'token')).toThrow();
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
describe('MongoDB Injection Prevention', ()=>{
|
|
242
|
-
it('should prevent NoSQL injection in queries', async ()=>{
|
|
243
|
-
// Malicious input attempting to bypass authentication
|
|
244
|
-
const maliciousInputs = [
|
|
245
|
-
{
|
|
246
|
-
email: {
|
|
247
|
-
$ne: null
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
email: {
|
|
252
|
-
$regex: '.*'
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
email: {
|
|
257
|
-
$where: 'this.isAdmin == true'
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
];
|
|
261
|
-
for (const input of maliciousInputs){
|
|
262
|
-
try {
|
|
263
|
-
await mockReq.payload.find({
|
|
264
|
-
collection: 'subscribers',
|
|
265
|
-
where: input
|
|
266
|
-
});
|
|
267
|
-
} catch (error) {
|
|
268
|
-
// Should either sanitize or reject
|
|
269
|
-
expect(error.message).toContain('Invalid');
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
it('should sanitize field names', async ()=>{
|
|
274
|
-
const maliciousFields = [
|
|
275
|
-
{
|
|
276
|
-
'$where': 'malicious code'
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
'__proto__': {
|
|
280
|
-
isAdmin: true
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
'constructor.prototype.isAdmin': true
|
|
285
|
-
}
|
|
286
|
-
];
|
|
287
|
-
for (const fields of maliciousFields){
|
|
288
|
-
try {
|
|
289
|
-
await mockReq.payload.create({
|
|
290
|
-
collection: 'subscribers',
|
|
291
|
-
data: {
|
|
292
|
-
email: 'test@example.com',
|
|
293
|
-
...fields
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
} catch (error) {
|
|
297
|
-
// Should reject dangerous field names
|
|
298
|
-
expect(error).toBeDefined();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
//# sourceMappingURL=xss-prevention.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/__tests__/security/xss-prevention.test.ts"],"sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest'\nimport { createPayloadRequestMock, seedCollection, clearCollections } from '../mocks/payload'\nimport { mockNewsletterSettings } from '../fixtures/newsletter-settings'\nimport type { NewsletterPluginConfig } from '../../types'\n\ndescribe('XSS Prevention', () => {\n let mockReq: any\n const config: NewsletterPluginConfig = {}\n\n beforeEach(() => {\n clearCollections()\n seedCollection('newsletter-settings', [mockNewsletterSettings])\n \n const payloadMock = createPayloadRequestMock()\n mockReq = {\n payload: payloadMock.payload,\n body: {},\n }\n \n vi.clearAllMocks()\n })\n\n describe('Input Sanitization', () => {\n it('should sanitize subscriber name field', async () => {\n const maliciousNames = [\n '<script>alert(\"xss\")</script>John',\n 'John<img src=x onerror=alert(\"xss\")>',\n 'John<iframe src=\"javascript:alert(\\'xss\\')\"></iframe>',\n '<svg onload=alert(\"xss\")>John</svg>',\n 'John<body onload=alert(\"xss\")>',\n ]\n\n for (const maliciousName of maliciousNames) {\n const result = await mockReq.payload.create({\n collection: 'subscribers',\n data: {\n email: `test${Date.now()}@example.com`,\n name: maliciousName,\n },\n })\n\n // Name should be sanitized (implementation dependent)\n expect(result.name).not.toContain('<script>')\n expect(result.name).not.toContain('alert(')\n expect(result.name).not.toContain('onerror=')\n expect(result.name).not.toContain('javascript:')\n }\n })\n\n it('should not allow HTML in email addresses', async () => {\n const maliciousEmails = [\n 'user<script>alert(\"xss\")</script>@example.com',\n 'user@example.com<img src=x onerror=alert(\"xss\")>',\n '<user@example.com>',\n ]\n\n for (const maliciousEmail of maliciousEmails) {\n try {\n await mockReq.payload.create({\n collection: 'subscribers',\n data: {\n email: maliciousEmail,\n name: 'Test User',\n },\n })\n } catch (error: any) {\n // Should fail validation\n expect(error.message).toContain('Invalid email')\n }\n }\n })\n\n it('should sanitize custom fields', async () => {\n const result = await mockReq.payload.create({\n collection: 'subscribers',\n data: {\n email: 'test@example.com',\n name: 'Test User',\n customField: '<script>alert(\"xss\")</script>Custom Value',\n },\n })\n\n if (result.customField) {\n expect(result.customField).not.toContain('<script>')\n expect(result.customField).not.toContain('alert(')\n }\n })\n })\n\n describe('Template Injection Prevention', () => {\n it('should prevent template injection in email subjects', async () => {\n const maliciousSubjects = [\n '{{process.env.JWT_SECRET}}',\n '${process.env.JWT_SECRET}',\n '<%= process.env.JWT_SECRET %>',\n '#{process.env.JWT_SECRET}',\n ]\n\n for (const subject of maliciousSubjects) {\n const settings = await mockReq.payload.update({\n collection: 'newsletter-settings',\n id: 'settings-1',\n data: {\n emailTemplates: {\n welcome: {\n subject: subject,\n },\n },\n },\n })\n\n // Subject should be treated as literal string, not evaluated\n expect(settings.emailTemplates.welcome.subject).toBe(subject)\n // When used, should not expose secrets\n }\n })\n\n it('should escape user data in email templates', () => {\n // Template rendering function (example)\n const renderTemplate = (template: string, data: any) => {\n // Should escape HTML entities\n const escaped: any = {}\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === 'string') {\n escaped[key] = value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n } else {\n escaped[key] = value\n }\n }\n \n // Simple template replacement\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => escaped[key] || '')\n }\n\n const template = '<p>Hello {{name}}, welcome to {{newsletter}}!</p>'\n const maliciousData = {\n name: '<script>alert(\"xss\")</script>',\n newsletter: 'Test Newsletter<img src=x onerror=alert(\"xss\")>',\n }\n\n const rendered = renderTemplate(template, maliciousData)\n \n expect(rendered).not.toContain('<script>')\n expect(rendered).not.toContain('<img src=x onerror=')\n expect(rendered).toContain('<script>')\n expect(rendered).toContain('<img')\n })\n })\n\n describe('Content Security Policy', () => {\n it('should set appropriate CSP headers for admin UI', () => {\n // Mock response headers\n const headers: Record<string, string> = {}\n \n // CSP middleware (example)\n const setCSPHeaders = (res: any) => {\n res.setHeader('Content-Security-Policy', [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-inline' 'unsafe-eval'\", // Payload admin needs these\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' data: https:\",\n \"font-src 'self'\",\n \"connect-src 'self'\",\n \"frame-ancestors 'none'\",\n \"base-uri 'self'\",\n \"form-action 'self'\",\n ].join('; '))\n }\n\n const mockRes = {\n setHeader: (name: string, value: string) => {\n headers[name] = value\n },\n }\n\n setCSPHeaders(mockRes)\n \n expect(headers['Content-Security-Policy']).toContain(\"default-src 'self'\")\n expect(headers['Content-Security-Policy']).toContain(\"frame-ancestors 'none'\")\n })\n })\n\n describe('JSON Injection Prevention', () => {\n it('should prevent JSON injection in API responses', async () => {\n const maliciousData = {\n email: 'test@example.com',\n name: 'Test\", \"isAdmin\": true, \"name\": \"Hacked',\n }\n\n const result = await mockReq.payload.create({\n collection: 'subscribers',\n data: maliciousData,\n })\n\n // The name should be stored as a string, not parsed as JSON\n expect(result.name).toBe(maliciousData.name)\n expect(result.isAdmin).toBeUndefined()\n })\n\n it('should properly escape JSON in responses', () => {\n const escapeJSON = (data: any): string => {\n return JSON.stringify(data)\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n }\n\n const data = {\n name: 'Test\\u2028User\\u2029',\n email: 'test@example.com',\n }\n\n const escaped = escapeJSON(data)\n expect(escaped).not.toContain('\\u2028')\n expect(escaped).not.toContain('\\u2029')\n })\n })\n\n describe('URL Injection Prevention', () => {\n it('should validate redirect URLs', () => {\n const validateRedirectUrl = (url: string, allowedHosts: string[]): boolean => {\n try {\n const parsed = new URL(url)\n return allowedHosts.includes(parsed.host)\n } catch {\n return false\n }\n }\n\n const allowedHosts = ['example.com', 'app.example.com']\n \n // Valid URLs\n expect(validateRedirectUrl('https://example.com/preferences', allowedHosts)).toBe(true)\n expect(validateRedirectUrl('https://app.example.com/unsubscribe', allowedHosts)).toBe(true)\n \n // Invalid URLs\n expect(validateRedirectUrl('https://evil.com/phishing', allowedHosts)).toBe(false)\n expect(validateRedirectUrl('javascript:alert(\"xss\")', allowedHosts)).toBe(false)\n expect(validateRedirectUrl('data:text/html,<script>alert(\"xss\")</script>', allowedHosts)).toBe(false)\n })\n\n it('should sanitize magic link URLs', () => {\n const generateMagicLink = (baseUrl: string, token: string): string => {\n // Validate base URL\n try {\n const url = new URL(baseUrl)\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw new Error('Invalid protocol')\n }\n \n // Encode token to prevent injection\n url.searchParams.set('token', encodeURIComponent(token))\n return url.toString()\n } catch {\n throw new Error('Invalid base URL')\n }\n }\n\n // Valid usage\n const link = generateMagicLink('https://example.com/verify', 'abc123')\n expect(link).toBe('https://example.com/verify?token=abc123')\n \n // Token with special characters\n const maliciousToken = '\"><script>alert(\"xss\")</script>'\n const safeLink = generateMagicLink('https://example.com/verify', maliciousToken)\n expect(safeLink).not.toContain('<script>')\n // Verify the token is properly encoded (double-encoding is actually safer)\n expect(safeLink).toContain('%253Cscript%253E')\n expect(safeLink).toContain('%2522')\n \n // Invalid base URLs\n expect(() => generateMagicLink('javascript:alert(\"xss\")', 'token')).toThrow()\n expect(() => generateMagicLink('data:text/html,test', 'token')).toThrow()\n })\n })\n\n describe('MongoDB Injection Prevention', () => {\n it('should prevent NoSQL injection in queries', async () => {\n // Malicious input attempting to bypass authentication\n const maliciousInputs = [\n { email: { $ne: null } }, // Trying to get all records\n { email: { $regex: '.*' } }, // Regex injection\n { email: { $where: 'this.isAdmin == true' } }, // JavaScript injection\n ]\n\n for (const input of maliciousInputs) {\n try {\n await mockReq.payload.find({\n collection: 'subscribers',\n where: input as any,\n })\n } catch (error: any) {\n // Should either sanitize or reject\n expect(error.message).toContain('Invalid')\n }\n }\n })\n\n it('should sanitize field names', async () => {\n const maliciousFields = [\n { '$where': 'malicious code' },\n { '__proto__': { isAdmin: true } },\n { 'constructor.prototype.isAdmin': true },\n ]\n\n for (const fields of maliciousFields) {\n try {\n await mockReq.payload.create({\n collection: 'subscribers',\n data: {\n email: 'test@example.com',\n ...fields,\n },\n })\n } catch (error: any) {\n // Should reject dangerous field names\n expect(error).toBeDefined()\n }\n }\n })\n })\n})"],"names":["describe","it","expect","beforeEach","vi","createPayloadRequestMock","seedCollection","clearCollections","mockNewsletterSettings","mockReq","config","payloadMock","payload","body","clearAllMocks","maliciousNames","maliciousName","result","create","collection","data","email","Date","now","name","not","toContain","maliciousEmails","maliciousEmail","error","message","customField","maliciousSubjects","subject","settings","update","id","emailTemplates","welcome","toBe","renderTemplate","template","escaped","key","value","Object","entries","replace","match","maliciousData","newsletter","rendered","headers","setCSPHeaders","res","setHeader","join","mockRes","isAdmin","toBeUndefined","escapeJSON","JSON","stringify","validateRedirectUrl","url","allowedHosts","parsed","URL","includes","host","generateMagicLink","baseUrl","token","protocol","Error","searchParams","set","encodeURIComponent","toString","link","maliciousToken","safeLink","toThrow","maliciousInputs","$ne","$regex","$where","input","find","where","maliciousFields","fields","toBeDefined"],"mappings":"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"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { vi } from 'vitest';
|
|
2
|
-
import { MongoMemoryServer } from 'mongodb-memory-server';
|
|
3
|
-
// MongoDB Memory Server instance
|
|
4
|
-
let mongoServer;
|
|
5
|
-
// Mock environment variables
|
|
6
|
-
process.env.JWT_SECRET = 'test-jwt-secret';
|
|
7
|
-
process.env.PAYLOAD_SECRET = 'test-payload-secret';
|
|
8
|
-
process.env.NODE_ENV = 'test';
|
|
9
|
-
// Setup MongoDB Memory Server
|
|
10
|
-
beforeAll(async ()=>{
|
|
11
|
-
if (process.env.TEST_USE_MONGODB_MEMORY_SERVER === 'true') {
|
|
12
|
-
mongoServer = await MongoMemoryServer.create();
|
|
13
|
-
const mongoUri = mongoServer.getUri();
|
|
14
|
-
process.env.DATABASE_URI = mongoUri;
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
// Cleanup MongoDB Memory Server
|
|
18
|
-
afterAll(async ()=>{
|
|
19
|
-
if (mongoServer) {
|
|
20
|
-
await mongoServer.stop();
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
// Reset mocks before each test
|
|
24
|
-
beforeEach(()=>{
|
|
25
|
-
vi.clearAllMocks();
|
|
26
|
-
});
|
|
27
|
-
// Suppress console logs in tests unless debugging
|
|
28
|
-
if (!process.env.DEBUG_TESTS) {
|
|
29
|
-
global.console = {
|
|
30
|
-
...console,
|
|
31
|
-
log: vi.fn(),
|
|
32
|
-
error: vi.fn(),
|
|
33
|
-
warn: vi.fn(),
|
|
34
|
-
info: vi.fn()
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=integration.setup.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/__tests__/setup/integration.setup.ts"],"sourcesContent":["import { vi } from 'vitest'\nimport { MongoMemoryServer } from 'mongodb-memory-server'\n\n// MongoDB Memory Server instance\nlet mongoServer: MongoMemoryServer | undefined\n\n// Mock environment variables\nprocess.env.JWT_SECRET = 'test-jwt-secret'\nprocess.env.PAYLOAD_SECRET = 'test-payload-secret'\nprocess.env.NODE_ENV = 'test'\n\n// Setup MongoDB Memory Server\nbeforeAll(async () => {\n if (process.env.TEST_USE_MONGODB_MEMORY_SERVER === 'true') {\n mongoServer = await MongoMemoryServer.create()\n const mongoUri = mongoServer.getUri()\n process.env.DATABASE_URI = mongoUri\n }\n})\n\n// Cleanup MongoDB Memory Server\nafterAll(async () => {\n if (mongoServer) {\n await mongoServer.stop()\n }\n})\n\n// Reset mocks before each test\nbeforeEach(() => {\n vi.clearAllMocks()\n})\n\n// Suppress console logs in tests unless debugging\nif (!process.env.DEBUG_TESTS) {\n global.console = {\n ...console,\n log: vi.fn(),\n error: vi.fn(),\n warn: vi.fn(),\n info: vi.fn(),\n }\n}"],"names":["vi","MongoMemoryServer","mongoServer","process","env","JWT_SECRET","PAYLOAD_SECRET","NODE_ENV","beforeAll","TEST_USE_MONGODB_MEMORY_SERVER","create","mongoUri","getUri","DATABASE_URI","afterAll","stop","beforeEach","clearAllMocks","DEBUG_TESTS","global","console","log","fn","error","warn","info"],"mappings":"AAAA,SAASA,EAAE,QAAQ,SAAQ;AAC3B,SAASC,iBAAiB,QAAQ,wBAAuB;AAEzD,iCAAiC;AACjC,IAAIC;AAEJ,6BAA6B;AAC7BC,QAAQC,GAAG,CAACC,UAAU,GAAG;AACzBF,QAAQC,GAAG,CAACE,cAAc,GAAG;AAC7BH,QAAQC,GAAG,CAACG,QAAQ,GAAG;AAEvB,8BAA8B;AAC9BC,UAAU;IACR,IAAIL,QAAQC,GAAG,CAACK,8BAA8B,KAAK,QAAQ;QACzDP,cAAc,MAAMD,kBAAkBS,MAAM;QAC5C,MAAMC,WAAWT,YAAYU,MAAM;QACnCT,QAAQC,GAAG,CAACS,YAAY,GAAGF;IAC7B;AACF;AAEA,gCAAgC;AAChCG,SAAS;IACP,IAAIZ,aAAa;QACf,MAAMA,YAAYa,IAAI;IACxB;AACF;AAEA,+BAA+B;AAC/BC,WAAW;IACThB,GAAGiB,aAAa;AAClB;AAEA,kDAAkD;AAClD,IAAI,CAACd,QAAQC,GAAG,CAACc,WAAW,EAAE;IAC5BC,OAAOC,OAAO,GAAG;QACf,GAAGA,OAAO;QACVC,KAAKrB,GAAGsB,EAAE;QACVC,OAAOvB,GAAGsB,EAAE;QACZE,MAAMxB,GAAGsB,EAAE;QACXG,MAAMzB,GAAGsB,EAAE;IACb;AACF"}
|