@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,208 @@
|
|
|
1
|
+
// src/oidc-continuation.ts
|
|
2
|
+
function htmlAttribute(value) {
|
|
3
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
4
|
+
}
|
|
5
|
+
function inlineJson(value) {
|
|
6
|
+
return JSON.stringify(value).replaceAll("&", "\\u0026").replaceAll("<", "\\u003c").replaceAll(">", "\\u003e").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
7
|
+
}
|
|
8
|
+
var SUITE_OIDC_JUST_SIGNED_IN_STORAGE_KEY = "hraness:suite:signed-in:v1";
|
|
9
|
+
function createOidcContinuationResponse(returnTo, nonce, cookies) {
|
|
10
|
+
const headers = new Headers({
|
|
11
|
+
"cache-control": "no-store",
|
|
12
|
+
"content-security-policy": [
|
|
13
|
+
"default-src 'none'",
|
|
14
|
+
"base-uri 'none'",
|
|
15
|
+
"form-action 'none'",
|
|
16
|
+
"frame-ancestors 'none'",
|
|
17
|
+
`script-src 'nonce-${nonce}'`
|
|
18
|
+
].join("; "),
|
|
19
|
+
"content-type": "text/html; charset=utf-8",
|
|
20
|
+
pragma: "no-cache",
|
|
21
|
+
"referrer-policy": "no-referrer",
|
|
22
|
+
"x-content-type-options": "nosniff",
|
|
23
|
+
"x-frame-options": "DENY"
|
|
24
|
+
});
|
|
25
|
+
for (const cookie of cookies)
|
|
26
|
+
headers.append("set-cookie", cookie);
|
|
27
|
+
const body = [
|
|
28
|
+
"<!doctype html>",
|
|
29
|
+
'<html lang="en">',
|
|
30
|
+
"<head>",
|
|
31
|
+
'<meta charset="utf-8">',
|
|
32
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
33
|
+
"<title>Signed in</title>",
|
|
34
|
+
"</head>",
|
|
35
|
+
"<body>",
|
|
36
|
+
"<p>Signed in.</p>",
|
|
37
|
+
`<p><a href="${htmlAttribute(returnTo)}">Continue</a></p>`,
|
|
38
|
+
`<script nonce="${nonce}">try{sessionStorage.setItem(${inlineJson(SUITE_OIDC_JUST_SIGNED_IN_STORAGE_KEY)},"1")}catch{}location.replace(${inlineJson(returnTo)});</script>`,
|
|
39
|
+
"</body>",
|
|
40
|
+
"</html>"
|
|
41
|
+
].join("");
|
|
42
|
+
return new Response(body, { headers, status: 200 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/browser-session.ts
|
|
46
|
+
var MAXIMUM_SESSION_RESPONSE_BYTES = 32768;
|
|
47
|
+
var SESSION_PATH = "/api/suite-auth/session";
|
|
48
|
+
var REFRESH_PATH = "/api/suite-auth/refresh";
|
|
49
|
+
var SIGN_OUT_PATH = "/api/suite-auth/sign-out";
|
|
50
|
+
var SUITE_OIDC_REFRESH_LOCK_NAME = "jungle-suite-accounts:oidc-refresh:v1";
|
|
51
|
+
var SUITE_OIDC_SESSION_CHANNEL_NAME = "jungle-suite-accounts:oidc-session:v1";
|
|
52
|
+
var SIGNED_OUT_EVENT = Object.freeze({
|
|
53
|
+
kind: "signed_out",
|
|
54
|
+
version: "suite-oidc-session-event-v1"
|
|
55
|
+
});
|
|
56
|
+
var localRefresh = null;
|
|
57
|
+
function isExactRefreshRequired(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && Reflect.get(value, "kind") === "refresh_required";
|
|
59
|
+
}
|
|
60
|
+
async function readBoundedJson(response) {
|
|
61
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
62
|
+
if (contentType !== "application/json" || response.body === null) {
|
|
63
|
+
throw new Error("The suite session response was not JSON.");
|
|
64
|
+
}
|
|
65
|
+
const declaredLength = response.headers.get("content-length");
|
|
66
|
+
if (declaredLength !== null && (!/^(?:0|[1-9]\d*)$/u.test(declaredLength) || Number(declaredLength) > MAXIMUM_SESSION_RESPONSE_BYTES)) {
|
|
67
|
+
await response.body.cancel().catch(() => {
|
|
68
|
+
return;
|
|
69
|
+
});
|
|
70
|
+
throw new Error("The suite session response was too large.");
|
|
71
|
+
}
|
|
72
|
+
const reader = response.body.getReader();
|
|
73
|
+
const chunks = [];
|
|
74
|
+
let total = 0;
|
|
75
|
+
try {
|
|
76
|
+
while (true) {
|
|
77
|
+
const result = await reader.read();
|
|
78
|
+
if (result.done)
|
|
79
|
+
break;
|
|
80
|
+
total += result.value.byteLength;
|
|
81
|
+
if (total > MAXIMUM_SESSION_RESPONSE_BYTES) {
|
|
82
|
+
await reader.cancel().catch(() => {
|
|
83
|
+
return;
|
|
84
|
+
});
|
|
85
|
+
throw new Error("The suite session response was too large.");
|
|
86
|
+
}
|
|
87
|
+
chunks.push(result.value);
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
reader.releaseLock();
|
|
91
|
+
}
|
|
92
|
+
const bytes = new Uint8Array(total);
|
|
93
|
+
let offset = 0;
|
|
94
|
+
for (const chunk of chunks) {
|
|
95
|
+
bytes.set(chunk, offset);
|
|
96
|
+
offset += chunk.byteLength;
|
|
97
|
+
}
|
|
98
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
99
|
+
}
|
|
100
|
+
async function requestJson(fetcher, path, method) {
|
|
101
|
+
const response = await fetcher(path, {
|
|
102
|
+
cache: "no-store",
|
|
103
|
+
credentials: "same-origin",
|
|
104
|
+
headers: { accept: "application/json" },
|
|
105
|
+
method,
|
|
106
|
+
redirect: "error"
|
|
107
|
+
});
|
|
108
|
+
const value = await readBoundedJson(response);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error("The suite session request failed.");
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function exactSignedOut(value) {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && Reflect.get(value, "kind") === "signed_out";
|
|
116
|
+
}
|
|
117
|
+
function broadcastSuiteOidcBrowserSignOut() {
|
|
118
|
+
if (typeof BroadcastChannel === "undefined")
|
|
119
|
+
return;
|
|
120
|
+
const channel = new BroadcastChannel(SUITE_OIDC_SESSION_CHANNEL_NAME);
|
|
121
|
+
try {
|
|
122
|
+
channel.postMessage(SIGNED_OUT_EVENT);
|
|
123
|
+
} finally {
|
|
124
|
+
channel.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function subscribeSuiteOidcBrowserSignOut(listener) {
|
|
128
|
+
if (typeof BroadcastChannel === "undefined")
|
|
129
|
+
return () => {
|
|
130
|
+
return;
|
|
131
|
+
};
|
|
132
|
+
const channel = new BroadcastChannel(SUITE_OIDC_SESSION_CHANNEL_NAME);
|
|
133
|
+
const onMessage = (event) => {
|
|
134
|
+
const value = event.data;
|
|
135
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 2 && Reflect.get(value, "kind") === SIGNED_OUT_EVENT.kind && Reflect.get(value, "version") === SIGNED_OUT_EVENT.version) {
|
|
136
|
+
listener();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
channel.addEventListener("message", onMessage);
|
|
140
|
+
return () => {
|
|
141
|
+
channel.removeEventListener("message", onMessage);
|
|
142
|
+
channel.close();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function withLocalRefreshLock(task) {
|
|
146
|
+
if (localRefresh !== null)
|
|
147
|
+
return await localRefresh;
|
|
148
|
+
const pending = task();
|
|
149
|
+
localRefresh = pending;
|
|
150
|
+
try {
|
|
151
|
+
return await pending;
|
|
152
|
+
} finally {
|
|
153
|
+
if (localRefresh === pending)
|
|
154
|
+
localRefresh = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function withRefreshLock(task, injectedLock) {
|
|
158
|
+
if (injectedLock !== undefined) {
|
|
159
|
+
return injectedLock === null ? await withLocalRefreshLock(task) : await injectedLock(SUITE_OIDC_REFRESH_LOCK_NAME, task);
|
|
160
|
+
}
|
|
161
|
+
if (typeof navigator !== "undefined" && navigator.locks !== undefined) {
|
|
162
|
+
return await navigator.locks.request(SUITE_OIDC_REFRESH_LOCK_NAME, { mode: "exclusive" }, task);
|
|
163
|
+
}
|
|
164
|
+
return await withLocalRefreshLock(task);
|
|
165
|
+
}
|
|
166
|
+
async function loadSuiteOidcBrowserSession(dependencies = {}) {
|
|
167
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
168
|
+
const withExclusiveLock = dependencies.withExclusiveLock;
|
|
169
|
+
const current = await requestJson(fetcher, SESSION_PATH, "GET");
|
|
170
|
+
if (!isExactRefreshRequired(current))
|
|
171
|
+
return current;
|
|
172
|
+
return await withRefreshLock(async () => {
|
|
173
|
+
const latest = await requestJson(fetcher, SESSION_PATH, "GET");
|
|
174
|
+
return isExactRefreshRequired(latest) ? await requestJson(fetcher, REFRESH_PATH, "POST") : latest;
|
|
175
|
+
}, withExclusiveLock);
|
|
176
|
+
}
|
|
177
|
+
async function signOutSuiteOidcBrowserSession(dependencies = {}) {
|
|
178
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
179
|
+
const notifySignedOut = dependencies.notifySignedOut ?? broadcastSuiteOidcBrowserSignOut;
|
|
180
|
+
const withExclusiveLock = dependencies.withExclusiveLock;
|
|
181
|
+
await withRefreshLock(async () => {
|
|
182
|
+
const value = await requestJson(fetcher, SIGN_OUT_PATH, "POST");
|
|
183
|
+
if (!exactSignedOut(value)) {
|
|
184
|
+
throw new Error("The suite sign-out response was invalid.");
|
|
185
|
+
}
|
|
186
|
+
}, withExclusiveLock);
|
|
187
|
+
notifySignedOut();
|
|
188
|
+
}
|
|
189
|
+
function consumeSuiteOidcJustSignedIn() {
|
|
190
|
+
try {
|
|
191
|
+
if (typeof sessionStorage === "undefined" || sessionStorage.getItem(SUITE_OIDC_JUST_SIGNED_IN_STORAGE_KEY) === null) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
sessionStorage.removeItem(SUITE_OIDC_JUST_SIGNED_IN_STORAGE_KEY);
|
|
195
|
+
return true;
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
subscribeSuiteOidcBrowserSignOut,
|
|
202
|
+
signOutSuiteOidcBrowserSession,
|
|
203
|
+
loadSuiteOidcBrowserSession,
|
|
204
|
+
consumeSuiteOidcJustSignedIn,
|
|
205
|
+
broadcastSuiteOidcBrowserSignOut,
|
|
206
|
+
SUITE_OIDC_SESSION_CHANNEL_NAME,
|
|
207
|
+
SUITE_OIDC_REFRESH_LOCK_NAME
|
|
208
|
+
};
|
|
@@ -0,0 +1,470 @@
|
|
|
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/client-configuration.ts
|
|
382
|
+
import { err as err2, isRecord, ok as ok2 } from "@hraness/result";
|
|
383
|
+
var SUITE_ACCOUNTS_CLIENT_CONFIGURATION_VERSION = "suite-accounts-client-configuration-v1";
|
|
384
|
+
var SUITE_ACCOUNTS_WIRE_VERSION = "v1";
|
|
385
|
+
var BINDING_KEYS = [
|
|
386
|
+
"authMode",
|
|
387
|
+
"callbackUrl",
|
|
388
|
+
"clientId",
|
|
389
|
+
"consumer",
|
|
390
|
+
"environment",
|
|
391
|
+
"origin"
|
|
392
|
+
];
|
|
393
|
+
function snapshotBinding(input) {
|
|
394
|
+
if (!isRecord(input))
|
|
395
|
+
return null;
|
|
396
|
+
try {
|
|
397
|
+
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
398
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
399
|
+
if (keys.length !== BINDING_KEYS.length || keys.some((key) => typeof key !== "string" || !BINDING_KEYS.includes(key))) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
for (const key of BINDING_KEYS) {
|
|
403
|
+
const descriptor = descriptors[key];
|
|
404
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
authMode: descriptors.authMode.value,
|
|
409
|
+
callbackUrl: descriptors.callbackUrl.value,
|
|
410
|
+
clientId: descriptors.clientId.value,
|
|
411
|
+
consumer: descriptors.consumer.value,
|
|
412
|
+
environment: descriptors.environment.value,
|
|
413
|
+
origin: descriptors.origin.value
|
|
414
|
+
};
|
|
415
|
+
} catch {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function freezeConfiguration(binding, authBasePath, provider) {
|
|
420
|
+
return deepFreeze({
|
|
421
|
+
authBasePath,
|
|
422
|
+
binding,
|
|
423
|
+
configurationVersion: SUITE_ACCOUNTS_CLIENT_CONFIGURATION_VERSION,
|
|
424
|
+
provider,
|
|
425
|
+
wireVersion: SUITE_ACCOUNTS_WIRE_VERSION
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function createSuiteAccountsClientConfiguration(input) {
|
|
429
|
+
const binding = snapshotBinding(input);
|
|
430
|
+
if (binding === null)
|
|
431
|
+
return err2("invalid-binding");
|
|
432
|
+
if (!isSuiteAccountsCurrentConsumerId(binding.consumer)) {
|
|
433
|
+
return err2("invalid-consumer");
|
|
434
|
+
}
|
|
435
|
+
if (binding.environment !== "production") {
|
|
436
|
+
return err2("invalid-environment");
|
|
437
|
+
}
|
|
438
|
+
const consumer = binding.consumer;
|
|
439
|
+
const environment = binding.environment;
|
|
440
|
+
const registration = getSuiteAccountsCurrentConsumer(consumer);
|
|
441
|
+
const deployed = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
|
|
442
|
+
if (deployed === null || binding.origin !== deployed.siteUrl) {
|
|
443
|
+
return err2("invalid-origin");
|
|
444
|
+
}
|
|
445
|
+
if (binding.authMode !== registration.auth.kind) {
|
|
446
|
+
return err2("invalid-auth-mode");
|
|
447
|
+
}
|
|
448
|
+
const oauth = suiteAccountsCurrentOidcClientRegistration(consumer, environment);
|
|
449
|
+
const expectedClientId = oauth?.clientId ?? null;
|
|
450
|
+
const expectedCallbackUrl = oauth?.callbackUrl ?? null;
|
|
451
|
+
if (binding.clientId !== expectedClientId) {
|
|
452
|
+
return err2("invalid-client-id");
|
|
453
|
+
}
|
|
454
|
+
if (binding.callbackUrl !== expectedCallbackUrl) {
|
|
455
|
+
return err2("invalid-callback-url");
|
|
456
|
+
}
|
|
457
|
+
return ok2(freezeConfiguration({
|
|
458
|
+
authMode: registration.auth.kind,
|
|
459
|
+
callbackUrl: expectedCallbackUrl,
|
|
460
|
+
clientId: expectedClientId,
|
|
461
|
+
consumer,
|
|
462
|
+
environment,
|
|
463
|
+
origin: deployed.siteUrl
|
|
464
|
+
}, registration.auth.basePath, suiteAccountsOidcProviderConfiguration(environment)));
|
|
465
|
+
}
|
|
466
|
+
export {
|
|
467
|
+
createSuiteAccountsClientConfiguration,
|
|
468
|
+
SUITE_ACCOUNTS_WIRE_VERSION,
|
|
469
|
+
SUITE_ACCOUNTS_CLIENT_CONFIGURATION_VERSION
|
|
470
|
+
};
|