@spacefast/common 0.0.12 → 0.0.17
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/agents/client-registry.d.ts +22 -0
- package/dist/agents/client-registry.js +41 -0
- package/dist/agents/connect-targets.d.ts +51 -6
- package/dist/agents/connect-targets.js +93 -28
- package/dist/brand.d.ts +0 -11
- package/dist/brand.js +0 -20
- package/dist/config/domains.d.ts +15 -6
- package/dist/config/domains.js +24 -10
- package/dist/config/postgres-budget.d.ts +3 -0
- package/dist/config/postgres-budget.js +6 -1
- package/dist/config/postgres-budget.json +2 -1
- package/dist/contracts/abuse.js +1 -1
- package/dist/contracts/access.d.ts +486 -1196
- package/dist/contracts/access.js +323 -1054
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +11 -15
- package/dist/contracts/api-keys.d.ts +16 -0
- package/dist/contracts/api-keys.js +11 -9
- package/dist/contracts/archives.d.ts +1 -2
- package/dist/contracts/archives.js +15 -17
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/builds.js +14 -14
- package/dist/contracts/channels.js +2 -2
- package/dist/contracts/comments.d.ts +143 -99
- package/dist/contracts/comments.js +194 -79
- package/dist/contracts/common.d.ts +12 -12
- package/dist/contracts/common.js +33 -30
- package/dist/contracts/continuation.js +5 -5
- package/dist/contracts/countries.js +2 -7
- package/dist/contracts/data-source-routes.d.ts +20 -0
- package/dist/contracts/data-source-routes.js +15 -0
- package/dist/contracts/device-auth.d.ts +13 -9
- package/dist/contracts/device-auth.js +12 -12
- package/dist/contracts/docs.d.ts +10 -10
- package/dist/contracts/domains.js +39 -33
- package/dist/contracts/enums.d.ts +4 -14
- package/dist/contracts/enums.js +3 -10
- package/dist/contracts/error-code-meta.d.ts +122 -56
- package/dist/contracts/error-code-meta.js +43 -19
- package/dist/contracts/error-codes.d.ts +7 -4
- package/dist/contracts/error-codes.js +48 -24
- package/dist/contracts/events.d.ts +22 -15
- package/dist/contracts/events.js +33 -5
- package/dist/contracts/execution.d.ts +238 -0
- package/dist/contracts/execution.js +309 -0
- package/dist/contracts/feature-lifecycle.d.ts +39 -188
- package/dist/contracts/feature-lifecycle.js +52 -196
- package/dist/contracts/features.d.ts +13 -115
- package/dist/contracts/features.js +5 -35
- package/dist/contracts/functions.d.ts +597 -0
- package/dist/contracts/functions.js +475 -0
- package/dist/contracts/grants.d.ts +1175 -0
- package/dist/contracts/grants.js +783 -0
- package/dist/contracts/ids.d.ts +7 -5
- package/dist/contracts/ids.js +7 -7
- package/dist/contracts/internal.js +2 -2
- package/dist/contracts/mcp.d.ts +17 -556
- package/dist/contracts/mcp.js +9 -103
- package/dist/contracts/me.js +3 -4
- package/dist/contracts/notifications.d.ts +108 -0
- package/dist/contracts/notifications.js +54 -0
- package/dist/contracts/oauth-resources.d.ts +22 -5
- package/dist/contracts/oauth-resources.js +23 -2
- package/dist/contracts/operations.d.ts +0 -48
- package/dist/contracts/operations.js +0 -19
- package/dist/contracts/pages.d.ts +10 -14
- package/dist/contracts/pages.js +17 -21
- package/dist/contracts/plan-policy.d.ts +1 -8
- package/dist/contracts/plan-policy.js +1 -4
- package/dist/contracts/platform.d.ts +78 -30
- package/dist/contracts/platform.js +69 -20
- package/dist/contracts/problem-document.d.ts +14 -0
- package/dist/contracts/problem-document.js +35 -0
- package/dist/contracts/push-new.d.ts +41 -2
- package/dist/contracts/push-new.js +4 -0
- package/dist/contracts/quotas.d.ts +0 -1
- package/dist/contracts/quotas.js +0 -5
- package/dist/contracts/repository-connections.d.ts +1 -1
- package/dist/contracts/resources.d.ts +16 -71
- package/dist/contracts/resources.js +13 -16
- package/dist/contracts/runtime-api.d.ts +666 -303
- package/dist/contracts/runtime-api.js +272 -113
- package/dist/contracts/runtime-app.d.ts +168 -0
- package/dist/contracts/runtime-app.js +42 -0
- package/dist/contracts/runtime-db.d.ts +424 -0
- package/dist/contracts/runtime-db.js +108 -0
- package/dist/contracts/runtime-storage.d.ts +120 -0
- package/dist/contracts/runtime-storage.js +40 -0
- package/dist/contracts/sf-config-v1.d.ts +54 -1
- package/dist/contracts/sf-config-v1.js +2 -2
- package/dist/contracts/sites.d.ts +6 -0
- package/dist/contracts/sites.js +30 -11
- package/dist/contracts/space-config.d.ts +91 -121
- package/dist/contracts/space-config.js +464 -42
- package/dist/contracts/spaces.d.ts +300 -171
- package/dist/contracts/spaces.js +128 -98
- package/dist/contracts/superadmin-emails.d.ts +2 -2
- package/dist/contracts/superadmin-spaces.d.ts +26 -130
- package/dist/contracts/superadmin-spaces.js +22 -0
- package/dist/contracts/superadmin-tenants.js +4 -4
- package/dist/contracts/superadmin.d.ts +4 -0
- package/dist/contracts/superadmin.js +14 -1
- package/dist/contracts/tags.d.ts +6 -6
- package/dist/contracts/tags.js +1 -1
- package/dist/contracts/teams.d.ts +9 -9
- package/dist/contracts/teams.js +25 -16
- package/dist/contracts/theme-json.js +1 -1
- package/dist/contracts/transfers.d.ts +0 -1
- package/dist/contracts/transfers.js +4 -4
- package/dist/contracts/variables.js +3 -3
- package/dist/contracts/zero.d.ts +384 -596
- package/dist/contracts/zero.js +167 -299
- package/dist/dashboard-paths/index.d.ts +18 -3
- package/dist/dashboard-paths/index.js +19 -0
- package/dist/docs/agent-handoff-document.d.ts +2 -2
- package/dist/docs/agent-handoff-document.js +8 -7
- package/dist/docs/agent-prose.d.ts +22 -3
- package/dist/docs/agent-prose.js +227 -55
- package/dist/docs/agent-setup.d.ts +14 -11
- package/dist/docs/agent-setup.js +289 -61
- package/dist/docs/agent-solutions.d.ts +6 -6
- package/dist/docs/agent-solutions.js +24 -22
- package/dist/docs/catalog.d.ts +53 -98
- package/dist/docs/catalog.js +90 -117
- package/dist/docs/error-docs.js +304 -236
- package/dist/docs/skill-distribution.d.ts +2 -0
- package/dist/docs/skill-distribution.js +7 -4
- package/dist/docs/skills.d.ts +546 -0
- package/dist/docs/skills.js +340 -0
- package/dist/utils/auth-redirect.d.ts +1 -3
- package/dist/utils/auth-redirect.js +1 -21
- package/dist/utils/browser-credential.d.ts +19 -0
- package/dist/utils/browser-credential.js +62 -0
- package/dist/utils/build-settings.d.ts +2 -0
- package/dist/utils/build-settings.js +61 -9
- package/dist/utils/comment-avatars.d.ts +7 -0
- package/dist/utils/comment-avatars.js +42 -0
- package/dist/utils/content-type.d.ts +3 -3
- package/dist/utils/content-type.js +38 -61
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/credential-policy.js +4 -0
- package/dist/utils/domain-diagnostics.d.ts +5 -5
- package/dist/utils/domain-diagnostics.js +21 -10
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/grants.d.ts +53 -0
- package/dist/utils/grants.js +160 -0
- package/dist/utils/local-space-state.d.ts +78 -4
- package/dist/utils/local-space-state.js +384 -82
- package/dist/utils/one-shot-replay.d.ts +14 -0
- package/dist/utils/one-shot-replay.js +21 -0
- package/dist/utils/page-preview.d.ts +7 -0
- package/dist/utils/page-preview.js +9 -0
- package/dist/utils/pages.d.ts +2 -5
- package/dist/utils/pages.js +64 -33
- package/dist/utils/publish-form-data.d.ts +22 -0
- package/dist/utils/publish-form-data.js +26 -0
- package/dist/utils/publish-policy.d.ts +34 -4
- package/dist/utils/publish-policy.js +149 -62
- package/dist/utils/query-keys.d.ts +8 -10
- package/dist/utils/query-keys.js +14 -10
- package/dist/utils/runtime-paths.d.ts +3 -0
- package/dist/utils/runtime-paths.js +7 -0
- package/dist/utils/secure-local-file.d.ts +73 -0
- package/dist/utils/secure-local-file.js +1373 -0
- package/dist/utils/space-config.d.ts +17 -4
- package/dist/utils/space-config.js +151 -8
- package/dist/utils/static-runtime-policy.d.ts +5 -0
- package/dist/utils/static-runtime-policy.js +54 -10
- package/dist/utils/storage-policy.d.ts +20 -0
- package/dist/utils/storage-policy.fixtures.json +174 -0
- package/dist/utils/storage-policy.js +193 -0
- package/dist/utils/upload-session.d.ts +15 -1
- package/dist/utils/upload-session.js +68 -2
- package/dist/utils/version-file-browser.d.ts +13 -0
- package/dist/utils/version-file-browser.js +59 -0
- package/package.json +20 -1
- package/dist/contracts/mounts.d.ts +0 -142
- package/dist/contracts/mounts.js +0 -56
- package/dist/utils/access-grant.d.ts +0 -14
- package/dist/utils/access-grant.js +0 -88
- package/dist/utils/access-match.d.ts +0 -46
- package/dist/utils/access-match.fixtures.json +0 -513
- package/dist/utils/access-match.js +0 -312
- package/dist/utils/access-verdict.fixtures.json +0 -421
- package/dist/utils/asset-fingerprint.d.ts +0 -53
- package/dist/utils/asset-fingerprint.js +0 -574
- package/dist/utils/sf-config-v1.d.ts +0 -2
- package/dist/utils/sf-config-v1.fixtures.json +0 -9
- package/dist/utils/sf-config-v1.js +0 -551
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ISO_COUNTRY_CODES } from "./countries.js";
|
|
3
|
+
import { registeredIdSchema } from "./ids.js";
|
|
4
|
+
export const GRANT_CAPABILITIES = [
|
|
5
|
+
"page.view",
|
|
6
|
+
"comments.read",
|
|
7
|
+
"comments.write",
|
|
8
|
+
"content.publish",
|
|
9
|
+
"access.manage",
|
|
10
|
+
];
|
|
11
|
+
export const grantCapabilitySchema = z.enum(GRANT_CAPABILITIES);
|
|
12
|
+
export const GRANT_CAPABILITY_PRESETS = ["view", "comment", "publish", "manage"];
|
|
13
|
+
export const GRANT_PRESET_CAPABILITIES = {
|
|
14
|
+
view: ["page.view"],
|
|
15
|
+
comment: ["page.view", "comments.read", "comments.write"],
|
|
16
|
+
publish: ["page.view", "content.publish"],
|
|
17
|
+
manage: ["page.view", "comments.read", "comments.write", "content.publish", "access.manage"],
|
|
18
|
+
};
|
|
19
|
+
export const GRANT_AUDIENCE_KINDS = [
|
|
20
|
+
"public",
|
|
21
|
+
"team",
|
|
22
|
+
"person",
|
|
23
|
+
"link",
|
|
24
|
+
"password",
|
|
25
|
+
"machine",
|
|
26
|
+
"external",
|
|
27
|
+
];
|
|
28
|
+
export const grantAudienceKindSchema = z.enum(GRANT_AUDIENCE_KINDS);
|
|
29
|
+
const grantReferenceSchema = z.string().trim().min(1).max(255);
|
|
30
|
+
export const grantAudienceSchema = z.discriminatedUnion("kind", [
|
|
31
|
+
z.object({ kind: z.literal("public") }).strict(),
|
|
32
|
+
z.object({ kind: z.literal("team"), teamId: grantReferenceSchema }).strict(),
|
|
33
|
+
z.object({ kind: z.literal("person"), personId: grantReferenceSchema }).strict(),
|
|
34
|
+
z.object({ kind: z.literal("link"), linkId: grantReferenceSchema }).strict(),
|
|
35
|
+
z.object({ kind: z.literal("password"), credentialId: grantReferenceSchema }).strict(),
|
|
36
|
+
z.object({ kind: z.literal("machine"), machineId: grantReferenceSchema }).strict(),
|
|
37
|
+
z
|
|
38
|
+
.object({
|
|
39
|
+
kind: z.literal("external"),
|
|
40
|
+
issuer: grantReferenceSchema,
|
|
41
|
+
subject: grantReferenceSchema,
|
|
42
|
+
})
|
|
43
|
+
.strict(),
|
|
44
|
+
]);
|
|
45
|
+
function hasUnsafePatternCharacter(value) {
|
|
46
|
+
for (const character of value) {
|
|
47
|
+
const codePoint = character.codePointAt(0);
|
|
48
|
+
if (codePoint === undefined ||
|
|
49
|
+
codePoint <= 0x1f ||
|
|
50
|
+
codePoint === 0x7f ||
|
|
51
|
+
character === "\\" ||
|
|
52
|
+
character === "?" ||
|
|
53
|
+
character === "#") {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Canonical resource-pattern syntax shared by config compilation and evaluation.
|
|
61
|
+
* `*` matches one path segment and `**` matches zero or more segments. Wildcards
|
|
62
|
+
* are whole segments so a pattern can never disguise an encoded separator.
|
|
63
|
+
*
|
|
64
|
+
* Unlike URL-route canonicalization, resource patterns retain a terminal
|
|
65
|
+
* `index.html`: Grants can constrain the visitor-facing route and the artifact
|
|
66
|
+
* it resolves to independently.
|
|
67
|
+
*/
|
|
68
|
+
export function normalizeGrantResourcePattern(input) {
|
|
69
|
+
if (!input.startsWith("/") || input.startsWith("//") || hasUnsafePatternCharacter(input)) {
|
|
70
|
+
throw new TypeError("resource pattern must be a plain absolute URL path pattern");
|
|
71
|
+
}
|
|
72
|
+
if (/%2f|%5c|%2a/iu.test(input)) {
|
|
73
|
+
throw new TypeError("resource pattern contains an ambiguous encoded separator or wildcard");
|
|
74
|
+
}
|
|
75
|
+
let decoded;
|
|
76
|
+
try {
|
|
77
|
+
decoded = decodeURIComponent(input);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new TypeError("resource pattern contains malformed percent encoding");
|
|
81
|
+
}
|
|
82
|
+
if (hasUnsafePatternCharacter(decoded)) {
|
|
83
|
+
throw new TypeError("resource pattern contains an unsafe character");
|
|
84
|
+
}
|
|
85
|
+
const segments = [];
|
|
86
|
+
for (const rawSegment of decoded.split("/")) {
|
|
87
|
+
const segment = rawSegment.normalize("NFC");
|
|
88
|
+
if (segment === "" || segment === ".")
|
|
89
|
+
continue;
|
|
90
|
+
if (segment === "..") {
|
|
91
|
+
if (segments.length === 0)
|
|
92
|
+
throw new TypeError("resource pattern escapes the URL root");
|
|
93
|
+
segments.pop();
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (segment.includes("*") && segment !== "*" && segment !== "**") {
|
|
97
|
+
throw new TypeError("resource wildcards must occupy a whole path segment");
|
|
98
|
+
}
|
|
99
|
+
segments.push(segment);
|
|
100
|
+
}
|
|
101
|
+
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
102
|
+
}
|
|
103
|
+
export const grantResourcePatternSchema = z
|
|
104
|
+
.string()
|
|
105
|
+
.min(1)
|
|
106
|
+
.max(1024)
|
|
107
|
+
.check((context) => {
|
|
108
|
+
try {
|
|
109
|
+
context.value = normalizeGrantResourcePattern(context.value);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
context.issues.push({
|
|
113
|
+
code: "custom",
|
|
114
|
+
input: context.value,
|
|
115
|
+
message: error instanceof Error ? error.message : "invalid resource pattern",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
function uniqueArray(values) {
|
|
120
|
+
return new Set(values).size === values.length;
|
|
121
|
+
}
|
|
122
|
+
export const GRANT_RESOURCE_PATTERN_LIMIT = 100;
|
|
123
|
+
export const grantResourceSetSchema = z
|
|
124
|
+
.object({
|
|
125
|
+
include: z.array(grantResourcePatternSchema).min(1).max(GRANT_RESOURCE_PATTERN_LIMIT),
|
|
126
|
+
exclude: z.array(grantResourcePatternSchema).max(GRANT_RESOURCE_PATTERN_LIMIT).default([]),
|
|
127
|
+
})
|
|
128
|
+
.strict()
|
|
129
|
+
.superRefine((value, context) => {
|
|
130
|
+
if (value.include.length + value.exclude.length > GRANT_RESOURCE_PATTERN_LIMIT) {
|
|
131
|
+
context.addIssue({
|
|
132
|
+
code: "custom",
|
|
133
|
+
message: `resource sets support up to ${GRANT_RESOURCE_PATTERN_LIMIT} patterns`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (!uniqueArray(value.include)) {
|
|
137
|
+
context.addIssue({
|
|
138
|
+
code: "custom",
|
|
139
|
+
path: ["include"],
|
|
140
|
+
message: "include patterns must be unique",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (!uniqueArray(value.exclude)) {
|
|
144
|
+
context.addIssue({
|
|
145
|
+
code: "custom",
|
|
146
|
+
path: ["exclude"],
|
|
147
|
+
message: "exclude patterns must be unique",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
export const GRANT_TARGET_KINDS = ["live", "version", "branch", "all_versions"];
|
|
152
|
+
export const grantTargetKindSchema = z.enum(GRANT_TARGET_KINDS);
|
|
153
|
+
export const grantTargetSelectorSchema = z.discriminatedUnion("kind", [
|
|
154
|
+
z.object({ kind: z.literal("live") }).strict(),
|
|
155
|
+
z.object({ kind: z.literal("version"), versionId: grantReferenceSchema }).strict(),
|
|
156
|
+
z.object({ kind: z.literal("branch"), branch: grantReferenceSchema }).strict(),
|
|
157
|
+
z.object({ kind: z.literal("all_versions") }).strict(),
|
|
158
|
+
]);
|
|
159
|
+
function parseIpv4(input) {
|
|
160
|
+
const parts = input.split(".");
|
|
161
|
+
if (parts.length !== 4)
|
|
162
|
+
return null;
|
|
163
|
+
let bits = 0n;
|
|
164
|
+
for (const part of parts) {
|
|
165
|
+
if (!/^(?:0|[1-9][0-9]{0,2})$/u.test(part))
|
|
166
|
+
return null;
|
|
167
|
+
const value = Number(part);
|
|
168
|
+
if (value > 255)
|
|
169
|
+
return null;
|
|
170
|
+
bits = (bits << 8n) | BigInt(value);
|
|
171
|
+
}
|
|
172
|
+
return { bits, width: 32 };
|
|
173
|
+
}
|
|
174
|
+
function parseIpv6(input) {
|
|
175
|
+
if (input === "" || input.includes("%") || input.split("::").length > 2)
|
|
176
|
+
return null;
|
|
177
|
+
let value = input.toLowerCase();
|
|
178
|
+
const ipv4Tail = value.match(/(?:^|:)([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/u)?.[1];
|
|
179
|
+
if (ipv4Tail) {
|
|
180
|
+
const parsed = parseIpv4(ipv4Tail);
|
|
181
|
+
if (!parsed)
|
|
182
|
+
return null;
|
|
183
|
+
value = `${value.slice(0, -ipv4Tail.length)}${Number((parsed.bits >> 16n) & 0xffffn).toString(16)}:${Number(parsed.bits & 0xffffn).toString(16)}`;
|
|
184
|
+
}
|
|
185
|
+
const [leftText, rightText] = value.split("::");
|
|
186
|
+
const left = leftText ? leftText.split(":") : [];
|
|
187
|
+
const right = rightText ? rightText.split(":") : [];
|
|
188
|
+
if ([...left, ...right].some((part) => !/^[0-9a-f]{1,4}$/u.test(part)))
|
|
189
|
+
return null;
|
|
190
|
+
const compressed = value.includes("::");
|
|
191
|
+
const missing = 8 - left.length - right.length;
|
|
192
|
+
if ((!compressed && missing !== 0) || (compressed && missing < 1))
|
|
193
|
+
return null;
|
|
194
|
+
const parts = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
|
|
195
|
+
if (parts.length !== 8)
|
|
196
|
+
return null;
|
|
197
|
+
return {
|
|
198
|
+
bits: parts.reduce((bits, part) => (bits << 16n) | BigInt(`0x${part}`), 0n),
|
|
199
|
+
width: 128,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function parseIpAddress(input) {
|
|
203
|
+
return parseIpv4(input) ?? parseIpv6(input);
|
|
204
|
+
}
|
|
205
|
+
export function normalizeGrantNetworkCidr(input) {
|
|
206
|
+
const value = input.trim().toLowerCase();
|
|
207
|
+
const separator = value.lastIndexOf("/");
|
|
208
|
+
const address = separator === -1 ? value : value.slice(0, separator);
|
|
209
|
+
const parsed = parseIpAddress(address);
|
|
210
|
+
if (!parsed)
|
|
211
|
+
throw new TypeError("network rules require a valid IPv4 or IPv6 address");
|
|
212
|
+
if (separator === -1)
|
|
213
|
+
return value;
|
|
214
|
+
const prefixText = value.slice(separator + 1);
|
|
215
|
+
if (!/^(?:0|[1-9][0-9]{0,2})$/u.test(prefixText)) {
|
|
216
|
+
throw new TypeError("network rules require a valid CIDR prefix");
|
|
217
|
+
}
|
|
218
|
+
const prefix = Number(prefixText);
|
|
219
|
+
if (prefix > parsed.width)
|
|
220
|
+
throw new TypeError("network CIDR prefix exceeds its address width");
|
|
221
|
+
return `${address}/${prefix}`;
|
|
222
|
+
}
|
|
223
|
+
export function grantNetworkCidrMatches(cidr, ipAddress) {
|
|
224
|
+
let canonical;
|
|
225
|
+
try {
|
|
226
|
+
canonical = normalizeGrantNetworkCidr(cidr);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const separator = canonical.lastIndexOf("/");
|
|
232
|
+
const networkText = separator === -1 ? canonical : canonical.slice(0, separator);
|
|
233
|
+
const network = parseIpAddress(networkText);
|
|
234
|
+
const address = parseIpAddress(ipAddress.trim().toLowerCase());
|
|
235
|
+
if (!network || !address || network.width !== address.width)
|
|
236
|
+
return false;
|
|
237
|
+
const prefix = separator === -1 ? network.width : Number(canonical.slice(separator + 1));
|
|
238
|
+
if (prefix === 0)
|
|
239
|
+
return true;
|
|
240
|
+
const shift = BigInt(network.width - prefix);
|
|
241
|
+
return network.bits >> shift === address.bits >> shift;
|
|
242
|
+
}
|
|
243
|
+
const grantNetworkCidrSchema = z
|
|
244
|
+
.string()
|
|
245
|
+
.min(1)
|
|
246
|
+
.max(64)
|
|
247
|
+
.check((context) => {
|
|
248
|
+
try {
|
|
249
|
+
context.value = normalizeGrantNetworkCidr(context.value);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
context.issues.push({
|
|
253
|
+
code: "custom",
|
|
254
|
+
input: context.value,
|
|
255
|
+
message: error instanceof Error ? error.message : "invalid network rule",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const grantCountrySchema = z
|
|
260
|
+
.string()
|
|
261
|
+
.length(2)
|
|
262
|
+
.overwrite((country) => country.toUpperCase())
|
|
263
|
+
.refine((country) => ISO_COUNTRY_CODES.includes(country), "Country must be an ISO 3166-1 alpha-2 code.");
|
|
264
|
+
const grantConstraintDateSchema = z.string().datetime();
|
|
265
|
+
const grantConstraintMaxUsesSchema = z.number().int().positive().max(1_000_000);
|
|
266
|
+
const grantConstraintIpCidrsSchema = z.array(grantNetworkCidrSchema).min(1).max(50);
|
|
267
|
+
const grantConstraintCountriesSchema = z.array(grantCountrySchema).min(1).max(250);
|
|
268
|
+
const grantConstraintUserAgentsSchema = z.array(z.string().trim().min(1).max(200)).min(1).max(50);
|
|
269
|
+
const grantNetworkConstraintsSchema = z
|
|
270
|
+
.object({
|
|
271
|
+
ipCidrs: grantConstraintIpCidrsSchema.optional(),
|
|
272
|
+
countries: grantConstraintCountriesSchema.optional(),
|
|
273
|
+
excludedCountries: grantConstraintCountriesSchema.optional(),
|
|
274
|
+
excludedUserAgentSubstrings: grantConstraintUserAgentsSchema.optional(),
|
|
275
|
+
})
|
|
276
|
+
.strict()
|
|
277
|
+
.superRefine((network, context) => {
|
|
278
|
+
if (network.ipCidrs === undefined &&
|
|
279
|
+
network.countries === undefined &&
|
|
280
|
+
network.excludedCountries === undefined &&
|
|
281
|
+
network.excludedUserAgentSubstrings === undefined) {
|
|
282
|
+
context.addIssue({
|
|
283
|
+
code: "custom",
|
|
284
|
+
message: "network requires at least one IP, country, or user-agent selector",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
export const grantConstraintsSchema = z
|
|
289
|
+
.object({
|
|
290
|
+
notBefore: grantConstraintDateSchema.optional(),
|
|
291
|
+
expiresAt: grantConstraintDateSchema.optional(),
|
|
292
|
+
maxUses: grantConstraintMaxUsesSchema.optional(),
|
|
293
|
+
requireVerifiedEmail: z.boolean().optional(),
|
|
294
|
+
network: grantNetworkConstraintsSchema.optional(),
|
|
295
|
+
})
|
|
296
|
+
.strict()
|
|
297
|
+
.superRefine((value, context) => {
|
|
298
|
+
if (value.notBefore !== undefined &&
|
|
299
|
+
value.expiresAt !== undefined &&
|
|
300
|
+
Date.parse(value.notBefore) >= Date.parse(value.expiresAt)) {
|
|
301
|
+
context.addIssue({
|
|
302
|
+
code: "custom",
|
|
303
|
+
path: ["expiresAt"],
|
|
304
|
+
message: "expiresAt must be later than notBefore",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
/** Field-scoped PATCH shape. `null` clears one constraint without replacing its siblings. */
|
|
309
|
+
export const grantConstraintsPatchSchema = z
|
|
310
|
+
.object({
|
|
311
|
+
notBefore: grantConstraintDateSchema.nullable().optional(),
|
|
312
|
+
expiresAt: grantConstraintDateSchema.nullable().optional(),
|
|
313
|
+
maxUses: grantConstraintMaxUsesSchema.nullable().optional(),
|
|
314
|
+
requireVerifiedEmail: z.boolean().nullable().optional(),
|
|
315
|
+
network: z
|
|
316
|
+
.object({
|
|
317
|
+
ipCidrs: grantConstraintIpCidrsSchema.nullable().optional(),
|
|
318
|
+
countries: grantConstraintCountriesSchema.nullable().optional(),
|
|
319
|
+
excludedCountries: grantConstraintCountriesSchema.nullable().optional(),
|
|
320
|
+
excludedUserAgentSubstrings: grantConstraintUserAgentsSchema.nullable().optional(),
|
|
321
|
+
})
|
|
322
|
+
.strict()
|
|
323
|
+
.nullable()
|
|
324
|
+
.optional(),
|
|
325
|
+
})
|
|
326
|
+
.strict();
|
|
327
|
+
export function diffGrantConstraints(original, draft) {
|
|
328
|
+
const network = JSON.stringify(original.network) === JSON.stringify(draft.network)
|
|
329
|
+
? undefined
|
|
330
|
+
: draft.network === undefined
|
|
331
|
+
? null
|
|
332
|
+
: {
|
|
333
|
+
...(JSON.stringify(original.network?.ipCidrs) === JSON.stringify(draft.network.ipCidrs)
|
|
334
|
+
? {}
|
|
335
|
+
: { ipCidrs: draft.network.ipCidrs ?? null }),
|
|
336
|
+
...(JSON.stringify(original.network?.countries) ===
|
|
337
|
+
JSON.stringify(draft.network.countries)
|
|
338
|
+
? {}
|
|
339
|
+
: { countries: draft.network.countries ?? null }),
|
|
340
|
+
...(JSON.stringify(original.network?.excludedCountries) ===
|
|
341
|
+
JSON.stringify(draft.network.excludedCountries)
|
|
342
|
+
? {}
|
|
343
|
+
: { excludedCountries: draft.network.excludedCountries ?? null }),
|
|
344
|
+
...(JSON.stringify(original.network?.excludedUserAgentSubstrings) ===
|
|
345
|
+
JSON.stringify(draft.network.excludedUserAgentSubstrings)
|
|
346
|
+
? {}
|
|
347
|
+
: {
|
|
348
|
+
excludedUserAgentSubstrings: draft.network.excludedUserAgentSubstrings ?? null,
|
|
349
|
+
}),
|
|
350
|
+
};
|
|
351
|
+
const patch = {
|
|
352
|
+
...(original.notBefore === draft.notBefore ? {} : { notBefore: draft.notBefore ?? null }),
|
|
353
|
+
...(original.expiresAt === draft.expiresAt ? {} : { expiresAt: draft.expiresAt ?? null }),
|
|
354
|
+
...(original.maxUses === draft.maxUses ? {} : { maxUses: draft.maxUses ?? null }),
|
|
355
|
+
...(original.requireVerifiedEmail === draft.requireVerifiedEmail
|
|
356
|
+
? {}
|
|
357
|
+
: { requireVerifiedEmail: draft.requireVerifiedEmail ?? null }),
|
|
358
|
+
...(network === undefined ? {} : { network }),
|
|
359
|
+
};
|
|
360
|
+
return Object.keys(patch).length === 0 ? undefined : patch;
|
|
361
|
+
}
|
|
362
|
+
export function applyGrantConstraintsPatch(current, patch) {
|
|
363
|
+
const network = patch.network === undefined
|
|
364
|
+
? current.network
|
|
365
|
+
: patch.network === null
|
|
366
|
+
? undefined
|
|
367
|
+
: {
|
|
368
|
+
...current.network,
|
|
369
|
+
...(patch.network.ipCidrs === undefined
|
|
370
|
+
? {}
|
|
371
|
+
: { ipCidrs: patch.network.ipCidrs ?? undefined }),
|
|
372
|
+
...(patch.network.countries === undefined
|
|
373
|
+
? {}
|
|
374
|
+
: { countries: patch.network.countries ?? undefined }),
|
|
375
|
+
...(patch.network.excludedCountries === undefined
|
|
376
|
+
? {}
|
|
377
|
+
: { excludedCountries: patch.network.excludedCountries ?? undefined }),
|
|
378
|
+
...(patch.network.excludedUserAgentSubstrings === undefined
|
|
379
|
+
? {}
|
|
380
|
+
: {
|
|
381
|
+
excludedUserAgentSubstrings: patch.network.excludedUserAgentSubstrings ?? undefined,
|
|
382
|
+
}),
|
|
383
|
+
};
|
|
384
|
+
const compactNetwork = network && Object.values(network).some((value) => value !== undefined) ? network : undefined;
|
|
385
|
+
return grantConstraintsSchema.parse({
|
|
386
|
+
...current,
|
|
387
|
+
...(patch.notBefore === undefined ? {} : { notBefore: patch.notBefore ?? undefined }),
|
|
388
|
+
...(patch.expiresAt === undefined ? {} : { expiresAt: patch.expiresAt ?? undefined }),
|
|
389
|
+
...(patch.maxUses === undefined ? {} : { maxUses: patch.maxUses ?? undefined }),
|
|
390
|
+
...(patch.requireVerifiedEmail === undefined
|
|
391
|
+
? {}
|
|
392
|
+
: { requireVerifiedEmail: patch.requireVerifiedEmail ?? undefined }),
|
|
393
|
+
network: compactNetwork,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
export const GRANT_SOURCE_KINDS = ["config", "managed", "system"];
|
|
397
|
+
export const grantSourceKindSchema = z.enum(GRANT_SOURCE_KINDS);
|
|
398
|
+
export const PRECLAIM_OPEN_GRANT_SOURCE_REFERENCE = "open:preclaim-author";
|
|
399
|
+
export const SPACE_OPEN_GRANT_SOURCE_REFERENCE = "open:space";
|
|
400
|
+
export const OWNING_TEAM_MANAGEMENT_GRANT_SOURCE_REFERENCE = "ownership:team";
|
|
401
|
+
export const OWNING_TEAM_VERSION_GRANT_SOURCE_REFERENCE = "ownership:team:versions";
|
|
402
|
+
export const OWNING_EXTERNAL_MANAGEMENT_GRANT_SOURCE_REFERENCE = "ownership:external";
|
|
403
|
+
export const OWNING_EXTERNAL_VERSION_GRANT_SOURCE_REFERENCE = "ownership:external:versions";
|
|
404
|
+
export const CONFIG_PUBLIC_GRANT_SOURCE_REFERENCE = "sf.jsonc#$.access.public";
|
|
405
|
+
export const grantSourceSchema = z
|
|
406
|
+
.object({
|
|
407
|
+
kind: grantSourceKindSchema,
|
|
408
|
+
reference: grantReferenceSchema,
|
|
409
|
+
})
|
|
410
|
+
.strict();
|
|
411
|
+
const grantDimensionsSchema = z
|
|
412
|
+
.object({
|
|
413
|
+
name: z.string().trim().min(1).max(120).optional(),
|
|
414
|
+
audience: grantAudienceSchema,
|
|
415
|
+
resources: grantResourceSetSchema,
|
|
416
|
+
capabilities: z
|
|
417
|
+
.array(grantCapabilitySchema)
|
|
418
|
+
.min(1)
|
|
419
|
+
.max(GRANT_CAPABILITIES.length)
|
|
420
|
+
.refine(uniqueArray, "capabilities must be unique"),
|
|
421
|
+
constraints: grantConstraintsSchema.default({}),
|
|
422
|
+
target: grantTargetSelectorSchema,
|
|
423
|
+
})
|
|
424
|
+
.strict();
|
|
425
|
+
function validateGrantAudienceCapabilityBoundary(value, context) {
|
|
426
|
+
if (value.audience.kind === "public" &&
|
|
427
|
+
value.capabilities.some((capability) => capability === "content.publish" || capability === "access.manage")) {
|
|
428
|
+
context.addIssue({
|
|
429
|
+
code: "custom",
|
|
430
|
+
path: ["capabilities"],
|
|
431
|
+
message: "public audiences cannot receive content.publish or access.manage",
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (value.audience.kind === "public" && value.target.kind === "branch") {
|
|
436
|
+
context.addIssue({
|
|
437
|
+
code: "custom",
|
|
438
|
+
path: ["target"],
|
|
439
|
+
message: "public audiences cannot target moving branches; use a Link or another explicit audience",
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (["link", "password"].includes(value.audience.kind) &&
|
|
443
|
+
value.capabilities.includes("access.manage")) {
|
|
444
|
+
context.addIssue({
|
|
445
|
+
code: "custom",
|
|
446
|
+
path: ["capabilities"],
|
|
447
|
+
message: `${value.audience.kind} audiences cannot receive access.manage`,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (value.constraints.maxUses !== undefined &&
|
|
451
|
+
!["link", "password", "machine", "external"].includes(value.audience.kind)) {
|
|
452
|
+
context.addIssue({
|
|
453
|
+
code: "custom",
|
|
454
|
+
path: ["constraints", "maxUses"],
|
|
455
|
+
message: "maxUses is available only for Link, password, service-token, and external-identity credential redemptions",
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (value.constraints.requireVerifiedEmail === true &&
|
|
459
|
+
["public", "machine"].includes(value.audience.kind)) {
|
|
460
|
+
context.addIssue({
|
|
461
|
+
code: "custom",
|
|
462
|
+
path: ["constraints", "requireVerifiedEmail"],
|
|
463
|
+
message: "requireVerifiedEmail is unavailable for public and machine audiences",
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const grantObjectSchema = grantDimensionsSchema.extend({
|
|
468
|
+
id: z.string().trim().min(1).max(128),
|
|
469
|
+
source: grantSourceSchema,
|
|
470
|
+
});
|
|
471
|
+
function validateGrantCapabilityBoundary(value, context) {
|
|
472
|
+
validateGrantAudienceCapabilityBoundary(value, context);
|
|
473
|
+
if (value.audience.kind === "link" && value.capabilities.includes("content.publish")) {
|
|
474
|
+
context.addIssue({
|
|
475
|
+
code: "custom",
|
|
476
|
+
path: ["capabilities"],
|
|
477
|
+
message: "link audiences cannot publish content",
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
export const grantSchema = grantObjectSchema.superRefine(validateGrantCapabilityBoundary);
|
|
482
|
+
export const managedGrantWriteSchema = grantDimensionsSchema.superRefine((value, context) => {
|
|
483
|
+
validateGrantAudienceCapabilityBoundary(value, context);
|
|
484
|
+
if (value.audience.kind === "link" && value.capabilities.includes("content.publish")) {
|
|
485
|
+
context.addIssue({
|
|
486
|
+
code: "custom",
|
|
487
|
+
path: ["capabilities"],
|
|
488
|
+
message: "link audiences can publish only through the system-issued pre-claim Open author Grant",
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
export const directManagedGrantWriteSchema = managedGrantWriteSchema.superRefine((value, context) => {
|
|
493
|
+
if (!["public", "team", "person"].includes(value.audience.kind)) {
|
|
494
|
+
context.addIssue({
|
|
495
|
+
code: "custom",
|
|
496
|
+
path: ["audience", "kind"],
|
|
497
|
+
message: "direct managed Grants support only public, team, and person audiences; credential-backed audiences use their dedicated workflow",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
export const grantSourceExplanationSchema = z
|
|
502
|
+
.object({
|
|
503
|
+
editable: z.boolean(),
|
|
504
|
+
label: z.string().min(1),
|
|
505
|
+
detail: z.string().min(1),
|
|
506
|
+
})
|
|
507
|
+
.strict();
|
|
508
|
+
export const spaceGrantSchema = z
|
|
509
|
+
.object({
|
|
510
|
+
grant: grantObjectSchema
|
|
511
|
+
.extend({ id: registeredIdSchema("grt") })
|
|
512
|
+
.superRefine(validateGrantCapabilityBoundary),
|
|
513
|
+
generation: z.number().int().positive(),
|
|
514
|
+
status: z.enum(["scheduled", "active", "expired"]),
|
|
515
|
+
sourceExplanation: grantSourceExplanationSchema,
|
|
516
|
+
createdAt: z.string().datetime(),
|
|
517
|
+
updatedAt: z.string().datetime(),
|
|
518
|
+
})
|
|
519
|
+
.strict();
|
|
520
|
+
export const grantCheckQuerySchema = z
|
|
521
|
+
.object({
|
|
522
|
+
path: z.string().min(1).max(2048).default("/"),
|
|
523
|
+
target: z.enum(["live", "version", "branch"]).default("live"),
|
|
524
|
+
targetId: z.string().trim().min(1).max(255).optional(),
|
|
525
|
+
audience: grantAudienceKindSchema.default("public"),
|
|
526
|
+
personId: z.string().trim().min(1).max(255).optional(),
|
|
527
|
+
linkId: z.string().trim().min(1).max(255).optional(),
|
|
528
|
+
passwordCredentialId: z.string().trim().min(1).max(255).optional(),
|
|
529
|
+
machineId: z.string().trim().min(1).max(255).optional(),
|
|
530
|
+
issuer: z.string().trim().min(1).max(255).optional(),
|
|
531
|
+
subject: z.string().trim().min(1).max(255).optional(),
|
|
532
|
+
verifiedEmail: z.enum(["true", "false"]).optional(),
|
|
533
|
+
ipAddress: z.string().trim().min(1).max(64).optional(),
|
|
534
|
+
})
|
|
535
|
+
.strict()
|
|
536
|
+
.superRefine((value, context) => {
|
|
537
|
+
if (value.target === "live" && value.targetId !== undefined) {
|
|
538
|
+
context.addIssue({
|
|
539
|
+
code: "custom",
|
|
540
|
+
path: ["targetId"],
|
|
541
|
+
message: "targetId is only valid for a version or branch target",
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (value.target !== "live" && value.targetId === undefined) {
|
|
545
|
+
context.addIssue({
|
|
546
|
+
code: "custom",
|
|
547
|
+
path: ["targetId"],
|
|
548
|
+
message: `targetId is required for a ${value.target} target`,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const audienceFields = [
|
|
552
|
+
["person", "personId"],
|
|
553
|
+
["link", "linkId"],
|
|
554
|
+
["password", "passwordCredentialId"],
|
|
555
|
+
["machine", "machineId"],
|
|
556
|
+
["external", "issuer"],
|
|
557
|
+
["external", "subject"],
|
|
558
|
+
];
|
|
559
|
+
for (const [audience, field] of audienceFields) {
|
|
560
|
+
if (value.audience === audience && value[field] === undefined) {
|
|
561
|
+
context.addIssue({
|
|
562
|
+
code: "custom",
|
|
563
|
+
path: [field],
|
|
564
|
+
message: `${field} is required when simulating ${audience} access`,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
if (value.audience !== audience && value[field] !== undefined) {
|
|
568
|
+
context.addIssue({
|
|
569
|
+
code: "custom",
|
|
570
|
+
path: [field],
|
|
571
|
+
message: `${field} is only valid when simulating ${audience} access`,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
export const GRANT_CHECK_FAILURES = [
|
|
577
|
+
"space_unclaimed_config",
|
|
578
|
+
"audience",
|
|
579
|
+
"target",
|
|
580
|
+
"invalid_time",
|
|
581
|
+
"not_before",
|
|
582
|
+
"expired",
|
|
583
|
+
"max_uses",
|
|
584
|
+
"verified_email",
|
|
585
|
+
"network",
|
|
586
|
+
"resource_not_included",
|
|
587
|
+
"resource_excluded",
|
|
588
|
+
];
|
|
589
|
+
export const grantCheckFailureSchema = z.enum(GRANT_CHECK_FAILURES);
|
|
590
|
+
export const grantCheckSchema = z
|
|
591
|
+
.object({
|
|
592
|
+
path: z.string().min(1),
|
|
593
|
+
target: z.discriminatedUnion("kind", [
|
|
594
|
+
z.object({ kind: z.literal("live") }).strict(),
|
|
595
|
+
z.object({ kind: z.literal("version"), versionId: z.string().min(1) }).strict(),
|
|
596
|
+
z.object({ kind: z.literal("branch"), branch: z.string().min(1) }).strict(),
|
|
597
|
+
]),
|
|
598
|
+
simulatedAudience: grantAudienceSchema,
|
|
599
|
+
capabilities: z.array(grantCapabilitySchema),
|
|
600
|
+
matchingGrants: z.array(z
|
|
601
|
+
.object({
|
|
602
|
+
grant: spaceGrantSchema,
|
|
603
|
+
capabilities: z.array(grantCapabilitySchema).min(1),
|
|
604
|
+
})
|
|
605
|
+
.strict()),
|
|
606
|
+
evaluatedGrants: z.array(z
|
|
607
|
+
.object({
|
|
608
|
+
grant: spaceGrantSchema,
|
|
609
|
+
failures: z.array(grantCheckFailureSchema),
|
|
610
|
+
})
|
|
611
|
+
.strict()),
|
|
612
|
+
})
|
|
613
|
+
.strict();
|
|
614
|
+
const credentialGrantFields = {
|
|
615
|
+
name: z.string().trim().min(1).max(120),
|
|
616
|
+
resources: grantResourceSetSchema,
|
|
617
|
+
capabilities: z
|
|
618
|
+
.array(grantCapabilitySchema)
|
|
619
|
+
.min(1)
|
|
620
|
+
.max(GRANT_CAPABILITIES.length)
|
|
621
|
+
.refine(uniqueArray, "capabilities must be unique"),
|
|
622
|
+
constraints: grantConstraintsSchema.default({}),
|
|
623
|
+
target: grantTargetSelectorSchema,
|
|
624
|
+
};
|
|
625
|
+
export const passwordCredentialCreateSchema = z
|
|
626
|
+
.object({
|
|
627
|
+
...credentialGrantFields,
|
|
628
|
+
password: z.string().min(1).max(1024),
|
|
629
|
+
})
|
|
630
|
+
.strict()
|
|
631
|
+
.superRefine((value, context) => {
|
|
632
|
+
const unsafeCapability = value.capabilities.find((capability) => capability === "content.publish" || capability === "access.manage");
|
|
633
|
+
if (unsafeCapability) {
|
|
634
|
+
context.addIssue({
|
|
635
|
+
code: "custom",
|
|
636
|
+
path: ["capabilities"],
|
|
637
|
+
message: `password audiences cannot receive ${unsafeCapability}`,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
export const passwordCredentialRotateSchema = z
|
|
642
|
+
.object({ password: z.string().min(1).max(1024) })
|
|
643
|
+
.strict();
|
|
644
|
+
export const machineCredentialCreateSchema = z
|
|
645
|
+
.object(credentialGrantFields)
|
|
646
|
+
.strict()
|
|
647
|
+
.superRefine((value, context) => {
|
|
648
|
+
if (value.constraints.requireVerifiedEmail === true) {
|
|
649
|
+
context.addIssue({
|
|
650
|
+
code: "custom",
|
|
651
|
+
path: ["constraints", "requireVerifiedEmail"],
|
|
652
|
+
message: "requireVerifiedEmail is unavailable for machine audiences",
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
export const passwordCredentialSchema = z
|
|
657
|
+
.object({
|
|
658
|
+
id: registeredIdSchema("pwd"),
|
|
659
|
+
grant: spaceGrantSchema,
|
|
660
|
+
rotatedAt: z.string().datetime().nullable(),
|
|
661
|
+
lastUsedAt: z.string().datetime().nullable(),
|
|
662
|
+
revokedAt: z.string().datetime().nullable(),
|
|
663
|
+
})
|
|
664
|
+
.strict();
|
|
665
|
+
export const machineCredentialSchema = z
|
|
666
|
+
.object({
|
|
667
|
+
id: registeredIdSchema("mch"),
|
|
668
|
+
grant: spaceGrantSchema,
|
|
669
|
+
tokenHint: z.string().min(1).max(32),
|
|
670
|
+
rotatedAt: z.string().datetime().nullable(),
|
|
671
|
+
lastUsedAt: z.string().datetime().nullable(),
|
|
672
|
+
revokedAt: z.string().datetime().nullable(),
|
|
673
|
+
})
|
|
674
|
+
.strict();
|
|
675
|
+
export const machineCredentialSecretSchema = z
|
|
676
|
+
.object({
|
|
677
|
+
credential: machineCredentialSchema,
|
|
678
|
+
token: z.string().min(32).max(256),
|
|
679
|
+
})
|
|
680
|
+
.strict();
|
|
681
|
+
export const machineCredentialExchangeRequestSchema = z
|
|
682
|
+
.object({
|
|
683
|
+
spaceId: registeredIdSchema("spc"),
|
|
684
|
+
host: z.string().trim().min(1).max(255),
|
|
685
|
+
resource: z.string().min(1).max(1024),
|
|
686
|
+
target: z
|
|
687
|
+
.discriminatedUnion("kind", [
|
|
688
|
+
z.object({ kind: z.literal("live") }).strict(),
|
|
689
|
+
z.object({ kind: z.literal("version"), versionId: grantReferenceSchema }).strict(),
|
|
690
|
+
z.object({ kind: z.literal("branch"), branch: grantReferenceSchema }).strict(),
|
|
691
|
+
])
|
|
692
|
+
.default({ kind: "live" }),
|
|
693
|
+
})
|
|
694
|
+
.strict();
|
|
695
|
+
export const machineCredentialExchangeResponseSchema = z
|
|
696
|
+
.object({
|
|
697
|
+
token: z.string().min(1),
|
|
698
|
+
expiresAt: z.string().datetime(),
|
|
699
|
+
})
|
|
700
|
+
.strict();
|
|
701
|
+
export const credentialRevocationSchema = z
|
|
702
|
+
.object({
|
|
703
|
+
grantId: registeredIdSchema("grt"),
|
|
704
|
+
revoked: z.literal(true),
|
|
705
|
+
})
|
|
706
|
+
.strict();
|
|
707
|
+
export const IDENTITY_CONNECTION_DEFAULT_TTL_SECONDS = 8 * 60 * 60;
|
|
708
|
+
export const EXTERNAL_IDENTITY_MAX_TTL_SECONDS = 30 * 60;
|
|
709
|
+
export const identityConnectionPublicKeySchema = z
|
|
710
|
+
.object({
|
|
711
|
+
kid: z.string().min(1).max(64),
|
|
712
|
+
publicKey: z
|
|
713
|
+
.string()
|
|
714
|
+
.regex(/^[A-Za-z0-9_-]{43}$/, "publicKey must be a raw Ed25519 public key"),
|
|
715
|
+
})
|
|
716
|
+
.strict();
|
|
717
|
+
export const identityConnectionCreateSchema = z.discriminatedUnion("type", [
|
|
718
|
+
z
|
|
719
|
+
.object({
|
|
720
|
+
type: z.literal("oidc"),
|
|
721
|
+
name: z.string().trim().min(1).max(200),
|
|
722
|
+
issuer: z.string().min(1).max(1024),
|
|
723
|
+
clientId: z.string().min(1).max(512),
|
|
724
|
+
clientSecret: z.string().min(1).max(2048),
|
|
725
|
+
sessionTtlSeconds: z
|
|
726
|
+
.number()
|
|
727
|
+
.int()
|
|
728
|
+
.min(60)
|
|
729
|
+
.max(24 * 60 * 60)
|
|
730
|
+
.optional(),
|
|
731
|
+
})
|
|
732
|
+
.strict(),
|
|
733
|
+
z
|
|
734
|
+
.object({
|
|
735
|
+
type: z.literal("signer"),
|
|
736
|
+
name: z.string().trim().min(1).max(200),
|
|
737
|
+
issuer: z.string().min(1).max(1024),
|
|
738
|
+
authorizeUrl: z.string().min(1).max(2048),
|
|
739
|
+
publicKeys: z.array(identityConnectionPublicKeySchema).min(1).max(5),
|
|
740
|
+
sessionTtlSeconds: z.number().int().min(60).max(EXTERNAL_IDENTITY_MAX_TTL_SECONDS).optional(),
|
|
741
|
+
})
|
|
742
|
+
.strict(),
|
|
743
|
+
]);
|
|
744
|
+
export const identityConnectionUpdateSchema = z
|
|
745
|
+
.object({
|
|
746
|
+
name: z.string().trim().min(1).max(200).optional(),
|
|
747
|
+
clientId: z.string().min(1).max(512).optional(),
|
|
748
|
+
clientSecret: z.string().min(1).max(2048).optional(),
|
|
749
|
+
authorizeUrl: z.string().min(1).max(2048).optional(),
|
|
750
|
+
publicKeys: z.array(identityConnectionPublicKeySchema).min(1).max(5).optional(),
|
|
751
|
+
sessionTtlSeconds: z
|
|
752
|
+
.number()
|
|
753
|
+
.int()
|
|
754
|
+
.min(60)
|
|
755
|
+
.max(24 * 60 * 60)
|
|
756
|
+
.optional(),
|
|
757
|
+
})
|
|
758
|
+
.strict();
|
|
759
|
+
export const identityConnectionSchema = z
|
|
760
|
+
.object({
|
|
761
|
+
id: registeredIdSchema("acn"),
|
|
762
|
+
teamId: z.string().min(1),
|
|
763
|
+
type: z.enum(["oidc", "signer"]),
|
|
764
|
+
name: z.string().min(1),
|
|
765
|
+
issuer: z.string().min(1),
|
|
766
|
+
clientId: z.string().nullable(),
|
|
767
|
+
clientSecretSet: z.boolean(),
|
|
768
|
+
authorizeUrl: z.string().nullable(),
|
|
769
|
+
publicKeys: z.array(identityConnectionPublicKeySchema),
|
|
770
|
+
sessionTtlSeconds: z.number().int().positive(),
|
|
771
|
+
createdAt: z.string().datetime(),
|
|
772
|
+
rotatedAt: z.string().datetime().nullable(),
|
|
773
|
+
lastUsedAt: z.string().datetime().nullable(),
|
|
774
|
+
revokedAt: z.string().datetime().nullable(),
|
|
775
|
+
})
|
|
776
|
+
.strict();
|
|
777
|
+
export const externalGrantCreateSchema = z
|
|
778
|
+
.object({
|
|
779
|
+
connectionId: registeredIdSchema("acn"),
|
|
780
|
+
subject: z.string().min(1).max(255),
|
|
781
|
+
...credentialGrantFields,
|
|
782
|
+
})
|
|
783
|
+
.strict();
|