@nfcard/validation 0.6.0 → 0.9.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/dist/index.d.ts +279 -197
- package/dist/index.js +0 -0
- package/package.json +2 -2
- package/src/index.test.ts +76 -0
- package/src/index.ts +0 -0
package/dist/index.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nfcard/validation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Shared Zod validation schemas for the NFCard product family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"zod": "^3.24.0",
|
|
20
|
-
"@nfcard/types": "0.
|
|
20
|
+
"@nfcard/types": "0.8.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"tsup": "^8.0.0"
|
package/src/index.test.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
sanitizeText,
|
|
17
17
|
signupWithProfileSchema,
|
|
18
18
|
socialLinksSchema,
|
|
19
|
+
socialOverridesSchema,
|
|
19
20
|
userDataSchema,
|
|
20
21
|
validateConfiguration,
|
|
21
22
|
validateDigitalConfig,
|
|
@@ -209,6 +210,64 @@ describe('socialLinksSchema', () => {
|
|
|
209
210
|
});
|
|
210
211
|
});
|
|
211
212
|
|
|
213
|
+
// ── socialOverridesSchema (FOXHOLE-595 input side) ──────────────
|
|
214
|
+
|
|
215
|
+
describe('socialOverridesSchema (FOXHOLE-595 input side)', () => {
|
|
216
|
+
it('sanitizes every present URL field to safe https (or drops unsafe)', () => {
|
|
217
|
+
const out = socialOverridesSchema.parse({
|
|
218
|
+
facebook: 'https://facebook.com/foxhome',
|
|
219
|
+
linkedin: 'javascript:alert(1)', // unsafe scheme → ''
|
|
220
|
+
instagram: 'instagram.com/foxhome', // bare host → https
|
|
221
|
+
x: 'http://x.com/foxhome', // http upgraded → https
|
|
222
|
+
customUrl: '<script>x</script>https://foo.bar', // tags stripped
|
|
223
|
+
customUrlLabel: '<b>My link</b>',
|
|
224
|
+
});
|
|
225
|
+
expect(out.facebook).toBe('https://facebook.com/foxhome');
|
|
226
|
+
expect(out.linkedin).toBe('');
|
|
227
|
+
expect(out.instagram).toBe('https://instagram.com/foxhome');
|
|
228
|
+
expect(out.x?.startsWith('https://')).toBe(true);
|
|
229
|
+
expect(out.customUrl).not.toMatch(/script/i);
|
|
230
|
+
expect(out.customUrlLabel).toBe('My link');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('preserves SPARSE semantics — omitted keys stay omitted (no merge clobber)', () => {
|
|
234
|
+
// The renderer merges { ...baseSocialLinks, ...socialOverrides }; an
|
|
235
|
+
// omitted override key must NOT appear as '' or it would wipe the
|
|
236
|
+
// inherited link. See socialOverridesSchema comment.
|
|
237
|
+
const out = socialOverridesSchema.parse({ facebook: 'https://fb.com/x' });
|
|
238
|
+
expect(out.facebook).toBe('https://fb.com/x');
|
|
239
|
+
expect('linkedin' in out).toBe(false);
|
|
240
|
+
expect('x' in out).toBe(false);
|
|
241
|
+
expect('customUrl' in out).toBe(false);
|
|
242
|
+
expect('customUrlLabel' in out).toBe(false);
|
|
243
|
+
expect(Object.keys(out)).toEqual(['facebook']);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('includes x for parity with the SocialLinks type + renderer', () => {
|
|
247
|
+
const out = socialOverridesSchema.parse({ x: 'x.com/foo' });
|
|
248
|
+
expect(out.x).toBe('https://x.com/foo');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it.each([
|
|
252
|
+
'javascript:alert(1)',
|
|
253
|
+
'JaVaScRiPt:alert(1)',
|
|
254
|
+
'data:text/html,<script>alert(1)</script>',
|
|
255
|
+
'vbscript:msgbox(1)',
|
|
256
|
+
'file:///etc/passwd',
|
|
257
|
+
'mailto:a@b.c',
|
|
258
|
+
])('coerces a dangerous override to empty rather than 400ing: %s', (bad) => {
|
|
259
|
+
const r = socialOverridesSchema.safeParse({ customUrl: bad });
|
|
260
|
+
expect(r.success).toBe(true);
|
|
261
|
+
if (r.success) expect(r.data.customUrl).toBe('');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('accepts an empty object (every field optional)', () => {
|
|
265
|
+
const r = socialOverridesSchema.safeParse({});
|
|
266
|
+
expect(r.success).toBe(true);
|
|
267
|
+
if (r.success) expect(Object.keys(r.data)).toEqual([]);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
212
271
|
// ── userDataSchema ──────────────────────────────────────────────
|
|
213
272
|
|
|
214
273
|
describe('userDataSchema / validateUserData', () => {
|
|
@@ -379,6 +438,23 @@ describe('digitalConfigSchema / validateDigitalConfig', () => {
|
|
|
379
438
|
const r = validateDigitalConfig({ ...validDigitalConfig, fontKey: '' });
|
|
380
439
|
expect(r.success).toBe(false);
|
|
381
440
|
});
|
|
441
|
+
|
|
442
|
+
it('sanitizes socialOverrides on the full config (FOXHOLE-595 input side)', () => {
|
|
443
|
+
const r = validateDigitalConfig({
|
|
444
|
+
...validDigitalConfig,
|
|
445
|
+
socialOverrides: {
|
|
446
|
+
linkedin: 'javascript:alert(1)', // unsafe → ''
|
|
447
|
+
facebook: 'fb.com/x', // bare host → https
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
expect(r.success).toBe(true);
|
|
451
|
+
if (r.success) {
|
|
452
|
+
expect(r.data.socialOverrides?.linkedin).toBe('');
|
|
453
|
+
expect(r.data.socialOverrides?.facebook).toBe('https://fb.com/x');
|
|
454
|
+
// omitted override keys stay absent — no merge clobber downstream
|
|
455
|
+
expect('instagram' in (r.data.socialOverrides ?? {})).toBe(false);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
382
458
|
});
|
|
383
459
|
|
|
384
460
|
// ── configurationCreateSchema ───────────────────────────────────
|
package/src/index.ts
CHANGED
|
Binary file
|