@hraness/suite-accounts 0.9.20
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/LICENSE +21 -0
- package/README.md +496 -0
- package/compiler-foundation.css +1 -0
- package/dist/auth-client.js +16 -0
- package/dist/auth-proxy.js +757 -0
- package/dist/bearer-verifier.js +1119 -0
- package/dist/browser-session.js +208 -0
- package/dist/client-configuration.js +470 -0
- package/dist/convex-browser-auth-browser.js +923 -0
- package/dist/convex-browser-auth-server.js +846 -0
- package/dist/convex-browser-auth.js +597 -0
- package/dist/entitlements.js +502 -0
- package/dist/identity/functions.js +44 -0
- package/dist/identity/index.js +1716 -0
- package/dist/identity/return-targets.js +33 -0
- package/dist/index.js +1133 -0
- package/dist/oidc-device-code.js +253 -0
- package/dist/oidc-rp.js +2259 -0
- package/dist/oidc-session-policy.js +5 -0
- package/dist/oidc-surface-server.js +2332 -0
- package/dist/profile-form.js +1934 -0
- package/dist/profile.js +17 -0
- package/dist/public-config.js +462 -0
- package/dist/react.js +137 -0
- package/dist/receipt-verifier.js +619 -0
- package/dist/registry.js +327 -0
- package/dist/stylex-manifest.json +1 -0
- package/dist/stylex.css +435 -0
- package/dist/urls.js +391 -0
- package/package.json +263 -0
- package/portfolio-inventory.json +37 -0
- package/src/auth-client.ts +37 -0
- package/src/auth-proxy.ts +473 -0
- package/src/bearer-verifier.ts +478 -0
- package/src/browser-session.ts +260 -0
- package/src/client-configuration.ts +172 -0
- package/src/convex-browser-auth-browser.ts +238 -0
- package/src/convex-browser-auth-server.ts +492 -0
- package/src/convex-browser-auth.ts +208 -0
- package/src/convex-url.ts +74 -0
- package/src/entitlements.ts +317 -0
- package/src/identity/catalog.ts +227 -0
- package/src/identity/consumers.ts +53 -0
- package/src/identity/functions.ts +70 -0
- package/src/identity/identifiers.ts +82 -0
- package/src/identity/index.ts +10 -0
- package/src/identity/links.ts +280 -0
- package/src/identity/membership.ts +156 -0
- package/src/identity/principals.ts +301 -0
- package/src/identity/profiles-v2.ts +330 -0
- package/src/identity/profiles.ts +524 -0
- package/src/identity/return-targets.ts +20 -0
- package/src/identity/usernames.ts +101 -0
- package/src/identity/views.ts +289 -0
- package/src/immutable.ts +25 -0
- package/src/index.ts +7 -0
- package/src/oidc-continuation.ts +59 -0
- package/src/oidc-device-code.ts +401 -0
- package/src/oidc-rp.ts +2217 -0
- package/src/oidc-session-policy.ts +6 -0
- package/src/oidc-surface-server.ts +186 -0
- package/src/profile-form.css +1 -0
- package/src/profile-form.stylex.ts +226 -0
- package/src/profile-form.tsx +314 -0
- package/src/profile.ts +33 -0
- package/src/public-config.ts +294 -0
- package/src/public-profile-form-result.ts +82 -0
- package/src/public-profile-form.tsx +319 -0
- package/src/public-profile-links.ts +100 -0
- package/src/react.tsx +196 -0
- package/src/receipt-verifier.ts +273 -0
- package/src/registry.ts +600 -0
- package/src/urls.ts +234 -0
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
// src/identity/consumers.ts
|
|
2
|
+
import { err, ok } from "@hraness/result";
|
|
3
|
+
|
|
4
|
+
// src/immutable.ts
|
|
5
|
+
function deepFreeze(value) {
|
|
6
|
+
const visited = new WeakSet;
|
|
7
|
+
function freezeOwned(current) {
|
|
8
|
+
if (current === null || typeof current !== "object")
|
|
9
|
+
return;
|
|
10
|
+
if (visited.has(current))
|
|
11
|
+
return;
|
|
12
|
+
visited.add(current);
|
|
13
|
+
for (const key of Reflect.ownKeys(current)) {
|
|
14
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
15
|
+
if (descriptor !== undefined && "value" in descriptor) {
|
|
16
|
+
freezeOwned(descriptor.value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
Object.freeze(current);
|
|
20
|
+
}
|
|
21
|
+
freezeOwned(value);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/identity/consumers.ts
|
|
26
|
+
var SUITE_CONSUMER_IDS = deepFreeze([
|
|
27
|
+
"accounts",
|
|
28
|
+
"act60",
|
|
29
|
+
"elders",
|
|
30
|
+
"soundfish",
|
|
31
|
+
"oh-computer",
|
|
32
|
+
"draw-money",
|
|
33
|
+
"oprte",
|
|
34
|
+
"sponge"
|
|
35
|
+
]);
|
|
36
|
+
var LEGACY_SUITE_CONSUMER_IDS = deepFreeze([
|
|
37
|
+
"kitchen"
|
|
38
|
+
]);
|
|
39
|
+
function parseSuiteConsumerId(value) {
|
|
40
|
+
switch (value) {
|
|
41
|
+
case "accounts":
|
|
42
|
+
case "act60":
|
|
43
|
+
case "elders":
|
|
44
|
+
case "soundfish":
|
|
45
|
+
case "oh-computer":
|
|
46
|
+
case "draw-money":
|
|
47
|
+
case "oprte":
|
|
48
|
+
case "sponge":
|
|
49
|
+
return ok(value);
|
|
50
|
+
case "kitchen":
|
|
51
|
+
return ok("oprte");
|
|
52
|
+
default:
|
|
53
|
+
return err("invalid-consumer");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/registry.ts
|
|
58
|
+
var SUITE_ACCOUNTS_REMOTE_ENVIRONMENTS = deepFreeze([
|
|
59
|
+
"production"
|
|
60
|
+
]);
|
|
61
|
+
var accountsCookies = deepFreeze({
|
|
62
|
+
chunked: ["account_data", "session_data"],
|
|
63
|
+
names: [
|
|
64
|
+
"account_data",
|
|
65
|
+
"convex_jwt",
|
|
66
|
+
"dont_remember",
|
|
67
|
+
"session_data",
|
|
68
|
+
"session_token"
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
var consumerCookies = deepFreeze({
|
|
72
|
+
chunked: ["session_data"],
|
|
73
|
+
names: ["dont_remember", "session_data", "session_token"]
|
|
74
|
+
});
|
|
75
|
+
var SUITE_ACCOUNTS_DEPLOYMENTS = deepFreeze({
|
|
76
|
+
production: {
|
|
77
|
+
accountsOrigin: "https://account.hraness.com",
|
|
78
|
+
convexSiteUrl: "https://qualified-marmot-22.convex.site",
|
|
79
|
+
convexUrl: "https://qualified-marmot-22.convex.cloud"
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
function unsupported(siteUrl) {
|
|
83
|
+
return { billingReturn: { kind: "unsupported" }, siteUrl };
|
|
84
|
+
}
|
|
85
|
+
function oidcSite(id, displayName, productionSiteUrl) {
|
|
86
|
+
return {
|
|
87
|
+
auth: { basePath: "/api/suite-auth", kind: "oidc-rp" },
|
|
88
|
+
displayName,
|
|
89
|
+
environments: {
|
|
90
|
+
production: unsupported(productionSiteUrl)
|
|
91
|
+
},
|
|
92
|
+
id
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
var SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS = deepFreeze([
|
|
96
|
+
"accounts",
|
|
97
|
+
"act60",
|
|
98
|
+
"soundfish",
|
|
99
|
+
"oh-computer",
|
|
100
|
+
"draw-money",
|
|
101
|
+
"sponge"
|
|
102
|
+
]);
|
|
103
|
+
var SUITE_ACCOUNTS_CONSUMERS = deepFreeze({
|
|
104
|
+
accounts: {
|
|
105
|
+
auth: {
|
|
106
|
+
basePath: "/api/auth",
|
|
107
|
+
cookies: accountsCookies,
|
|
108
|
+
kind: "authority"
|
|
109
|
+
},
|
|
110
|
+
displayName: "Accounts",
|
|
111
|
+
environments: {
|
|
112
|
+
production: {
|
|
113
|
+
billingReturn: { kind: "supported", path: "/account" },
|
|
114
|
+
siteUrl: "https://account.hraness.com"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
id: "accounts"
|
|
118
|
+
},
|
|
119
|
+
act60: oidcSite("act60", "ACT60", "https://act60.me"),
|
|
120
|
+
soundfish: {
|
|
121
|
+
auth: { basePath: "/api/suite-auth", kind: "oidc-rp" },
|
|
122
|
+
displayName: "Soundfish",
|
|
123
|
+
environments: {
|
|
124
|
+
production: unsupported("https://sound.fish")
|
|
125
|
+
},
|
|
126
|
+
id: "soundfish"
|
|
127
|
+
},
|
|
128
|
+
"oh-computer": oidcSite("oh-computer", "Oh", "https://oh.computer"),
|
|
129
|
+
"draw-money": {
|
|
130
|
+
auth: {
|
|
131
|
+
basePath: "/api/auth",
|
|
132
|
+
cookies: consumerCookies,
|
|
133
|
+
kind: "proxy"
|
|
134
|
+
},
|
|
135
|
+
displayName: "Draw Money",
|
|
136
|
+
environments: {
|
|
137
|
+
production: unsupported("https://draw.money")
|
|
138
|
+
},
|
|
139
|
+
id: "draw-money"
|
|
140
|
+
},
|
|
141
|
+
sponge: oidcSite("sponge", "Sponge", "https://spongesearch.com")
|
|
142
|
+
});
|
|
143
|
+
var SUITE_ACCOUNTS_CURRENT_ORIGIN_OVERRIDES = deepFreeze({
|
|
144
|
+
sponge: {
|
|
145
|
+
production: unsupported("https://sponge.computer")
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
var SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS = deepFreeze([
|
|
149
|
+
"accounts",
|
|
150
|
+
"act60",
|
|
151
|
+
"soundfish",
|
|
152
|
+
"oh-computer",
|
|
153
|
+
"sponge",
|
|
154
|
+
"peopleblade",
|
|
155
|
+
"aicharts",
|
|
156
|
+
"hraness",
|
|
157
|
+
"soulscrape",
|
|
158
|
+
"platonik",
|
|
159
|
+
"ghostget",
|
|
160
|
+
"alt",
|
|
161
|
+
"algal"
|
|
162
|
+
]);
|
|
163
|
+
function currentOidcSite(id, displayName, productionSiteUrl) {
|
|
164
|
+
return {
|
|
165
|
+
auth: { basePath: "/api/suite-auth", kind: "oidc-rp" },
|
|
166
|
+
displayName,
|
|
167
|
+
environments: {
|
|
168
|
+
production: unsupported(productionSiteUrl)
|
|
169
|
+
},
|
|
170
|
+
id
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
var SUITE_ACCOUNTS_CURRENT_CONSUMERS = deepFreeze({
|
|
174
|
+
accounts: SUITE_ACCOUNTS_CONSUMERS.accounts,
|
|
175
|
+
act60: SUITE_ACCOUNTS_CONSUMERS.act60,
|
|
176
|
+
soundfish: SUITE_ACCOUNTS_CONSUMERS.soundfish,
|
|
177
|
+
"oh-computer": SUITE_ACCOUNTS_CONSUMERS["oh-computer"],
|
|
178
|
+
sponge: currentOidcSite("sponge", "Sponge", SUITE_ACCOUNTS_CURRENT_ORIGIN_OVERRIDES.sponge.production.siteUrl),
|
|
179
|
+
peopleblade: currentOidcSite("peopleblade", "PeopleBlade", "https://peopleblade.com"),
|
|
180
|
+
aicharts: currentOidcSite("aicharts", "AI Charts", "https://aicharts.io"),
|
|
181
|
+
hraness: currentOidcSite("hraness", "Hraness", "https://hraness.com"),
|
|
182
|
+
soulscrape: currentOidcSite("soulscrape", "Soulscrape", "https://soulscrape.com"),
|
|
183
|
+
platonik: currentOidcSite("platonik", "Platonik", "https://platonik.space"),
|
|
184
|
+
ghostget: currentOidcSite("ghostget", "Ghostget", "https://ghostget.com"),
|
|
185
|
+
alt: currentOidcSite("alt", "Alt", "https://alt.dog"),
|
|
186
|
+
algal: currentOidcSite("algal", "Algal", "https://algal.cloud")
|
|
187
|
+
});
|
|
188
|
+
var SUITE_ACCOUNTS_ACTIVE_CONSUMER_IDS = deepFreeze([
|
|
189
|
+
"accounts",
|
|
190
|
+
"act60",
|
|
191
|
+
"soundfish",
|
|
192
|
+
"oh-computer",
|
|
193
|
+
"sponge"
|
|
194
|
+
]);
|
|
195
|
+
var SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS = deepFreeze(SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS.filter((consumer) => SUITE_ACCOUNTS_CONSUMERS[consumer].auth.kind === "oidc-rp"));
|
|
196
|
+
function suiteAccountsConsumerRequiresEmailOtp(consumer) {
|
|
197
|
+
return SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.includes(consumer);
|
|
198
|
+
}
|
|
199
|
+
var SUITE_ACCOUNTS_LINKED_OIDC_CONSUMER_IDS = deepFreeze([
|
|
200
|
+
"soundfish"
|
|
201
|
+
]);
|
|
202
|
+
var SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS = deepFreeze(SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS.filter((consumer) => SUITE_ACCOUNTS_CURRENT_CONSUMERS[consumer].auth.kind === "oidc-rp"));
|
|
203
|
+
var SUITE_ACCOUNTS_CURRENT_LINKED_OIDC_CONSUMER_IDS = deepFreeze([
|
|
204
|
+
"soundfish",
|
|
205
|
+
"peopleblade",
|
|
206
|
+
"ghostget"
|
|
207
|
+
]);
|
|
208
|
+
var SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS = deepFreeze([
|
|
209
|
+
"act60",
|
|
210
|
+
"soundfish",
|
|
211
|
+
"oh-computer",
|
|
212
|
+
"sponge",
|
|
213
|
+
"peopleblade",
|
|
214
|
+
"aicharts",
|
|
215
|
+
"hraness",
|
|
216
|
+
"soulscrape",
|
|
217
|
+
"platonik",
|
|
218
|
+
"ghostget",
|
|
219
|
+
"alt"
|
|
220
|
+
]);
|
|
221
|
+
var SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS = deepFreeze([
|
|
222
|
+
"algal-cli"
|
|
223
|
+
]);
|
|
224
|
+
var SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS = deepFreeze({
|
|
225
|
+
"algal-cli": {
|
|
226
|
+
consumer: "algal",
|
|
227
|
+
displayName: "Algal CLI",
|
|
228
|
+
environments: ["production"],
|
|
229
|
+
id: "algal-cli"
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
function isSuiteAccountsConsumerId(value) {
|
|
233
|
+
return typeof value === "string" && SUITE_CONSUMER_IDS.includes(value);
|
|
234
|
+
}
|
|
235
|
+
function isSuiteAccountsRegisteredConsumerId(value) {
|
|
236
|
+
return typeof value === "string" && SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS.includes(value);
|
|
237
|
+
}
|
|
238
|
+
function isSuiteAccountsOidcConsumerId(value) {
|
|
239
|
+
return isSuiteAccountsRegisteredConsumerId(value) && SUITE_ACCOUNTS_CONSUMERS[value].auth.kind === "oidc-rp";
|
|
240
|
+
}
|
|
241
|
+
function isSuiteAccountsLinkedOidcConsumerId(value) {
|
|
242
|
+
return SUITE_ACCOUNTS_LINKED_OIDC_CONSUMER_IDS.includes(value);
|
|
243
|
+
}
|
|
244
|
+
function isSuiteAccountsOAuthConsumerId(value) {
|
|
245
|
+
return isSuiteAccountsRegisteredConsumerId(value) && SUITE_ACCOUNTS_CONSUMERS[value].auth.kind === "oidc-rp";
|
|
246
|
+
}
|
|
247
|
+
function isSuiteAccountsCurrentConsumerId(value) {
|
|
248
|
+
return typeof value === "string" && SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS.includes(value);
|
|
249
|
+
}
|
|
250
|
+
function isSuiteAccountsCurrentOidcConsumerId(value) {
|
|
251
|
+
return getSuiteAccountsCurrentConsumer(value).auth.kind === "oidc-rp";
|
|
252
|
+
}
|
|
253
|
+
function isSuiteAccountsCurrentLinkedOidcConsumerId(value) {
|
|
254
|
+
return SUITE_ACCOUNTS_CURRENT_LINKED_OIDC_CONSUMER_IDS.includes(value);
|
|
255
|
+
}
|
|
256
|
+
function isSuiteAccountsCurrentOAuthConsumerId(value) {
|
|
257
|
+
return getSuiteAccountsCurrentConsumer(value).auth.kind === "oidc-rp";
|
|
258
|
+
}
|
|
259
|
+
function isSuiteAccountsCurrentBrowserDeviceCodeConsumerId(value) {
|
|
260
|
+
return typeof value === "string" && SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS.includes(value);
|
|
261
|
+
}
|
|
262
|
+
function isSuiteAccountsCurrentDeviceClientId(value) {
|
|
263
|
+
return typeof value === "string" && SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS.includes(value);
|
|
264
|
+
}
|
|
265
|
+
function suiteAccountsCurrentConsumerRequiresEmailOtp(consumer) {
|
|
266
|
+
return SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.includes(consumer);
|
|
267
|
+
}
|
|
268
|
+
function getSuiteAccountsConsumer(consumer) {
|
|
269
|
+
return SUITE_ACCOUNTS_CONSUMERS[consumer];
|
|
270
|
+
}
|
|
271
|
+
function getSuiteAccountsConsumerEnvironment(consumer, environment) {
|
|
272
|
+
if (!isSuiteAccountsRegisteredConsumerId(consumer))
|
|
273
|
+
return null;
|
|
274
|
+
const registration = getSuiteAccountsConsumer(consumer);
|
|
275
|
+
return registration.environments[environment] ?? null;
|
|
276
|
+
}
|
|
277
|
+
function getSuiteAccountsCurrentConsumer(consumer) {
|
|
278
|
+
return SUITE_ACCOUNTS_CURRENT_CONSUMERS[consumer];
|
|
279
|
+
}
|
|
280
|
+
function getSuiteAccountsCurrentConsumerEnvironment(consumer, environment) {
|
|
281
|
+
if (!isSuiteAccountsCurrentConsumerId(consumer))
|
|
282
|
+
return null;
|
|
283
|
+
return getSuiteAccountsCurrentConsumer(consumer).environments[environment] ?? null;
|
|
284
|
+
}
|
|
285
|
+
function isSuiteAccountsActiveConsumerId(value) {
|
|
286
|
+
return SUITE_ACCOUNTS_ACTIVE_CONSUMER_IDS.includes(value);
|
|
287
|
+
}
|
|
288
|
+
function getSuiteAccountsDeployment(environment) {
|
|
289
|
+
return SUITE_ACCOUNTS_DEPLOYMENTS[environment];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/identity/catalog.ts
|
|
293
|
+
import { err as err2, ok as ok2 } from "@hraness/result";
|
|
294
|
+
var SUITE_CATALOG_REVISION = "hraness-suite-v4";
|
|
295
|
+
var PREVIOUS_SUITE_CATALOG_REVISION = "cclrte-suite-v3";
|
|
296
|
+
var LEGACY_SUITE_CATALOG_REVISION = "cclrte-suite-v2";
|
|
297
|
+
var ARCHIVED_SUITE_CATALOG_REVISION = "cclrte-suite-v1";
|
|
298
|
+
var SUITE_CATALOG_REVISIONS = deepFreeze([
|
|
299
|
+
ARCHIVED_SUITE_CATALOG_REVISION,
|
|
300
|
+
LEGACY_SUITE_CATALOG_REVISION,
|
|
301
|
+
PREVIOUS_SUITE_CATALOG_REVISION,
|
|
302
|
+
SUITE_CATALOG_REVISION
|
|
303
|
+
]);
|
|
304
|
+
var SUITE_PLAN_IDS = deepFreeze(["community", "pro"]);
|
|
305
|
+
var SUITE_HISTORICAL_PLAN_IDS = deepFreeze([
|
|
306
|
+
"individual",
|
|
307
|
+
"business"
|
|
308
|
+
]);
|
|
309
|
+
var SUITE_CURRENT_FEATURE_IDS = deepFreeze([
|
|
310
|
+
"suite.paid",
|
|
311
|
+
"suite.community",
|
|
312
|
+
"suite.pro"
|
|
313
|
+
]);
|
|
314
|
+
var SUITE_PREVIOUS_FEATURE_IDS = deepFreeze([
|
|
315
|
+
"suite.paid",
|
|
316
|
+
"suite.believer"
|
|
317
|
+
]);
|
|
318
|
+
var SUITE_LEGACY_FEATURE_IDS = deepFreeze([
|
|
319
|
+
"suite.paid",
|
|
320
|
+
"suite.business"
|
|
321
|
+
]);
|
|
322
|
+
var SUITE_FEATURE_IDS = deepFreeze([
|
|
323
|
+
"suite.paid",
|
|
324
|
+
"suite.community",
|
|
325
|
+
"suite.pro",
|
|
326
|
+
"suite.believer",
|
|
327
|
+
"suite.business"
|
|
328
|
+
]);
|
|
329
|
+
var CURRENT_PLAN_FEATURES = deepFreeze({
|
|
330
|
+
community: ["suite.paid", "suite.community"],
|
|
331
|
+
pro: ["suite.paid", "suite.community", "suite.pro"]
|
|
332
|
+
});
|
|
333
|
+
var PREVIOUS_PLAN_FEATURES = deepFreeze({
|
|
334
|
+
business: ["suite.paid", "suite.believer"],
|
|
335
|
+
individual: ["suite.paid"]
|
|
336
|
+
});
|
|
337
|
+
var LEGACY_PLAN_FEATURES = deepFreeze({
|
|
338
|
+
business: ["suite.paid", "suite.business"],
|
|
339
|
+
individual: ["suite.paid"]
|
|
340
|
+
});
|
|
341
|
+
function parseSuitePlanId(value, revision = SUITE_CATALOG_REVISION) {
|
|
342
|
+
const ids = revision === SUITE_CATALOG_REVISION ? SUITE_PLAN_IDS : SUITE_HISTORICAL_PLAN_IDS;
|
|
343
|
+
return typeof value === "string" && ids.includes(value) ? ok2(value) : err2("invalid-plan");
|
|
344
|
+
}
|
|
345
|
+
function parseAnySuitePlanId(value) {
|
|
346
|
+
return typeof value === "string" && [...SUITE_PLAN_IDS, ...SUITE_HISTORICAL_PLAN_IDS].includes(value) ? ok2(value) : err2("invalid-plan");
|
|
347
|
+
}
|
|
348
|
+
function parseCurrentSuiteFeatureId(value) {
|
|
349
|
+
return value === "suite.paid" || value === "suite.community" || value === "suite.pro" ? ok2(value) : err2("invalid-feature");
|
|
350
|
+
}
|
|
351
|
+
function parseSuiteFeatureId(value) {
|
|
352
|
+
return typeof value === "string" && SUITE_FEATURE_IDS.includes(value) ? ok2(value) : err2("invalid-feature");
|
|
353
|
+
}
|
|
354
|
+
function parseSuiteCatalogRevision(value) {
|
|
355
|
+
return typeof value === "string" && SUITE_CATALOG_REVISIONS.includes(value) ? ok2(value) : err2("invalid-catalog-revision");
|
|
356
|
+
}
|
|
357
|
+
function featuresForSuitePlan(plan, revision = SUITE_CATALOG_REVISION) {
|
|
358
|
+
const table = revision === SUITE_CATALOG_REVISION ? CURRENT_PLAN_FEATURES : revision === ARCHIVED_SUITE_CATALOG_REVISION ? LEGACY_PLAN_FEATURES : PREVIOUS_PLAN_FEATURES;
|
|
359
|
+
const features = table[plan];
|
|
360
|
+
return features === undefined ? [] : [...features];
|
|
361
|
+
}
|
|
362
|
+
function grantedSuiteFeatureSets(revision) {
|
|
363
|
+
if (revision === SUITE_CATALOG_REVISION) {
|
|
364
|
+
return deepFreeze([
|
|
365
|
+
[],
|
|
366
|
+
CURRENT_PLAN_FEATURES.community,
|
|
367
|
+
CURRENT_PLAN_FEATURES.pro
|
|
368
|
+
]);
|
|
369
|
+
}
|
|
370
|
+
const table = revision === ARCHIVED_SUITE_CATALOG_REVISION ? LEGACY_PLAN_FEATURES : PREVIOUS_PLAN_FEATURES;
|
|
371
|
+
return deepFreeze([
|
|
372
|
+
[],
|
|
373
|
+
table.individual,
|
|
374
|
+
table.business
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
function suitePlanIncludesFeature(plan, feature) {
|
|
378
|
+
return plan !== null && featuresForSuitePlan(plan).includes(feature);
|
|
379
|
+
}
|
|
380
|
+
// src/identity/identifiers.ts
|
|
381
|
+
import { err as err3, ok as ok3 } from "@hraness/result";
|
|
382
|
+
var suiteAccountIdPattern = /^acct_[0-9a-f]{32}$/u;
|
|
383
|
+
var suiteInvoiceRefPattern = /^invref_[0-9a-f]{32}$/u;
|
|
384
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
385
|
+
function parseSuiteAccountId(value) {
|
|
386
|
+
return typeof value === "string" && suiteAccountIdPattern.test(value) ? ok3(value) : err3("invalid-suite-account-id");
|
|
387
|
+
}
|
|
388
|
+
function parseSuiteInvoiceRef(value) {
|
|
389
|
+
return typeof value === "string" && suiteInvoiceRefPattern.test(value) ? ok3(value) : err3("invalid-suite-invoice-ref");
|
|
390
|
+
}
|
|
391
|
+
function generateSuiteAccountId(randomUuid = () => crypto.randomUUID()) {
|
|
392
|
+
const uuid = randomUuid();
|
|
393
|
+
if (!uuidPattern.test(uuid)) {
|
|
394
|
+
throw new TypeError("The suite account ID source did not return a UUID.");
|
|
395
|
+
}
|
|
396
|
+
const candidate = `acct_${uuid.replaceAll("-", "").toLowerCase()}`;
|
|
397
|
+
const parsed = parseSuiteAccountId(candidate);
|
|
398
|
+
if (!parsed.ok) {
|
|
399
|
+
throw new TypeError("The suite account ID source produced an invalid value.");
|
|
400
|
+
}
|
|
401
|
+
return parsed.value;
|
|
402
|
+
}
|
|
403
|
+
function generateSuiteInvoiceRef(randomUuid = () => crypto.randomUUID()) {
|
|
404
|
+
const uuid = randomUuid();
|
|
405
|
+
if (!uuidPattern.test(uuid)) {
|
|
406
|
+
throw new TypeError("The suite invoice reference source did not return a UUID.");
|
|
407
|
+
}
|
|
408
|
+
const candidate = `invref_${uuid.replaceAll("-", "").toLowerCase()}`;
|
|
409
|
+
const parsed = parseSuiteInvoiceRef(candidate);
|
|
410
|
+
if (!parsed.ok) {
|
|
411
|
+
throw new TypeError("The suite invoice reference source produced an invalid value.");
|
|
412
|
+
}
|
|
413
|
+
return parsed.value;
|
|
414
|
+
}
|
|
415
|
+
// src/identity/links.ts
|
|
416
|
+
import { ok as ok6 } from "@hraness/result";
|
|
417
|
+
|
|
418
|
+
// src/identity/principals.ts
|
|
419
|
+
import { err as err5, isRecord, ok as ok5 } from "@hraness/result";
|
|
420
|
+
|
|
421
|
+
// src/identity/usernames.ts
|
|
422
|
+
import { err as err4, ok as ok4 } from "@hraness/result";
|
|
423
|
+
var SUITE_USERNAME_MIN_LENGTH = 3;
|
|
424
|
+
var SUITE_USERNAME_MAX_LENGTH = 24;
|
|
425
|
+
var suiteUsernamePattern = /^[a-z0-9](?:[a-z0-9]|[-_](?=[a-z0-9]))*[a-z0-9]$/u;
|
|
426
|
+
var reservedSuiteUsernames = new Set([
|
|
427
|
+
"account",
|
|
428
|
+
"accounts",
|
|
429
|
+
"admin",
|
|
430
|
+
"api",
|
|
431
|
+
"auth",
|
|
432
|
+
"billing",
|
|
433
|
+
"design",
|
|
434
|
+
"docs",
|
|
435
|
+
"help",
|
|
436
|
+
"hraness",
|
|
437
|
+
"login",
|
|
438
|
+
"logout",
|
|
439
|
+
"new",
|
|
440
|
+
"newsletter",
|
|
441
|
+
"party",
|
|
442
|
+
"place",
|
|
443
|
+
"preview",
|
|
444
|
+
"pub",
|
|
445
|
+
"root",
|
|
446
|
+
"settings",
|
|
447
|
+
"social-image",
|
|
448
|
+
"source",
|
|
449
|
+
"sources",
|
|
450
|
+
"support",
|
|
451
|
+
"system",
|
|
452
|
+
"user",
|
|
453
|
+
"users",
|
|
454
|
+
"www"
|
|
455
|
+
]);
|
|
456
|
+
function validateCanonicalSuiteUsername(value) {
|
|
457
|
+
if (value.length < SUITE_USERNAME_MIN_LENGTH) {
|
|
458
|
+
return err4("suite-username-too-short");
|
|
459
|
+
}
|
|
460
|
+
if (value.length > SUITE_USERNAME_MAX_LENGTH) {
|
|
461
|
+
return err4("suite-username-too-long");
|
|
462
|
+
}
|
|
463
|
+
if (!suiteUsernamePattern.test(value)) {
|
|
464
|
+
return err4("invalid-suite-username");
|
|
465
|
+
}
|
|
466
|
+
return ok4(value);
|
|
467
|
+
}
|
|
468
|
+
function normalizeSuiteUsername(value) {
|
|
469
|
+
if (typeof value !== "string")
|
|
470
|
+
return err4("invalid-suite-username");
|
|
471
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
472
|
+
const code = value.charCodeAt(index);
|
|
473
|
+
if (code > 127 || code < 32 || code === 127) {
|
|
474
|
+
return err4("invalid-suite-username");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const trimmed = value.trim();
|
|
478
|
+
const parsed = validateCanonicalSuiteUsername(trimmed.toLowerCase());
|
|
479
|
+
if (!parsed.ok)
|
|
480
|
+
return parsed;
|
|
481
|
+
return reservedSuiteUsernames.has(parsed.value) ? err4("suite-username-reserved") : parsed;
|
|
482
|
+
}
|
|
483
|
+
function parseSuiteUsername(value) {
|
|
484
|
+
if (typeof value !== "string")
|
|
485
|
+
return err4("invalid-suite-username");
|
|
486
|
+
const parsed = validateCanonicalSuiteUsername(value);
|
|
487
|
+
return parsed.ok && parsed.value === value ? parsed : err4(parsed.ok ? "invalid-suite-username" : parsed.error);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/identity/principals.ts
|
|
491
|
+
var SUITE_PRODUCTS = deepFreeze([
|
|
492
|
+
"soundfish",
|
|
493
|
+
"hra",
|
|
494
|
+
"peopleblade",
|
|
495
|
+
"ghostget",
|
|
496
|
+
"crclte",
|
|
497
|
+
"pub"
|
|
498
|
+
]);
|
|
499
|
+
var LEGACY_SUITE_PRODUCT_IDS = deepFreeze([
|
|
500
|
+
"oprte",
|
|
501
|
+
"kitchen"
|
|
502
|
+
]);
|
|
503
|
+
var SUITE_ENVIRONMENTS = deepFreeze([
|
|
504
|
+
"development",
|
|
505
|
+
"staging",
|
|
506
|
+
"production"
|
|
507
|
+
]);
|
|
508
|
+
var SUITE_ISSUABLE_ENVIRONMENTS = deepFreeze([
|
|
509
|
+
"development",
|
|
510
|
+
"production"
|
|
511
|
+
]);
|
|
512
|
+
var localIssuerHosts = new Set(["127.0.0.1", "[::1]", "localhost"]);
|
|
513
|
+
function containsAsciiControl(value) {
|
|
514
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
515
|
+
const code = value.charCodeAt(index);
|
|
516
|
+
if (code <= 31 || code === 127)
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
function parseIdentityIssuer(value) {
|
|
522
|
+
if (typeof value !== "string" || value.length > 2048 || value.trim() !== value) {
|
|
523
|
+
return err5("invalid-issuer");
|
|
524
|
+
}
|
|
525
|
+
let parsed;
|
|
526
|
+
try {
|
|
527
|
+
parsed = new URL(value);
|
|
528
|
+
} catch {
|
|
529
|
+
return err5("invalid-issuer");
|
|
530
|
+
}
|
|
531
|
+
const local = localIssuerHosts.has(parsed.hostname);
|
|
532
|
+
if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:") || parsed.username !== "" || parsed.password !== "" || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
|
|
533
|
+
return err5("invalid-issuer");
|
|
534
|
+
}
|
|
535
|
+
return ok5(parsed.origin);
|
|
536
|
+
}
|
|
537
|
+
function parseIdentitySubject(value) {
|
|
538
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 255 || value.trim() !== value || containsAsciiControl(value)) {
|
|
539
|
+
return err5("invalid-subject");
|
|
540
|
+
}
|
|
541
|
+
return ok5(value);
|
|
542
|
+
}
|
|
543
|
+
function parseIssuerSubject(value) {
|
|
544
|
+
if (!isRecord(value))
|
|
545
|
+
return err5("invalid-subject");
|
|
546
|
+
const issuer = parseIdentityIssuer(value["issuer"]);
|
|
547
|
+
if (!issuer.ok)
|
|
548
|
+
return issuer;
|
|
549
|
+
const subject = parseIdentitySubject(value["subject"]);
|
|
550
|
+
return subject.ok ? ok5({ issuer: issuer.value, subject: subject.value }) : subject;
|
|
551
|
+
}
|
|
552
|
+
function parseSuiteProduct(value) {
|
|
553
|
+
switch (value) {
|
|
554
|
+
case "soundfish":
|
|
555
|
+
case "hra":
|
|
556
|
+
case "peopleblade":
|
|
557
|
+
case "ghostget":
|
|
558
|
+
case "crclte":
|
|
559
|
+
case "pub":
|
|
560
|
+
return ok5(value);
|
|
561
|
+
case "oprte":
|
|
562
|
+
case "kitchen":
|
|
563
|
+
return ok5("hra");
|
|
564
|
+
default:
|
|
565
|
+
return err5("invalid-product");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function parseSuiteEnvironment(value) {
|
|
569
|
+
return typeof value === "string" && SUITE_ENVIRONMENTS.includes(value) ? ok5(value) : err5("invalid-environment");
|
|
570
|
+
}
|
|
571
|
+
function isSuiteIssuableEnvironment(value) {
|
|
572
|
+
return value === "development" || value === "production";
|
|
573
|
+
}
|
|
574
|
+
function parseLegacyPrincipalLink(value) {
|
|
575
|
+
if (!isRecord(value))
|
|
576
|
+
return err5("invalid-legacy-link");
|
|
577
|
+
const product = parseSuiteProduct(value["product"]);
|
|
578
|
+
if (!product.ok)
|
|
579
|
+
return product;
|
|
580
|
+
const environment = parseSuiteEnvironment(value["environment"]);
|
|
581
|
+
if (!environment.ok)
|
|
582
|
+
return environment;
|
|
583
|
+
const legacySubject = parseIdentitySubject(value["legacySubject"]);
|
|
584
|
+
if (!legacySubject.ok)
|
|
585
|
+
return legacySubject;
|
|
586
|
+
return ok5({
|
|
587
|
+
environment: environment.value,
|
|
588
|
+
legacySubject: legacySubject.value,
|
|
589
|
+
product: product.value
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function parseAudience(value) {
|
|
593
|
+
const values = typeof value === "string" ? [value] : value;
|
|
594
|
+
if (!Array.isArray(values) || values.length < 1 || values.length > 8 || values.some((entry) => typeof entry !== "string" || entry.length < 1 || entry.length > 255 || entry.trim() !== entry || containsAsciiControl(entry)) || new Set(values).size !== values.length) {
|
|
595
|
+
return err5("invalid-audience");
|
|
596
|
+
}
|
|
597
|
+
return ok5(values);
|
|
598
|
+
}
|
|
599
|
+
function parseTimestamp(value) {
|
|
600
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
601
|
+
}
|
|
602
|
+
function parseSuiteJwtClaims(value) {
|
|
603
|
+
if (!isRecord(value))
|
|
604
|
+
return err5("invalid-jwt-claims");
|
|
605
|
+
const principal = parseIssuerSubject({
|
|
606
|
+
issuer: value["iss"],
|
|
607
|
+
subject: value["sub"]
|
|
608
|
+
});
|
|
609
|
+
if (!principal.ok)
|
|
610
|
+
return principal;
|
|
611
|
+
const suiteAccountId = parseSuiteAccountId(value["suite_account_id"]);
|
|
612
|
+
if (!suiteAccountId.ok)
|
|
613
|
+
return suiteAccountId;
|
|
614
|
+
const audience = parseAudience(value["aud"]);
|
|
615
|
+
if (!audience.ok)
|
|
616
|
+
return audience;
|
|
617
|
+
const issuedAtSeconds = parseTimestamp(value["iat"]);
|
|
618
|
+
const expiresAtSeconds = parseTimestamp(value["exp"]);
|
|
619
|
+
const notBeforeSeconds = value["nbf"] === undefined ? undefined : parseTimestamp(value["nbf"]);
|
|
620
|
+
const legacyProfile = value["profile_revision"] === undefined && value["profile_complete"] === undefined && value["username"] === undefined;
|
|
621
|
+
const profileRevision = legacyProfile ? null : value["profile_revision"] === "username-v1" ? "username-v1" : undefined;
|
|
622
|
+
const profileComplete = legacyProfile ? false : typeof value["profile_complete"] === "boolean" ? value["profile_complete"] : undefined;
|
|
623
|
+
const username = legacyProfile || value["username"] === null ? null : parseSuiteUsername(value["username"]);
|
|
624
|
+
if (issuedAtSeconds === null || expiresAtSeconds === null || expiresAtSeconds <= issuedAtSeconds || notBeforeSeconds === null || notBeforeSeconds !== undefined && notBeforeSeconds > expiresAtSeconds || profileRevision === undefined || profileComplete === undefined || username !== null && !username.ok || profileComplete !== (username !== null)) {
|
|
625
|
+
return err5("invalid-jwt-claims");
|
|
626
|
+
}
|
|
627
|
+
return ok5({
|
|
628
|
+
audience: audience.value,
|
|
629
|
+
expiresAtSeconds,
|
|
630
|
+
issuedAtSeconds,
|
|
631
|
+
...notBeforeSeconds === undefined ? {} : { notBeforeSeconds },
|
|
632
|
+
principal: principal.value,
|
|
633
|
+
profileComplete,
|
|
634
|
+
profileRevision,
|
|
635
|
+
suiteAccountId: suiteAccountId.value,
|
|
636
|
+
username: username?.value ?? null
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/identity/links.ts
|
|
641
|
+
var IDENTITY_LINK_PROOF_VERSION = "suite-product-link-proof-v1";
|
|
642
|
+
var IDENTITY_LINK_RECEIPT_VERSION = "suite-link-receipt-v1";
|
|
643
|
+
var SUITE_ENTITLEMENTS_CLAIM_VERSION = "suite-entitlements-v1";
|
|
644
|
+
var SUITE_ENTITLEMENT_RECEIPT_VERSION = "suite-entitlement-receipt-v1";
|
|
645
|
+
var IDENTITY_LINK_MAX_TTL_MS = 5 * 60000;
|
|
646
|
+
var SUITE_ENTITLEMENT_RECEIPT_MAX_TTL_MS = 5 * 60000;
|
|
647
|
+
var IDENTITY_LINK_CLOCK_SKEW_MS = 30000;
|
|
648
|
+
var SUITE_LINK_PRODUCTS = deepFreeze([
|
|
649
|
+
"soundfish",
|
|
650
|
+
"hra",
|
|
651
|
+
"peopleblade",
|
|
652
|
+
"ghostget",
|
|
653
|
+
"crclte",
|
|
654
|
+
"pub"
|
|
655
|
+
]);
|
|
656
|
+
var LEGACY_SUITE_LINK_PRODUCTS = deepFreeze([
|
|
657
|
+
"oprte",
|
|
658
|
+
"kitchen"
|
|
659
|
+
]);
|
|
660
|
+
function parseSuiteLinkProduct(value) {
|
|
661
|
+
const parsed = parseSuiteProduct(value);
|
|
662
|
+
if (!parsed.ok)
|
|
663
|
+
return parsed;
|
|
664
|
+
switch (parsed.value) {
|
|
665
|
+
case "soundfish":
|
|
666
|
+
case "hra":
|
|
667
|
+
case "peopleblade":
|
|
668
|
+
case "ghostget":
|
|
669
|
+
case "crclte":
|
|
670
|
+
case "pub":
|
|
671
|
+
return ok6(parsed.value);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function safeInteger(value) {
|
|
675
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
676
|
+
}
|
|
677
|
+
function validateProductLinkProof(input, now) {
|
|
678
|
+
const product = parseSuiteLinkProduct(input.product);
|
|
679
|
+
const environment = parseSuiteEnvironment(input.environment);
|
|
680
|
+
const localSubject = parseIdentitySubject(input.localSubject);
|
|
681
|
+
if (!safeInteger(now) || !product.ok || !environment.ok || !localSubject.ok || !/^[A-Za-z0-9_-]{22,128}$/u.test(input.challengeId) || !/^[a-z0-9][a-z0-9._-]{0,31}$/u.test(input.keyVersion) || !safeInteger(input.issuedAtMs) || !safeInteger(input.expiresAtMs) || input.expiresAtMs <= input.issuedAtMs || input.expiresAtMs - input.issuedAtMs > IDENTITY_LINK_MAX_TTL_MS) {
|
|
682
|
+
return "invalid";
|
|
683
|
+
}
|
|
684
|
+
if (input.issuedAtMs > now + IDENTITY_LINK_CLOCK_SKEW_MS) {
|
|
685
|
+
return "not-yet-valid";
|
|
686
|
+
}
|
|
687
|
+
return input.expiresAtMs <= now ? "expired" : null;
|
|
688
|
+
}
|
|
689
|
+
function productLinkProofMessage(input) {
|
|
690
|
+
return JSON.stringify([
|
|
691
|
+
IDENTITY_LINK_PROOF_VERSION,
|
|
692
|
+
input.product,
|
|
693
|
+
input.environment,
|
|
694
|
+
input.localSubject,
|
|
695
|
+
input.challengeId,
|
|
696
|
+
input.issuedAtMs,
|
|
697
|
+
input.expiresAtMs,
|
|
698
|
+
input.keyVersion
|
|
699
|
+
]);
|
|
700
|
+
}
|
|
701
|
+
function suiteLinkReceiptMessage(input) {
|
|
702
|
+
return JSON.stringify([
|
|
703
|
+
IDENTITY_LINK_RECEIPT_VERSION,
|
|
704
|
+
input.product,
|
|
705
|
+
input.environment,
|
|
706
|
+
input.localSubject,
|
|
707
|
+
input.suiteAccountId,
|
|
708
|
+
input.challengeId,
|
|
709
|
+
input.issuedAtMs,
|
|
710
|
+
input.expiresAtMs,
|
|
711
|
+
input.keyVersion
|
|
712
|
+
]);
|
|
713
|
+
}
|
|
714
|
+
function validateSuiteLinkReceipt(input, now) {
|
|
715
|
+
const proofIssue = validateProductLinkProof(input, now);
|
|
716
|
+
if (proofIssue !== null || input.version !== IDENTITY_LINK_RECEIPT_VERSION || !parseSuiteAccountId(input.suiteAccountId).ok || !/^[A-Za-z0-9_-]{43}$/u.test(input.signature)) {
|
|
717
|
+
return proofIssue ?? "invalid";
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
function exactGrantedFeatures(revision, values) {
|
|
722
|
+
if (values.length > SUITE_FEATURE_IDS.length)
|
|
723
|
+
return false;
|
|
724
|
+
const parsed = [];
|
|
725
|
+
for (const value of values) {
|
|
726
|
+
const feature = parseSuiteFeatureId(value);
|
|
727
|
+
if (!feature.ok || parsed.includes(feature.value))
|
|
728
|
+
return false;
|
|
729
|
+
parsed.push(feature.value);
|
|
730
|
+
}
|
|
731
|
+
return grantedSuiteFeatureSets(revision).some((set) => set.length === parsed.length && set.every((feature, index) => feature === parsed[index]));
|
|
732
|
+
}
|
|
733
|
+
function validateSuiteEntitlementsClaim(input) {
|
|
734
|
+
const catalogRevision = parseSuiteCatalogRevision(input.catalogRevision);
|
|
735
|
+
return input.version === SUITE_ENTITLEMENTS_CLAIM_VERSION && catalogRevision.ok && safeInteger(input.observedAtMs) && safeInteger(input.expiresAtMs) && input.expiresAtMs > input.observedAtMs && safeInteger(input.projectionRevision) && Array.isArray(input.features) && exactGrantedFeatures(catalogRevision.value, input.features);
|
|
736
|
+
}
|
|
737
|
+
function suiteEntitlementReceiptMessage(input) {
|
|
738
|
+
return JSON.stringify([
|
|
739
|
+
SUITE_ENTITLEMENT_RECEIPT_VERSION,
|
|
740
|
+
input.product,
|
|
741
|
+
input.environment,
|
|
742
|
+
input.suiteAccountId,
|
|
743
|
+
input.keyVersion,
|
|
744
|
+
input.issuedAtMs,
|
|
745
|
+
input.expiresAtMs,
|
|
746
|
+
input.entitlements.version,
|
|
747
|
+
input.entitlements.catalogRevision,
|
|
748
|
+
input.entitlements.observedAtMs,
|
|
749
|
+
input.entitlements.expiresAtMs,
|
|
750
|
+
input.entitlements.projectionRevision,
|
|
751
|
+
input.entitlements.features
|
|
752
|
+
]);
|
|
753
|
+
}
|
|
754
|
+
function validateSuiteEntitlementReceipt(input, now) {
|
|
755
|
+
const product = parseSuiteLinkProduct(input.product);
|
|
756
|
+
const environment = parseSuiteEnvironment(input.environment);
|
|
757
|
+
if (!safeInteger(now) || input.version !== SUITE_ENTITLEMENT_RECEIPT_VERSION || !product.ok || !environment.ok || !parseSuiteAccountId(input.suiteAccountId).ok || !/^[a-z0-9][a-z0-9._-]{0,31}$/u.test(input.keyVersion) || !safeInteger(input.issuedAtMs) || !safeInteger(input.expiresAtMs) || input.expiresAtMs <= input.issuedAtMs || input.expiresAtMs - input.issuedAtMs > SUITE_ENTITLEMENT_RECEIPT_MAX_TTL_MS || !validateSuiteEntitlementsClaim(input.entitlements) || input.expiresAtMs > input.entitlements.expiresAtMs || !/^[A-Za-z0-9_-]{43}$/u.test(input.signature)) {
|
|
758
|
+
return "invalid";
|
|
759
|
+
}
|
|
760
|
+
if (input.issuedAtMs > now + IDENTITY_LINK_CLOCK_SKEW_MS || input.entitlements.observedAtMs > now + IDENTITY_LINK_CLOCK_SKEW_MS) {
|
|
761
|
+
return "not-yet-valid";
|
|
762
|
+
}
|
|
763
|
+
return input.expiresAtMs <= now ? "expired" : null;
|
|
764
|
+
}
|
|
765
|
+
// src/identity/membership.ts
|
|
766
|
+
var SUITE_MEMBERSHIP_PRODUCTS = deepFreeze([
|
|
767
|
+
{
|
|
768
|
+
description: "model benchmark scores plotted against cost and tokens per task",
|
|
769
|
+
emoji: "\uD83D\uDCC8",
|
|
770
|
+
href: "https://aicharts.io",
|
|
771
|
+
id: "aicharts",
|
|
772
|
+
name: "AI CHARTS"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
description: "markdown knowledge base that gives agents the decisions behind code",
|
|
776
|
+
emoji: "\uD83D\uDCDD",
|
|
777
|
+
href: "https://wordcell.io",
|
|
778
|
+
id: "kb",
|
|
779
|
+
name: "WORDCELL"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
description: "memory for agents that stores each fact with its sources and history",
|
|
783
|
+
emoji: "\uD83D\uDCDA",
|
|
784
|
+
href: "https://oh.computer",
|
|
785
|
+
id: "oh-computer",
|
|
786
|
+
name: "OH"
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
description: "private library for what you read, with notes your agent can cite",
|
|
790
|
+
emoji: "\uD83E\uDDFD",
|
|
791
|
+
href: "https://sponge.computer",
|
|
792
|
+
id: "sponge",
|
|
793
|
+
name: "SPONGE"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
description: "local personal crm for everyone you know, built for your agent",
|
|
797
|
+
emoji: "\uD83D\uDCC7",
|
|
798
|
+
href: "https://peopleblade.com",
|
|
799
|
+
id: "peopleblade",
|
|
800
|
+
name: "PEOPLEBLADE"
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
description: "named web actions for ai agents: read pages, save media, use connected accounts",
|
|
804
|
+
emoji: "\uD83D\uDC7B",
|
|
805
|
+
href: "https://ghostget.com",
|
|
806
|
+
id: "ghostget",
|
|
807
|
+
name: "GHOSTGET"
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
description: "free agent skill that writes dated dossiers on people, sources cited",
|
|
811
|
+
emoji: "\uD83E\uDDBE",
|
|
812
|
+
href: "https://soulscrape.com",
|
|
813
|
+
id: "soulscrape",
|
|
814
|
+
name: "SOULSCRAPE"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
description: "media studio for agents: images, 3d, animation, and video to revise",
|
|
818
|
+
emoji: "\uD83D\uDCF8",
|
|
819
|
+
href: "https://slopcamera.com",
|
|
820
|
+
id: "slopcamera",
|
|
821
|
+
name: "SLOPCAMERA"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
description: "build prompt that turns a coding agent into a trading-system designer",
|
|
825
|
+
emoji: "\uD83D\uDCC8",
|
|
826
|
+
href: "https://sloptrade.com",
|
|
827
|
+
id: "sloptrade",
|
|
828
|
+
name: "SLOPTRADE"
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
description: "software studio making tools for ai agents and for people",
|
|
832
|
+
emoji: "\uD83E\uDEA2",
|
|
833
|
+
href: "https://hraness.com",
|
|
834
|
+
id: "hraness",
|
|
835
|
+
name: "HRANESS"
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
description: "free sleep sounds made in your browser, with sourced sleep guides",
|
|
839
|
+
emoji: "\uD83D\uDCA4",
|
|
840
|
+
href: "https://sleepy.land",
|
|
841
|
+
id: "sleepyland",
|
|
842
|
+
name: "SLEEPYLAND"
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
description: "independent history of stripe where every event is dated and sourced",
|
|
846
|
+
emoji: "\uD83E\uDD93",
|
|
847
|
+
href: "https://hraness.com/stripe",
|
|
848
|
+
id: "stripe-history",
|
|
849
|
+
name: "STRIPE HISTORY"
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
description: "ehlers-danlos evidence stratified by kind, every record linked to sources",
|
|
853
|
+
emoji: "\uD83E\uDDEC",
|
|
854
|
+
href: "https://hraness.com/eds",
|
|
855
|
+
id: "eds-research",
|
|
856
|
+
name: "EDS RESEARCH INDEX"
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
description: "estimate act 60 savings after real costs, with sourced guides and a day tracker",
|
|
860
|
+
emoji: "⏱️",
|
|
861
|
+
href: "https://act60.me",
|
|
862
|
+
id: "act60",
|
|
863
|
+
name: "ACT60"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
description: "browser experiments for a game of algorithmic organisms",
|
|
867
|
+
emoji: "\uD83E\uDDA0",
|
|
868
|
+
href: "https://platonik.space",
|
|
869
|
+
id: "platonik",
|
|
870
|
+
name: "PLATONIK"
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
description: "repeatable app states for browser agents, opened by url",
|
|
874
|
+
emoji: "\uD83C\uDFAF",
|
|
875
|
+
href: "https://hraness.com/direct",
|
|
876
|
+
id: "direct",
|
|
877
|
+
name: "DIRECT"
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
description: "fresh puzzles for ai agents, scored exactly, with a signed receipt",
|
|
881
|
+
emoji: "\uD83D\uDCE1",
|
|
882
|
+
href: "https://clankdar.com",
|
|
883
|
+
id: "clankdar",
|
|
884
|
+
name: "CLANKDAR"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
description: "turn the chapters of your life into one timeline you can share",
|
|
888
|
+
emoji: "\uD83D\uDCCA",
|
|
889
|
+
href: "https://lifecharts.io",
|
|
890
|
+
id: "lifedaysleft",
|
|
891
|
+
name: "LIFECHARTS"
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
description: "free publication about how companies put ai agents to work",
|
|
895
|
+
emoji: "\uD83C\uDFED",
|
|
896
|
+
href: "https://swft.io",
|
|
897
|
+
id: "swft",
|
|
898
|
+
name: "SWFT"
|
|
899
|
+
}
|
|
900
|
+
]);
|
|
901
|
+
// src/identity/profiles.ts
|
|
902
|
+
import { err as err6, isRecord as isRecord2, ok as ok7 } from "@hraness/result";
|
|
903
|
+
var SUITE_PROFILE_NAME_MAX_LENGTH = 120;
|
|
904
|
+
var SUITE_PROFILE_BIO_MAX_LENGTH = 1000;
|
|
905
|
+
var SUITE_PROFILE_URL_MAX_LENGTH = 2048;
|
|
906
|
+
var SUITE_COMMUNITY_APPLICATION_STATUSES = deepFreeze([
|
|
907
|
+
"submitted",
|
|
908
|
+
"accepted",
|
|
909
|
+
"declined",
|
|
910
|
+
"withdrawn"
|
|
911
|
+
]);
|
|
912
|
+
var PROFILE_LINK_KEYS = [
|
|
913
|
+
"bluesky",
|
|
914
|
+
"instagram",
|
|
915
|
+
"linkedin",
|
|
916
|
+
"telegram",
|
|
917
|
+
"website",
|
|
918
|
+
"x"
|
|
919
|
+
];
|
|
920
|
+
function exactKeys(value, expected) {
|
|
921
|
+
const actual = Object.keys(value).sort();
|
|
922
|
+
const sortedExpected = [...expected].sort();
|
|
923
|
+
return actual.length === sortedExpected.length && actual.every((key, index) => key === sortedExpected[index]);
|
|
924
|
+
}
|
|
925
|
+
function hasInvalidSingleLineControl(value) {
|
|
926
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
927
|
+
const code = value.charCodeAt(index);
|
|
928
|
+
if (code <= 31 || code >= 127 && code <= 159)
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
function hasInvalidBioControl(value) {
|
|
934
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
935
|
+
const code = value.charCodeAt(index);
|
|
936
|
+
if (code <= 31 && code !== 10 || code >= 127 && code <= 159) {
|
|
937
|
+
return true;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
function normalizedName(value) {
|
|
943
|
+
if (typeof value !== "string") {
|
|
944
|
+
return err6({ field: "name", reason: "required" });
|
|
945
|
+
}
|
|
946
|
+
if (hasInvalidSingleLineControl(value)) {
|
|
947
|
+
return err6({ field: "name", reason: "invalid" });
|
|
948
|
+
}
|
|
949
|
+
const name = value.trim().replace(/\s+/gu, " ");
|
|
950
|
+
if (name.length === 0) {
|
|
951
|
+
return err6({ field: "name", reason: "required" });
|
|
952
|
+
}
|
|
953
|
+
return name.length <= SUITE_PROFILE_NAME_MAX_LENGTH ? ok7(name) : err6({ field: "name", reason: "too_long" });
|
|
954
|
+
}
|
|
955
|
+
function normalizedProfileViewName(value) {
|
|
956
|
+
if (typeof value !== "string") {
|
|
957
|
+
return err6({ field: "name", reason: "invalid" });
|
|
958
|
+
}
|
|
959
|
+
if (hasInvalidSingleLineControl(value)) {
|
|
960
|
+
return err6({ field: "name", reason: "invalid" });
|
|
961
|
+
}
|
|
962
|
+
const name = value.trim().replace(/\s+/gu, " ");
|
|
963
|
+
return name.length <= SUITE_PROFILE_NAME_MAX_LENGTH ? ok7(name) : err6({ field: "name", reason: "too_long" });
|
|
964
|
+
}
|
|
965
|
+
function normalizedBio(value) {
|
|
966
|
+
if (typeof value !== "string") {
|
|
967
|
+
return err6({ field: "bio", reason: "invalid" });
|
|
968
|
+
}
|
|
969
|
+
const normalizedNewlines = value.replaceAll(`\r
|
|
970
|
+
`, `
|
|
971
|
+
`).replaceAll("\r", `
|
|
972
|
+
`);
|
|
973
|
+
if (hasInvalidBioControl(normalizedNewlines)) {
|
|
974
|
+
return err6({ field: "bio", reason: "invalid" });
|
|
975
|
+
}
|
|
976
|
+
const bio = normalizedNewlines.trim();
|
|
977
|
+
return bio.length <= SUITE_PROFILE_BIO_MAX_LENGTH ? ok7(bio) : err6({ field: "bio", reason: "too_long" });
|
|
978
|
+
}
|
|
979
|
+
function parsedHttpsUrl(value, options) {
|
|
980
|
+
if (value.length === 0 || value.length > SUITE_PROFILE_URL_MAX_LENGTH || hasInvalidSingleLineControl(value) || value.trim() !== value) {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
let url;
|
|
984
|
+
try {
|
|
985
|
+
url = new URL(value);
|
|
986
|
+
} catch {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.hash !== "" || url.port !== "" || !options.allowQuery && url.search !== "" || options.hosts !== undefined && !options.hosts.has(url.hostname.toLowerCase())) {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
return url;
|
|
993
|
+
}
|
|
994
|
+
function simpleHandle(value, pattern) {
|
|
995
|
+
const withoutAt = value.startsWith("@") ? value.slice(1) : value;
|
|
996
|
+
return pattern.test(withoutAt) ? withoutAt.toLowerCase() : null;
|
|
997
|
+
}
|
|
998
|
+
function exactPathSegments(url) {
|
|
999
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1000
|
+
return url.pathname === `/${segments.join("/")}` || url.pathname === `/${segments.join("/")}/` ? segments : null;
|
|
1001
|
+
}
|
|
1002
|
+
var X_HOSTS = new Set(["x.com", "www.x.com", "twitter.com", "www.twitter.com"]);
|
|
1003
|
+
var INSTAGRAM_HOSTS = new Set(["instagram.com", "www.instagram.com"]);
|
|
1004
|
+
var TELEGRAM_HOSTS = new Set([
|
|
1005
|
+
"t.me",
|
|
1006
|
+
"www.t.me",
|
|
1007
|
+
"telegram.me",
|
|
1008
|
+
"www.telegram.me"
|
|
1009
|
+
]);
|
|
1010
|
+
var BLUESKY_HOSTS = new Set(["bsky.app", "www.bsky.app"]);
|
|
1011
|
+
var LINKEDIN_HOSTS = new Set(["linkedin.com", "www.linkedin.com"]);
|
|
1012
|
+
function normalizedX(value) {
|
|
1013
|
+
const enteredHandle = simpleHandle(value, /^[A-Za-z0-9_]{1,15}$/u);
|
|
1014
|
+
if (enteredHandle !== null)
|
|
1015
|
+
return `https://x.com/${enteredHandle}`;
|
|
1016
|
+
const url = parsedHttpsUrl(value, { allowQuery: false, hosts: X_HOSTS });
|
|
1017
|
+
const segments = url === null ? null : exactPathSegments(url);
|
|
1018
|
+
const handle = segments?.length === 1 ? simpleHandle(segments[0], /^[A-Za-z0-9_]{1,15}$/u) : null;
|
|
1019
|
+
return handle === null ? null : `https://x.com/${handle}`;
|
|
1020
|
+
}
|
|
1021
|
+
function normalizedInstagram(value) {
|
|
1022
|
+
const pattern = /^(?!.*\.\.)[A-Za-z0-9](?:[A-Za-z0-9._]{0,28}[A-Za-z0-9_])?$/u;
|
|
1023
|
+
const enteredHandle = simpleHandle(value, pattern);
|
|
1024
|
+
if (enteredHandle !== null) {
|
|
1025
|
+
return `https://www.instagram.com/${enteredHandle}`;
|
|
1026
|
+
}
|
|
1027
|
+
const url = parsedHttpsUrl(value, {
|
|
1028
|
+
allowQuery: false,
|
|
1029
|
+
hosts: INSTAGRAM_HOSTS
|
|
1030
|
+
});
|
|
1031
|
+
const segments = url === null ? null : exactPathSegments(url);
|
|
1032
|
+
const handle = segments?.length === 1 ? simpleHandle(segments[0], pattern) : null;
|
|
1033
|
+
return handle === null ? null : `https://www.instagram.com/${handle}`;
|
|
1034
|
+
}
|
|
1035
|
+
function normalizedTelegram(value) {
|
|
1036
|
+
const enteredHandle = simpleHandle(value, /^[A-Za-z][A-Za-z0-9_]{4,31}$/u);
|
|
1037
|
+
if (enteredHandle !== null)
|
|
1038
|
+
return `https://t.me/${enteredHandle}`;
|
|
1039
|
+
const url = parsedHttpsUrl(value, {
|
|
1040
|
+
allowQuery: false,
|
|
1041
|
+
hosts: TELEGRAM_HOSTS
|
|
1042
|
+
});
|
|
1043
|
+
const segments = url === null ? null : exactPathSegments(url);
|
|
1044
|
+
const handle = segments?.length === 1 ? simpleHandle(segments[0], /^[A-Za-z][A-Za-z0-9_]{4,31}$/u) : null;
|
|
1045
|
+
return handle === null ? null : `https://t.me/${handle}`;
|
|
1046
|
+
}
|
|
1047
|
+
function validBlueskyHandle(value) {
|
|
1048
|
+
if (value.length < 3 || value.length > 253 || !value.includes(".") || value.startsWith(".") || value.endsWith(".")) {
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
const labels = value.split(".");
|
|
1052
|
+
return labels.every((label) => label.length >= 1 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u.test(label));
|
|
1053
|
+
}
|
|
1054
|
+
function normalizedBluesky(value) {
|
|
1055
|
+
const withoutAt = value.startsWith("@") ? value.slice(1) : value;
|
|
1056
|
+
const enteredHandle = withoutAt.toLowerCase();
|
|
1057
|
+
if (validBlueskyHandle(enteredHandle)) {
|
|
1058
|
+
return `https://bsky.app/profile/${enteredHandle}`;
|
|
1059
|
+
}
|
|
1060
|
+
const url = parsedHttpsUrl(value, {
|
|
1061
|
+
allowQuery: false,
|
|
1062
|
+
hosts: BLUESKY_HOSTS
|
|
1063
|
+
});
|
|
1064
|
+
const segments = url === null ? null : exactPathSegments(url);
|
|
1065
|
+
const handle = segments?.length === 2 && segments[0] === "profile" ? segments[1].toLowerCase() : null;
|
|
1066
|
+
return handle !== null && validBlueskyHandle(handle) ? `https://bsky.app/profile/${handle}` : null;
|
|
1067
|
+
}
|
|
1068
|
+
function normalizedLinkedIn(value) {
|
|
1069
|
+
const url = parsedHttpsUrl(value, {
|
|
1070
|
+
allowQuery: false,
|
|
1071
|
+
hosts: LINKEDIN_HOSTS
|
|
1072
|
+
});
|
|
1073
|
+
const segments = url === null ? null : exactPathSegments(url);
|
|
1074
|
+
if (segments?.length !== 2 || segments[0] !== "in" || !/^[A-Za-z0-9][A-Za-z0-9-]{1,99}$/u.test(segments[1])) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
return `https://www.linkedin.com/in/${segments[1].toLowerCase()}`;
|
|
1078
|
+
}
|
|
1079
|
+
function normalizedWebsite(value) {
|
|
1080
|
+
const url = parsedHttpsUrl(value, { allowQuery: true });
|
|
1081
|
+
return url === null ? null : url.href;
|
|
1082
|
+
}
|
|
1083
|
+
function normalizeSuiteProfileLink(key, value) {
|
|
1084
|
+
if (value === null)
|
|
1085
|
+
return ok7(null);
|
|
1086
|
+
if (typeof value !== "string") {
|
|
1087
|
+
return err6({ field: key, reason: "invalid" });
|
|
1088
|
+
}
|
|
1089
|
+
const trimmed = value.trim();
|
|
1090
|
+
if (trimmed.length === 0)
|
|
1091
|
+
return ok7(null);
|
|
1092
|
+
const normalized = (() => {
|
|
1093
|
+
switch (key) {
|
|
1094
|
+
case "x":
|
|
1095
|
+
return normalizedX(trimmed);
|
|
1096
|
+
case "linkedin":
|
|
1097
|
+
return normalizedLinkedIn(trimmed);
|
|
1098
|
+
case "bluesky":
|
|
1099
|
+
return normalizedBluesky(trimmed);
|
|
1100
|
+
case "instagram":
|
|
1101
|
+
return normalizedInstagram(trimmed);
|
|
1102
|
+
case "telegram":
|
|
1103
|
+
return normalizedTelegram(trimmed);
|
|
1104
|
+
case "website":
|
|
1105
|
+
return normalizedWebsite(trimmed);
|
|
1106
|
+
}
|
|
1107
|
+
})();
|
|
1108
|
+
return normalized === null ? err6({ field: key, reason: "invalid" }) : ok7(normalized);
|
|
1109
|
+
}
|
|
1110
|
+
function parsedLinks(value, canonicalOnly) {
|
|
1111
|
+
if (!isRecord2(value) || !exactKeys(value, PROFILE_LINK_KEYS)) {
|
|
1112
|
+
return err6({ field: "profile", reason: "invalid" });
|
|
1113
|
+
}
|
|
1114
|
+
const links = {
|
|
1115
|
+
bluesky: null,
|
|
1116
|
+
instagram: null,
|
|
1117
|
+
linkedin: null,
|
|
1118
|
+
telegram: null,
|
|
1119
|
+
website: null,
|
|
1120
|
+
x: null
|
|
1121
|
+
};
|
|
1122
|
+
for (const key of PROFILE_LINK_KEYS) {
|
|
1123
|
+
const parsed = normalizeSuiteProfileLink(key, value[key]);
|
|
1124
|
+
if (!parsed.ok)
|
|
1125
|
+
return parsed;
|
|
1126
|
+
if (canonicalOnly && parsed.value !== value[key]) {
|
|
1127
|
+
return err6({ field: key, reason: "invalid" });
|
|
1128
|
+
}
|
|
1129
|
+
links[key] = parsed.value;
|
|
1130
|
+
}
|
|
1131
|
+
return ok7(links);
|
|
1132
|
+
}
|
|
1133
|
+
function nonnegativeInteger(value) {
|
|
1134
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
1135
|
+
}
|
|
1136
|
+
function parsedEmail(value) {
|
|
1137
|
+
return typeof value === "string" && value.length <= 320 && value.trim() === value && !hasInvalidSingleLineControl(value) && /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(value) ? value : null;
|
|
1138
|
+
}
|
|
1139
|
+
function parseSuiteProfileUpdateRequest(value) {
|
|
1140
|
+
if (!isRecord2(value) || !exactKeys(value, ["bio", "expectedRevision", "links", "name"])) {
|
|
1141
|
+
return err6({ field: "profile", reason: "invalid" });
|
|
1142
|
+
}
|
|
1143
|
+
const name = normalizedName(value["name"]);
|
|
1144
|
+
if (!name.ok)
|
|
1145
|
+
return name;
|
|
1146
|
+
const bio = normalizedBio(value["bio"]);
|
|
1147
|
+
if (!bio.ok)
|
|
1148
|
+
return bio;
|
|
1149
|
+
const links = parsedLinks(value["links"], false);
|
|
1150
|
+
if (!links.ok)
|
|
1151
|
+
return links;
|
|
1152
|
+
const expectedRevision = nonnegativeInteger(value["expectedRevision"]);
|
|
1153
|
+
if (expectedRevision === null) {
|
|
1154
|
+
return err6({ field: "expectedRevision", reason: "invalid" });
|
|
1155
|
+
}
|
|
1156
|
+
return ok7({
|
|
1157
|
+
bio: bio.value,
|
|
1158
|
+
expectedRevision,
|
|
1159
|
+
links: links.value,
|
|
1160
|
+
name: name.value
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
function parseSuiteProfileView(value) {
|
|
1164
|
+
if (!isRecord2(value) || !exactKeys(value, ["bio", "email", "links", "name", "revision"])) {
|
|
1165
|
+
return err6({ field: "profile", reason: "invalid" });
|
|
1166
|
+
}
|
|
1167
|
+
const name = normalizedProfileViewName(value["name"]);
|
|
1168
|
+
const bio = normalizedBio(value["bio"]);
|
|
1169
|
+
const links = parsedLinks(value["links"], true);
|
|
1170
|
+
const email = parsedEmail(value["email"]);
|
|
1171
|
+
const revision = nonnegativeInteger(value["revision"]);
|
|
1172
|
+
if (!name.ok)
|
|
1173
|
+
return name;
|
|
1174
|
+
if (!bio.ok)
|
|
1175
|
+
return bio;
|
|
1176
|
+
if (!links.ok)
|
|
1177
|
+
return links;
|
|
1178
|
+
if (email === null)
|
|
1179
|
+
return err6({ field: "email", reason: "invalid" });
|
|
1180
|
+
if (revision === null) {
|
|
1181
|
+
return err6({ field: "expectedRevision", reason: "invalid" });
|
|
1182
|
+
}
|
|
1183
|
+
if (name.value !== value["name"] || bio.value !== value["bio"]) {
|
|
1184
|
+
return err6({ field: "profile", reason: "invalid" });
|
|
1185
|
+
}
|
|
1186
|
+
return ok7({
|
|
1187
|
+
bio: bio.value,
|
|
1188
|
+
email,
|
|
1189
|
+
links: links.value,
|
|
1190
|
+
name: name.value,
|
|
1191
|
+
revision
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
function isApplicationStatus(value) {
|
|
1195
|
+
return typeof value === "string" && SUITE_COMMUNITY_APPLICATION_STATUSES.includes(value);
|
|
1196
|
+
}
|
|
1197
|
+
function parsedApplication(value) {
|
|
1198
|
+
if (value === null)
|
|
1199
|
+
return ok7(null);
|
|
1200
|
+
if (!isRecord2(value) || !exactKeys(value, [
|
|
1201
|
+
"community",
|
|
1202
|
+
"status",
|
|
1203
|
+
"submittedAtMs",
|
|
1204
|
+
"updatedAtMs"
|
|
1205
|
+
]) || value["community"] !== "oh-computer" || !isApplicationStatus(value["status"])) {
|
|
1206
|
+
return err6({ field: "application", reason: "invalid" });
|
|
1207
|
+
}
|
|
1208
|
+
const submittedAtMs = nonnegativeInteger(value["submittedAtMs"]);
|
|
1209
|
+
const updatedAtMs = nonnegativeInteger(value["updatedAtMs"]);
|
|
1210
|
+
if (submittedAtMs === null || updatedAtMs === null || updatedAtMs < submittedAtMs) {
|
|
1211
|
+
return err6({ field: "application", reason: "invalid" });
|
|
1212
|
+
}
|
|
1213
|
+
return ok7({
|
|
1214
|
+
community: "oh-computer",
|
|
1215
|
+
status: value["status"],
|
|
1216
|
+
submittedAtMs,
|
|
1217
|
+
updatedAtMs
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
function parseSuiteCommunityProfileView(value) {
|
|
1221
|
+
if (!isRecord2(value) || !exactKeys(value, ["application", "profile"])) {
|
|
1222
|
+
return err6({ field: "profile", reason: "invalid" });
|
|
1223
|
+
}
|
|
1224
|
+
const application = parsedApplication(value["application"]);
|
|
1225
|
+
if (!application.ok)
|
|
1226
|
+
return application;
|
|
1227
|
+
const profile = parseSuiteProfileView(value["profile"]);
|
|
1228
|
+
return profile.ok ? ok7({ application: application.value, profile: profile.value }) : profile;
|
|
1229
|
+
}
|
|
1230
|
+
// src/identity/profiles-v2.ts
|
|
1231
|
+
import { err as err7, ok as ok8 } from "@hraness/result";
|
|
1232
|
+
var LINK_KEYS = [
|
|
1233
|
+
"bluesky",
|
|
1234
|
+
"github",
|
|
1235
|
+
"instagram",
|
|
1236
|
+
"linkedin",
|
|
1237
|
+
"telegram",
|
|
1238
|
+
"website",
|
|
1239
|
+
"x"
|
|
1240
|
+
];
|
|
1241
|
+
var PUBLIC_KEYS = [
|
|
1242
|
+
"schemaVersion",
|
|
1243
|
+
"accountId",
|
|
1244
|
+
"username",
|
|
1245
|
+
"name",
|
|
1246
|
+
"bio",
|
|
1247
|
+
"links",
|
|
1248
|
+
"avatarRef",
|
|
1249
|
+
"revision"
|
|
1250
|
+
];
|
|
1251
|
+
var EDITOR_KEYS = [...PUBLIC_KEYS, "publication"];
|
|
1252
|
+
var UPDATE_KEYS = [
|
|
1253
|
+
"schemaVersion",
|
|
1254
|
+
"expectedRevision",
|
|
1255
|
+
"name",
|
|
1256
|
+
"bio",
|
|
1257
|
+
"links",
|
|
1258
|
+
"avatarRef",
|
|
1259
|
+
"publication"
|
|
1260
|
+
];
|
|
1261
|
+
var MAX_INPUT_TEXT_LENGTH = 32768;
|
|
1262
|
+
var AVATAR_PATTERN = /^avref_(?!0{64}$)[0-9a-f]{64}$/u;
|
|
1263
|
+
function issue(field = "profile", reason = "invalid") {
|
|
1264
|
+
return deepFreeze(err7({ field, reason }));
|
|
1265
|
+
}
|
|
1266
|
+
function success(value) {
|
|
1267
|
+
return deepFreeze(ok8(value));
|
|
1268
|
+
}
|
|
1269
|
+
function snapshot(value, keys) {
|
|
1270
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1274
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
1275
|
+
return null;
|
|
1276
|
+
const actual = Reflect.ownKeys(value);
|
|
1277
|
+
if (actual.length !== keys.length)
|
|
1278
|
+
return null;
|
|
1279
|
+
const copy = Object.create(null);
|
|
1280
|
+
for (const key of actual) {
|
|
1281
|
+
if (typeof key !== "string" || !keys.includes(key))
|
|
1282
|
+
return null;
|
|
1283
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1284
|
+
if (descriptor === undefined || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
const field = descriptor.value;
|
|
1288
|
+
copy[key] = field;
|
|
1289
|
+
}
|
|
1290
|
+
return copy;
|
|
1291
|
+
}
|
|
1292
|
+
function nonnegativeInteger2(value) {
|
|
1293
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1294
|
+
}
|
|
1295
|
+
function parseSuiteAvatarRef(value) {
|
|
1296
|
+
return typeof value === "string" && value.length === 70 && AVATAR_PATTERN.test(value) ? success(value) : issue("avatarRef");
|
|
1297
|
+
}
|
|
1298
|
+
function optionalAvatar(value) {
|
|
1299
|
+
return value === null ? success(null) : parseSuiteAvatarRef(value);
|
|
1300
|
+
}
|
|
1301
|
+
function legacyIssue(error) {
|
|
1302
|
+
const field = error.field === "application" || error.field === "email" ? "profile" : error.field;
|
|
1303
|
+
return issue(field, error.reason);
|
|
1304
|
+
}
|
|
1305
|
+
function normalizeSuiteProfileLinkV2(key, value) {
|
|
1306
|
+
if (typeof key !== "string" || !LINK_KEYS.some((candidate) => candidate === key)) {
|
|
1307
|
+
return issue();
|
|
1308
|
+
}
|
|
1309
|
+
const field = key;
|
|
1310
|
+
if (value !== null && typeof value !== "string")
|
|
1311
|
+
return issue(field);
|
|
1312
|
+
if (typeof value === "string" && value.length > SUITE_PROFILE_URL_MAX_LENGTH) {
|
|
1313
|
+
return issue(field, "too_long");
|
|
1314
|
+
}
|
|
1315
|
+
if (field === "github") {
|
|
1316
|
+
if (value === null || value === "")
|
|
1317
|
+
return success(null);
|
|
1318
|
+
if (/[^\x21-\x7e]/u.test(value))
|
|
1319
|
+
return issue("github");
|
|
1320
|
+
const match = /^https:\/\/(?:www\.)?github\.com\/([A-Za-z0-9_-]{1,100})\/?$/iu.exec(value);
|
|
1321
|
+
return match === null || match[0] !== value ? issue("github") : success(`https://github.com/${match[1].toLowerCase()}`);
|
|
1322
|
+
}
|
|
1323
|
+
const parsed = normalizeSuiteProfileLink(field, value);
|
|
1324
|
+
if (!parsed.ok)
|
|
1325
|
+
return legacyIssue(parsed.error);
|
|
1326
|
+
if (parsed.value !== null && parsed.value.length > SUITE_PROFILE_URL_MAX_LENGTH) {
|
|
1327
|
+
return issue(field, "too_long");
|
|
1328
|
+
}
|
|
1329
|
+
return success(parsed.value);
|
|
1330
|
+
}
|
|
1331
|
+
function parsedContent(value, canonical) {
|
|
1332
|
+
for (const field of ["name", "bio"]) {
|
|
1333
|
+
if (typeof value[field] === "string" && value[field].length > MAX_INPUT_TEXT_LENGTH) {
|
|
1334
|
+
return issue(field, "too_long");
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
const inputLinks = snapshot(value["links"], LINK_KEYS);
|
|
1338
|
+
if (inputLinks === null)
|
|
1339
|
+
return issue();
|
|
1340
|
+
const links = {
|
|
1341
|
+
bluesky: null,
|
|
1342
|
+
github: null,
|
|
1343
|
+
instagram: null,
|
|
1344
|
+
linkedin: null,
|
|
1345
|
+
telegram: null,
|
|
1346
|
+
website: null,
|
|
1347
|
+
x: null
|
|
1348
|
+
};
|
|
1349
|
+
for (const key of LINK_KEYS) {
|
|
1350
|
+
const result = normalizeSuiteProfileLinkV2(key, inputLinks[key]);
|
|
1351
|
+
if (!result.ok)
|
|
1352
|
+
return result;
|
|
1353
|
+
if (canonical && result.value !== inputLinks[key])
|
|
1354
|
+
return issue(key);
|
|
1355
|
+
links[key] = result.value;
|
|
1356
|
+
}
|
|
1357
|
+
const parsed = parseSuiteProfileUpdateRequest({
|
|
1358
|
+
expectedRevision: 0,
|
|
1359
|
+
name: value["name"],
|
|
1360
|
+
bio: value["bio"],
|
|
1361
|
+
links: {
|
|
1362
|
+
bluesky: links.bluesky,
|
|
1363
|
+
instagram: links.instagram,
|
|
1364
|
+
linkedin: links.linkedin,
|
|
1365
|
+
telegram: links.telegram,
|
|
1366
|
+
website: links.website,
|
|
1367
|
+
x: links.x
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
if (!parsed.ok)
|
|
1371
|
+
return legacyIssue(parsed.error);
|
|
1372
|
+
if (canonical && (parsed.value.name !== value["name"] || parsed.value.bio !== value["bio"])) {
|
|
1373
|
+
return issue();
|
|
1374
|
+
}
|
|
1375
|
+
return success({ name: parsed.value.name, bio: parsed.value.bio, links });
|
|
1376
|
+
}
|
|
1377
|
+
function parseView(input, editor) {
|
|
1378
|
+
try {
|
|
1379
|
+
const value = snapshot(input, editor ? EDITOR_KEYS : PUBLIC_KEYS);
|
|
1380
|
+
if (value === null)
|
|
1381
|
+
return issue();
|
|
1382
|
+
if (value["schemaVersion"] !== 2)
|
|
1383
|
+
return issue("schemaVersion");
|
|
1384
|
+
const accountId = parseSuiteAccountId(value["accountId"]);
|
|
1385
|
+
if (!accountId.ok)
|
|
1386
|
+
return issue("accountId");
|
|
1387
|
+
const username = editor && value["username"] === null ? success(null) : parseSuiteUsername(value["username"]);
|
|
1388
|
+
if (!username.ok)
|
|
1389
|
+
return issue("username");
|
|
1390
|
+
const revision = value["revision"];
|
|
1391
|
+
if (!nonnegativeInteger2(revision) || !editor && revision === 0)
|
|
1392
|
+
return issue("revision");
|
|
1393
|
+
const avatarRef = optionalAvatar(value["avatarRef"]);
|
|
1394
|
+
if (!avatarRef.ok)
|
|
1395
|
+
return avatarRef;
|
|
1396
|
+
const publication = value["publication"];
|
|
1397
|
+
if (editor && publication !== "private" && publication !== "published") {
|
|
1398
|
+
return issue("publication");
|
|
1399
|
+
}
|
|
1400
|
+
if (editor && publication === "published" && username.value === null)
|
|
1401
|
+
return issue("username");
|
|
1402
|
+
let content;
|
|
1403
|
+
if (revision === 0) {
|
|
1404
|
+
const links = snapshot(value["links"], LINK_KEYS);
|
|
1405
|
+
if (value["name"] !== "" || value["bio"] !== "" || avatarRef.value !== null || publication !== "private" || links === null || LINK_KEYS.some((key) => links[key] !== null))
|
|
1406
|
+
return issue();
|
|
1407
|
+
content = {
|
|
1408
|
+
name: "",
|
|
1409
|
+
bio: "",
|
|
1410
|
+
links: {
|
|
1411
|
+
bluesky: null,
|
|
1412
|
+
github: null,
|
|
1413
|
+
instagram: null,
|
|
1414
|
+
linkedin: null,
|
|
1415
|
+
telegram: null,
|
|
1416
|
+
website: null,
|
|
1417
|
+
x: null
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
} else {
|
|
1421
|
+
const parsed = parsedContent(value, true);
|
|
1422
|
+
if (!parsed.ok)
|
|
1423
|
+
return parsed;
|
|
1424
|
+
content = parsed.value;
|
|
1425
|
+
}
|
|
1426
|
+
const profile = {
|
|
1427
|
+
schemaVersion: 2,
|
|
1428
|
+
accountId: accountId.value,
|
|
1429
|
+
...content,
|
|
1430
|
+
avatarRef: avatarRef.value,
|
|
1431
|
+
revision
|
|
1432
|
+
};
|
|
1433
|
+
if (editor && (publication === "private" || publication === "published")) {
|
|
1434
|
+
return success({ ...profile, username: username.value, publication });
|
|
1435
|
+
}
|
|
1436
|
+
if (username.value === null)
|
|
1437
|
+
return issue("username");
|
|
1438
|
+
return success({ ...profile, username: username.value });
|
|
1439
|
+
} catch {
|
|
1440
|
+
return issue();
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function parseSuitePublicProfileV2(value) {
|
|
1444
|
+
const parsed = parseView(value, false);
|
|
1445
|
+
if (!parsed.ok)
|
|
1446
|
+
return parsed;
|
|
1447
|
+
if (parsed.value.username === null || isEditor(parsed.value))
|
|
1448
|
+
return issue();
|
|
1449
|
+
return success({ ...parsed.value, username: parsed.value.username });
|
|
1450
|
+
}
|
|
1451
|
+
function parseSuiteProfileEditorV2(value) {
|
|
1452
|
+
const parsed = parseView(value, true);
|
|
1453
|
+
if (!parsed.ok)
|
|
1454
|
+
return parsed;
|
|
1455
|
+
return isEditor(parsed.value) ? success(parsed.value) : issue();
|
|
1456
|
+
}
|
|
1457
|
+
function isEditor(value) {
|
|
1458
|
+
return Object.hasOwn(value, "publication");
|
|
1459
|
+
}
|
|
1460
|
+
function parseSuiteProfileUpdateV2(input) {
|
|
1461
|
+
try {
|
|
1462
|
+
const value = snapshot(input, UPDATE_KEYS);
|
|
1463
|
+
if (value === null)
|
|
1464
|
+
return issue();
|
|
1465
|
+
if (value["schemaVersion"] !== 2)
|
|
1466
|
+
return issue("schemaVersion");
|
|
1467
|
+
const expectedRevision = value["expectedRevision"];
|
|
1468
|
+
if (!nonnegativeInteger2(expectedRevision))
|
|
1469
|
+
return issue("expectedRevision");
|
|
1470
|
+
const publication = value["publication"];
|
|
1471
|
+
if (publication !== "publish" && publication !== "private")
|
|
1472
|
+
return issue("publication");
|
|
1473
|
+
const avatarRef = optionalAvatar(value["avatarRef"]);
|
|
1474
|
+
if (!avatarRef.ok)
|
|
1475
|
+
return avatarRef;
|
|
1476
|
+
const content = parsedContent(value, false);
|
|
1477
|
+
if (!content.ok)
|
|
1478
|
+
return content;
|
|
1479
|
+
return success({
|
|
1480
|
+
schemaVersion: 2,
|
|
1481
|
+
expectedRevision,
|
|
1482
|
+
...content.value,
|
|
1483
|
+
avatarRef: avatarRef.value,
|
|
1484
|
+
publication
|
|
1485
|
+
});
|
|
1486
|
+
} catch {
|
|
1487
|
+
return issue();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function avatarUrl(ref, path) {
|
|
1491
|
+
const parsed = parseSuiteAvatarRef(ref);
|
|
1492
|
+
return parsed.ok ? success(`${getSuiteAccountsDeployment("production").accountsOrigin}${path}/${parsed.value}.webp`) : parsed;
|
|
1493
|
+
}
|
|
1494
|
+
function suiteProfileAvatarPublicUrl(ref) {
|
|
1495
|
+
return avatarUrl(ref, "/suite/profile/avatar/v1");
|
|
1496
|
+
}
|
|
1497
|
+
function suiteProfileAvatarEditorUrl(ref) {
|
|
1498
|
+
return avatarUrl(ref, "/api/profile/avatar/v1");
|
|
1499
|
+
}
|
|
1500
|
+
// src/identity/views.ts
|
|
1501
|
+
import { err as err8, isRecord as isRecord3, ok as ok9 } from "@hraness/result";
|
|
1502
|
+
var SUITE_SUBSCRIPTION_STATUSES = deepFreeze([
|
|
1503
|
+
"incomplete",
|
|
1504
|
+
"trialing",
|
|
1505
|
+
"active",
|
|
1506
|
+
"past_due",
|
|
1507
|
+
"paused",
|
|
1508
|
+
"canceled",
|
|
1509
|
+
"unpaid"
|
|
1510
|
+
]);
|
|
1511
|
+
var SUITE_INVOICE_STATUSES = deepFreeze([
|
|
1512
|
+
"draft",
|
|
1513
|
+
"open",
|
|
1514
|
+
"paid",
|
|
1515
|
+
"void",
|
|
1516
|
+
"uncollectible"
|
|
1517
|
+
]);
|
|
1518
|
+
function containsAsciiControl2(value) {
|
|
1519
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
1520
|
+
const code = value.charCodeAt(index);
|
|
1521
|
+
if (code <= 31 || code === 127)
|
|
1522
|
+
return true;
|
|
1523
|
+
}
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
function parseEmail(value) {
|
|
1527
|
+
return typeof value === "string" && value.length <= 320 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(value) ? value : null;
|
|
1528
|
+
}
|
|
1529
|
+
function parseOptionalName(value) {
|
|
1530
|
+
if (value === null)
|
|
1531
|
+
return null;
|
|
1532
|
+
return typeof value === "string" && value.length >= 1 && value.length <= 160 && value.trim() === value && !containsAsciiControl2(value) ? value : undefined;
|
|
1533
|
+
}
|
|
1534
|
+
function parseNonnegativeInteger(value) {
|
|
1535
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
1536
|
+
}
|
|
1537
|
+
function parseOptionalTimestamp(value) {
|
|
1538
|
+
if (value === null)
|
|
1539
|
+
return null;
|
|
1540
|
+
return parseNonnegativeInteger(value) ?? undefined;
|
|
1541
|
+
}
|
|
1542
|
+
function isOneOf(values, value) {
|
|
1543
|
+
return typeof value === "string" && values.includes(value);
|
|
1544
|
+
}
|
|
1545
|
+
function parseSuiteSubscriptionView(value) {
|
|
1546
|
+
if (!isRecord3(value))
|
|
1547
|
+
return err8("invalid-subscription-view");
|
|
1548
|
+
const catalogRevision = parseSuiteCatalogRevision(value["catalogRevision"]);
|
|
1549
|
+
const plan = catalogRevision.ok ? parseSuitePlanId(value["plan"], catalogRevision.value) : catalogRevision;
|
|
1550
|
+
const currentPeriodEndMs = parseOptionalTimestamp(value["currentPeriodEndMs"]);
|
|
1551
|
+
if (!plan.ok || !catalogRevision.ok || !isOneOf(SUITE_SUBSCRIPTION_STATUSES, value["status"]) || typeof value["cancelAtPeriodEnd"] !== "boolean" || currentPeriodEndMs === undefined) {
|
|
1552
|
+
return err8("invalid-subscription-view");
|
|
1553
|
+
}
|
|
1554
|
+
return ok9({
|
|
1555
|
+
cancelAtPeriodEnd: value["cancelAtPeriodEnd"],
|
|
1556
|
+
catalogRevision: catalogRevision.value,
|
|
1557
|
+
currentPeriodEndMs,
|
|
1558
|
+
plan: plan.value,
|
|
1559
|
+
status: value["status"]
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
function parseSuiteInvoiceView(value) {
|
|
1563
|
+
if (!isRecord3(value) || !isOneOf(SUITE_INVOICE_STATUSES, value["status"]) || value["currency"] !== "usd") {
|
|
1564
|
+
return err8("invalid-invoice-view");
|
|
1565
|
+
}
|
|
1566
|
+
const amountDueCents = parseNonnegativeInteger(value["amountDueCents"]);
|
|
1567
|
+
const amountPaidCents = parseNonnegativeInteger(value["amountPaidCents"]);
|
|
1568
|
+
const createdAtMs = parseNonnegativeInteger(value["createdAtMs"]);
|
|
1569
|
+
const number = value["number"] === null ? null : typeof value["number"] === "string" && value["number"].length >= 1 && value["number"].length <= 80 && value["number"].trim() === value["number"] ? value["number"] : undefined;
|
|
1570
|
+
const invoiceRef = value["invoiceRef"] === null ? ok9(null) : parseSuiteInvoiceRef(value["invoiceRef"]);
|
|
1571
|
+
if (amountDueCents === null || amountPaidCents === null || createdAtMs === null || number === undefined || !invoiceRef.ok) {
|
|
1572
|
+
return err8("invalid-invoice-view");
|
|
1573
|
+
}
|
|
1574
|
+
return ok9({
|
|
1575
|
+
amountDueCents,
|
|
1576
|
+
amountPaidCents,
|
|
1577
|
+
createdAtMs,
|
|
1578
|
+
currency: "usd",
|
|
1579
|
+
invoiceRef: invoiceRef.value,
|
|
1580
|
+
number,
|
|
1581
|
+
status: value["status"]
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
function parseFeatures(value) {
|
|
1585
|
+
if (!Array.isArray(value) || value.length > SUITE_FEATURE_IDS.length) {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
const parsed = [];
|
|
1589
|
+
for (const entry of value) {
|
|
1590
|
+
const feature = parseSuiteFeatureId(entry);
|
|
1591
|
+
if (!feature.ok || parsed.includes(feature.value))
|
|
1592
|
+
return null;
|
|
1593
|
+
parsed.push(feature.value);
|
|
1594
|
+
}
|
|
1595
|
+
return parsed;
|
|
1596
|
+
}
|
|
1597
|
+
function parseSuiteAccountView(value) {
|
|
1598
|
+
if (!isRecord3(value))
|
|
1599
|
+
return err8("invalid-account-view");
|
|
1600
|
+
const accountId = parseSuiteAccountId(value["accountId"]);
|
|
1601
|
+
const email = parseEmail(value["email"]);
|
|
1602
|
+
const name = parseOptionalName(value["name"]);
|
|
1603
|
+
const username = value["username"] === null || value["username"] === undefined ? ok9(null) : parseSuiteUsername(value["username"]);
|
|
1604
|
+
const subscription = value["subscription"] === null ? ok9(null) : parseSuiteSubscriptionView(value["subscription"]);
|
|
1605
|
+
const plan = value["plan"] === null ? ok9(null) : parseAnySuitePlanId(value["plan"]);
|
|
1606
|
+
const features = parseFeatures(value["features"]);
|
|
1607
|
+
if (!accountId.ok || value["catalogRevision"] !== SUITE_CATALOG_REVISION || email === null || name === undefined || !username.ok || !subscription.ok || !plan.ok || features === null || !Array.isArray(value["invoices"]) || value["invoices"].length > 100 || plan.value !== (subscription.value?.plan ?? null)) {
|
|
1608
|
+
return err8("invalid-account-view");
|
|
1609
|
+
}
|
|
1610
|
+
const statusCanGrant = subscription.value !== null && (subscription.value.status === "active" || subscription.value.status === "trialing");
|
|
1611
|
+
const planFeatures = subscription.value === null ? [] : featuresForSuitePlan(subscription.value.plan, subscription.value.catalogRevision);
|
|
1612
|
+
const exactPositiveGrant = statusCanGrant && features.length === planFeatures.length && features.every((feature, index) => feature === planFeatures[index]);
|
|
1613
|
+
if (features.length > 0 && !exactPositiveGrant) {
|
|
1614
|
+
return err8("invalid-account-view");
|
|
1615
|
+
}
|
|
1616
|
+
const invoices = [];
|
|
1617
|
+
for (const entry of value["invoices"]) {
|
|
1618
|
+
const invoice = parseSuiteInvoiceView(entry);
|
|
1619
|
+
if (!invoice.ok)
|
|
1620
|
+
return err8("invalid-account-view");
|
|
1621
|
+
invoices.push(invoice.value);
|
|
1622
|
+
}
|
|
1623
|
+
return ok9({
|
|
1624
|
+
accountId: accountId.value,
|
|
1625
|
+
catalogRevision: SUITE_CATALOG_REVISION,
|
|
1626
|
+
email,
|
|
1627
|
+
features,
|
|
1628
|
+
invoices,
|
|
1629
|
+
name,
|
|
1630
|
+
plan: plan.value,
|
|
1631
|
+
subscription: subscription.value,
|
|
1632
|
+
username: username.value
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
export {
|
|
1636
|
+
validateSuiteLinkReceipt,
|
|
1637
|
+
validateSuiteEntitlementsClaim,
|
|
1638
|
+
validateSuiteEntitlementReceipt,
|
|
1639
|
+
validateProductLinkProof,
|
|
1640
|
+
suiteProfileAvatarPublicUrl,
|
|
1641
|
+
suiteProfileAvatarEditorUrl,
|
|
1642
|
+
suitePlanIncludesFeature,
|
|
1643
|
+
suiteLinkReceiptMessage,
|
|
1644
|
+
suiteEntitlementReceiptMessage,
|
|
1645
|
+
productLinkProofMessage,
|
|
1646
|
+
parseSuiteUsername,
|
|
1647
|
+
parseSuiteSubscriptionView,
|
|
1648
|
+
parseSuitePublicProfileV2,
|
|
1649
|
+
parseSuiteProfileView,
|
|
1650
|
+
parseSuiteProfileUpdateV2,
|
|
1651
|
+
parseSuiteProfileUpdateRequest,
|
|
1652
|
+
parseSuiteProfileEditorV2,
|
|
1653
|
+
parseSuiteProduct,
|
|
1654
|
+
parseSuitePlanId,
|
|
1655
|
+
parseSuiteLinkProduct,
|
|
1656
|
+
parseSuiteJwtClaims,
|
|
1657
|
+
parseSuiteInvoiceView,
|
|
1658
|
+
parseSuiteInvoiceRef,
|
|
1659
|
+
parseSuiteFeatureId,
|
|
1660
|
+
parseSuiteEnvironment,
|
|
1661
|
+
parseSuiteConsumerId,
|
|
1662
|
+
parseSuiteCommunityProfileView,
|
|
1663
|
+
parseSuiteCatalogRevision,
|
|
1664
|
+
parseSuiteAvatarRef,
|
|
1665
|
+
parseSuiteAccountView,
|
|
1666
|
+
parseSuiteAccountId,
|
|
1667
|
+
parseLegacyPrincipalLink,
|
|
1668
|
+
parseIssuerSubject,
|
|
1669
|
+
parseIdentitySubject,
|
|
1670
|
+
parseIdentityIssuer,
|
|
1671
|
+
parseCurrentSuiteFeatureId,
|
|
1672
|
+
parseAnySuitePlanId,
|
|
1673
|
+
normalizeSuiteUsername,
|
|
1674
|
+
normalizeSuiteProfileLinkV2,
|
|
1675
|
+
normalizeSuiteProfileLink,
|
|
1676
|
+
isSuiteIssuableEnvironment,
|
|
1677
|
+
grantedSuiteFeatureSets,
|
|
1678
|
+
generateSuiteInvoiceRef,
|
|
1679
|
+
generateSuiteAccountId,
|
|
1680
|
+
featuresForSuitePlan,
|
|
1681
|
+
SUITE_USERNAME_MIN_LENGTH,
|
|
1682
|
+
SUITE_USERNAME_MAX_LENGTH,
|
|
1683
|
+
SUITE_SUBSCRIPTION_STATUSES,
|
|
1684
|
+
SUITE_PROFILE_URL_MAX_LENGTH,
|
|
1685
|
+
SUITE_PROFILE_NAME_MAX_LENGTH,
|
|
1686
|
+
SUITE_PROFILE_BIO_MAX_LENGTH,
|
|
1687
|
+
SUITE_PRODUCTS,
|
|
1688
|
+
SUITE_PREVIOUS_FEATURE_IDS,
|
|
1689
|
+
SUITE_PLAN_IDS,
|
|
1690
|
+
SUITE_MEMBERSHIP_PRODUCTS,
|
|
1691
|
+
SUITE_LINK_PRODUCTS,
|
|
1692
|
+
SUITE_LEGACY_FEATURE_IDS,
|
|
1693
|
+
SUITE_ISSUABLE_ENVIRONMENTS,
|
|
1694
|
+
SUITE_INVOICE_STATUSES,
|
|
1695
|
+
SUITE_HISTORICAL_PLAN_IDS,
|
|
1696
|
+
SUITE_FEATURE_IDS,
|
|
1697
|
+
SUITE_ENVIRONMENTS,
|
|
1698
|
+
SUITE_ENTITLEMENT_RECEIPT_VERSION,
|
|
1699
|
+
SUITE_ENTITLEMENT_RECEIPT_MAX_TTL_MS,
|
|
1700
|
+
SUITE_ENTITLEMENTS_CLAIM_VERSION,
|
|
1701
|
+
SUITE_CURRENT_FEATURE_IDS,
|
|
1702
|
+
SUITE_CONSUMER_IDS,
|
|
1703
|
+
SUITE_COMMUNITY_APPLICATION_STATUSES,
|
|
1704
|
+
SUITE_CATALOG_REVISIONS,
|
|
1705
|
+
SUITE_CATALOG_REVISION,
|
|
1706
|
+
PREVIOUS_SUITE_CATALOG_REVISION,
|
|
1707
|
+
LEGACY_SUITE_PRODUCT_IDS,
|
|
1708
|
+
LEGACY_SUITE_LINK_PRODUCTS,
|
|
1709
|
+
LEGACY_SUITE_CONSUMER_IDS,
|
|
1710
|
+
LEGACY_SUITE_CATALOG_REVISION,
|
|
1711
|
+
IDENTITY_LINK_RECEIPT_VERSION,
|
|
1712
|
+
IDENTITY_LINK_PROOF_VERSION,
|
|
1713
|
+
IDENTITY_LINK_MAX_TTL_MS,
|
|
1714
|
+
IDENTITY_LINK_CLOCK_SKEW_MS,
|
|
1715
|
+
ARCHIVED_SUITE_CATALOG_REVISION
|
|
1716
|
+
};
|