@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,253 @@
|
|
|
1
|
+
// src/immutable.ts
|
|
2
|
+
function deepFreeze(value) {
|
|
3
|
+
const visited = new WeakSet;
|
|
4
|
+
function freezeOwned(current) {
|
|
5
|
+
if (current === null || typeof current !== "object")
|
|
6
|
+
return;
|
|
7
|
+
if (visited.has(current))
|
|
8
|
+
return;
|
|
9
|
+
visited.add(current);
|
|
10
|
+
for (const key of Reflect.ownKeys(current)) {
|
|
11
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
12
|
+
if (descriptor !== undefined && "value" in descriptor) {
|
|
13
|
+
freezeOwned(descriptor.value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
Object.freeze(current);
|
|
17
|
+
}
|
|
18
|
+
freezeOwned(value);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/oidc-device-code.ts
|
|
23
|
+
var SUITE_OIDC_DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
24
|
+
var MIN_DEVICE_CODE_INTERVAL_MS = 1000;
|
|
25
|
+
var MAX_DEVICE_CODE_INTERVAL_MS = 600000;
|
|
26
|
+
var MAX_USER_CODE_LENGTH = 256;
|
|
27
|
+
var MAX_DEVICE_CODE_LENGTH = 2048;
|
|
28
|
+
var MAX_VERIFICATION_URI_LENGTH = 4096;
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function boundedString(value, minimum, maximum) {
|
|
33
|
+
return typeof value === "string" && value.length >= minimum && value.length <= maximum && !value.includes("\x00");
|
|
34
|
+
}
|
|
35
|
+
function safeInteger(value) {
|
|
36
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
37
|
+
}
|
|
38
|
+
function containsControlCharacter(value) {
|
|
39
|
+
for (const character of value) {
|
|
40
|
+
const codePoint = character.codePointAt(0) ?? -1;
|
|
41
|
+
if (codePoint <= 31 || codePoint === 127)
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
function normalizeScope(value) {
|
|
47
|
+
if (value === undefined || value === null)
|
|
48
|
+
return null;
|
|
49
|
+
if (typeof value !== "string")
|
|
50
|
+
return null;
|
|
51
|
+
const trimmed = value.trim();
|
|
52
|
+
if (trimmed.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
if (trimmed.length > 4096 || containsControlCharacter(trimmed))
|
|
55
|
+
return null;
|
|
56
|
+
return trimmed;
|
|
57
|
+
}
|
|
58
|
+
function absoluteHttpsUri(value) {
|
|
59
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_VERIFICATION_URI_LENGTH) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(value);
|
|
64
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
65
|
+
return null;
|
|
66
|
+
if (url.username !== "" || url.password !== "")
|
|
67
|
+
return null;
|
|
68
|
+
if (url.hash !== "")
|
|
69
|
+
return null;
|
|
70
|
+
return url.href;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function parseDeviceAuthorizationResponse(value, nowMs) {
|
|
76
|
+
if (!isRecord(value))
|
|
77
|
+
return null;
|
|
78
|
+
const deviceCode = value["device_code"];
|
|
79
|
+
const userCode = value["user_code"];
|
|
80
|
+
const verificationUri = value["verification_uri"];
|
|
81
|
+
const verificationUriComplete = value["verification_uri_complete"];
|
|
82
|
+
const expiresIn = value["expires_in"];
|
|
83
|
+
const interval = value["interval"];
|
|
84
|
+
if (!boundedString(deviceCode, 1, MAX_DEVICE_CODE_LENGTH) || !boundedString(userCode, 1, MAX_USER_CODE_LENGTH) || !boundedString(verificationUri, 1, MAX_VERIFICATION_URI_LENGTH) || !safeInteger(expiresIn) || expiresIn <= 0 || expiresIn > 86400 || !safeInteger(interval) || interval < 1 || interval > MAX_DEVICE_CODE_INTERVAL_MS / 1000) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const verificationUriCompleteValue = verificationUriComplete === undefined || verificationUriComplete === null ? null : absoluteHttpsUri(verificationUriComplete);
|
|
88
|
+
if (verificationUriComplete !== undefined && verificationUriComplete !== null && verificationUriCompleteValue === null) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const intervalMs = Math.max(MIN_DEVICE_CODE_INTERVAL_MS, interval * 1000);
|
|
92
|
+
return deepFreeze({
|
|
93
|
+
deviceCode,
|
|
94
|
+
userCode,
|
|
95
|
+
verificationUri,
|
|
96
|
+
verificationUriComplete: verificationUriCompleteValue,
|
|
97
|
+
expiresAtMs: nowMs + expiresIn * 1000,
|
|
98
|
+
intervalMs
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function parseDeviceTokenResponse(value, nowMs) {
|
|
102
|
+
if (!isRecord(value))
|
|
103
|
+
return null;
|
|
104
|
+
const error = value["error"];
|
|
105
|
+
const errorDescription = value["error_description"];
|
|
106
|
+
if (error !== undefined) {
|
|
107
|
+
if (typeof error !== "string" || error.length === 0 || error.length > 256) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const normalizedError = normalizeScope(error);
|
|
111
|
+
if (normalizedError === null || normalizedError !== error)
|
|
112
|
+
return null;
|
|
113
|
+
const normalizedDescription = errorDescription === undefined || errorDescription === null ? null : normalizeScope(errorDescription);
|
|
114
|
+
if (errorDescription !== undefined && errorDescription !== null && normalizedDescription === null) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
switch (normalizedError) {
|
|
118
|
+
case "authorization_pending":
|
|
119
|
+
return deepFreeze({ kind: "authorization_pending", intervalMs: 5000 });
|
|
120
|
+
case "slow_down":
|
|
121
|
+
return deepFreeze({ kind: "slow_down", intervalMs: 1e4 });
|
|
122
|
+
case "access_denied":
|
|
123
|
+
return deepFreeze({ kind: "access_denied" });
|
|
124
|
+
case "expired_token":
|
|
125
|
+
return deepFreeze({ kind: "expired_token" });
|
|
126
|
+
default:
|
|
127
|
+
return deepFreeze({
|
|
128
|
+
kind: "error",
|
|
129
|
+
error: normalizedError,
|
|
130
|
+
errorDescription: normalizedDescription
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const accessToken = value["access_token"];
|
|
135
|
+
const tokenType = value["token_type"];
|
|
136
|
+
const expiresIn = value["expires_in"];
|
|
137
|
+
const refreshToken = value["refresh_token"];
|
|
138
|
+
const idToken = value["id_token"];
|
|
139
|
+
const scope = value["scope"];
|
|
140
|
+
if (!boundedString(accessToken, 1, 16384) || !boundedString(tokenType, 1, 64) || !safeInteger(expiresIn) || expiresIn <= 0 || expiresIn > 86400) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const normalizedRefreshToken = refreshToken === undefined || refreshToken === null ? null : boundedString(refreshToken, 1, 16384) ? refreshToken : null;
|
|
144
|
+
if (refreshToken !== undefined && refreshToken !== null && normalizedRefreshToken === null) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const normalizedIdToken = idToken === undefined || idToken === null ? null : boundedString(idToken, 1, 16384) ? idToken : null;
|
|
148
|
+
if (idToken !== undefined && idToken !== null && normalizedIdToken === null) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const normalizedScope = normalizeScope(scope);
|
|
152
|
+
if (scope !== undefined && scope !== null && normalizedScope === null) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return deepFreeze({
|
|
156
|
+
kind: "token",
|
|
157
|
+
accessToken,
|
|
158
|
+
tokenType: tokenType.toLowerCase(),
|
|
159
|
+
expiresAtMs: nowMs + expiresIn * 1000,
|
|
160
|
+
refreshToken: normalizedRefreshToken,
|
|
161
|
+
idToken: normalizedIdToken,
|
|
162
|
+
scope: normalizedScope
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function deviceAuthorizationBody(request) {
|
|
166
|
+
const params = new URLSearchParams;
|
|
167
|
+
params.set("client_id", request.clientId);
|
|
168
|
+
if (request.scopes !== undefined && request.scopes.length > 0) {
|
|
169
|
+
for (const scope of request.scopes) {
|
|
170
|
+
if (typeof scope !== "string" || scope.length === 0 || scope.length > 256 || /\s/u.test(scope) || containsControlCharacter(scope)) {
|
|
171
|
+
throw new Error("Invalid device authorization scope.");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
params.set("scope", request.scopes.join(" "));
|
|
175
|
+
}
|
|
176
|
+
return params.toString();
|
|
177
|
+
}
|
|
178
|
+
async function readBoundedJson(response) {
|
|
179
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
180
|
+
if (contentType !== "application/json") {
|
|
181
|
+
throw new Error("The device-code response was not application/json.");
|
|
182
|
+
}
|
|
183
|
+
const text = await response.text();
|
|
184
|
+
if (text.length > 64 * 1024) {
|
|
185
|
+
throw new Error("The device-code response body was too large.");
|
|
186
|
+
}
|
|
187
|
+
return JSON.parse(text);
|
|
188
|
+
}
|
|
189
|
+
async function initiateSuiteOidcDeviceAuthorization(configuration, request, dependencies = {}) {
|
|
190
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
191
|
+
const now = dependencies.now?.().getTime() ?? Date.now();
|
|
192
|
+
const response = await fetcher(configuration.deviceAuthorizationEndpoint, {
|
|
193
|
+
body: deviceAuthorizationBody(request),
|
|
194
|
+
headers: {
|
|
195
|
+
accept: "application/json",
|
|
196
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
197
|
+
},
|
|
198
|
+
method: "POST",
|
|
199
|
+
redirect: "error"
|
|
200
|
+
});
|
|
201
|
+
const body = await readBoundedJson(response);
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error("The device authorization request failed.");
|
|
204
|
+
}
|
|
205
|
+
const parsed = parseDeviceAuthorizationResponse(body, now);
|
|
206
|
+
if (parsed === null) {
|
|
207
|
+
throw new Error("The device authorization response was invalid.");
|
|
208
|
+
}
|
|
209
|
+
const tokenRequest = {
|
|
210
|
+
clientId: request.clientId,
|
|
211
|
+
deviceCode: parsed.deviceCode
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
poll: () => pollSuiteOidcDeviceToken(configuration.deviceTokenEndpoint, tokenRequest, {
|
|
215
|
+
...dependencies.now === undefined ? {} : { now: dependencies.now },
|
|
216
|
+
fetch: fetcher
|
|
217
|
+
}),
|
|
218
|
+
response: parsed
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function tokenRequestBody(request) {
|
|
222
|
+
const params = new URLSearchParams;
|
|
223
|
+
params.set("grant_type", SUITE_OIDC_DEVICE_CODE_GRANT_TYPE);
|
|
224
|
+
params.set("client_id", request.clientId);
|
|
225
|
+
params.set("device_code", request.deviceCode);
|
|
226
|
+
return params.toString();
|
|
227
|
+
}
|
|
228
|
+
async function pollSuiteOidcDeviceToken(tokenEndpoint, request, dependencies = {}) {
|
|
229
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
230
|
+
const now = dependencies.now?.().getTime() ?? Date.now();
|
|
231
|
+
const response = await fetcher(tokenEndpoint, {
|
|
232
|
+
body: tokenRequestBody(request),
|
|
233
|
+
headers: {
|
|
234
|
+
accept: "application/json",
|
|
235
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
236
|
+
},
|
|
237
|
+
method: "POST",
|
|
238
|
+
redirect: "error"
|
|
239
|
+
});
|
|
240
|
+
const body = await readBoundedJson(response);
|
|
241
|
+
const parsed = parseDeviceTokenResponse(body, now);
|
|
242
|
+
if (parsed === null) {
|
|
243
|
+
throw new Error("The device token response was invalid.");
|
|
244
|
+
}
|
|
245
|
+
return parsed;
|
|
246
|
+
}
|
|
247
|
+
export {
|
|
248
|
+
pollSuiteOidcDeviceToken,
|
|
249
|
+
parseDeviceTokenResponse,
|
|
250
|
+
parseDeviceAuthorizationResponse,
|
|
251
|
+
initiateSuiteOidcDeviceAuthorization,
|
|
252
|
+
SUITE_OIDC_DEVICE_CODE_GRANT_TYPE
|
|
253
|
+
};
|