@object-ui/auth 17.3.0 → 17.6.0
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/CHANGELOG.md +213 -0
- package/README.md +184 -13
- package/dist/ActiveOrganizationStorage.d.ts +168 -0
- package/dist/ActiveOrganizationStorage.d.ts.map +1 -0
- package/dist/ActiveOrganizationStorage.js +515 -0
- package/dist/AuthContext.d.ts +34 -1
- package/dist/AuthContext.d.ts.map +1 -1
- package/dist/AuthGuard.d.ts +8 -1
- package/dist/AuthGuard.d.ts.map +1 -1
- package/dist/AuthGuard.js +16 -3
- package/dist/AuthProvider.d.ts +1 -1
- package/dist/AuthProvider.d.ts.map +1 -1
- package/dist/AuthProvider.js +359 -32
- package/dist/ForgotPasswordForm.d.ts +1 -1
- package/dist/ForgotPasswordForm.d.ts.map +1 -1
- package/dist/ForgotPasswordForm.js +5 -5
- package/dist/LoginForm.d.ts +1 -1
- package/dist/LoginForm.d.ts.map +1 -1
- package/dist/LoginForm.js +4 -4
- package/dist/PreviewBanner.js +1 -1
- package/dist/RegisterForm.d.ts +1 -1
- package/dist/RegisterForm.d.ts.map +1 -1
- package/dist/RegisterForm.js +3 -3
- package/dist/SocialSignInButtons.js +2 -2
- package/dist/UserMenu.js +2 -2
- package/dist/createAuthClient.d.ts +23 -1
- package/dist/createAuthClient.d.ts.map +1 -1
- package/dist/createAuthClient.js +112 -25
- package/dist/createAuthenticatedFetch.d.ts +9 -9
- package/dist/createAuthenticatedFetch.d.ts.map +1 -1
- package/dist/createAuthenticatedFetch.js +82 -37
- package/dist/index.d.ts +20 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -17
- package/dist/invitation-status.d.ts +59 -0
- package/dist/invitation-status.d.ts.map +1 -0
- package/dist/invitation-status.js +32 -0
- package/dist/types.d.ts +52 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/useAuth.d.ts +1 -1
- package/dist/useAuth.d.ts.map +1 -1
- package/dist/useAuth.js +6 -1
- package/dist/useWorkspaceAdminStatus.d.ts +85 -0
- package/dist/useWorkspaceAdminStatus.d.ts.map +1 -0
- package/dist/useWorkspaceAdminStatus.js +102 -0
- package/package.json +5 -5
- package/dist/useIsWorkspaceAdmin.d.ts +0 -21
- package/dist/useIsWorkspaceAdmin.d.ts.map +0 -1
- package/dist/useIsWorkspaceAdmin.js +0 -47
package/dist/AuthProvider.js
CHANGED
|
@@ -6,11 +6,113 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
* This source code is licensed under the MIT license found in the
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
|
-
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
10
|
-
import { authGateEvents } from './auth-gate-events';
|
|
11
|
-
import { AuthCtx } from './AuthContext';
|
|
12
|
-
import { createAuthClient } from './createAuthClient';
|
|
13
|
-
import { ActiveOrganizationStorage } from './
|
|
9
|
+
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
10
|
+
import { authGateEvents } from './auth-gate-events.js';
|
|
11
|
+
import { AuthCtx } from './AuthContext.js';
|
|
12
|
+
import { createAuthClient, TokenStorage } from './createAuthClient.js';
|
|
13
|
+
import { ActiveOrganizationStorage, SessionUserScope } from './ActiveOrganizationStorage.js';
|
|
14
|
+
/**
|
|
15
|
+
* Prefix of every `MetadataProvider` seed entry in `sessionStorage`
|
|
16
|
+
* (`objectui:metadata:<type>:<orgId>:<principal>`, `@object-ui/app-shell`).
|
|
17
|
+
*
|
|
18
|
+
* Spelled out here rather than imported: `@object-ui/app-shell` depends on
|
|
19
|
+
* `@object-ui/auth`, so the constant cannot travel in that direction without a
|
|
20
|
+
* dependency cycle. The two ends are held together by BEHAVIOUR instead of by
|
|
21
|
+
* a shared symbol — `MetadataProvider.crossPrincipalSeed.test.tsx` fills the
|
|
22
|
+
* cache by running the real `MetadataProvider` and empties it by signing out
|
|
23
|
+
* through this provider, so a prefix that drifts on either side fails there
|
|
24
|
+
* rather than silently purging nothing.
|
|
25
|
+
*/
|
|
26
|
+
const METADATA_SEED_CACHE_PREFIX = 'objectui:metadata:';
|
|
27
|
+
/**
|
|
28
|
+
* Drop the client-side caches that belong to the session being ended
|
|
29
|
+
* (objectui#5198).
|
|
30
|
+
*
|
|
31
|
+
* `sessionStorage` is per-TAB, not per-session, and no sign-out call site
|
|
32
|
+
* reloads the page — `AppSidebar`, `AppHeader`, `UserMenu` and
|
|
33
|
+
* `RemediationOverlay` all just call `signOut()` and let the SPA keep running.
|
|
34
|
+
* So without this the next person to sign in in the same tab (a shared or
|
|
35
|
+
* kiosk browser, a handover, a support session) is SEEDED from the previous
|
|
36
|
+
* user's entry, and what is in it is that user's PERMISSION-FILTERED app list —
|
|
37
|
+
* the server filters `GET /api/v1/meta/:type` per session. That makes it a
|
|
38
|
+
* cross-principal disclosure, not ordinary staleness, which is why it is
|
|
39
|
+
* cleared here and not left to the tab being closed.
|
|
40
|
+
*
|
|
41
|
+
* Two properties of the loop are load-bearing:
|
|
42
|
+
*
|
|
43
|
+
* - It matches by PREFIX. The keys are org-scoped (objectui#4486), so a purge
|
|
44
|
+
* that recomputed the scope would depend on `ActiveOrganizationStorage`
|
|
45
|
+
* still holding the org those entries were written under. A prefix sweep has
|
|
46
|
+
* no such dependency and therefore no ordering hazard of its own.
|
|
47
|
+
* - It runs BEFORE `ActiveOrganizationStorage.clear()` anyway, so the ordering
|
|
48
|
+
* stays correct for anything scope-derived added later: clearing the org
|
|
49
|
+
* first would leave such a purge computing the no-org scope and deleting
|
|
50
|
+
* nothing while the real entries survived.
|
|
51
|
+
*
|
|
52
|
+
* `MetadataProvider` additionally keys the entry by session fingerprint, so a
|
|
53
|
+
* blob that escapes this purge (a tab open across the upgrade, a session ended
|
|
54
|
+
* by expiry rather than by this call) is unreadable rather than merely
|
|
55
|
+
* undeleted. The two halves are deliberately independent.
|
|
56
|
+
*
|
|
57
|
+
* ## The `try` guards the SNAPSHOT, not the walk (objectui#5777)
|
|
58
|
+
*
|
|
59
|
+
* `Object.keys(sessionStorage)` can itself throw — partitioned iframes, some
|
|
60
|
+
* privacy modes — and that is the reason a guard exists here at all, so it
|
|
61
|
+
* stays. What it must NOT do is wrap the loop: a `removeItem` that throws on
|
|
62
|
+
* key `n` would abort the walk, leaving every still-unvisited MATCHING key
|
|
63
|
+
* unswept, with the failure swallowed so the caller believes the sweep
|
|
64
|
+
* completed. The keys this purges are a cross-principal disclosure risk on a
|
|
65
|
+
* shared browser (objectui#5198), not ordinary staleness — a partial sweep
|
|
66
|
+
* leaves an arbitrary subset of the previous principal's permission-filtered
|
|
67
|
+
* app list readable to whoever signs in next in this tab. So each
|
|
68
|
+
* `removeItem` gets its own `try`: one uncooperative key costs exactly that
|
|
69
|
+
* key. Same defect class and same remedy as `sweepStore` in
|
|
70
|
+
* `ActiveOrganizationStorage.ts` (objectui#5763) — different file and
|
|
71
|
+
* different caller (sign-out, not sign-in) is why it is fixed here rather
|
|
72
|
+
* than there.
|
|
73
|
+
*
|
|
74
|
+
* ## Reported, not quarantined — same reason as `sweepStore`
|
|
75
|
+
*
|
|
76
|
+
* This function walks keys it does not own reads for: `MetadataProvider`
|
|
77
|
+
* (`@object-ui/app-shell`) is the reader of the seed cache, not this
|
|
78
|
+
* provider — there is no local `get()` here to guard, so there is nothing to
|
|
79
|
+
* quarantine the way `ActiveOrganizationStorage.clear()` quarantines a key
|
|
80
|
+
* (objectui#5731). What IS mirrored is the reporting channel: a key whose
|
|
81
|
+
* `removeItem` throws is named in a `console.warn`, so a partial sweep is
|
|
82
|
+
* discoverable instead of silent. The caller (`signOut`, above) cannot act on
|
|
83
|
+
* it either way — the session is already ending — so, like `sweepStore` on
|
|
84
|
+
* the sign-in path, this must not throw.
|
|
85
|
+
*/
|
|
86
|
+
function purgeSignedOutClientCaches() {
|
|
87
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
88
|
+
let keys;
|
|
89
|
+
try {
|
|
90
|
+
// Snapshot the keys first (`Object.keys`) — removing entries during a
|
|
91
|
+
// live index walk shifts the ones behind it and skips half of them.
|
|
92
|
+
// Same idiom as the `MarketplacePackagePage` purge loop.
|
|
93
|
+
keys = Object.keys(sessionStorage);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
keys = []; /* storage unavailable */
|
|
97
|
+
}
|
|
98
|
+
const unswept = [];
|
|
99
|
+
for (const key of keys) {
|
|
100
|
+
if (!key.startsWith(METADATA_SEED_CACHE_PREFIX))
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
sessionStorage.removeItem(key);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
unswept.push(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (unswept.length > 0) {
|
|
110
|
+
console.warn(`[purgeSignedOutClientCaches] could not remove ${unswept.length} key(s) from sessionStorage: ` +
|
|
111
|
+
`${unswept.join(', ')}. The signed-out user's metadata seed cache may still be readable under these keys.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
ActiveOrganizationStorage.clear();
|
|
115
|
+
}
|
|
14
116
|
/**
|
|
15
117
|
* Authentication context provider.
|
|
16
118
|
*
|
|
@@ -49,12 +151,113 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
49
151
|
const [activeOrganization, setActiveOrganization] = useState(null);
|
|
50
152
|
const [activeMember, setActiveMember] = useState(null);
|
|
51
153
|
const [isOrganizationsLoading, setIsOrganizationsLoading] = useState(false);
|
|
154
|
+
// objectui#5619 — the two halves of "the membership answer has landed".
|
|
155
|
+
// Kept as POSITIVE resolution flags rather than another `isLoading`: a
|
|
156
|
+
// loading flag reads `false` both before the request starts and after it
|
|
157
|
+
// finishes, and that is precisely the ambiguity this card exists to remove.
|
|
158
|
+
const [organizationsResolved, setOrganizationsResolved] = useState(false);
|
|
159
|
+
const [activeMemberResolved, setActiveMemberResolved] = useState(false);
|
|
52
160
|
// Determine if we're in preview mode
|
|
53
161
|
const isPreviewMode = previewMode != null;
|
|
54
162
|
// If auth is disabled or in preview mode, automatically set as authenticated
|
|
55
163
|
const isAuthenticated = (enabled && !isPreviewMode)
|
|
56
164
|
? user !== null && session !== null
|
|
57
165
|
: true;
|
|
166
|
+
// True while `loadSession` is awaiting the server. Read by the rotation
|
|
167
|
+
// subscription below: a rotation the server performs INSIDE our own
|
|
168
|
+
// `getSession` is already reflected in the answer we are about to apply, and
|
|
169
|
+
// re-entering on it would loop this provider against the server forever.
|
|
170
|
+
const sessionLoadInFlight = useRef(false);
|
|
171
|
+
// objectui#5750 — the org id `switchOrganization` last resolved to, tracked
|
|
172
|
+
// independently of React state so a switch that starts before the PREVIOUS
|
|
173
|
+
// switch's render has committed still compares against a value, not a stale
|
|
174
|
+
// closure. Kept in sync with every path that can move `activeOrganization`
|
|
175
|
+
// (not just `switchOrganization` itself — `refreshOrganizations`'
|
|
176
|
+
// single-membership repair also does), via the effect below.
|
|
177
|
+
const activeOrgIdRef = useRef(null);
|
|
178
|
+
// Bumped by every `switchOrganization` call that decides to re-resolve
|
|
179
|
+
// identity. Lets an EARLIER switch's still-in-flight `getSession()` notice
|
|
180
|
+
// a LATER switch superseded it and discard its answer instead of applying
|
|
181
|
+
// stale, previous-organization data over the newer one (see
|
|
182
|
+
// `switchOrganization`).
|
|
183
|
+
const sessionLoadGeneration = useRef(0);
|
|
184
|
+
/**
|
|
185
|
+
* The ONE session loader. Runs on mount (below) and on every later
|
|
186
|
+
* re-resolution — `refreshSession` on the context, and the rotation
|
|
187
|
+
* subscription — so "how the console learns who it is" has a single
|
|
188
|
+
* implementation rather than one per caller.
|
|
189
|
+
*
|
|
190
|
+
* `isCancelled` mirrors `refreshOrganizations` further down: an unmount or a
|
|
191
|
+
* superseding call must not write state, and must not clear the loading flag
|
|
192
|
+
* on behalf of a newer load.
|
|
193
|
+
*/
|
|
194
|
+
const loadSession = useCallback(async (isCancelled) => {
|
|
195
|
+
sessionLoadInFlight.current = true;
|
|
196
|
+
try {
|
|
197
|
+
const result = await client.getSession();
|
|
198
|
+
if (isCancelled?.())
|
|
199
|
+
return;
|
|
200
|
+
if (result) {
|
|
201
|
+
setUser(result.user);
|
|
202
|
+
setSession(result.session);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// A re-resolution that comes back empty means the session ENDED
|
|
206
|
+
// (revoked, expired, signed out in another tab). On mount both are
|
|
207
|
+
// already null, so this changes nothing there.
|
|
208
|
+
setUser(null);
|
|
209
|
+
setSession(null);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
if (isCancelled?.())
|
|
214
|
+
return;
|
|
215
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
sessionLoadInFlight.current = false;
|
|
219
|
+
if (!isCancelled?.()) {
|
|
220
|
+
setIsLoading(false);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, [client]);
|
|
224
|
+
/**
|
|
225
|
+
* objectui#4467 — re-resolve identity in place. Never raises `isLoading`:
|
|
226
|
+
* the console stays on screen while the answer is fetched (`loadSession`
|
|
227
|
+
* only ever lowers the flag). Guest / preview identities are synthetic and
|
|
228
|
+
* have no server to ask, so this is a no-op there.
|
|
229
|
+
*/
|
|
230
|
+
const refreshSession = useCallback(async () => {
|
|
231
|
+
if (!enabled || isPreviewMode)
|
|
232
|
+
return;
|
|
233
|
+
await loadSession();
|
|
234
|
+
}, [enabled, isPreviewMode, loadSession]);
|
|
235
|
+
/**
|
|
236
|
+
* objectui#4467 — an OWNERLESS session rotation re-resolves identity.
|
|
237
|
+
*
|
|
238
|
+
* `TokenStorage` notifies only when a token we already held is replaced by a
|
|
239
|
+
* different one (see its header). Sign-in / sign-out do not qualify: those
|
|
240
|
+
* callers update identity themselves. What does qualify is a rotation
|
|
241
|
+
* observed on the wire by code that has no idea it just changed who the user
|
|
242
|
+
* is — `createAuthenticatedFetch` capturing `set-auth-token` from a generic
|
|
243
|
+
* metadata action, which is exactly how the console starts and stops
|
|
244
|
+
* impersonation.
|
|
245
|
+
*
|
|
246
|
+
* This is the honest seam for the refresh. The console's action runtime
|
|
247
|
+
* executes `type: 'api'` actions generically; it cannot know that one
|
|
248
|
+
* particular endpoint was auth-relevant without hard-coding that endpoint
|
|
249
|
+
* into a generic runtime. The rotation IS the signal, and it is the server's
|
|
250
|
+
* own declaration rather than our guess about the URL.
|
|
251
|
+
*/
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (!enabled || isPreviewMode)
|
|
254
|
+
return;
|
|
255
|
+
return TokenStorage.subscribeRotation(() => {
|
|
256
|
+
if (sessionLoadInFlight.current)
|
|
257
|
+
return;
|
|
258
|
+
void loadSession();
|
|
259
|
+
});
|
|
260
|
+
}, [enabled, isPreviewMode, loadSession]);
|
|
58
261
|
// Load session on mount (only if auth is enabled and not in preview mode)
|
|
59
262
|
useEffect(() => {
|
|
60
263
|
if (isPreviewMode) {
|
|
@@ -67,7 +270,13 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
67
270
|
email: 'preview@preview.local',
|
|
68
271
|
name,
|
|
69
272
|
role,
|
|
70
|
-
|
|
273
|
+
// `positions` is the published spelling (framework ADR-0090 D3); this
|
|
274
|
+
// line was the LAST producer of the retired `roles` key
|
|
275
|
+
// (objectui#5424, maintainer ruling 2026-08-22). Emitting the position
|
|
276
|
+
// keeps every `positions` consumer — `AuthGuard`'s `requiredRoles`,
|
|
277
|
+
// the approver-identity derivations, workspace-admin leg 3 — seeing a
|
|
278
|
+
// preview identity shaped like a protocol-17 session.
|
|
279
|
+
positions: [role],
|
|
71
280
|
});
|
|
72
281
|
setSession({
|
|
73
282
|
token: 'preview-token',
|
|
@@ -95,30 +304,9 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
95
304
|
return;
|
|
96
305
|
}
|
|
97
306
|
let cancelled = false;
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const result = await client.getSession();
|
|
101
|
-
if (cancelled)
|
|
102
|
-
return;
|
|
103
|
-
if (result) {
|
|
104
|
-
setUser(result.user);
|
|
105
|
-
setSession(result.session);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
if (cancelled)
|
|
110
|
-
return;
|
|
111
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
-
}
|
|
113
|
-
finally {
|
|
114
|
-
if (!cancelled) {
|
|
115
|
-
setIsLoading(false);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
loadSession();
|
|
307
|
+
loadSession(() => cancelled);
|
|
120
308
|
return () => { cancelled = true; };
|
|
121
|
-
}, [client, enabled, isPreviewMode, previewMode]);
|
|
309
|
+
}, [client, enabled, isPreviewMode, previewMode, loadSession]);
|
|
122
310
|
// Notify on auth state changes
|
|
123
311
|
useEffect(() => {
|
|
124
312
|
onAuthStateChange?.({
|
|
@@ -177,11 +365,27 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
177
365
|
setUser(null);
|
|
178
366
|
setSession(null);
|
|
179
367
|
setError(null);
|
|
368
|
+
// The organization block is the previous principal's data too: the list
|
|
369
|
+
// is the workspaces THAT user belongs to (the switcher renders it), and
|
|
370
|
+
// a lingering `activeOrganization` would additionally suppress the
|
|
371
|
+
// re-resolution below — `refreshOrganizations` only asks the server for
|
|
372
|
+
// the active org `if (orgs.length > 0 && !activeOrganization)`, so the
|
|
373
|
+
// next sign-in in this tab would inherit the previous user's active
|
|
374
|
+
// workspace in context while storage had (correctly) forgotten it.
|
|
375
|
+
// `activeMember` follows from the effect that watches `activeOrganization`.
|
|
376
|
+
// Same pairing `deleteOrganization` / `leaveOrganization` already use in
|
|
377
|
+
// this file: drop the in-memory reference and the stored id together.
|
|
378
|
+
setOrganizations([]);
|
|
379
|
+
setActiveOrganization(null);
|
|
180
380
|
}
|
|
181
381
|
catch (err) {
|
|
182
382
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
183
383
|
}
|
|
184
384
|
finally {
|
|
385
|
+
// Unconditional: `createAuthClient.signOut` clears `TokenStorage` BEFORE
|
|
386
|
+
// it rethrows a server error, so a failed call still leaves this tab
|
|
387
|
+
// without a session — the caches it authenticated for must go with it.
|
|
388
|
+
purgeSignedOutClientCaches();
|
|
185
389
|
setIsLoading(false);
|
|
186
390
|
}
|
|
187
391
|
}, [client]);
|
|
@@ -392,6 +596,10 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
392
596
|
finally {
|
|
393
597
|
if (!isCancelled?.()) {
|
|
394
598
|
setIsOrganizationsLoading(false);
|
|
599
|
+
// In `finally`, so a failed `listOrganizations` still counts as an
|
|
600
|
+
// answer. It is not a good answer, but leaving it unresolved would
|
|
601
|
+
// strand every gate that waits on it (objectui#5619).
|
|
602
|
+
setOrganizationsResolved(true);
|
|
395
603
|
}
|
|
396
604
|
}
|
|
397
605
|
}, [client, enabled, isPreviewMode, activeOrganization]);
|
|
@@ -407,12 +615,25 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
407
615
|
userId: 'preview-user',
|
|
408
616
|
role,
|
|
409
617
|
});
|
|
618
|
+
// Neither mode runs `refreshOrganizations` (its effect requires a real
|
|
619
|
+
// `user`, and the callback itself returns early), so this is the one
|
|
620
|
+
// place that can close BOTH halves for them (objectui#5619).
|
|
621
|
+
setOrganizationsResolved(true);
|
|
622
|
+
setActiveMemberResolved(true);
|
|
410
623
|
return;
|
|
411
624
|
}
|
|
412
625
|
if (!activeOrganization) {
|
|
626
|
+
// "No active organization" is a real answer about the member row — but
|
|
627
|
+
// only once the org list itself has come back, which is what
|
|
628
|
+
// `organizationsResolved` below contributes. On mount this branch runs
|
|
629
|
+
// with the pipeline not yet started, and the combined flag stays false.
|
|
413
630
|
setActiveMember(null);
|
|
631
|
+
setActiveMemberResolved(true);
|
|
414
632
|
return;
|
|
415
633
|
}
|
|
634
|
+
// A switch re-opens the question: until `getActiveMember()` answers for the
|
|
635
|
+
// NEW org, `activeMember` still holds the OLD org's row.
|
|
636
|
+
setActiveMemberResolved(false);
|
|
416
637
|
try {
|
|
417
638
|
const member = await client.getActiveMember();
|
|
418
639
|
setActiveMember(member);
|
|
@@ -421,7 +642,42 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
421
642
|
console.warn('[AuthProvider] Failed to load active member:', err);
|
|
422
643
|
setActiveMember(null);
|
|
423
644
|
}
|
|
645
|
+
finally {
|
|
646
|
+
setActiveMemberResolved(true);
|
|
647
|
+
}
|
|
424
648
|
}, [client, enabled, isPreviewMode, previewMode, activeOrganization]);
|
|
649
|
+
/**
|
|
650
|
+
* objectui#5664 — the session-user-change invariant.
|
|
651
|
+
*
|
|
652
|
+
* Declared BEFORE the two effects below on purpose: effects run in the order
|
|
653
|
+
* their hooks were called, so this settles "whose browser is this" before
|
|
654
|
+
* `refreshActiveMember` and `refreshOrganizations` read or write anything
|
|
655
|
+
* org-scoped. `refreshOrganizations` in particular calls
|
|
656
|
+
* `ActiveOrganizationStorage.set()`, which resolves its key through the
|
|
657
|
+
* pointer this establishes.
|
|
658
|
+
*
|
|
659
|
+
* Keyed on the session user ID rather than wired into each of the five
|
|
660
|
+
* places that set `user` (mount / `refreshSession` / rotation, sign-in,
|
|
661
|
+
* sign-up, and the two provider flows). One implementation, and a sign-in
|
|
662
|
+
* path added later is covered without being told to be.
|
|
663
|
+
*
|
|
664
|
+
* Preview and auth-disabled mounts are excluded because their identities are
|
|
665
|
+
* SYNTHETIC — a fixed `preview-user` / `guest` id. Adopting one would read as
|
|
666
|
+
* a user change and purge a real user's state on any browser that opened a
|
|
667
|
+
* marketplace demo.
|
|
668
|
+
*
|
|
669
|
+
* What "purge" means, and why it is wholesale rather than key-by-key, is in
|
|
670
|
+
* `ActiveOrganizationStorage.ts`: it is an allowlist sweep, so the NEXT
|
|
671
|
+
* un-namespaced key is covered before anyone writes it.
|
|
672
|
+
*/
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
if (!enabled || isPreviewMode)
|
|
675
|
+
return;
|
|
676
|
+
const sessionUserId = user?.id;
|
|
677
|
+
if (!sessionUserId)
|
|
678
|
+
return;
|
|
679
|
+
SessionUserScope.adopt(sessionUserId);
|
|
680
|
+
}, [user?.id, enabled, isPreviewMode]);
|
|
425
681
|
useEffect(() => {
|
|
426
682
|
refreshActiveMember();
|
|
427
683
|
}, [refreshActiveMember]);
|
|
@@ -433,8 +689,46 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
433
689
|
refreshOrganizations(() => cancelled);
|
|
434
690
|
return () => { cancelled = true; };
|
|
435
691
|
}, [user, enabled, isPreviewMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
692
|
+
// objectui#5750 — keep `activeOrgIdRef` current for every path that can
|
|
693
|
+
// move `activeOrganization`, not only `switchOrganization` (e.g.
|
|
694
|
+
// `refreshOrganizations`' single-membership repair calls
|
|
695
|
+
// `client.setActiveOrganization` directly). `switchOrganization` also
|
|
696
|
+
// writes it synchronously on its own success, below — this effect is the
|
|
697
|
+
// fallback for every OTHER path, and re-affirms after commit.
|
|
698
|
+
useEffect(() => {
|
|
699
|
+
activeOrgIdRef.current = activeOrganization?.id ?? null;
|
|
700
|
+
}, [activeOrganization]);
|
|
701
|
+
/**
|
|
702
|
+
* objectui#5750 — `switchOrganization` now DECLARES the re-resolution
|
|
703
|
+
* instead of relying on the objectui#4467 rotation subscription noticing
|
|
704
|
+
* one by accident.
|
|
705
|
+
*
|
|
706
|
+
* Why that accident cannot be trusted here: `POST /organization/set-active`
|
|
707
|
+
* always returns the SIGNED `token.signature` spelling in `set-auth-token`
|
|
708
|
+
* (better-auth's `bearer()` fires for any response that stages a session
|
|
709
|
+
* cookie), which differs from the UNSIGNED `session.token` spelling the
|
|
710
|
+
* client normally holds — so `TokenStorage.set` reads the flip as a
|
|
711
|
+
* rotation and the objectui#4467 subscription re-resolves identity. That
|
|
712
|
+
* accidental rotation is deterministic on the RAW token, not on the
|
|
713
|
+
* organization — so a SECOND switch whose response lands before the FIRST
|
|
714
|
+
* switch's follow-up `get-session` has re-armed it (re-stored the unsigned
|
|
715
|
+
* spelling) writes the SAME signed value already in storage. No value
|
|
716
|
+
* change, no notification, no re-resolution — `user.positions` is left
|
|
717
|
+
* answering for the organization the FIRST switch targeted, while
|
|
718
|
+
* `activeOrganization` already reads as the new one. Measured in #5749/#5750.
|
|
719
|
+
*
|
|
720
|
+
* The fix does not add a second call on the common path: it SUPPRESSES the
|
|
721
|
+
* accidental one (via `sessionLoadInFlight`, the same flag that already
|
|
722
|
+
* suppresses the rotation observed inside `loadSession`'s own
|
|
723
|
+
* `getSession()` call) and re-resolves explicitly instead, so a switch that
|
|
724
|
+
* changes the active organization still spends exactly one `get-session` —
|
|
725
|
+
* now unconditionally, so it does not depend on which spelling happened to
|
|
726
|
+
* already be in storage.
|
|
727
|
+
*/
|
|
436
728
|
const switchOrganization = useCallback(async (orgId) => {
|
|
437
729
|
setError(null);
|
|
730
|
+
const previousOrgId = activeOrgIdRef.current;
|
|
731
|
+
sessionLoadInFlight.current = true;
|
|
438
732
|
try {
|
|
439
733
|
const org = await client.setActiveOrganization(orgId);
|
|
440
734
|
setActiveOrganization(org);
|
|
@@ -445,13 +739,28 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
445
739
|
else {
|
|
446
740
|
ActiveOrganizationStorage.clear();
|
|
447
741
|
}
|
|
742
|
+
const newOrgId = org?.id ?? null;
|
|
743
|
+
if (newOrgId !== previousOrgId) {
|
|
744
|
+
activeOrgIdRef.current = newOrgId;
|
|
745
|
+
// A LATER switch may already be in flight by the time this
|
|
746
|
+
// `getSession()` answer comes back (no debounce guards repeat
|
|
747
|
+
// switches — see the WorkspaceSwitcher/OrganizationLayout callers).
|
|
748
|
+
// Discard this answer if a newer switch has since claimed the
|
|
749
|
+
// generation, rather than clobber fresher, later-organization data
|
|
750
|
+
// with a stale, earlier one.
|
|
751
|
+
const generation = ++sessionLoadGeneration.current;
|
|
752
|
+
await loadSession(() => sessionLoadGeneration.current !== generation);
|
|
753
|
+
}
|
|
448
754
|
}
|
|
449
755
|
catch (err) {
|
|
450
756
|
const authError = err instanceof Error ? err : new Error(String(err));
|
|
451
757
|
setError(authError);
|
|
452
758
|
throw authError;
|
|
453
759
|
}
|
|
454
|
-
|
|
760
|
+
finally {
|
|
761
|
+
sessionLoadInFlight.current = false;
|
|
762
|
+
}
|
|
763
|
+
}, [client, loadSession]);
|
|
455
764
|
const createOrganization = useCallback(async (data) => {
|
|
456
765
|
setError(null);
|
|
457
766
|
try {
|
|
@@ -515,6 +824,22 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
515
824
|
// Guest mode (enabled=false) and preview mode both have hardcoded `isAuthenticated`
|
|
516
825
|
// and a no-op signOut backend, so consumers should hide sign-out UIs.
|
|
517
826
|
const isAuthEnabled = enabled && !isPreviewMode;
|
|
827
|
+
// objectui#5619 — see `AuthContext.isMembershipResolved` for the contract.
|
|
828
|
+
//
|
|
829
|
+
// Three modes, three reasons:
|
|
830
|
+
// - preview / auth-disabled: no pipeline runs; `refreshActiveMember`'s first
|
|
831
|
+
// branch closes both halves on its own effect.
|
|
832
|
+
// - no user: there is no membership to fetch, so the question is answered.
|
|
833
|
+
// Without this the flag would sit false forever on an unauthenticated
|
|
834
|
+
// visitor and hang any gate that waits on it. Consumers that must also
|
|
835
|
+
// wait for the SESSION read `isLoading` alongside this (the hook does).
|
|
836
|
+
// - signed in: both halves, because "no active organization" only means
|
|
837
|
+
// "no member row" once the org list has actually come back.
|
|
838
|
+
const isMembershipResolved = !isAuthEnabled
|
|
839
|
+
? activeMemberResolved
|
|
840
|
+
: user == null
|
|
841
|
+
? true
|
|
842
|
+
: organizationsResolved && activeMemberResolved;
|
|
518
843
|
const value = useMemo(() => ({
|
|
519
844
|
user,
|
|
520
845
|
session,
|
|
@@ -527,6 +852,7 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
527
852
|
signIn,
|
|
528
853
|
signUp,
|
|
529
854
|
signOut,
|
|
855
|
+
refreshSession,
|
|
530
856
|
updateUser,
|
|
531
857
|
forgotPassword,
|
|
532
858
|
sendVerificationEmail,
|
|
@@ -549,6 +875,7 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
549
875
|
activeOrganization,
|
|
550
876
|
activeMember,
|
|
551
877
|
isOrganizationsLoading,
|
|
878
|
+
isMembershipResolved,
|
|
552
879
|
switchOrganization,
|
|
553
880
|
createOrganization,
|
|
554
881
|
refreshOrganizations,
|
|
@@ -568,10 +895,10 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
568
895
|
listUserInvitations,
|
|
569
896
|
}), [
|
|
570
897
|
user, session, isAuthenticated, isAuthEnabled, isLoading, error, isPreviewMode, previewMode,
|
|
571
|
-
signIn, signUp, signOut, updateUser, forgotPassword, sendVerificationEmail, resetPassword, changePassword, setInitialPassword, hasLocalPassword, getAuthConfig, signInWithProvider,
|
|
898
|
+
signIn, signUp, signOut, refreshSession, updateUser, forgotPassword, sendVerificationEmail, resetPassword, changePassword, setInitialPassword, hasLocalPassword, getAuthConfig, signInWithProvider,
|
|
572
899
|
sendPhoneOtp, signInWithPhoneOtp, signInWithPhonePassword, requestPhonePasswordReset, resetPasswordWithPhoneOtp,
|
|
573
900
|
remediationRequired, enrollTotp, verifyTotp,
|
|
574
|
-
organizations, activeOrganization, activeMember, isOrganizationsLoading, switchOrganization, createOrganization, refreshOrganizations,
|
|
901
|
+
organizations, activeOrganization, activeMember, isOrganizationsLoading, isMembershipResolved, switchOrganization, createOrganization, refreshOrganizations,
|
|
575
902
|
updateOrganization, deleteOrganization, leaveOrganization,
|
|
576
903
|
getMembers, inviteMember, describeDelegableScope, removeMember, updateMemberRole,
|
|
577
904
|
listInvitations, cancelInvitation, getInvitation, acceptInvitation, rejectInvitation, listUserInvitations,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import type { AuthLinkComponentProps } from './types';
|
|
9
|
+
import type { AuthLinkComponentProps } from './types.js';
|
|
10
10
|
/** Translatable labels for the ForgotPasswordForm */
|
|
11
11
|
export interface ForgotPasswordFormLabels {
|
|
12
12
|
emailLabel?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ForgotPasswordForm.d.ts","sourceRoot":"","sources":["../src/ForgotPasswordForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ForgotPasswordForm.d.ts","sourceRoot":"","sources":["../src/ForgotPasswordForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAYzD,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,wBAAwB;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC5D,uCAAuC;IACvC,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,OAAO,EACP,QAAmB,EACnB,KAA6B,EAC7B,WAAyF,EACzF,IAAI,EACJ,QAAgB,EAChB,aAAa,EAAE,QAAsB,EACrC,MAAW,GACZ,EAAE,uBAAuB,qBA0UzB"}
|
|
@@ -7,8 +7,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
9
|
import { useEffect, useState } from 'react';
|
|
10
|
-
import { useAuth } from './useAuth';
|
|
11
|
-
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthMailIcon, AuthSpinner, } from './authStyles';
|
|
10
|
+
import { useAuth } from './useAuth.js';
|
|
11
|
+
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthMailIcon, AuthSpinner, } from './authStyles.js';
|
|
12
12
|
const DefaultLink = ({ href, className, children }) => (_jsx("a", { href: href, className: className, children: children }));
|
|
13
13
|
const DefaultKeyIcon = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className: "h-6 w-6", "aria-hidden": "true", children: _jsx("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }) }));
|
|
14
14
|
/**
|
|
@@ -72,7 +72,7 @@ export function ForgotPasswordForm({ onSuccess, onError, loginUrl = '/login', ti
|
|
|
72
72
|
submittingButton: labels.submittingButton ?? 'Sending…',
|
|
73
73
|
successTitle: labels.successTitle ?? 'Check your email',
|
|
74
74
|
successDescription: labels.successDescription ??
|
|
75
|
-
"We've sent a password reset link to {
|
|
75
|
+
"We've sent a password reset link to {email}. Please check your inbox.",
|
|
76
76
|
backToSignInText: labels.backToSignInText ?? 'Back to sign in',
|
|
77
77
|
rememberPasswordText: labels.rememberPasswordText ?? 'Remember your password?',
|
|
78
78
|
signInText: labels.signInText ?? 'Sign in',
|
|
@@ -156,8 +156,8 @@ export function ForgotPasswordForm({ onSuccess, onError, loginUrl = '/login', ti
|
|
|
156
156
|
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: _jsx("span", { className: "flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-500/10 text-emerald-600 ring-1 ring-emerald-500/15 dark:text-emerald-400", children: _jsx(AuthMailIcon, {}) }), title: l.phoneSuccessTitle, description: l.phoneSuccessDescription }), loginUrl && (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: _jsxs(LinkComp, { href: loginUrl, className: AUTH_LINK_CLASS, children: ["\u2190 ", l.backToSignInText] }) }))] }));
|
|
157
157
|
}
|
|
158
158
|
if (submitted) {
|
|
159
|
-
const successMsg = l.successDescription.includes('{
|
|
160
|
-
? l.successDescription.replace('{
|
|
159
|
+
const successMsg = l.successDescription.includes('{email}')
|
|
160
|
+
? l.successDescription.replace('{email}', email)
|
|
161
161
|
: `${l.successDescription} ${email}`;
|
|
162
162
|
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: _jsx("span", { className: "flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-500/10 text-emerald-600 ring-1 ring-emerald-500/15 dark:text-emerald-400", children: _jsx(AuthMailIcon, {}) }), title: l.successTitle, description: successMsg }), loginUrl && (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: _jsxs(LinkComp, { href: loginUrl, className: AUTH_LINK_CLASS, children: ["\u2190 ", l.backToSignInText] }) }))] }));
|
|
163
163
|
}
|
package/dist/LoginForm.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import type { AuthLinkComponentProps } from './types';
|
|
9
|
+
import type { AuthLinkComponentProps } from './types.js';
|
|
10
10
|
/** Translatable labels for the LoginForm */
|
|
11
11
|
export interface LoginFormLabels {
|
|
12
12
|
emailLabel?: string;
|
package/dist/LoginForm.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoginForm.d.ts","sourceRoot":"","sources":["../src/LoginForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"LoginForm.d.ts","sourceRoot":"","sources":["../src/LoginForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAYzD,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8GAA8G;IAC9G,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,8FAA8F;IAC9F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iGAAiG;IACjG,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,yHAAyH;IACzH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC5D,uCAAuC;IACvC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,OAAO,EACP,WAAyB,EACzB,iBAAsC,EACtC,KAAiC,EACjC,WAAyD,EACzD,IAAI,EACJ,QAAgB,EAChB,aAAa,EAAE,QAAsB,EACrC,MAAW,EACX,aAAa,GACd,EAAE,cAAc,qBAgbhB"}
|
package/dist/LoginForm.js
CHANGED
|
@@ -7,10 +7,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
9
|
import { useEffect, useState } from 'react';
|
|
10
|
-
import { useAuth } from './useAuth';
|
|
11
|
-
import { SocialSignInButtons } from './SocialSignInButtons';
|
|
12
|
-
import { looksLikePhoneIdentifier, normalizePhoneIdentifier } from './phone-identifier';
|
|
13
|
-
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthSpinner, } from './authStyles';
|
|
10
|
+
import { useAuth } from './useAuth.js';
|
|
11
|
+
import { SocialSignInButtons } from './SocialSignInButtons.js';
|
|
12
|
+
import { looksLikePhoneIdentifier, normalizePhoneIdentifier } from './phone-identifier.js';
|
|
13
|
+
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthSpinner, } from './authStyles.js';
|
|
14
14
|
const DefaultLink = ({ href, className, children }) => (_jsx("a", { href: href, className: className, children: children }));
|
|
15
15
|
const DefaultLockIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className: "h-6 w-6", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] }));
|
|
16
16
|
/**
|
package/dist/PreviewBanner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth } from './useAuth';
|
|
2
|
+
import { useAuth } from './useAuth.js';
|
|
3
3
|
/**
|
|
4
4
|
* Banner component that displays a message when the app is in preview mode.
|
|
5
5
|
* Only renders when preview mode is active. Uses the bannerMessage from
|
package/dist/RegisterForm.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import type { AuthLinkComponentProps } from './types';
|
|
9
|
+
import type { AuthLinkComponentProps } from './types.js';
|
|
10
10
|
/** Translatable labels for the RegisterForm */
|
|
11
11
|
export interface RegisterFormLabels {
|
|
12
12
|
nameLabel?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegisterForm.d.ts","sourceRoot":"","sources":["../src/RegisterForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"RegisterForm.d.ts","sourceRoot":"","sources":["../src/RegisterForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAWzD,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC;sFACkF;IAClF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;2CAEuC;IACvC,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,qCAAqC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC5D,uCAAuC;IACvC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAwBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,sBAAsB,EACtB,OAAO,EACP,QAAmB,EACnB,KAA2B,EAC3B,WAAqD,EACrD,IAAI,EACJ,QAAgB,EAChB,aAAa,EAAE,QAAsB,EACrC,MAAW,EACX,aAAa,GACd,EAAE,iBAAiB,qBAuKnB"}
|
package/dist/RegisterForm.js
CHANGED
|
@@ -7,9 +7,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
9
|
import { useState } from 'react';
|
|
10
|
-
import { useAuth } from './useAuth';
|
|
11
|
-
import { SocialSignInButtons } from './SocialSignInButtons';
|
|
12
|
-
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthSpinner, } from './authStyles';
|
|
10
|
+
import { useAuth } from './useAuth.js';
|
|
11
|
+
import { SocialSignInButtons } from './SocialSignInButtons.js';
|
|
12
|
+
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthSpinner, } from './authStyles.js';
|
|
13
13
|
const DefaultLink = ({ href, className, children }) => (_jsx("a", { href: href, className: className, children: children }));
|
|
14
14
|
const DefaultUserPlusIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className: "h-6 w-6", "aria-hidden": "true", children: [_jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }), _jsx("circle", { cx: "9", cy: "7", r: "4" }), _jsx("line", { x1: "19", y1: "8", x2: "19", y2: "14" }), _jsx("line", { x1: "22", y1: "11", x2: "16", y2: "11" })] }));
|
|
15
15
|
/**
|