@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
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Refresh a central OAuth bearer before downstream short-token issuance would
3
+ * leave too little useful lifetime. The browser refresh coordinator and every
4
+ * server exchange share this exact window.
5
+ */
6
+ export const SUITE_OIDC_EARLY_REFRESH_WINDOW_MS = 30_000;
@@ -0,0 +1,186 @@
1
+ import {
2
+ createSuiteOidcRelyingParty,
3
+ type SuiteOidcRelyingParty,
4
+ type SuiteOidcServerAccountSession,
5
+ type SuiteOidcServerVerifiedAccountEmail,
6
+ type SuiteOidcServerSession,
7
+ type SuiteOidcServerVerifiedEmail,
8
+ } from "./oidc-rp.js";
9
+ import {
10
+ getSuiteAccountsCurrentConsumerEnvironment,
11
+ isSuiteAccountsCurrentLinkedOidcConsumerId,
12
+ type SuiteAccountsCurrentOidcConsumerId,
13
+ type SuiteAccountsRemoteEnvironment,
14
+ } from "./registry.js";
15
+
16
+ export type SuiteOidcSurfaceEnvironment = Readonly<{
17
+ NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN?: string | undefined;
18
+ NEXT_PUBLIC_SITE_URL?: string | undefined;
19
+ SUITE_IDENTITY_RECEIPT_KEY_VERSION?: string | undefined;
20
+ SUITE_OIDC_COOKIE_SECRET?: string | undefined;
21
+ }>;
22
+
23
+ export function suiteEnvironmentForConsumerOrigin(
24
+ consumer: SuiteAccountsCurrentOidcConsumerId,
25
+ siteUrl: string | undefined,
26
+ ): SuiteAccountsRemoteEnvironment | null {
27
+ if (siteUrl === undefined) return null;
28
+ return getSuiteAccountsCurrentConsumerEnvironment(
29
+ consumer,
30
+ "production",
31
+ )?.siteUrl === siteUrl
32
+ ? "production"
33
+ : null;
34
+ }
35
+
36
+ function processEnvironment(): SuiteOidcSurfaceEnvironment {
37
+ return {
38
+ NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN:
39
+ process.env.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN,
40
+ NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
41
+ SUITE_IDENTITY_RECEIPT_KEY_VERSION:
42
+ process.env.SUITE_IDENTITY_RECEIPT_KEY_VERSION,
43
+ SUITE_OIDC_COOKIE_SECRET: process.env.SUITE_OIDC_COOKIE_SECRET,
44
+ };
45
+ }
46
+
47
+ export function createSurfaceSuiteRelyingParty(
48
+ consumer: SuiteAccountsCurrentOidcConsumerId,
49
+ injectedEnvironment: SuiteOidcSurfaceEnvironment = processEnvironment(),
50
+ ): SuiteOidcRelyingParty | null {
51
+ const environment = suiteEnvironmentForConsumerOrigin(
52
+ consumer,
53
+ injectedEnvironment.NEXT_PUBLIC_SITE_URL,
54
+ );
55
+ const cookieSecret = injectedEnvironment.SUITE_OIDC_COOKIE_SECRET;
56
+ const receiptKeyVersion = isSuiteAccountsCurrentLinkedOidcConsumerId(consumer)
57
+ ? injectedEnvironment.SUITE_IDENTITY_RECEIPT_KEY_VERSION
58
+ : "identity-v1";
59
+ if (
60
+ injectedEnvironment.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN !== undefined
61
+ ||
62
+ environment === null
63
+ || cookieSecret === undefined
64
+ || receiptKeyVersion === undefined
65
+ ) {
66
+ return null;
67
+ }
68
+ try {
69
+ return createSuiteOidcRelyingParty({
70
+ consumer,
71
+ cookieSecret,
72
+ environment,
73
+ receiptKeyVersion,
74
+ });
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ function unavailable(): Response {
81
+ return Response.json(
82
+ {
83
+ error: {
84
+ code: "SUITE_OIDC_UNAVAILABLE",
85
+ message: "Suite sign-in is not configured for this surface.",
86
+ retryable: false,
87
+ },
88
+ schemaVersion: 1,
89
+ },
90
+ { headers: { "cache-control": "no-store" }, status: 503 },
91
+ );
92
+ }
93
+
94
+ export function suiteOidcSurfaceHandler(
95
+ consumer: SuiteAccountsCurrentOidcConsumerId,
96
+ options: Readonly<{
97
+ createRelyingParty?: (
98
+ request: Request,
99
+ ) => Pick<SuiteOidcRelyingParty, "handle"> | null;
100
+ }> = {},
101
+ ): (request: Request) => Promise<Response> {
102
+ return async (request: Request): Promise<Response> => {
103
+ if (process.env.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN !== undefined) {
104
+ return unavailable();
105
+ }
106
+ const relyingParty = options.createRelyingParty?.(request)
107
+ ?? createSurfaceSuiteRelyingParty(consumer);
108
+ return relyingParty === null
109
+ ? unavailable()
110
+ : await relyingParty.handle(request);
111
+ };
112
+ }
113
+
114
+ export async function suiteOidcSurfaceServerSession(
115
+ consumer: SuiteAccountsCurrentOidcConsumerId,
116
+ request: Request,
117
+ injectedEnvironment?: SuiteOidcSurfaceEnvironment,
118
+ ): Promise<SuiteOidcServerSession | null> {
119
+ const relyingParty = createSurfaceSuiteRelyingParty(
120
+ consumer,
121
+ injectedEnvironment,
122
+ );
123
+ return relyingParty === null ? null : await relyingParty.serverSession(request);
124
+ }
125
+
126
+ /**
127
+ * Returns the verified server-held account bearer before username onboarding
128
+ * completes. Product surfaces that require a permanent username must continue
129
+ * to use `suiteOidcSurfaceServerSession`.
130
+ */
131
+ export async function suiteOidcSurfaceServerAccountSession(
132
+ consumer: SuiteAccountsCurrentOidcConsumerId,
133
+ request: Request,
134
+ injectedEnvironment?: SuiteOidcSurfaceEnvironment,
135
+ ): Promise<SuiteOidcServerAccountSession | null> {
136
+ const relyingParty = createSurfaceSuiteRelyingParty(
137
+ consumer,
138
+ injectedEnvironment,
139
+ );
140
+ return relyingParty === null
141
+ ? null
142
+ : await relyingParty.serverAccountSession(request);
143
+ }
144
+
145
+ /**
146
+ * Returns a live verified email for a valid Suite account session, including
147
+ * sessions whose optional username onboarding is incomplete.
148
+ */
149
+ export async function suiteOidcSurfaceServerVerifiedAccountEmail(
150
+ consumer: SuiteAccountsCurrentOidcConsumerId,
151
+ request: Request,
152
+ injectedEnvironment?: SuiteOidcSurfaceEnvironment,
153
+ options: Readonly<{
154
+ createRelyingParty?: (
155
+ request: Request,
156
+ ) => Pick<SuiteOidcRelyingParty, "serverVerifiedAccountEmail"> | null;
157
+ }> = {},
158
+ ): Promise<SuiteOidcServerVerifiedAccountEmail | null> {
159
+ const relyingParty = options.createRelyingParty?.(request)
160
+ ?? createSurfaceSuiteRelyingParty(consumer, injectedEnvironment);
161
+ return relyingParty === null
162
+ ? null
163
+ : await relyingParty.serverVerifiedAccountEmail(request);
164
+ }
165
+
166
+ /**
167
+ * Returns the live verified email together with the Suite account it belongs
168
+ * to. The email remains a server-only projection and is never copied into the
169
+ * browser session or relying-party cookie.
170
+ */
171
+ export async function suiteOidcSurfaceServerVerifiedEmail(
172
+ consumer: SuiteAccountsCurrentOidcConsumerId,
173
+ request: Request,
174
+ injectedEnvironment?: SuiteOidcSurfaceEnvironment,
175
+ options: Readonly<{
176
+ createRelyingParty?: (
177
+ request: Request,
178
+ ) => Pick<SuiteOidcRelyingParty, "serverVerifiedEmail"> | null;
179
+ }> = {},
180
+ ): Promise<SuiteOidcServerVerifiedEmail | null> {
181
+ const relyingParty = options.createRelyingParty?.(request)
182
+ ?? createSurfaceSuiteRelyingParty(consumer, injectedEnvironment);
183
+ return relyingParty === null
184
+ ? null
185
+ : await relyingParty.serverVerifiedEmail(request);
186
+ }
@@ -0,0 +1 @@
1
+ @import "../dist/stylex.css";
@@ -0,0 +1,226 @@
1
+ import * as stylex from "@stylexjs/stylex";
2
+
3
+ const styles = stylex.create({
4
+ form: { display: "grid", gap: "1rem", width: "100%" },
5
+ field: { display: "grid", gap: "0.35rem" },
6
+ visibility: {
7
+ borderWidth: 0, marginBottom: 0, marginLeft: 0, marginRight: 0,
8
+ marginTop: "0.5rem", minWidth: 0, paddingBottom: 0, paddingLeft: 0,
9
+ paddingRight: 0, paddingTop: "0.5rem",
10
+ },
11
+ note: {
12
+ color: "var(--suite-profile-muted, currentColor)", fontSize: "0.9rem",
13
+ lineHeight: 1.5, marginBottom: "0.5rem", marginLeft: 0,
14
+ marginRight: 0, marginTop: 0, overflowWrap: "anywhere",
15
+ },
16
+ choice: {
17
+ alignItems: "center", cursor: "pointer", display: "flex", gap: "0.65rem",
18
+ minHeight: "2.75rem",
19
+ },
20
+ radio: {
21
+ accentColor: "var(--suite-profile-focus, currentColor)", flexShrink: 0,
22
+ height: "1.1rem", marginBottom: 0, marginLeft: 0, marginRight: 0,
23
+ marginTop: 0, width: "1.1rem",
24
+ },
25
+ label: { fontSize: "0.9rem", fontWeight: 700 },
26
+ control: {
27
+ backgroundAttachment: "scroll",
28
+ backgroundClip: "border-box",
29
+ backgroundColor: "var(--suite-profile-input-background, transparent)",
30
+ backgroundImage: "none",
31
+ backgroundOrigin: "padding-box",
32
+ backgroundPosition: "0% 0%",
33
+ backgroundRepeat: "repeat",
34
+ backgroundSize: "auto auto",
35
+ borderColor: "var(--suite-profile-line, currentColor)",
36
+ borderStyle: "solid",
37
+ borderWidth: "1px",
38
+ borderImageSource: "none",
39
+ borderImageSlice: "100%",
40
+ borderImageWidth: 1,
41
+ borderImageOutset: 0,
42
+ borderImageRepeat: "stretch",
43
+ borderRadius: "0.35rem",
44
+ color: "inherit",
45
+ fontFamily: "inherit",
46
+ fontSize: "inherit",
47
+ fontStretch: "inherit",
48
+ fontStyle: "inherit",
49
+ fontVariant: "inherit",
50
+ fontWeight: "inherit",
51
+ fontFeatureSettings: "inherit",
52
+ fontKerning: "inherit",
53
+ fontLanguageOverride: "inherit",
54
+ fontOpticalSizing: "inherit",
55
+ fontSizeAdjust: "inherit",
56
+ fontVariationSettings: "inherit",
57
+ lineHeight: 1.4,
58
+ minWidth: 0,
59
+ paddingBottom: "0.7rem",
60
+ paddingLeft: "0.75rem",
61
+ paddingRight: "0.75rem",
62
+ paddingTop: "0.7rem",
63
+ width: "100%",
64
+ },
65
+ textarea: { resize: "vertical" },
66
+ focus: {
67
+ outlineColor: { default: null, ":focus-visible": "var(--suite-profile-focus, currentColor)" },
68
+ outlineOffset: { default: null, ":focus-visible": "2px" },
69
+ outlineStyle: { default: null, ":focus-visible": "solid" },
70
+ outlineWidth: { default: null, ":focus-visible": "2px" },
71
+ },
72
+ readonly: {
73
+ color: "var(--suite-profile-muted, currentColor)",
74
+ cursor: "default",
75
+ opacity: 0.72,
76
+ },
77
+ button: {
78
+ backgroundAttachment: "scroll",
79
+ backgroundClip: "border-box",
80
+ backgroundColor: "var(--suite-profile-button-background, currentColor)",
81
+ backgroundImage: "none",
82
+ backgroundOrigin: "padding-box",
83
+ backgroundPosition: "0% 0%",
84
+ backgroundRepeat: "repeat",
85
+ backgroundSize: "auto auto",
86
+ borderColor: "var(--suite-profile-button-background, currentColor)",
87
+ borderStyle: "solid",
88
+ borderWidth: "1px",
89
+ borderImageSource: "none",
90
+ borderImageSlice: "100%",
91
+ borderImageWidth: 1,
92
+ borderImageOutset: 0,
93
+ borderImageRepeat: "stretch",
94
+ borderRadius: "999px",
95
+ color: "var(--suite-profile-button-foreground, Canvas)",
96
+ cursor: { default: "pointer", ":disabled": "wait" },
97
+ fontFamily: "inherit",
98
+ fontSize: "inherit",
99
+ fontStretch: "inherit",
100
+ fontStyle: "inherit",
101
+ fontVariant: "inherit",
102
+ fontWeight: 700,
103
+ fontFeatureSettings: "inherit",
104
+ fontKerning: "inherit",
105
+ fontLanguageOverride: "inherit",
106
+ fontOpticalSizing: "inherit",
107
+ fontSizeAdjust: "inherit",
108
+ fontVariationSettings: "inherit",
109
+ lineHeight: "inherit",
110
+ justifySelf: "start",
111
+ opacity: { default: null, ":disabled": 0.6 },
112
+ paddingBottom: "0.65rem",
113
+ paddingLeft: "1rem",
114
+ paddingRight: "1rem",
115
+ paddingTop: "0.65rem",
116
+ },
117
+ error: {
118
+ color: "var(--suite-profile-error, #a83232)",
119
+ fontSize: "0.9rem",
120
+ marginBottom: 0,
121
+ marginLeft: 0,
122
+ marginRight: 0,
123
+ marginTop: 0,
124
+ },
125
+ links: {
126
+ borderColor: "var(--suite-profile-line, currentColor)",
127
+ borderStyle: "solid",
128
+ borderTopWidth: "1px",
129
+ borderBottomWidth: 0,
130
+ borderLeftWidth: 0,
131
+ borderRightWidth: 0,
132
+ marginLeft: 0,
133
+ marginRight: 0,
134
+ minWidth: 0,
135
+ paddingTop: "0.75rem",
136
+ },
137
+ linksSummary: {
138
+ color: "var(--suite-profile-muted, currentColor)",
139
+ cursor: "pointer",
140
+ fontSize: "0.9rem",
141
+ fontWeight: 700,
142
+ paddingBottom: "0.25rem",
143
+ paddingTop: "0.25rem",
144
+ },
145
+ linksHint: {
146
+ fontWeight: 400,
147
+ marginLeft: "0.5rem",
148
+ },
149
+ linksBody: {
150
+ display: "grid",
151
+ gap: "1rem",
152
+ paddingTop: "0.75rem",
153
+ },
154
+ affix: {
155
+ alignItems: "center",
156
+ backgroundColor: "var(--suite-profile-input-background, transparent)",
157
+ borderColor: "var(--suite-profile-line, currentColor)",
158
+ borderRadius: "0.35rem",
159
+ borderStyle: "solid",
160
+ borderWidth: "1px",
161
+ display: "flex",
162
+ minWidth: 0,
163
+ opacity: { default: null, ":has(:disabled)": 0.6 },
164
+ outlineColor: { default: null, ":focus-within": "var(--suite-profile-focus, currentColor)" },
165
+ outlineOffset: { default: null, ":focus-within": "2px" },
166
+ outlineStyle: { default: null, ":focus-within": "solid" },
167
+ outlineWidth: { default: null, ":focus-within": "2px" },
168
+ },
169
+ affixLead: {
170
+ alignItems: "center",
171
+ color: "var(--suite-profile-muted, currentColor)",
172
+ display: "flex",
173
+ flexShrink: 0,
174
+ fontSize: "0.85rem",
175
+ gap: "0.45rem",
176
+ paddingLeft: "0.75rem",
177
+ whiteSpace: "nowrap",
178
+ },
179
+ affixInput: {
180
+ backgroundColor: "transparent",
181
+ borderWidth: 0,
182
+ color: "inherit",
183
+ flexGrow: 1,
184
+ flexShrink: 1,
185
+ fontFamily: "inherit",
186
+ fontSize: "inherit",
187
+ fontStyle: "inherit",
188
+ fontWeight: "inherit",
189
+ lineHeight: 1.4,
190
+ minWidth: "2rem",
191
+ outlineStyle: "none",
192
+ paddingBottom: "0.7rem",
193
+ paddingLeft: "0.5rem",
194
+ paddingRight: "0.75rem",
195
+ paddingTop: "0.7rem",
196
+ },
197
+ mark: {
198
+ display: "block",
199
+ flexShrink: 0,
200
+ height: "1.05rem",
201
+ width: "1.05rem",
202
+ },
203
+ });
204
+
205
+ export const profileFormClasses = {
206
+ affix: stylex.props(styles.affix).className,
207
+ affixInput: stylex.props(styles.affixInput).className,
208
+ affixLead: stylex.props(styles.affixLead).className,
209
+ choice: stylex.props(styles.choice).className,
210
+ button: stylex.props(styles.button, styles.focus).className,
211
+ control: stylex.props(styles.control, styles.focus).className,
212
+ email: stylex.props(styles.control, styles.focus, styles.readonly).className,
213
+ error: stylex.props(styles.error).className,
214
+ field: stylex.props(styles.field).className,
215
+ form: stylex.props(styles.form).className,
216
+ label: stylex.props(styles.label).className,
217
+ links: stylex.props(styles.links).className,
218
+ linksBody: stylex.props(styles.linksBody).className,
219
+ linksHint: stylex.props(styles.linksHint).className,
220
+ linksSummary: stylex.props(styles.linksSummary).className,
221
+ mark: stylex.props(styles.mark).className,
222
+ note: stylex.props(styles.note).className,
223
+ radio: stylex.props(styles.radio, styles.focus).className,
224
+ textarea: stylex.props(styles.control, styles.focus, styles.textarea).className,
225
+ visibility: stylex.props(styles.visibility).className,
226
+ } as const;