@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,1119 @@
|
|
|
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/urls.ts
|
|
293
|
+
var SUITE_ACCOUNTS_OAUTH_RESOURCE = "https://hraness.com/suite";
|
|
294
|
+
var CENTRAL_PATHS = deepFreeze({
|
|
295
|
+
account: "/account",
|
|
296
|
+
home: "/",
|
|
297
|
+
login: "/login"
|
|
298
|
+
});
|
|
299
|
+
function suiteAccountsCentralUrl(environment, destination) {
|
|
300
|
+
return new URL(CENTRAL_PATHS[destination], getSuiteAccountsDeployment(environment).accountsOrigin).href;
|
|
301
|
+
}
|
|
302
|
+
function suiteAccountsBillingReturnUrl(consumer, environment) {
|
|
303
|
+
const consumerEnvironment = getSuiteAccountsConsumerEnvironment(consumer, environment);
|
|
304
|
+
if (consumerEnvironment === null)
|
|
305
|
+
return null;
|
|
306
|
+
return consumerEnvironment.billingReturn.kind === "supported" ? new URL(consumerEnvironment.billingReturn.path, consumerEnvironment.siteUrl).href : null;
|
|
307
|
+
}
|
|
308
|
+
function suiteAccountsOidcClientRegistration(consumer, environment) {
|
|
309
|
+
if (!isSuiteAccountsConsumerId(consumer) || !isSuiteAccountsOAuthConsumerId(consumer))
|
|
310
|
+
return null;
|
|
311
|
+
const consumerEnvironment = getSuiteAccountsConsumerEnvironment(consumer, environment);
|
|
312
|
+
if (consumerEnvironment === null)
|
|
313
|
+
return null;
|
|
314
|
+
return deepFreeze({
|
|
315
|
+
callbackUrl: new URL("/api/suite-auth/callback", consumerEnvironment.siteUrl).href,
|
|
316
|
+
clientId: `hraness:${consumer}:${environment}:v1`
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function suiteAccountsCurrentOidcClientRegistration(consumer, environment) {
|
|
320
|
+
if (!isSuiteAccountsCurrentConsumerId(consumer) || !isSuiteAccountsCurrentOAuthConsumerId(consumer))
|
|
321
|
+
return null;
|
|
322
|
+
const consumerEnvironment = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
|
|
323
|
+
if (consumerEnvironment === null)
|
|
324
|
+
return null;
|
|
325
|
+
return deepFreeze({
|
|
326
|
+
callbackUrl: new URL("/api/suite-auth/callback", consumerEnvironment.siteUrl).href,
|
|
327
|
+
clientId: `hraness:${consumer}:${environment}:v1`
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function suiteAccountsOidcClientRequiresEmailOtp(clientId) {
|
|
331
|
+
if (typeof clientId !== "string")
|
|
332
|
+
return false;
|
|
333
|
+
return SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.some((consumer) => suiteAccountsOidcClientRegistration(consumer, "production")?.clientId === clientId);
|
|
334
|
+
}
|
|
335
|
+
function suiteAccountsCurrentOidcClientRequiresEmailOtp(clientId) {
|
|
336
|
+
if (typeof clientId !== "string")
|
|
337
|
+
return false;
|
|
338
|
+
return SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.some((consumer) => suiteAccountsCurrentOidcClientRegistration(consumer, "production")?.clientId === clientId);
|
|
339
|
+
}
|
|
340
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
341
|
+
function suiteAccountsCurrentDeviceClientRegistration(deviceClient, environment) {
|
|
342
|
+
if (!isSuiteAccountsCurrentDeviceClientId(deviceClient))
|
|
343
|
+
return null;
|
|
344
|
+
const registration = SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS[deviceClient];
|
|
345
|
+
if (!registration.environments.includes(environment) || suiteAccountsCurrentOidcClientRegistration(registration.consumer, environment) === null) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return deepFreeze({
|
|
349
|
+
clientId: `hraness:${deviceClient}:${environment}:v1`,
|
|
350
|
+
consumer: registration.consumer,
|
|
351
|
+
grantTypes: [DEVICE_CODE_GRANT_TYPE],
|
|
352
|
+
redirectUris: [],
|
|
353
|
+
scopes: ["openid", "email", "profile"],
|
|
354
|
+
tokenEndpointAuthMethod: "none"
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function suiteAccountsCurrentDeviceCodeClientAllowed(clientId) {
|
|
358
|
+
if (typeof clientId !== "string")
|
|
359
|
+
return false;
|
|
360
|
+
return SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS.some((deviceClient) => suiteAccountsCurrentDeviceClientRegistration(deviceClient, "production")?.clientId === clientId) || SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS.some((consumer) => isSuiteAccountsCurrentBrowserDeviceCodeConsumerId(consumer) && suiteAccountsCurrentOidcClientRegistration(consumer, "production")?.clientId === clientId);
|
|
361
|
+
}
|
|
362
|
+
function suiteAccountsOidcProviderConfiguration(environment) {
|
|
363
|
+
const issuer = getSuiteAccountsDeployment(environment).accountsOrigin;
|
|
364
|
+
const authBase = new URL("/api/auth/", issuer);
|
|
365
|
+
return deepFreeze({
|
|
366
|
+
authorizationEndpoint: new URL("oauth2/authorize", authBase).href,
|
|
367
|
+
deviceAuthorizationEndpoint: new URL("oauth2/device_authorization", authBase).href,
|
|
368
|
+
deviceTokenEndpoint: new URL("oauth2/device/token", authBase).href,
|
|
369
|
+
discoveryEndpoint: new URL("/.well-known/openid-configuration", issuer).href,
|
|
370
|
+
entitlementReceiptEndpoint: new URL("/suite/entitlements/receipt", issuer).href,
|
|
371
|
+
identityLinkReceiptEndpoint: new URL("/suite/identity-links/receipt", issuer).href,
|
|
372
|
+
issuer,
|
|
373
|
+
jwksEndpoint: new URL("jwks", authBase).href,
|
|
374
|
+
resource: SUITE_ACCOUNTS_OAUTH_RESOURCE,
|
|
375
|
+
revocationEndpoint: new URL("oauth2/revoke", authBase).href,
|
|
376
|
+
tokenEndpoint: new URL("oauth2/token", authBase).href,
|
|
377
|
+
userInfoAudience: new URL("oauth2/userinfo", authBase).href
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/identity/catalog.ts
|
|
382
|
+
import { err as err2, ok as ok2 } from "@hraness/result";
|
|
383
|
+
var SUITE_CATALOG_REVISION = "hraness-suite-v4";
|
|
384
|
+
var PREVIOUS_SUITE_CATALOG_REVISION = "cclrte-suite-v3";
|
|
385
|
+
var LEGACY_SUITE_CATALOG_REVISION = "cclrte-suite-v2";
|
|
386
|
+
var ARCHIVED_SUITE_CATALOG_REVISION = "cclrte-suite-v1";
|
|
387
|
+
var SUITE_CATALOG_REVISIONS = deepFreeze([
|
|
388
|
+
ARCHIVED_SUITE_CATALOG_REVISION,
|
|
389
|
+
LEGACY_SUITE_CATALOG_REVISION,
|
|
390
|
+
PREVIOUS_SUITE_CATALOG_REVISION,
|
|
391
|
+
SUITE_CATALOG_REVISION
|
|
392
|
+
]);
|
|
393
|
+
var SUITE_PLAN_IDS = deepFreeze(["community", "pro"]);
|
|
394
|
+
var SUITE_HISTORICAL_PLAN_IDS = deepFreeze([
|
|
395
|
+
"individual",
|
|
396
|
+
"business"
|
|
397
|
+
]);
|
|
398
|
+
var SUITE_CURRENT_FEATURE_IDS = deepFreeze([
|
|
399
|
+
"suite.paid",
|
|
400
|
+
"suite.community",
|
|
401
|
+
"suite.pro"
|
|
402
|
+
]);
|
|
403
|
+
var SUITE_PREVIOUS_FEATURE_IDS = deepFreeze([
|
|
404
|
+
"suite.paid",
|
|
405
|
+
"suite.believer"
|
|
406
|
+
]);
|
|
407
|
+
var SUITE_LEGACY_FEATURE_IDS = deepFreeze([
|
|
408
|
+
"suite.paid",
|
|
409
|
+
"suite.business"
|
|
410
|
+
]);
|
|
411
|
+
var SUITE_FEATURE_IDS = deepFreeze([
|
|
412
|
+
"suite.paid",
|
|
413
|
+
"suite.community",
|
|
414
|
+
"suite.pro",
|
|
415
|
+
"suite.believer",
|
|
416
|
+
"suite.business"
|
|
417
|
+
]);
|
|
418
|
+
var CURRENT_PLAN_FEATURES = deepFreeze({
|
|
419
|
+
community: ["suite.paid", "suite.community"],
|
|
420
|
+
pro: ["suite.paid", "suite.community", "suite.pro"]
|
|
421
|
+
});
|
|
422
|
+
var PREVIOUS_PLAN_FEATURES = deepFreeze({
|
|
423
|
+
business: ["suite.paid", "suite.believer"],
|
|
424
|
+
individual: ["suite.paid"]
|
|
425
|
+
});
|
|
426
|
+
var LEGACY_PLAN_FEATURES = deepFreeze({
|
|
427
|
+
business: ["suite.paid", "suite.business"],
|
|
428
|
+
individual: ["suite.paid"]
|
|
429
|
+
});
|
|
430
|
+
function parseSuitePlanId(value, revision = SUITE_CATALOG_REVISION) {
|
|
431
|
+
const ids = revision === SUITE_CATALOG_REVISION ? SUITE_PLAN_IDS : SUITE_HISTORICAL_PLAN_IDS;
|
|
432
|
+
return typeof value === "string" && ids.includes(value) ? ok2(value) : err2("invalid-plan");
|
|
433
|
+
}
|
|
434
|
+
function parseAnySuitePlanId(value) {
|
|
435
|
+
return typeof value === "string" && [...SUITE_PLAN_IDS, ...SUITE_HISTORICAL_PLAN_IDS].includes(value) ? ok2(value) : err2("invalid-plan");
|
|
436
|
+
}
|
|
437
|
+
function parseCurrentSuiteFeatureId(value) {
|
|
438
|
+
return value === "suite.paid" || value === "suite.community" || value === "suite.pro" ? ok2(value) : err2("invalid-feature");
|
|
439
|
+
}
|
|
440
|
+
function parseSuiteFeatureId(value) {
|
|
441
|
+
return typeof value === "string" && SUITE_FEATURE_IDS.includes(value) ? ok2(value) : err2("invalid-feature");
|
|
442
|
+
}
|
|
443
|
+
function parseSuiteCatalogRevision(value) {
|
|
444
|
+
return typeof value === "string" && SUITE_CATALOG_REVISIONS.includes(value) ? ok2(value) : err2("invalid-catalog-revision");
|
|
445
|
+
}
|
|
446
|
+
function featuresForSuitePlan(plan, revision = SUITE_CATALOG_REVISION) {
|
|
447
|
+
const table = revision === SUITE_CATALOG_REVISION ? CURRENT_PLAN_FEATURES : revision === ARCHIVED_SUITE_CATALOG_REVISION ? LEGACY_PLAN_FEATURES : PREVIOUS_PLAN_FEATURES;
|
|
448
|
+
const features = table[plan];
|
|
449
|
+
return features === undefined ? [] : [...features];
|
|
450
|
+
}
|
|
451
|
+
function grantedSuiteFeatureSets(revision) {
|
|
452
|
+
if (revision === SUITE_CATALOG_REVISION) {
|
|
453
|
+
return deepFreeze([
|
|
454
|
+
[],
|
|
455
|
+
CURRENT_PLAN_FEATURES.community,
|
|
456
|
+
CURRENT_PLAN_FEATURES.pro
|
|
457
|
+
]);
|
|
458
|
+
}
|
|
459
|
+
const table = revision === ARCHIVED_SUITE_CATALOG_REVISION ? LEGACY_PLAN_FEATURES : PREVIOUS_PLAN_FEATURES;
|
|
460
|
+
return deepFreeze([
|
|
461
|
+
[],
|
|
462
|
+
table.individual,
|
|
463
|
+
table.business
|
|
464
|
+
]);
|
|
465
|
+
}
|
|
466
|
+
function suitePlanIncludesFeature(plan, feature) {
|
|
467
|
+
return plan !== null && featuresForSuitePlan(plan).includes(feature);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/identity/principals.ts
|
|
471
|
+
import { err as err5, isRecord, ok as ok5 } from "@hraness/result";
|
|
472
|
+
|
|
473
|
+
// src/identity/identifiers.ts
|
|
474
|
+
import { err as err3, ok as ok3 } from "@hraness/result";
|
|
475
|
+
var suiteAccountIdPattern = /^acct_[0-9a-f]{32}$/u;
|
|
476
|
+
var suiteInvoiceRefPattern = /^invref_[0-9a-f]{32}$/u;
|
|
477
|
+
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;
|
|
478
|
+
function parseSuiteAccountId(value) {
|
|
479
|
+
return typeof value === "string" && suiteAccountIdPattern.test(value) ? ok3(value) : err3("invalid-suite-account-id");
|
|
480
|
+
}
|
|
481
|
+
function parseSuiteInvoiceRef(value) {
|
|
482
|
+
return typeof value === "string" && suiteInvoiceRefPattern.test(value) ? ok3(value) : err3("invalid-suite-invoice-ref");
|
|
483
|
+
}
|
|
484
|
+
function generateSuiteAccountId(randomUuid = () => crypto.randomUUID()) {
|
|
485
|
+
const uuid = randomUuid();
|
|
486
|
+
if (!uuidPattern.test(uuid)) {
|
|
487
|
+
throw new TypeError("The suite account ID source did not return a UUID.");
|
|
488
|
+
}
|
|
489
|
+
const candidate = `acct_${uuid.replaceAll("-", "").toLowerCase()}`;
|
|
490
|
+
const parsed = parseSuiteAccountId(candidate);
|
|
491
|
+
if (!parsed.ok) {
|
|
492
|
+
throw new TypeError("The suite account ID source produced an invalid value.");
|
|
493
|
+
}
|
|
494
|
+
return parsed.value;
|
|
495
|
+
}
|
|
496
|
+
function generateSuiteInvoiceRef(randomUuid = () => crypto.randomUUID()) {
|
|
497
|
+
const uuid = randomUuid();
|
|
498
|
+
if (!uuidPattern.test(uuid)) {
|
|
499
|
+
throw new TypeError("The suite invoice reference source did not return a UUID.");
|
|
500
|
+
}
|
|
501
|
+
const candidate = `invref_${uuid.replaceAll("-", "").toLowerCase()}`;
|
|
502
|
+
const parsed = parseSuiteInvoiceRef(candidate);
|
|
503
|
+
if (!parsed.ok) {
|
|
504
|
+
throw new TypeError("The suite invoice reference source produced an invalid value.");
|
|
505
|
+
}
|
|
506
|
+
return parsed.value;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/identity/usernames.ts
|
|
510
|
+
import { err as err4, ok as ok4 } from "@hraness/result";
|
|
511
|
+
var SUITE_USERNAME_MIN_LENGTH = 3;
|
|
512
|
+
var SUITE_USERNAME_MAX_LENGTH = 24;
|
|
513
|
+
var suiteUsernamePattern = /^[a-z0-9](?:[a-z0-9]|[-_](?=[a-z0-9]))*[a-z0-9]$/u;
|
|
514
|
+
var reservedSuiteUsernames = new Set([
|
|
515
|
+
"account",
|
|
516
|
+
"accounts",
|
|
517
|
+
"admin",
|
|
518
|
+
"api",
|
|
519
|
+
"auth",
|
|
520
|
+
"billing",
|
|
521
|
+
"design",
|
|
522
|
+
"docs",
|
|
523
|
+
"help",
|
|
524
|
+
"hraness",
|
|
525
|
+
"login",
|
|
526
|
+
"logout",
|
|
527
|
+
"new",
|
|
528
|
+
"newsletter",
|
|
529
|
+
"party",
|
|
530
|
+
"place",
|
|
531
|
+
"preview",
|
|
532
|
+
"pub",
|
|
533
|
+
"root",
|
|
534
|
+
"settings",
|
|
535
|
+
"social-image",
|
|
536
|
+
"source",
|
|
537
|
+
"sources",
|
|
538
|
+
"support",
|
|
539
|
+
"system",
|
|
540
|
+
"user",
|
|
541
|
+
"users",
|
|
542
|
+
"www"
|
|
543
|
+
]);
|
|
544
|
+
function validateCanonicalSuiteUsername(value) {
|
|
545
|
+
if (value.length < SUITE_USERNAME_MIN_LENGTH) {
|
|
546
|
+
return err4("suite-username-too-short");
|
|
547
|
+
}
|
|
548
|
+
if (value.length > SUITE_USERNAME_MAX_LENGTH) {
|
|
549
|
+
return err4("suite-username-too-long");
|
|
550
|
+
}
|
|
551
|
+
if (!suiteUsernamePattern.test(value)) {
|
|
552
|
+
return err4("invalid-suite-username");
|
|
553
|
+
}
|
|
554
|
+
return ok4(value);
|
|
555
|
+
}
|
|
556
|
+
function normalizeSuiteUsername(value) {
|
|
557
|
+
if (typeof value !== "string")
|
|
558
|
+
return err4("invalid-suite-username");
|
|
559
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
560
|
+
const code = value.charCodeAt(index);
|
|
561
|
+
if (code > 127 || code < 32 || code === 127) {
|
|
562
|
+
return err4("invalid-suite-username");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const trimmed = value.trim();
|
|
566
|
+
const parsed = validateCanonicalSuiteUsername(trimmed.toLowerCase());
|
|
567
|
+
if (!parsed.ok)
|
|
568
|
+
return parsed;
|
|
569
|
+
return reservedSuiteUsernames.has(parsed.value) ? err4("suite-username-reserved") : parsed;
|
|
570
|
+
}
|
|
571
|
+
function parseSuiteUsername(value) {
|
|
572
|
+
if (typeof value !== "string")
|
|
573
|
+
return err4("invalid-suite-username");
|
|
574
|
+
const parsed = validateCanonicalSuiteUsername(value);
|
|
575
|
+
return parsed.ok && parsed.value === value ? parsed : err4(parsed.ok ? "invalid-suite-username" : parsed.error);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/identity/principals.ts
|
|
579
|
+
var SUITE_PRODUCTS = deepFreeze([
|
|
580
|
+
"soundfish",
|
|
581
|
+
"hra",
|
|
582
|
+
"peopleblade",
|
|
583
|
+
"ghostget",
|
|
584
|
+
"crclte",
|
|
585
|
+
"pub"
|
|
586
|
+
]);
|
|
587
|
+
var LEGACY_SUITE_PRODUCT_IDS = deepFreeze([
|
|
588
|
+
"oprte",
|
|
589
|
+
"kitchen"
|
|
590
|
+
]);
|
|
591
|
+
var SUITE_ENVIRONMENTS = deepFreeze([
|
|
592
|
+
"development",
|
|
593
|
+
"staging",
|
|
594
|
+
"production"
|
|
595
|
+
]);
|
|
596
|
+
var SUITE_ISSUABLE_ENVIRONMENTS = deepFreeze([
|
|
597
|
+
"development",
|
|
598
|
+
"production"
|
|
599
|
+
]);
|
|
600
|
+
var localIssuerHosts = new Set(["127.0.0.1", "[::1]", "localhost"]);
|
|
601
|
+
function containsAsciiControl(value) {
|
|
602
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
603
|
+
const code = value.charCodeAt(index);
|
|
604
|
+
if (code <= 31 || code === 127)
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
function parseIdentityIssuer(value) {
|
|
610
|
+
if (typeof value !== "string" || value.length > 2048 || value.trim() !== value) {
|
|
611
|
+
return err5("invalid-issuer");
|
|
612
|
+
}
|
|
613
|
+
let parsed;
|
|
614
|
+
try {
|
|
615
|
+
parsed = new URL(value);
|
|
616
|
+
} catch {
|
|
617
|
+
return err5("invalid-issuer");
|
|
618
|
+
}
|
|
619
|
+
const local = localIssuerHosts.has(parsed.hostname);
|
|
620
|
+
if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:") || parsed.username !== "" || parsed.password !== "" || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
|
|
621
|
+
return err5("invalid-issuer");
|
|
622
|
+
}
|
|
623
|
+
return ok5(parsed.origin);
|
|
624
|
+
}
|
|
625
|
+
function parseIdentitySubject(value) {
|
|
626
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 255 || value.trim() !== value || containsAsciiControl(value)) {
|
|
627
|
+
return err5("invalid-subject");
|
|
628
|
+
}
|
|
629
|
+
return ok5(value);
|
|
630
|
+
}
|
|
631
|
+
function parseIssuerSubject(value) {
|
|
632
|
+
if (!isRecord(value))
|
|
633
|
+
return err5("invalid-subject");
|
|
634
|
+
const issuer = parseIdentityIssuer(value["issuer"]);
|
|
635
|
+
if (!issuer.ok)
|
|
636
|
+
return issuer;
|
|
637
|
+
const subject = parseIdentitySubject(value["subject"]);
|
|
638
|
+
return subject.ok ? ok5({ issuer: issuer.value, subject: subject.value }) : subject;
|
|
639
|
+
}
|
|
640
|
+
function parseSuiteProduct(value) {
|
|
641
|
+
switch (value) {
|
|
642
|
+
case "soundfish":
|
|
643
|
+
case "hra":
|
|
644
|
+
case "peopleblade":
|
|
645
|
+
case "ghostget":
|
|
646
|
+
case "crclte":
|
|
647
|
+
case "pub":
|
|
648
|
+
return ok5(value);
|
|
649
|
+
case "oprte":
|
|
650
|
+
case "kitchen":
|
|
651
|
+
return ok5("hra");
|
|
652
|
+
default:
|
|
653
|
+
return err5("invalid-product");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function parseSuiteEnvironment(value) {
|
|
657
|
+
return typeof value === "string" && SUITE_ENVIRONMENTS.includes(value) ? ok5(value) : err5("invalid-environment");
|
|
658
|
+
}
|
|
659
|
+
function isSuiteIssuableEnvironment(value) {
|
|
660
|
+
return value === "development" || value === "production";
|
|
661
|
+
}
|
|
662
|
+
function parseLegacyPrincipalLink(value) {
|
|
663
|
+
if (!isRecord(value))
|
|
664
|
+
return err5("invalid-legacy-link");
|
|
665
|
+
const product = parseSuiteProduct(value["product"]);
|
|
666
|
+
if (!product.ok)
|
|
667
|
+
return product;
|
|
668
|
+
const environment = parseSuiteEnvironment(value["environment"]);
|
|
669
|
+
if (!environment.ok)
|
|
670
|
+
return environment;
|
|
671
|
+
const legacySubject = parseIdentitySubject(value["legacySubject"]);
|
|
672
|
+
if (!legacySubject.ok)
|
|
673
|
+
return legacySubject;
|
|
674
|
+
return ok5({
|
|
675
|
+
environment: environment.value,
|
|
676
|
+
legacySubject: legacySubject.value,
|
|
677
|
+
product: product.value
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
function parseAudience(value) {
|
|
681
|
+
const values = typeof value === "string" ? [value] : value;
|
|
682
|
+
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) {
|
|
683
|
+
return err5("invalid-audience");
|
|
684
|
+
}
|
|
685
|
+
return ok5(values);
|
|
686
|
+
}
|
|
687
|
+
function parseTimestamp(value) {
|
|
688
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
689
|
+
}
|
|
690
|
+
function parseSuiteJwtClaims(value) {
|
|
691
|
+
if (!isRecord(value))
|
|
692
|
+
return err5("invalid-jwt-claims");
|
|
693
|
+
const principal = parseIssuerSubject({
|
|
694
|
+
issuer: value["iss"],
|
|
695
|
+
subject: value["sub"]
|
|
696
|
+
});
|
|
697
|
+
if (!principal.ok)
|
|
698
|
+
return principal;
|
|
699
|
+
const suiteAccountId = parseSuiteAccountId(value["suite_account_id"]);
|
|
700
|
+
if (!suiteAccountId.ok)
|
|
701
|
+
return suiteAccountId;
|
|
702
|
+
const audience = parseAudience(value["aud"]);
|
|
703
|
+
if (!audience.ok)
|
|
704
|
+
return audience;
|
|
705
|
+
const issuedAtSeconds = parseTimestamp(value["iat"]);
|
|
706
|
+
const expiresAtSeconds = parseTimestamp(value["exp"]);
|
|
707
|
+
const notBeforeSeconds = value["nbf"] === undefined ? undefined : parseTimestamp(value["nbf"]);
|
|
708
|
+
const legacyProfile = value["profile_revision"] === undefined && value["profile_complete"] === undefined && value["username"] === undefined;
|
|
709
|
+
const profileRevision = legacyProfile ? null : value["profile_revision"] === "username-v1" ? "username-v1" : undefined;
|
|
710
|
+
const profileComplete = legacyProfile ? false : typeof value["profile_complete"] === "boolean" ? value["profile_complete"] : undefined;
|
|
711
|
+
const username = legacyProfile || value["username"] === null ? null : parseSuiteUsername(value["username"]);
|
|
712
|
+
if (issuedAtSeconds === null || expiresAtSeconds === null || expiresAtSeconds <= issuedAtSeconds || notBeforeSeconds === null || notBeforeSeconds !== undefined && notBeforeSeconds > expiresAtSeconds || profileRevision === undefined || profileComplete === undefined || username !== null && !username.ok || profileComplete !== (username !== null)) {
|
|
713
|
+
return err5("invalid-jwt-claims");
|
|
714
|
+
}
|
|
715
|
+
return ok5({
|
|
716
|
+
audience: audience.value,
|
|
717
|
+
expiresAtSeconds,
|
|
718
|
+
issuedAtSeconds,
|
|
719
|
+
...notBeforeSeconds === undefined ? {} : { notBeforeSeconds },
|
|
720
|
+
principal: principal.value,
|
|
721
|
+
profileComplete,
|
|
722
|
+
profileRevision,
|
|
723
|
+
suiteAccountId: suiteAccountId.value,
|
|
724
|
+
username: username?.value ?? null
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/entitlements.ts
|
|
729
|
+
var SUITE_ENTITLEMENTS_CLAIM_VERSION = "suite-entitlements-v1";
|
|
730
|
+
var SUITE_ENTITLEMENT_MAX_PROVIDER_AGE_MS = 26 * 60 * 60000;
|
|
731
|
+
function isRecord2(value) {
|
|
732
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
733
|
+
}
|
|
734
|
+
function nonNegativeInteger(value) {
|
|
735
|
+
return Number.isSafeInteger(value) && typeof value === "number" && value >= 0;
|
|
736
|
+
}
|
|
737
|
+
function canonicalFeatures(revision, features) {
|
|
738
|
+
return grantedSuiteFeatureSets(revision).some((set) => set.length === features.length && set.every((feature, index) => feature === features[index]));
|
|
739
|
+
}
|
|
740
|
+
function validReceiptProjection(projection) {
|
|
741
|
+
const revision = parseSuiteCatalogRevision(projection.catalogRevision);
|
|
742
|
+
return revision.ok && projection.suiteAccountId.length >= 1 && projection.suiteAccountId.length <= 128 && canonicalFeatures(revision.value, projection.features) && nonNegativeInteger(projection.observedAtMs) && nonNegativeInteger(projection.expiresAtMs) && projection.expiresAtMs > projection.observedAtMs && nonNegativeInteger(projection.projectionRevision) && nonNegativeInteger(projection.receiptIssuedAtMs) && projection.receiptIssuedAtMs < projection.expiresAtMs && (projection.receiptDigest === undefined || /^[a-f0-9]{64}$/u.test(projection.receiptDigest));
|
|
743
|
+
}
|
|
744
|
+
function sameFeatures(left, right) {
|
|
745
|
+
return left.length === right.length && left.every((feature, index) => feature === right[index]);
|
|
746
|
+
}
|
|
747
|
+
function decideSuiteEntitlementReceiptProjection(current, incoming) {
|
|
748
|
+
if (!validReceiptProjection(incoming))
|
|
749
|
+
return "conflict";
|
|
750
|
+
if (current === null)
|
|
751
|
+
return "insert";
|
|
752
|
+
if (!validReceiptProjection(current) || current.suiteAccountId !== incoming.suiteAccountId || incoming.projectionRevision < current.projectionRevision || incoming.observedAtMs < current.observedAtMs || incoming.receiptIssuedAtMs < current.receiptIssuedAtMs) {
|
|
753
|
+
return "conflict";
|
|
754
|
+
}
|
|
755
|
+
if (incoming.projectionRevision > current.projectionRevision || incoming.receiptIssuedAtMs > current.receiptIssuedAtMs) {
|
|
756
|
+
return "replace";
|
|
757
|
+
}
|
|
758
|
+
return incoming.catalogRevision === current.catalogRevision && incoming.expiresAtMs === current.expiresAtMs && incoming.observedAtMs === current.observedAtMs && sameFeatures(incoming.features, current.features) && incoming.receiptDigest === current.receiptDigest ? "replay" : "conflict";
|
|
759
|
+
}
|
|
760
|
+
function parseEntitlements(value) {
|
|
761
|
+
if (!isRecord2(value))
|
|
762
|
+
return null;
|
|
763
|
+
const catalogRevision = parseSuiteCatalogRevision(value["catalogRevision"]);
|
|
764
|
+
if (value["version"] !== SUITE_ENTITLEMENTS_CLAIM_VERSION || !catalogRevision.ok || !nonNegativeInteger(value["observedAtMs"]) || !nonNegativeInteger(value["expiresAtMs"]) || !nonNegativeInteger(value["projectionRevision"]) || !Array.isArray(value["features"]) || value["features"].length > SUITE_FEATURE_IDS.length) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
const features = [];
|
|
768
|
+
for (const valueFeature of value["features"]) {
|
|
769
|
+
const feature = parseSuiteFeatureId(valueFeature);
|
|
770
|
+
if (!feature.ok || features.includes(feature.value))
|
|
771
|
+
return null;
|
|
772
|
+
features.push(feature.value);
|
|
773
|
+
}
|
|
774
|
+
if (value["expiresAtMs"] <= value["observedAtMs"] || !canonicalFeatures(catalogRevision.value, features))
|
|
775
|
+
return null;
|
|
776
|
+
return {
|
|
777
|
+
catalogRevision: catalogRevision.value,
|
|
778
|
+
expiresAtMs: value["expiresAtMs"],
|
|
779
|
+
features,
|
|
780
|
+
observedAtMs: value["observedAtMs"],
|
|
781
|
+
projectionRevision: value["projectionRevision"],
|
|
782
|
+
version: SUITE_ENTITLEMENTS_CLAIM_VERSION
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function validExpectedString(value) {
|
|
786
|
+
let hasControl = false;
|
|
787
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
788
|
+
const code = value.charCodeAt(index);
|
|
789
|
+
if (code <= 31 || code === 127) {
|
|
790
|
+
hasControl = true;
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return value.length >= 1 && value.length <= 2048 && value.trim() === value && !hasControl;
|
|
795
|
+
}
|
|
796
|
+
async function verifySuiteEntitlementToken(compactToken, options) {
|
|
797
|
+
if (compactToken.length < 1 || compactToken.length > 16384 || !validExpectedString(options.expectedAudience) || !validExpectedString(options.expectedIssuer)) {
|
|
798
|
+
return { kind: "invalid", reason: "claims" };
|
|
799
|
+
}
|
|
800
|
+
let payload;
|
|
801
|
+
try {
|
|
802
|
+
payload = await options.verify(compactToken);
|
|
803
|
+
} catch {
|
|
804
|
+
return { kind: "invalid", reason: "signature" };
|
|
805
|
+
}
|
|
806
|
+
const parsed = parseSuiteJwtClaims(payload);
|
|
807
|
+
if (!parsed.ok)
|
|
808
|
+
return { kind: "invalid", reason: "claims" };
|
|
809
|
+
const claims = parsed.value;
|
|
810
|
+
if (claims.principal.issuer !== options.expectedIssuer) {
|
|
811
|
+
return { kind: "invalid", reason: "issuer" };
|
|
812
|
+
}
|
|
813
|
+
if (!claims.audience.includes(options.expectedAudience)) {
|
|
814
|
+
return { kind: "invalid", reason: "audience" };
|
|
815
|
+
}
|
|
816
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
817
|
+
const clockSkewMs = options.clockSkewMs ?? 30000;
|
|
818
|
+
const maxTokenLifetimeMs = options.maxTokenLifetimeMs ?? 20 * 60000;
|
|
819
|
+
const maxProjectionAgeMs = options.maxProjectionAgeMs ?? SUITE_ENTITLEMENT_MAX_PROVIDER_AGE_MS;
|
|
820
|
+
if (!nonNegativeInteger(nowMs) || !nonNegativeInteger(clockSkewMs) || !nonNegativeInteger(maxTokenLifetimeMs) || !nonNegativeInteger(maxProjectionAgeMs)) {
|
|
821
|
+
return { kind: "invalid", reason: "time" };
|
|
822
|
+
}
|
|
823
|
+
const issuedAtMs = claims.issuedAtSeconds * 1000;
|
|
824
|
+
const expiresAtMs = claims.expiresAtSeconds * 1000;
|
|
825
|
+
const notBeforeMs = claims.notBeforeSeconds === undefined ? issuedAtMs : claims.notBeforeSeconds * 1000;
|
|
826
|
+
if (expiresAtMs - issuedAtMs > maxTokenLifetimeMs || issuedAtMs > nowMs + clockSkewMs || notBeforeMs > nowMs + clockSkewMs || expiresAtMs <= nowMs - clockSkewMs) {
|
|
827
|
+
return { kind: "invalid", reason: "time" };
|
|
828
|
+
}
|
|
829
|
+
if (!isRecord2(payload))
|
|
830
|
+
return { kind: "invalid", reason: "claims" };
|
|
831
|
+
const rawEntitlements = payload["suite_entitlements"];
|
|
832
|
+
if (rawEntitlements === undefined) {
|
|
833
|
+
return {
|
|
834
|
+
claims,
|
|
835
|
+
entitlements: { claim: null, features: [], kind: "legacy" },
|
|
836
|
+
kind: "verified"
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
const claim = parseEntitlements(rawEntitlements);
|
|
840
|
+
if (claim === null || claim.expiresAtMs > expiresAtMs) {
|
|
841
|
+
return { kind: "invalid", reason: "entitlements" };
|
|
842
|
+
}
|
|
843
|
+
const fresh = claim.observedAtMs <= nowMs + clockSkewMs && claim.expiresAtMs > nowMs - clockSkewMs && nowMs - claim.observedAtMs <= maxProjectionAgeMs;
|
|
844
|
+
return {
|
|
845
|
+
claims,
|
|
846
|
+
entitlements: fresh ? { claim, features: claim.features, kind: "fresh" } : { claim, features: [], kind: "stale" },
|
|
847
|
+
kind: "verified"
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function suiteTokenGrantsFeature(result, feature) {
|
|
851
|
+
return result.kind === "verified" && result.entitlements.kind === "fresh" && result.entitlements.features.includes(feature);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/bearer-verifier.ts
|
|
855
|
+
import {
|
|
856
|
+
base64url,
|
|
857
|
+
createLocalJWKSet,
|
|
858
|
+
decodeProtectedHeader,
|
|
859
|
+
importJWK,
|
|
860
|
+
jwtVerify
|
|
861
|
+
} from "jose";
|
|
862
|
+
var MAX_TOKEN_BYTES = 16384;
|
|
863
|
+
var MAX_JWKS_BYTES = 64 * 1024;
|
|
864
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 5000;
|
|
865
|
+
var DEFAULT_JWKS_CACHE_TTL_MS = 5 * 60000;
|
|
866
|
+
var DEFAULT_JWKS_REFRESH_COOLDOWN_MS = 60000;
|
|
867
|
+
var ALGORITHMS = ["ES256"];
|
|
868
|
+
function isRecord3(value) {
|
|
869
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
870
|
+
}
|
|
871
|
+
function safeInteger(value) {
|
|
872
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
873
|
+
}
|
|
874
|
+
function containsAsciiControl2(value) {
|
|
875
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
876
|
+
const code = value.charCodeAt(index);
|
|
877
|
+
if (code <= 31 || code === 127)
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
function boundedString(value, minimum, maximum) {
|
|
883
|
+
return typeof value === "string" && value.length >= minimum && value.length <= maximum && value.trim() === value && !containsAsciiControl2(value);
|
|
884
|
+
}
|
|
885
|
+
function compactJwt(value) {
|
|
886
|
+
return value.length >= 32 && value.length <= MAX_TOKEN_BYTES && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u.test(value);
|
|
887
|
+
}
|
|
888
|
+
function parseSuiteBearerAuthorization(value) {
|
|
889
|
+
if (typeof value !== "string" || value.length > "Bearer ".length + MAX_TOKEN_BYTES || !value.startsWith("Bearer ") || value.includes(",")) {
|
|
890
|
+
return { error: "invalid-authorization", ok: false };
|
|
891
|
+
}
|
|
892
|
+
const token = value.slice("Bearer ".length);
|
|
893
|
+
return compactJwt(token) ? { ok: true, value: token } : { error: "invalid-authorization", ok: false };
|
|
894
|
+
}
|
|
895
|
+
function canonicalP256Coordinate(value) {
|
|
896
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]{43}$/u.test(value)) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
const decoded = base64url.decode(value);
|
|
901
|
+
return decoded.byteLength === 32 && base64url.encode(decoded) === value;
|
|
902
|
+
} catch {
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async function validJwks(value) {
|
|
907
|
+
if (!isRecord3(value) || !Array.isArray(value["keys"]) || value["keys"].length < 1 || value["keys"].length > 8) {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
const kids = new Set;
|
|
911
|
+
for (const key of value["keys"]) {
|
|
912
|
+
if (!isRecord3(key) || !boundedString(key["kid"], 1, 128) || kids.has(key["kid"]) || key["kty"] !== "EC" || key["crv"] !== "P-256" || key["alg"] !== undefined && key["alg"] !== "ES256" || key["use"] !== undefined && key["use"] !== "sig" || !canonicalP256Coordinate(key["x"]) || !canonicalP256Coordinate(key["y"]) || key["d"] !== undefined) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
const imported = await importJWK(key, "ES256");
|
|
917
|
+
if (imported instanceof Uint8Array || imported.type !== "public" || !imported.usages.includes("verify")) {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
} catch {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
kids.add(key["kid"]);
|
|
924
|
+
}
|
|
925
|
+
return value;
|
|
926
|
+
}
|
|
927
|
+
async function readBoundedJson(response, maximumBytes) {
|
|
928
|
+
const declared = response.headers.get("content-length");
|
|
929
|
+
if (declared !== null && (!/^(?:0|[1-9]\d*)$/u.test(declared) || Number(declared) > maximumBytes)) {
|
|
930
|
+
throw new Error("The JWKS response was too large.");
|
|
931
|
+
}
|
|
932
|
+
if (response.body === null)
|
|
933
|
+
throw new Error("The JWKS response was empty.");
|
|
934
|
+
const reader = response.body.getReader();
|
|
935
|
+
const chunks = [];
|
|
936
|
+
let length = 0;
|
|
937
|
+
while (true) {
|
|
938
|
+
const result = await reader.read();
|
|
939
|
+
if (result.done)
|
|
940
|
+
break;
|
|
941
|
+
if (result.value.byteLength > maximumBytes - length) {
|
|
942
|
+
await reader.cancel();
|
|
943
|
+
throw new Error("The JWKS response was too large.");
|
|
944
|
+
}
|
|
945
|
+
chunks.push(result.value);
|
|
946
|
+
length += result.value.byteLength;
|
|
947
|
+
}
|
|
948
|
+
const bytes = new Uint8Array(length);
|
|
949
|
+
let offset = 0;
|
|
950
|
+
for (const chunk of chunks) {
|
|
951
|
+
bytes.set(chunk, offset);
|
|
952
|
+
offset += chunk.byteLength;
|
|
953
|
+
}
|
|
954
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
955
|
+
}
|
|
956
|
+
function exactAudience(value, audiences) {
|
|
957
|
+
if (!Array.isArray(value) || value.length !== 2)
|
|
958
|
+
return false;
|
|
959
|
+
const actual = new Set(value);
|
|
960
|
+
return actual.size === 2 && actual.has(audiences[0]) && actual.has(audiences[1]);
|
|
961
|
+
}
|
|
962
|
+
function jwtFailureReason(error) {
|
|
963
|
+
if (!isRecord3(error))
|
|
964
|
+
return "signature";
|
|
965
|
+
const code = error["code"];
|
|
966
|
+
if (code === "ERR_JWT_EXPIRED")
|
|
967
|
+
return "time";
|
|
968
|
+
if (code === "ERR_JWT_CLAIM_VALIDATION_FAILED" && (error["claim"] === "exp" || error["claim"] === "iat" || error["claim"] === "nbf")) {
|
|
969
|
+
return "time";
|
|
970
|
+
}
|
|
971
|
+
return "signature";
|
|
972
|
+
}
|
|
973
|
+
function missingJwksKey(error) {
|
|
974
|
+
return isRecord3(error) && error["code"] === "ERR_JWKS_NO_MATCHING_KEY";
|
|
975
|
+
}
|
|
976
|
+
function createSuiteBearerVerifier(options) {
|
|
977
|
+
const client = suiteAccountsCurrentOidcClientRegistration(options.consumer, options.environment);
|
|
978
|
+
if (client === null) {
|
|
979
|
+
throw new Error("The suite bearer consumer has no OAuth client.");
|
|
980
|
+
}
|
|
981
|
+
const provider = suiteAccountsOidcProviderConfiguration(options.environment);
|
|
982
|
+
const configuration = deepFreeze({
|
|
983
|
+
audiences: [provider.resource, provider.userInfoAudience],
|
|
984
|
+
clientId: client.clientId,
|
|
985
|
+
issuer: provider.issuer,
|
|
986
|
+
jwksEndpoint: provider.jwksEndpoint
|
|
987
|
+
});
|
|
988
|
+
const fetchImplementation = options.fetch ?? fetch;
|
|
989
|
+
const fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
990
|
+
const jwksCacheTtlMs = options.jwksCacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;
|
|
991
|
+
const jwksRefreshCooldownMs = options.jwksRefreshCooldownMs ?? DEFAULT_JWKS_REFRESH_COOLDOWN_MS;
|
|
992
|
+
if (!safeInteger(fetchTimeoutMs) || fetchTimeoutMs < 100 || fetchTimeoutMs > 30000) {
|
|
993
|
+
throw new Error("The suite JWKS fetch timeout must be 100–30000ms.");
|
|
994
|
+
}
|
|
995
|
+
if (!safeInteger(jwksCacheTtlMs) || jwksCacheTtlMs < 1000 || jwksCacheTtlMs > 24 * 60 * 60000) {
|
|
996
|
+
throw new Error("The suite JWKS cache TTL must be 1000–86400000ms.");
|
|
997
|
+
}
|
|
998
|
+
if (!safeInteger(jwksRefreshCooldownMs) || jwksRefreshCooldownMs < 1000 || jwksRefreshCooldownMs > 5 * 60000) {
|
|
999
|
+
throw new Error("The suite JWKS refresh cooldown must be 1000–300000ms.");
|
|
1000
|
+
}
|
|
1001
|
+
const now = options.now ?? Date.now;
|
|
1002
|
+
let cache = null;
|
|
1003
|
+
let lastForcedRefreshAtMs = null;
|
|
1004
|
+
let pending = null;
|
|
1005
|
+
async function fetchJwks() {
|
|
1006
|
+
const controller = new AbortController;
|
|
1007
|
+
const timeout = setTimeout(() => controller.abort(), fetchTimeoutMs);
|
|
1008
|
+
try {
|
|
1009
|
+
const response = await fetchImplementation(configuration.jwksEndpoint, {
|
|
1010
|
+
cache: "no-store",
|
|
1011
|
+
headers: { accept: "application/json" },
|
|
1012
|
+
method: "GET",
|
|
1013
|
+
redirect: "manual",
|
|
1014
|
+
signal: controller.signal
|
|
1015
|
+
});
|
|
1016
|
+
if (response.status !== 200 || response.headers.has("location") || response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
|
|
1017
|
+
throw new Error("The suite JWKS endpoint was unavailable.");
|
|
1018
|
+
}
|
|
1019
|
+
const value = await validJwks(await readBoundedJson(response, MAX_JWKS_BYTES));
|
|
1020
|
+
if (value === null)
|
|
1021
|
+
throw new Error("The suite JWKS was invalid.");
|
|
1022
|
+
cache = {
|
|
1023
|
+
expiresAtMs: now() + jwksCacheTtlMs,
|
|
1024
|
+
value
|
|
1025
|
+
};
|
|
1026
|
+
return value;
|
|
1027
|
+
} finally {
|
|
1028
|
+
clearTimeout(timeout);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
async function jwks(forceRefresh = false) {
|
|
1032
|
+
const nowMs = now();
|
|
1033
|
+
if (!forceRefresh && cache !== null && cache.expiresAtMs > nowMs) {
|
|
1034
|
+
return cache.value;
|
|
1035
|
+
}
|
|
1036
|
+
if (pending !== null)
|
|
1037
|
+
return await pending;
|
|
1038
|
+
pending = fetchJwks();
|
|
1039
|
+
try {
|
|
1040
|
+
return await pending;
|
|
1041
|
+
} finally {
|
|
1042
|
+
pending = null;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
async function verifySignature(token, nowMs, forceRefresh = false) {
|
|
1046
|
+
let keySet;
|
|
1047
|
+
try {
|
|
1048
|
+
keySet = await jwks(forceRefresh);
|
|
1049
|
+
} catch {
|
|
1050
|
+
return { kind: "unavailable", reason: "jwks" };
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
const result = await jwtVerify(token, createLocalJWKSet(keySet), {
|
|
1054
|
+
algorithms: [...ALGORITHMS],
|
|
1055
|
+
clockTolerance: 30,
|
|
1056
|
+
currentDate: new Date(nowMs),
|
|
1057
|
+
maxTokenAge: "20m"
|
|
1058
|
+
});
|
|
1059
|
+
return { kind: "verified", payload: result.payload };
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
if (!forceRefresh && missingJwksKey(error)) {
|
|
1062
|
+
if (lastForcedRefreshAtMs !== null && nowMs - lastForcedRefreshAtMs < jwksRefreshCooldownMs) {
|
|
1063
|
+
return { kind: "invalid", reason: "signature" };
|
|
1064
|
+
}
|
|
1065
|
+
lastForcedRefreshAtMs = nowMs;
|
|
1066
|
+
return await verifySignature(token, nowMs, true);
|
|
1067
|
+
}
|
|
1068
|
+
return { kind: "invalid", reason: jwtFailureReason(error) };
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
async function verify(bearerToken) {
|
|
1072
|
+
if (!compactJwt(bearerToken)) {
|
|
1073
|
+
return { kind: "invalid", reason: "authorization" };
|
|
1074
|
+
}
|
|
1075
|
+
let header;
|
|
1076
|
+
try {
|
|
1077
|
+
header = decodeProtectedHeader(bearerToken);
|
|
1078
|
+
} catch {
|
|
1079
|
+
return { kind: "invalid", reason: "authorization" };
|
|
1080
|
+
}
|
|
1081
|
+
if (header.alg !== "ES256" || !boundedString(header.kid, 1, 128)) {
|
|
1082
|
+
return { kind: "invalid", reason: "signature" };
|
|
1083
|
+
}
|
|
1084
|
+
const nowMs = now();
|
|
1085
|
+
if (!safeInteger(nowMs))
|
|
1086
|
+
return { kind: "invalid", reason: "time" };
|
|
1087
|
+
const signed = await verifySignature(bearerToken, nowMs);
|
|
1088
|
+
if (signed.kind !== "verified")
|
|
1089
|
+
return signed;
|
|
1090
|
+
const parsed = await verifySuiteEntitlementToken(bearerToken, {
|
|
1091
|
+
expectedAudience: provider.resource,
|
|
1092
|
+
expectedIssuer: provider.issuer,
|
|
1093
|
+
nowMs,
|
|
1094
|
+
verify: () => Promise.resolve(signed.payload)
|
|
1095
|
+
});
|
|
1096
|
+
if (parsed.kind !== "verified") {
|
|
1097
|
+
return {
|
|
1098
|
+
kind: "invalid",
|
|
1099
|
+
reason: parsed.reason === "issuer" || parsed.reason === "signature" ? "claims" : parsed.reason
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
if (!exactAudience(signed.payload.aud, configuration.audiences)) {
|
|
1103
|
+
return { kind: "invalid", reason: "audience" };
|
|
1104
|
+
}
|
|
1105
|
+
if (signed.payload["azp"] !== configuration.clientId || signed.payload["suite_client_id"] !== configuration.clientId) {
|
|
1106
|
+
return { kind: "invalid", reason: "client" };
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
claims: parsed.claims,
|
|
1110
|
+
entitlements: parsed.entitlements,
|
|
1111
|
+
kind: "verified"
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
return Object.freeze({ configuration, verify });
|
|
1115
|
+
}
|
|
1116
|
+
export {
|
|
1117
|
+
parseSuiteBearerAuthorization,
|
|
1118
|
+
createSuiteBearerVerifier
|
|
1119
|
+
};
|