@plasius/schema 1.0.18 → 1.1.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/README.md +140 -1
- package/dist/index.cjs +1934 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +391 -0
- package/dist/index.d.ts +391 -0
- package/dist/index.js +1883 -0
- package/dist/index.js.map +1 -0
- package/package.json +18 -6
- package/.eslintrc.cjs +0 -7
- package/.github/workflows/cd.yml +0 -186
- package/.github/workflows/ci.yml +0 -16
- package/.nvmrc +0 -1
- package/.vscode/launch.json +0 -15
- package/CHANGELOG.md +0 -86
- package/CODE_OF_CONDUCT.md +0 -79
- package/CONTRIBUTING.md +0 -201
- package/CONTRIBUTORS.md +0 -27
- package/SECURITY.md +0 -17
- package/docs/adrs/adr-0001: schema.md +0 -45
- package/docs/adrs/adr-template.md +0 -67
- package/legal/CLA-REGISTRY.csv +0 -2
- package/legal/CLA.md +0 -22
- package/legal/CORPORATE_CLA.md +0 -57
- package/legal/INDIVIDUAL_CLA.md +0 -91
- package/sbom.cdx.json +0 -66
- package/src/components.ts +0 -39
- package/src/field.builder.ts +0 -119
- package/src/field.ts +0 -14
- package/src/index.ts +0 -7
- package/src/infer.ts +0 -34
- package/src/pii.ts +0 -165
- package/src/schema.ts +0 -826
- package/src/types.ts +0 -156
- package/src/validation/countryCode.ISO3166.ts +0 -256
- package/src/validation/currencyCode.ISO4217.ts +0 -191
- package/src/validation/dateTime.ISO8601.ts +0 -9
- package/src/validation/email.RFC5322.ts +0 -9
- package/src/validation/generalText.OWASP.ts +0 -39
- package/src/validation/index.ts +0 -13
- package/src/validation/name.OWASP.ts +0 -25
- package/src/validation/percentage.ISO80000-1.ts +0 -8
- package/src/validation/phone.E.164.ts +0 -9
- package/src/validation/richtext.OWASP.ts +0 -34
- package/src/validation/url.WHATWG.ts +0 -16
- package/src/validation/user.MS-GOOGLE-APPLE.ts +0 -31
- package/src/validation/uuid.RFC4122.ts +0 -10
- package/src/validation/version.SEMVER2.0.0.ts +0 -8
- package/tests/pii.test.ts +0 -139
- package/tests/schema.test.ts +0 -501
- package/tests/test-utils.ts +0 -97
- package/tests/validate.test.ts +0 -97
- package/tests/validation.test.ts +0 -98
- package/tsconfig.build.json +0 -19
- package/tsconfig.json +0 -7
- package/tsup.config.ts +0 -10
- package/vitest.config.js +0 -20
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates a phone number string in strict E.164 format.
|
|
3
|
-
* Returns true for strings like "+441632960960" (max 15 digits, starting with a '+').
|
|
4
|
-
*/
|
|
5
|
-
export const validatePhone = (value: unknown): boolean => {
|
|
6
|
-
if (typeof value !== "string") return false;
|
|
7
|
-
const phoneRegex = /^\+[1-9]\d{1,14}$/; // E.164 format
|
|
8
|
-
return phoneRegex.test(value);
|
|
9
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates rich text input to ensure it contains only safe HTML/Markdown.
|
|
3
|
-
* Global Standard: OWASP HTML Sanitization Guidelines (2024).
|
|
4
|
-
* This validator checks for dangerous patterns — does not sanitize — assumes text will be sanitized downstream.
|
|
5
|
-
*/
|
|
6
|
-
export function validateRichText(value: unknown): boolean {
|
|
7
|
-
if (typeof value !== "string") return false;
|
|
8
|
-
const trimmed = value.trim();
|
|
9
|
-
if (trimmed.length === 0) return true; // Allow empty rich text
|
|
10
|
-
|
|
11
|
-
// Reject known dangerous tags
|
|
12
|
-
if (
|
|
13
|
-
/<(script|iframe|object|embed|style|link|meta|base|form|input|button|textarea|select)\b/i.test(
|
|
14
|
-
trimmed
|
|
15
|
-
)
|
|
16
|
-
) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Reject javascript: links
|
|
21
|
-
if (/javascript:/i.test(trimmed)) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Reject event handlers (onload, onclick, etc)
|
|
26
|
-
if (/on\w+=["']?/i.test(trimmed)) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Optionally: limit max length (e.g. 10,000 chars)
|
|
31
|
-
if (trimmed.length > 10000) return false;
|
|
32
|
-
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates a URL string using the WHATWG URL API.
|
|
3
|
-
* Accepts only 'http' or 'https' protocols.
|
|
4
|
-
* Returns true if the URL is syntactically valid.
|
|
5
|
-
*/
|
|
6
|
-
export const validateUrl = (value: unknown): boolean => {
|
|
7
|
-
if (typeof value !== "string") return false;
|
|
8
|
-
try {
|
|
9
|
-
const url = new URL(value);
|
|
10
|
-
// Enforce scheme:
|
|
11
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
12
|
-
return true;
|
|
13
|
-
} catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates that a user ID is a valid `sub` from one of the supported identity providers.
|
|
3
|
-
* Global Standard: OpenID Connect Core 1.0 `sub` claim.
|
|
4
|
-
*/
|
|
5
|
-
export function validateUserId(value: unknown): boolean {
|
|
6
|
-
if (typeof value !== "string") return false;
|
|
7
|
-
|
|
8
|
-
const trimmed = value.trim();
|
|
9
|
-
if (trimmed.length === 0) return false;
|
|
10
|
-
|
|
11
|
-
// Google: all digits
|
|
12
|
-
const googlePattern = /^\d{21,22}$/;
|
|
13
|
-
|
|
14
|
-
// Microsoft: UUID v4
|
|
15
|
-
const microsoftPattern =
|
|
16
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
17
|
-
|
|
18
|
-
// Apple: opaque, usually UUID but allow any printable string up to 255 chars
|
|
19
|
-
const applePattern = /^[\w\-.]{6,255}$/; // Alphanumeric + - . _ (common safe characters)
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
googlePattern.test(trimmed) ||
|
|
23
|
-
microsoftPattern.test(trimmed) ||
|
|
24
|
-
applePattern.test(trimmed)
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function validateUserIdArray(value: unknown): boolean {
|
|
29
|
-
if (!Array.isArray(value)) return false;
|
|
30
|
-
return value.every(validateUserId);
|
|
31
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates a string against the RFC 4122 format for UUIDs.
|
|
3
|
-
* Matches UUIDs of versions 1 to 5, e.g., "123e4567-e89b-12d3-a456-426614174000".
|
|
4
|
-
*/
|
|
5
|
-
export const validateUUID = (value: unknown): boolean => {
|
|
6
|
-
if (typeof value !== "string") return false;
|
|
7
|
-
const uuidRegex =
|
|
8
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
9
|
-
return uuidRegex.test(value);
|
|
10
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates that a version string conforms to Semantic Versioning (SemVer 2.0.0).
|
|
3
|
-
* Global Standard: https://semver.org/
|
|
4
|
-
*/
|
|
5
|
-
export function validateSemVer(value: unknown): boolean {
|
|
6
|
-
if (typeof value !== "string") return false;
|
|
7
|
-
return /^(\d+)\.(\d+)\.(\d+)$/.test(value);
|
|
8
|
-
}
|
package/tests/pii.test.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createSchema } from "../src/schema.js";
|
|
3
|
-
import { field } from "../src/field.js";
|
|
4
|
-
import { expectValid, expectInvalid } from "./test-utils.js";
|
|
5
|
-
|
|
6
|
-
// --- PII: sanitizeForLog should clean output correctly ---
|
|
7
|
-
describe("PII log sanitization", () => {
|
|
8
|
-
const LogSchema = createSchema(
|
|
9
|
-
{
|
|
10
|
-
email: field.string().PID({
|
|
11
|
-
classification: "high",
|
|
12
|
-
action: "encrypt",
|
|
13
|
-
logHandling: "redact",
|
|
14
|
-
purpose: "user contact",
|
|
15
|
-
}),
|
|
16
|
-
sessionId: field.string().PID({
|
|
17
|
-
classification: "low",
|
|
18
|
-
action: "hash",
|
|
19
|
-
logHandling: "pseudonym",
|
|
20
|
-
purpose: "session correlation",
|
|
21
|
-
}),
|
|
22
|
-
nickname: field.string().PID({
|
|
23
|
-
classification: "none",
|
|
24
|
-
action: "none",
|
|
25
|
-
logHandling: "plain",
|
|
26
|
-
}),
|
|
27
|
-
internalNote: field.string().PID({
|
|
28
|
-
classification: "low",
|
|
29
|
-
action: "clear",
|
|
30
|
-
logHandling: "omit",
|
|
31
|
-
purpose: "ops-only",
|
|
32
|
-
}),
|
|
33
|
-
},
|
|
34
|
-
"LogSchema",
|
|
35
|
-
{ version: "1.0", piiEnforcement: "strict" }
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
it("should redact, pseudonymize, pass-through, and omit according to logHandling", () => {
|
|
39
|
-
const input = {
|
|
40
|
-
email: "a@b.com",
|
|
41
|
-
sessionId: "abc123",
|
|
42
|
-
nickname: "phil",
|
|
43
|
-
internalNote: "sensitive",
|
|
44
|
-
other: "visible-but-not-in-shape",
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
|
-
const pseudo = (v: any) => `pseudo(${String(v).length})`;
|
|
48
|
-
const out = LogSchema.sanitizeForLog(input as any, pseudo);
|
|
49
|
-
|
|
50
|
-
// redact
|
|
51
|
-
expect(out.email).toBe("[REDACTED]");
|
|
52
|
-
// pseudonym
|
|
53
|
-
expect(out.sessionId).toBe("pseudo(6)");
|
|
54
|
-
// plain
|
|
55
|
-
expect(out.nickname).toBe("phil");
|
|
56
|
-
// omitted entirely
|
|
57
|
-
expect(Object.prototype.hasOwnProperty.call(out, "internalNote")).toBe(
|
|
58
|
-
false
|
|
59
|
-
);
|
|
60
|
-
// unknown fields are not included (shape-driven)
|
|
61
|
-
expect(Object.prototype.hasOwnProperty.call(out, "other")).toBe(false);
|
|
62
|
-
|
|
63
|
-
// ensure no storage artifacts leak into logs
|
|
64
|
-
expect(Object.keys(out).some((k) => k.endsWith("Encrypted"))).toBe(false);
|
|
65
|
-
expect(Object.keys(out).some((k) => k.endsWith("Hash"))).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("should handle missing optional fields by simply excluding them from sanitized output", () => {
|
|
69
|
-
const minimal = { email: "x@y.com", sessionId: "id" } as const;
|
|
70
|
-
const out = LogSchema.sanitizeForLog(minimal as any, (v) => `p(${v})`);
|
|
71
|
-
expect(out.email).toBe("[REDACTED]");
|
|
72
|
-
expect(out.sessionId).toBe("p(id)");
|
|
73
|
-
// nickname/internalNote omitted if not present
|
|
74
|
-
expect(out.nickname).toBe(undefined);
|
|
75
|
-
expect(Object.prototype.hasOwnProperty.call(out, "internalNote")).toBe(
|
|
76
|
-
false
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// --- PII: auditing of shape metadata ---
|
|
81
|
-
describe("PII auditing", () => {
|
|
82
|
-
it("getPiiAudit should include only fields with classification !== 'none' and capture action/logHandling/purpose", () => {
|
|
83
|
-
const audit = LogSchema.getPiiAudit();
|
|
84
|
-
expect(audit).toEqual([
|
|
85
|
-
{
|
|
86
|
-
field: "email",
|
|
87
|
-
classification: "high",
|
|
88
|
-
action: "encrypt",
|
|
89
|
-
logHandling: "redact",
|
|
90
|
-
purpose: "user contact",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
field: "sessionId",
|
|
94
|
-
classification: "low",
|
|
95
|
-
action: "hash",
|
|
96
|
-
logHandling: "pseudonym",
|
|
97
|
-
purpose: "session correlation",
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
field: "internalNote",
|
|
101
|
-
classification: "low",
|
|
102
|
-
action: "clear",
|
|
103
|
-
logHandling: "omit",
|
|
104
|
-
purpose: "ops-only",
|
|
105
|
-
},
|
|
106
|
-
]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("getPiiAudit should not include fields with classification 'none' (e.g., nickname)", () => {
|
|
110
|
-
const audit = LogSchema.getPiiAudit();
|
|
111
|
-
const fields = audit?.map((a) => a.field);
|
|
112
|
-
expect(fields?.includes("nickname")).toBe(false);
|
|
113
|
-
expect(fields).toEqual(["email", "sessionId", "internalNote"]);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("audit content should be independent of enforcement mode", () => {
|
|
117
|
-
const AltSchema = createSchema(
|
|
118
|
-
{
|
|
119
|
-
email: field.string().PID({
|
|
120
|
-
classification: "high",
|
|
121
|
-
action: "encrypt",
|
|
122
|
-
logHandling: "redact",
|
|
123
|
-
}),
|
|
124
|
-
},
|
|
125
|
-
"Alt",
|
|
126
|
-
{ version: "1.0", piiEnforcement: "none" }
|
|
127
|
-
);
|
|
128
|
-
expect(AltSchema.getPiiAudit()).toEqual([
|
|
129
|
-
{
|
|
130
|
-
field: "email",
|
|
131
|
-
classification: "high",
|
|
132
|
-
action: "encrypt",
|
|
133
|
-
logHandling: "redact",
|
|
134
|
-
purpose: undefined,
|
|
135
|
-
},
|
|
136
|
-
]);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
});
|