@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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +496 -0
  3. package/compiler-foundation.css +1 -0
  4. package/dist/auth-client.js +16 -0
  5. package/dist/auth-proxy.js +757 -0
  6. package/dist/bearer-verifier.js +1119 -0
  7. package/dist/browser-session.js +208 -0
  8. package/dist/client-configuration.js +470 -0
  9. package/dist/convex-browser-auth-browser.js +923 -0
  10. package/dist/convex-browser-auth-server.js +846 -0
  11. package/dist/convex-browser-auth.js +597 -0
  12. package/dist/entitlements.js +502 -0
  13. package/dist/identity/functions.js +44 -0
  14. package/dist/identity/index.js +1716 -0
  15. package/dist/identity/return-targets.js +33 -0
  16. package/dist/index.js +1133 -0
  17. package/dist/oidc-device-code.js +253 -0
  18. package/dist/oidc-rp.js +2259 -0
  19. package/dist/oidc-session-policy.js +5 -0
  20. package/dist/oidc-surface-server.js +2332 -0
  21. package/dist/profile-form.js +1934 -0
  22. package/dist/profile.js +17 -0
  23. package/dist/public-config.js +462 -0
  24. package/dist/react.js +137 -0
  25. package/dist/receipt-verifier.js +619 -0
  26. package/dist/registry.js +327 -0
  27. package/dist/stylex-manifest.json +1 -0
  28. package/dist/stylex.css +435 -0
  29. package/dist/urls.js +391 -0
  30. package/package.json +263 -0
  31. package/portfolio-inventory.json +37 -0
  32. package/src/auth-client.ts +37 -0
  33. package/src/auth-proxy.ts +473 -0
  34. package/src/bearer-verifier.ts +478 -0
  35. package/src/browser-session.ts +260 -0
  36. package/src/client-configuration.ts +172 -0
  37. package/src/convex-browser-auth-browser.ts +238 -0
  38. package/src/convex-browser-auth-server.ts +492 -0
  39. package/src/convex-browser-auth.ts +208 -0
  40. package/src/convex-url.ts +74 -0
  41. package/src/entitlements.ts +317 -0
  42. package/src/identity/catalog.ts +227 -0
  43. package/src/identity/consumers.ts +53 -0
  44. package/src/identity/functions.ts +70 -0
  45. package/src/identity/identifiers.ts +82 -0
  46. package/src/identity/index.ts +10 -0
  47. package/src/identity/links.ts +280 -0
  48. package/src/identity/membership.ts +156 -0
  49. package/src/identity/principals.ts +301 -0
  50. package/src/identity/profiles-v2.ts +330 -0
  51. package/src/identity/profiles.ts +524 -0
  52. package/src/identity/return-targets.ts +20 -0
  53. package/src/identity/usernames.ts +101 -0
  54. package/src/identity/views.ts +289 -0
  55. package/src/immutable.ts +25 -0
  56. package/src/index.ts +7 -0
  57. package/src/oidc-continuation.ts +59 -0
  58. package/src/oidc-device-code.ts +401 -0
  59. package/src/oidc-rp.ts +2217 -0
  60. package/src/oidc-session-policy.ts +6 -0
  61. package/src/oidc-surface-server.ts +186 -0
  62. package/src/profile-form.css +1 -0
  63. package/src/profile-form.stylex.ts +226 -0
  64. package/src/profile-form.tsx +314 -0
  65. package/src/profile.ts +33 -0
  66. package/src/public-config.ts +294 -0
  67. package/src/public-profile-form-result.ts +82 -0
  68. package/src/public-profile-form.tsx +319 -0
  69. package/src/public-profile-links.ts +100 -0
  70. package/src/react.tsx +196 -0
  71. package/src/receipt-verifier.ts +273 -0
  72. package/src/registry.ts +600 -0
  73. package/src/urls.ts +234 -0
package/src/urls.ts ADDED
@@ -0,0 +1,234 @@
1
+ import {
2
+ getSuiteAccountsCurrentConsumerEnvironment,
3
+ getSuiteAccountsConsumerEnvironment,
4
+ getSuiteAccountsDeployment,
5
+ isSuiteAccountsCurrentBrowserDeviceCodeConsumerId,
6
+ isSuiteAccountsCurrentConsumerId,
7
+ isSuiteAccountsCurrentDeviceClientId,
8
+ isSuiteAccountsCurrentOAuthConsumerId,
9
+ isSuiteAccountsConsumerId,
10
+ isSuiteAccountsOAuthConsumerId,
11
+ SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS,
12
+ SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS,
13
+ SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS,
14
+ SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS,
15
+ SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS,
16
+ type SuiteAccountsConsumerId,
17
+ type SuiteAccountsCurrentDeviceClient,
18
+ type SuiteAccountsCurrentDeviceClientId,
19
+ type SuiteAccountsCurrentOidcClientRegistration,
20
+ type SuiteAccountsOidcClientRegistration,
21
+ type SuiteAccountsRemoteEnvironment,
22
+ } from "./registry.js";
23
+ import { deepFreeze } from "./immutable.js";
24
+
25
+ export type SuiteAccountsCentralDestination = "account" | "home" | "login";
26
+ export const SUITE_ACCOUNTS_OAUTH_RESOURCE =
27
+ "https://hraness.com/suite" as const;
28
+
29
+ export type SuiteAccountsOidcProviderConfiguration = Readonly<{
30
+ authorizationEndpoint: string;
31
+ deviceAuthorizationEndpoint: string;
32
+ deviceTokenEndpoint: string;
33
+ discoveryEndpoint: string;
34
+ entitlementReceiptEndpoint: string;
35
+ identityLinkReceiptEndpoint: string;
36
+ issuer: string;
37
+ jwksEndpoint: string;
38
+ resource: typeof SUITE_ACCOUNTS_OAUTH_RESOURCE;
39
+ revocationEndpoint: string;
40
+ tokenEndpoint: string;
41
+ userInfoAudience: string;
42
+ }>;
43
+
44
+ const CENTRAL_PATHS = deepFreeze({
45
+ account: "/account",
46
+ home: "/",
47
+ login: "/login",
48
+ } as const satisfies Readonly<
49
+ Record<SuiteAccountsCentralDestination, `/${string}`>
50
+ >);
51
+
52
+ export function suiteAccountsCentralUrl(
53
+ environment: SuiteAccountsRemoteEnvironment,
54
+ destination: SuiteAccountsCentralDestination,
55
+ ): string {
56
+ return new URL(
57
+ CENTRAL_PATHS[destination],
58
+ getSuiteAccountsDeployment(environment).accountsOrigin,
59
+ ).href;
60
+ }
61
+
62
+ export function suiteAccountsBillingReturnUrl(
63
+ consumer: SuiteAccountsConsumerId,
64
+ environment: SuiteAccountsRemoteEnvironment,
65
+ ): string | null {
66
+ const consumerEnvironment = getSuiteAccountsConsumerEnvironment(
67
+ consumer,
68
+ environment,
69
+ );
70
+ if (consumerEnvironment === null) return null;
71
+ return consumerEnvironment.billingReturn.kind === "supported"
72
+ ? new URL(
73
+ consumerEnvironment.billingReturn.path,
74
+ consumerEnvironment.siteUrl,
75
+ ).href
76
+ : null;
77
+ }
78
+
79
+ export function suiteAccountsOidcClientRegistration(
80
+ consumer: unknown,
81
+ environment: SuiteAccountsRemoteEnvironment,
82
+ ): SuiteAccountsOidcClientRegistration | null {
83
+ if (
84
+ !isSuiteAccountsConsumerId(consumer)
85
+ || !isSuiteAccountsOAuthConsumerId(consumer)
86
+ ) return null;
87
+ const consumerEnvironment = getSuiteAccountsConsumerEnvironment(
88
+ consumer, environment,
89
+ );
90
+ if (consumerEnvironment === null) return null;
91
+ return deepFreeze({
92
+ callbackUrl: new URL(
93
+ "/api/suite-auth/callback",
94
+ consumerEnvironment.siteUrl,
95
+ ).href,
96
+ clientId: `hraness:${consumer}:${environment}:v1`,
97
+ });
98
+ }
99
+
100
+ /** Active registration, including reviewed origin migrations after v1. */
101
+ export function suiteAccountsCurrentOidcClientRegistration(
102
+ consumer: unknown,
103
+ environment: SuiteAccountsRemoteEnvironment,
104
+ ): SuiteAccountsCurrentOidcClientRegistration | null {
105
+ if (
106
+ !isSuiteAccountsCurrentConsumerId(consumer)
107
+ || !isSuiteAccountsCurrentOAuthConsumerId(consumer)
108
+ ) return null;
109
+ const consumerEnvironment = getSuiteAccountsCurrentConsumerEnvironment(
110
+ consumer,
111
+ environment,
112
+ );
113
+ if (consumerEnvironment === null) return null;
114
+ return deepFreeze({
115
+ callbackUrl: new URL(
116
+ "/api/suite-auth/callback",
117
+ consumerEnvironment.siteUrl,
118
+ ).href,
119
+ clientId: `hraness:${consumer}:${environment}:v1`,
120
+ });
121
+ }
122
+
123
+ /** Match an exact registered browser client that accepts email-code sessions. */
124
+ export function suiteAccountsOidcClientRequiresEmailOtp(
125
+ clientId: unknown,
126
+ ): boolean {
127
+ if (typeof clientId !== "string") return false;
128
+ return SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.some(consumer =>
129
+ suiteAccountsOidcClientRegistration(consumer, "production")?.clientId
130
+ === clientId
131
+ );
132
+ }
133
+
134
+ /** Match an exact current browser client that accepts email-code sessions. */
135
+ export function suiteAccountsCurrentOidcClientRequiresEmailOtp(
136
+ clientId: unknown,
137
+ ): boolean {
138
+ if (typeof clientId !== "string") return false;
139
+ return SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.some(
140
+ consumer =>
141
+ suiteAccountsCurrentOidcClientRegistration(
142
+ consumer,
143
+ "production",
144
+ )?.clientId === clientId,
145
+ );
146
+ }
147
+
148
+ /** RFC 8628 grant type; equal to `SUITE_OIDC_DEVICE_CODE_GRANT_TYPE`. */
149
+ const DEVICE_CODE_GRANT_TYPE =
150
+ "urn:ietf:params:oauth:grant-type:device_code" as const;
151
+
152
+ export type SuiteAccountsCurrentDeviceClientRegistration = Readonly<{
153
+ clientId:
154
+ `hraness:${SuiteAccountsCurrentDeviceClientId}:${SuiteAccountsRemoteEnvironment}:v1`;
155
+ consumer: SuiteAccountsCurrentDeviceClient["consumer"];
156
+ grantTypes: readonly [typeof DEVICE_CODE_GRANT_TYPE];
157
+ redirectUris: readonly [];
158
+ scopes: readonly ["openid", "email", "profile"];
159
+ tokenEndpointAuthMethod: "none";
160
+ }>;
161
+
162
+ /**
163
+ * Registration for a dedicated public device-authorization client. It has no
164
+ * redirect URI, no client secret, and exactly one grant.
165
+ */
166
+ export function suiteAccountsCurrentDeviceClientRegistration(
167
+ deviceClient: unknown,
168
+ environment: SuiteAccountsRemoteEnvironment,
169
+ ): SuiteAccountsCurrentDeviceClientRegistration | null {
170
+ if (!isSuiteAccountsCurrentDeviceClientId(deviceClient)) return null;
171
+ const registration: SuiteAccountsCurrentDeviceClient =
172
+ SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS[deviceClient];
173
+ if (
174
+ !registration.environments.includes(environment)
175
+ || suiteAccountsCurrentOidcClientRegistration(
176
+ registration.consumer,
177
+ environment,
178
+ ) === null
179
+ ) {
180
+ return null;
181
+ }
182
+ return deepFreeze({
183
+ clientId: `hraness:${deviceClient}:${environment}:v1`,
184
+ consumer: registration.consumer,
185
+ grantTypes: [DEVICE_CODE_GRANT_TYPE],
186
+ redirectUris: [],
187
+ scopes: ["openid", "email", "profile"],
188
+ tokenEndpointAuthMethod: "none",
189
+ } as const);
190
+ }
191
+
192
+ /**
193
+ * Match an exact client that may start the device-code grant: a dedicated
194
+ * device client, or a browser client on the closed pre-v0.9.20 list. A
195
+ * browser client whose product has a dedicated device client never matches.
196
+ */
197
+ export function suiteAccountsCurrentDeviceCodeClientAllowed(
198
+ clientId: unknown,
199
+ ): boolean {
200
+ if (typeof clientId !== "string") return false;
201
+ return SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS.some(deviceClient =>
202
+ suiteAccountsCurrentDeviceClientRegistration(deviceClient, "production")
203
+ ?.clientId === clientId
204
+ ) || SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS.some(consumer =>
205
+ isSuiteAccountsCurrentBrowserDeviceCodeConsumerId(consumer)
206
+ && suiteAccountsCurrentOidcClientRegistration(consumer, "production")
207
+ ?.clientId === clientId
208
+ );
209
+ }
210
+
211
+ export function suiteAccountsOidcProviderConfiguration(
212
+ environment: SuiteAccountsRemoteEnvironment,
213
+ ): SuiteAccountsOidcProviderConfiguration {
214
+ const issuer = getSuiteAccountsDeployment(environment).accountsOrigin;
215
+ const authBase = new URL("/api/auth/", issuer);
216
+ return deepFreeze({
217
+ authorizationEndpoint: new URL("oauth2/authorize", authBase).href,
218
+ deviceAuthorizationEndpoint:
219
+ new URL("oauth2/device_authorization", authBase).href,
220
+ deviceTokenEndpoint: new URL("oauth2/device/token", authBase).href,
221
+ discoveryEndpoint:
222
+ new URL("/.well-known/openid-configuration", issuer).href,
223
+ entitlementReceiptEndpoint:
224
+ new URL("/suite/entitlements/receipt", issuer).href,
225
+ identityLinkReceiptEndpoint:
226
+ new URL("/suite/identity-links/receipt", issuer).href,
227
+ issuer,
228
+ jwksEndpoint: new URL("jwks", authBase).href,
229
+ resource: SUITE_ACCOUNTS_OAUTH_RESOURCE,
230
+ revocationEndpoint: new URL("oauth2/revoke", authBase).href,
231
+ tokenEndpoint: new URL("oauth2/token", authBase).href,
232
+ userInfoAudience: new URL("oauth2/userinfo", authBase).href,
233
+ });
234
+ }