@object-ui/auth 17.6.0 → 18.0.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 +598 -0
- package/README.md +124 -19
- package/dist/ActiveOrganizationStorage.d.ts +32 -1
- package/dist/ActiveOrganizationStorage.d.ts.map +1 -1
- package/dist/ActiveOrganizationStorage.js +72 -3
- package/dist/AuthContext.d.ts +10 -3
- package/dist/AuthContext.d.ts.map +1 -1
- package/dist/AuthProvider.d.ts.map +1 -1
- package/dist/AuthProvider.js +101 -18
- package/dist/LoginForm.d.ts +35 -4
- package/dist/LoginForm.d.ts.map +1 -1
- package/dist/LoginForm.js +16 -4
- package/dist/RegisterForm.d.ts +21 -2
- package/dist/RegisterForm.d.ts.map +1 -1
- package/dist/RegisterForm.js +3 -4
- package/dist/SocialSignInButtons.d.ts +15 -2
- package/dist/SocialSignInButtons.d.ts.map +1 -1
- package/dist/SocialSignInButtons.js +3 -3
- package/dist/UserMenu.d.ts +1 -1
- package/dist/UserMenu.js +1 -1
- package/dist/auth-gate-events.d.ts +6 -4
- package/dist/auth-gate-events.d.ts.map +1 -1
- package/dist/auth-gate-events.js +11 -1
- package/dist/authStyles.d.ts +8 -3
- package/dist/authStyles.d.ts.map +1 -1
- package/dist/authStyles.js +8 -3
- package/dist/createAuthClient.d.ts +15 -1
- package/dist/createAuthClient.d.ts.map +1 -1
- package/dist/createAuthClient.js +54 -1
- package/dist/createAuthenticatedFetch.d.ts +5 -4
- package/dist/createAuthenticatedFetch.d.ts.map +1 -1
- package/dist/createAuthenticatedFetch.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/types.d.ts +53 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/useWorkspaceAdminStatus.d.ts +76 -27
- package/dist/useWorkspaceAdminStatus.d.ts.map +1 -1
- package/dist/useWorkspaceAdminStatus.js +146 -40
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -25,16 +25,18 @@ npm install @object-ui/auth
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```tsx
|
|
28
|
-
import {
|
|
29
|
-
import { createAuthClient } from '@object-ui/auth';
|
|
28
|
+
import type { ReactNode } from 'react';
|
|
29
|
+
import { AuthProvider, AuthGuard, createAuthClient, useAuth } from '@object-ui/auth';
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
// Your app supplies the sign-in page shown to unauthenticated visitors.
|
|
32
|
+
declare const LoginPage: () => ReactNode;
|
|
33
|
+
|
|
34
|
+
const authUrl = 'https://api.example.com/auth';
|
|
35
|
+
const authClient = createAuthClient({ baseURL: authUrl });
|
|
34
36
|
|
|
35
37
|
function App() {
|
|
36
38
|
return (
|
|
37
|
-
<AuthProvider client={authClient}>
|
|
39
|
+
<AuthProvider authUrl={authUrl} client={authClient}>
|
|
38
40
|
<AuthGuard fallback={<LoginPage />}>
|
|
39
41
|
<Dashboard />
|
|
40
42
|
</AuthGuard>
|
|
@@ -57,12 +59,21 @@ function Dashboard() {
|
|
|
57
59
|
|
|
58
60
|
### AuthProvider
|
|
59
61
|
|
|
60
|
-
Wraps your application with authentication context
|
|
62
|
+
Wraps your application with authentication context. `authUrl` is **required**;
|
|
63
|
+
`client` is optional, and when you pass one it is used instead of the client
|
|
64
|
+
`AuthProvider` would otherwise build from `authUrl`:
|
|
61
65
|
|
|
62
66
|
```tsx
|
|
63
|
-
|
|
67
|
+
import type { ReactNode } from 'react';
|
|
68
|
+
import { AuthProvider } from '@object-ui/auth';
|
|
69
|
+
import type { AuthClient } from '@object-ui/auth';
|
|
70
|
+
|
|
71
|
+
declare const authClient: AuthClient;
|
|
72
|
+
declare const App: () => ReactNode;
|
|
73
|
+
|
|
74
|
+
<AuthProvider authUrl="/api/v1/auth" client={authClient}>
|
|
64
75
|
<App />
|
|
65
|
-
</AuthProvider
|
|
76
|
+
</AuthProvider>;
|
|
66
77
|
```
|
|
67
78
|
|
|
68
79
|
### useAuth
|
|
@@ -70,6 +81,8 @@ Wraps your application with authentication context:
|
|
|
70
81
|
Hook for accessing auth state and methods:
|
|
71
82
|
|
|
72
83
|
```tsx
|
|
84
|
+
import { useAuth } from '@object-ui/auth';
|
|
85
|
+
|
|
73
86
|
const {
|
|
74
87
|
user,
|
|
75
88
|
session,
|
|
@@ -100,9 +113,14 @@ const {
|
|
|
100
113
|
Protects children from unauthenticated access:
|
|
101
114
|
|
|
102
115
|
```tsx
|
|
116
|
+
import type { ReactNode } from 'react';
|
|
117
|
+
import { AuthGuard, LoginForm } from '@object-ui/auth';
|
|
118
|
+
|
|
119
|
+
declare const ProtectedContent: () => ReactNode;
|
|
120
|
+
|
|
103
121
|
<AuthGuard fallback={<LoginForm />}>
|
|
104
122
|
<ProtectedContent />
|
|
105
|
-
</AuthGuard
|
|
123
|
+
</AuthGuard>;
|
|
106
124
|
```
|
|
107
125
|
|
|
108
126
|
### LoginForm / RegisterForm / ForgotPasswordForm
|
|
@@ -110,9 +128,48 @@ Protects children from unauthenticated access:
|
|
|
110
128
|
Pre-built authentication form components:
|
|
111
129
|
|
|
112
130
|
```tsx
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
import { ForgotPasswordForm, LoginForm, RegisterForm } from '@object-ui/auth';
|
|
132
|
+
|
|
133
|
+
declare const navigate: (to: string) => void;
|
|
134
|
+
|
|
135
|
+
<LoginForm onSuccess={() => navigate('/dashboard')} />;
|
|
136
|
+
<RegisterForm onSuccess={() => navigate('/welcome')} />;
|
|
137
|
+
<ForgotPasswordForm onSuccess={() => navigate('/check-email')} />;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`labels` is how the forms take translated text, and it reaches the social
|
|
141
|
+
provider buttons the server's `/auth/config` turns on: `socialButton`
|
|
142
|
+
is a template whose `{provider}` is replaced with the provider's display name
|
|
143
|
+
(defaults "Continue with {provider}" on `LoginForm`, "Sign up with {provider}"
|
|
144
|
+
on `RegisterForm`), and `orText` is the divider under the buttons (default
|
|
145
|
+
"or continue with email").
|
|
146
|
+
|
|
147
|
+
The divider introduces the email form, so `LoginForm` draws it only while that
|
|
148
|
+
form is shown: under SSO-only mode (`features.ssoEnforced`) the buttons stand
|
|
149
|
+
alone until "Use a password instead" reveals the form. A host rendering
|
|
150
|
+
`SocialSignInButtons` itself omits the divider with `hideDivider` (default
|
|
151
|
+
`false`). While the auth config is still loading, `LoginForm`'s header shows
|
|
152
|
+
its title and no description, because the description may describe a password
|
|
153
|
+
form the server does not offer.
|
|
154
|
+
|
|
155
|
+
When the server requires email verification, a registration ends in the user's
|
|
156
|
+
inbox, and the verification link is the only way back. `verificationCallbackURL`
|
|
157
|
+
says where that link lands, e.g. the invitation the user signed up from. It is
|
|
158
|
+
sent as better-auth's sign-up `callbackURL` (the same value as the fourth
|
|
159
|
+
argument of `useAuth().signUp`). The server accepts only a root-relative path
|
|
160
|
+
or an absolute URL on a trusted origin, and refuses a document-relative `./…`
|
|
161
|
+
with `INVALID_CALLBACK_URL`, which fails the sign-up. Resolve an SPA route
|
|
162
|
+
against its mount first. Omitted, the server default (`/`) applies.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { RegisterForm } from '@object-ui/auth';
|
|
166
|
+
|
|
167
|
+
declare const navigate: (to: string) => void;
|
|
168
|
+
|
|
169
|
+
<RegisterForm
|
|
170
|
+
verificationCallbackURL="/_console/accept-invitation/inv_123"
|
|
171
|
+
onVerificationRequired={(email) => navigate(`/verify-email-prompt?email=${encodeURIComponent(email)}`)}
|
|
172
|
+
/>;
|
|
116
173
|
```
|
|
117
174
|
|
|
118
175
|
### UserMenu
|
|
@@ -120,7 +177,9 @@ Pre-built authentication form components:
|
|
|
120
177
|
Displays current user info with avatar and sign-out:
|
|
121
178
|
|
|
122
179
|
```tsx
|
|
123
|
-
|
|
180
|
+
import { UserMenu } from '@object-ui/auth';
|
|
181
|
+
|
|
182
|
+
<UserMenu />;
|
|
124
183
|
```
|
|
125
184
|
|
|
126
185
|
### createAuthenticatedFetch
|
|
@@ -129,14 +188,40 @@ Creates a fetch wrapper that injects the stored Bearer token (plus
|
|
|
129
188
|
`X-Tenant-ID` and `Accept-Language`) into API requests:
|
|
130
189
|
|
|
131
190
|
```tsx
|
|
191
|
+
import { createAuthenticatedFetch } from '@object-ui/auth';
|
|
192
|
+
|
|
132
193
|
const authedFetch = createAuthenticatedFetch();
|
|
133
194
|
|
|
134
|
-
// For fetches whose target URL
|
|
135
|
-
//
|
|
136
|
-
// so the platform token never leaks to third-party hosts:
|
|
137
|
-
const
|
|
195
|
+
// For fetches whose target URL may come from metadata (an API-call
|
|
196
|
+
// action's target), restrict credential injection to the current page's
|
|
197
|
+
// origin so the platform token never leaks to third-party hosts:
|
|
198
|
+
const sameOriginFetch = createAuthenticatedFetch({ sameOriginOnly: true });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### getSessionOwnerChangeCount / subscribeSessionOwnerChange
|
|
202
|
+
|
|
203
|
+
When the session resolves to a different user than the one this browser last
|
|
204
|
+
held, `AuthProvider` drops the previous user's client state (objectui#5664).
|
|
205
|
+
That clears storage only. State already derived from that storage and held in
|
|
206
|
+
memory, such as the UI language the boot resolved, has to be re-derived by
|
|
207
|
+
whoever owns it. These two exports tell that owner a change happened in this
|
|
208
|
+
page-load (objectui#10193). The count lets an owner that mounts after the purge
|
|
209
|
+
still see it:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { useSyncExternalStore } from 'react';
|
|
213
|
+
import { getSessionOwnerChangeCount, subscribeSessionOwnerChange } from '@object-ui/auth';
|
|
214
|
+
|
|
215
|
+
const ownerChanges = useSyncExternalStore(
|
|
216
|
+
subscribeSessionOwnerChange,
|
|
217
|
+
getSessionOwnerChangeCount,
|
|
218
|
+
getSessionOwnerChangeCount,
|
|
219
|
+
);
|
|
138
220
|
```
|
|
139
221
|
|
|
222
|
+
The count lives in memory for one page-load and is never persisted.
|
|
223
|
+
`@object-ui/app-shell`'s `useSignedInUserLocale` is the in-repo consumer.
|
|
224
|
+
|
|
140
225
|
## The `X-Tenant-ID` edge contract
|
|
141
226
|
|
|
142
227
|
`createAuthenticatedFetch` stamps `X-Tenant-ID` on the requests it wraps. This section is
|
|
@@ -294,6 +379,21 @@ gates the "Sign in with SSO" button, `features.phoneNumberOtp` gates the
|
|
|
294
379
|
verification-code mode, `features.deviceAuthorization` gates the device-approval page,
|
|
295
380
|
and so on.
|
|
296
381
|
|
|
382
|
+
### `features.audiencePosture` — read it beside `emailPassword.disableSignUp`
|
|
383
|
+
|
|
384
|
+
`features.audiencePosture` reports which audience posture is in force: `invite_only` (the
|
|
385
|
+
default when the deployment declares none), `email_domain` or `open`. It is a value, not a
|
|
386
|
+
flag, and the server does **not** force `emailPassword.disableSignUp` from it: under
|
|
387
|
+
`invite_only` the sign-up route still admits a pending invitee, and a fresh deployment's
|
|
388
|
+
first owner. So `disableSignUp: false` on its own does not mean a stranger may register.
|
|
389
|
+
|
|
390
|
+
A surface that offers a generic sign-up reads both keys. `disableSignUp: true` hides
|
|
391
|
+
sign-up outright. Otherwise offer it when the posture is `open` or `email_domain`, or when
|
|
392
|
+
the visitor came from an invitation; under `invite_only` say that registration is by
|
|
393
|
+
invitation instead of rendering a form the server will refuse. A server that sends no
|
|
394
|
+
`audiencePosture` predates the key, and `disableSignUp` alone decides there. The
|
|
395
|
+
console's login and register pages are that reader (objectui#11691).
|
|
396
|
+
|
|
297
397
|
### Reserved flags — advertised by the server, consumed by nothing
|
|
298
398
|
|
|
299
399
|
Two members of that map are **declared but deliberately not consumed** by this package:
|
|
@@ -325,13 +425,16 @@ not a gap.
|
|
|
325
425
|
|
|
326
426
|
Preview mode allows visitors (e.g. marketplace customers) to explore the platform without registering or logging in. The `AuthProvider` auto-authenticates with a simulated user identity and bypasses login/registration screens.
|
|
327
427
|
|
|
328
|
-
This
|
|
428
|
+
This capability is host-supplied and has no `@objectstack/spec` anchor. It aligned with `PreviewModeConfig` from `@objectstack/spec/kernel` until that symbol was retired upstream (objectstack#11846), which removed it together with the `RuntimeMode` value `'preview'`; the spec this package resolves no longer exports it. The `previewMode` prop below is unaffected — it is the host's to supply, and always was.
|
|
329
429
|
|
|
330
430
|
### Usage
|
|
331
431
|
|
|
332
432
|
```tsx
|
|
433
|
+
import type { ReactNode } from 'react';
|
|
333
434
|
import { AuthProvider, PreviewBanner } from '@object-ui/auth';
|
|
334
435
|
|
|
436
|
+
declare const Dashboard: () => ReactNode;
|
|
437
|
+
|
|
335
438
|
function App() {
|
|
336
439
|
return (
|
|
337
440
|
<AuthProvider
|
|
@@ -377,6 +480,8 @@ import { PreviewBanner } from '@object-ui/auth';
|
|
|
377
480
|
Use the `useAuth` hook to check if the app is in preview mode:
|
|
378
481
|
|
|
379
482
|
```tsx
|
|
483
|
+
import { useAuth } from '@object-ui/auth';
|
|
484
|
+
|
|
380
485
|
function MyComponent() {
|
|
381
486
|
const { isPreviewMode, previewMode } = useAuth();
|
|
382
487
|
|
|
@@ -21,6 +21,30 @@
|
|
|
21
21
|
* next in that tab unless something removes them.
|
|
22
22
|
*/
|
|
23
23
|
export declare function purgePreviousUserClientState(): void;
|
|
24
|
+
/**
|
|
25
|
+
* The number of owner changes {@link SessionUserScope.adopt} has performed in
|
|
26
|
+
* this page-load — `0` until a DIFFERENT user than the one the browser
|
|
27
|
+
* previously held is adopted.
|
|
28
|
+
*
|
|
29
|
+
* ## Why a count and not only an event (objectui#10193)
|
|
30
|
+
*
|
|
31
|
+
* The purge sweeps the previous owner's STORAGE, but state that was already
|
|
32
|
+
* derived from that storage before the purge — the UI language the boot
|
|
33
|
+
* resolved out of the previous owner's language slots, above all — lives in
|
|
34
|
+
* memory and is untouched by it. Whoever owns such state has to re-derive it,
|
|
35
|
+
* and the component that does may mount AFTER the purge happened (the shell
|
|
36
|
+
* mounts behind the session gate, the purge fires as the session resolves). A
|
|
37
|
+
* bare event would be missed by it; a count lets a late reader compare against
|
|
38
|
+
* what it has already handled. Shaped for React's `useSyncExternalStore`
|
|
39
|
+
* together with {@link subscribeSessionOwnerChange}.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getSessionOwnerChangeCount(): number;
|
|
42
|
+
/**
|
|
43
|
+
* Be told each time {@link SessionUserScope.adopt} drops a previous owner's
|
|
44
|
+
* state. Returns the unsubscribe function. A listener that throws is reported
|
|
45
|
+
* and skipped, never allowed to break the sign-in path that called `adopt`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function subscribeSessionOwnerChange(listener: () => void): () => void;
|
|
24
48
|
/**
|
|
25
49
|
* Which user's client state this browser holds, and the transition that drops
|
|
26
50
|
* the previous user's state.
|
|
@@ -39,6 +63,13 @@ export declare const SessionUserScope: {
|
|
|
39
63
|
* Record `userId` as the owner of this browser's client state, dropping the
|
|
40
64
|
* previous owner's state wholesale when it changes.
|
|
41
65
|
*
|
|
66
|
+
* Returns `true` when it dropped a previous owner's state, `false`
|
|
67
|
+
* otherwise (same user, first-ever owner, empty id). A drop is also reported
|
|
68
|
+
* to {@link subscribeSessionOwnerChange} listeners and counted by
|
|
69
|
+
* {@link getSessionOwnerChangeCount} (objectui#10193): the purge clears
|
|
70
|
+
* storage, and anything already derived from that storage in memory is the
|
|
71
|
+
* listener's to re-derive.
|
|
72
|
+
*
|
|
42
73
|
* Idempotent: re-adopting the same user purges nothing. Called from
|
|
43
74
|
* `AuthProvider` for real sessions only — the synthetic `preview-user` /
|
|
44
75
|
* `guest` identities never reach it, so a preview mount cannot purge a real
|
|
@@ -51,7 +82,7 @@ export declare const SessionUserScope: {
|
|
|
51
82
|
* it removes every active-org spelling and leaves `auth-session-user-id`
|
|
52
83
|
* alone on purpose.
|
|
53
84
|
*/
|
|
54
|
-
adopt(userId: string):
|
|
85
|
+
adopt(userId: string): boolean;
|
|
55
86
|
/**
|
|
56
87
|
* Forget the in-memory pointer. Test seam only — production has no reason to
|
|
57
88
|
* un-know who is signed in, and sign-out deliberately KEEPS the persisted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActiveOrganizationStorage.d.ts","sourceRoot":"","sources":["../src/ActiveOrganizationStorage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyOH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAInD;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB;IAC3B;;;;;OAKG;aACc,MAAM,GAAG,IAAI;IAE9B,mFAAmF;eACxE,MAAM,GAAG,IAAI;IAKxB
|
|
1
|
+
{"version":3,"file":"ActiveOrganizationStorage.d.ts","sourceRoot":"","sources":["../src/ActiveOrganizationStorage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyOH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAInD;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK5E;AAaD;;;GAGG;AACH,eAAO,MAAM,gBAAgB;IAC3B;;;;;OAKG;aACc,MAAM,GAAG,IAAI;IAE9B,mFAAmF;eACxE,MAAM,GAAG,IAAI;IAKxB;;;;;;;;;;;;;;;;;;;;;;OAsBG;kBACW,MAAM,GAAG,OAAO;IA+B9B;;;;;OAKG;sBACe,IAAI;CAGvB,CAAC;AAqBF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,yBAAyB;kBACd,MAAM,GAAG,IAAI;IAEnC;;;;;;;;OAQG;;IAGH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;WACI,MAAM,GAAG,IAAI;IAsBpB;;;;OAIG;eACQ,MAAM,GAAG,IAAI;IAOxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;aACM,IAAI;CAyCd,CAAC"}
|
|
@@ -252,6 +252,62 @@ export function purgePreviousUserClientState() {
|
|
|
252
252
|
sweepStore(safeStore('local'));
|
|
253
253
|
sweepStore(safeStore('session'));
|
|
254
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Listeners told that {@link SessionUserScope.adopt} dropped a previous
|
|
257
|
+
* owner's state (objectui#10193).
|
|
258
|
+
*/
|
|
259
|
+
const ownerChangeListeners = new Set();
|
|
260
|
+
/**
|
|
261
|
+
* How many times THIS page-load has dropped a previous owner's state.
|
|
262
|
+
*
|
|
263
|
+
* In memory on purpose, and never persisted: the question it answers is "has a
|
|
264
|
+
* purge happened since this code started running", and a storage copy would
|
|
265
|
+
* answer it for a page-load that no longer exists. ⛔ It is not a storage key,
|
|
266
|
+
* and the ruling on objectui#10193 refuses one.
|
|
267
|
+
*/
|
|
268
|
+
let ownerChangeCount = 0;
|
|
269
|
+
/**
|
|
270
|
+
* The number of owner changes {@link SessionUserScope.adopt} has performed in
|
|
271
|
+
* this page-load — `0` until a DIFFERENT user than the one the browser
|
|
272
|
+
* previously held is adopted.
|
|
273
|
+
*
|
|
274
|
+
* ## Why a count and not only an event (objectui#10193)
|
|
275
|
+
*
|
|
276
|
+
* The purge sweeps the previous owner's STORAGE, but state that was already
|
|
277
|
+
* derived from that storage before the purge — the UI language the boot
|
|
278
|
+
* resolved out of the previous owner's language slots, above all — lives in
|
|
279
|
+
* memory and is untouched by it. Whoever owns such state has to re-derive it,
|
|
280
|
+
* and the component that does may mount AFTER the purge happened (the shell
|
|
281
|
+
* mounts behind the session gate, the purge fires as the session resolves). A
|
|
282
|
+
* bare event would be missed by it; a count lets a late reader compare against
|
|
283
|
+
* what it has already handled. Shaped for React's `useSyncExternalStore`
|
|
284
|
+
* together with {@link subscribeSessionOwnerChange}.
|
|
285
|
+
*/
|
|
286
|
+
export function getSessionOwnerChangeCount() {
|
|
287
|
+
return ownerChangeCount;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Be told each time {@link SessionUserScope.adopt} drops a previous owner's
|
|
291
|
+
* state. Returns the unsubscribe function. A listener that throws is reported
|
|
292
|
+
* and skipped, never allowed to break the sign-in path that called `adopt`.
|
|
293
|
+
*/
|
|
294
|
+
export function subscribeSessionOwnerChange(listener) {
|
|
295
|
+
ownerChangeListeners.add(listener);
|
|
296
|
+
return () => {
|
|
297
|
+
ownerChangeListeners.delete(listener);
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function notifyOwnerChange() {
|
|
301
|
+
ownerChangeCount += 1;
|
|
302
|
+
for (const listener of [...ownerChangeListeners]) {
|
|
303
|
+
try {
|
|
304
|
+
listener();
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
console.warn('[SessionUserScope] an owner-change listener threw:', err);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
255
311
|
/**
|
|
256
312
|
* Which user's client state this browser holds, and the transition that drops
|
|
257
313
|
* the previous user's state.
|
|
@@ -274,6 +330,13 @@ export const SessionUserScope = {
|
|
|
274
330
|
* Record `userId` as the owner of this browser's client state, dropping the
|
|
275
331
|
* previous owner's state wholesale when it changes.
|
|
276
332
|
*
|
|
333
|
+
* Returns `true` when it dropped a previous owner's state, `false`
|
|
334
|
+
* otherwise (same user, first-ever owner, empty id). A drop is also reported
|
|
335
|
+
* to {@link subscribeSessionOwnerChange} listeners and counted by
|
|
336
|
+
* {@link getSessionOwnerChangeCount} (objectui#10193): the purge clears
|
|
337
|
+
* storage, and anything already derived from that storage in memory is the
|
|
338
|
+
* listener's to re-derive.
|
|
339
|
+
*
|
|
277
340
|
* Idempotent: re-adopting the same user purges nothing. Called from
|
|
278
341
|
* `AuthProvider` for real sessions only — the synthetic `preview-user` /
|
|
279
342
|
* `guest` identities never reach it, so a preview mount cannot purge a real
|
|
@@ -288,13 +351,13 @@ export const SessionUserScope = {
|
|
|
288
351
|
*/
|
|
289
352
|
adopt(userId) {
|
|
290
353
|
if (!userId)
|
|
291
|
-
return;
|
|
354
|
+
return false;
|
|
292
355
|
// Already this page-load's owner. Short-circuits before the storage read
|
|
293
356
|
// below, which matters in a browser that REJECTS WRITES: there the pointer
|
|
294
357
|
// write further down silently fails, so every later call would re-read a
|
|
295
358
|
// stale persisted owner and purge again.
|
|
296
359
|
if (this._userId === userId)
|
|
297
|
-
return;
|
|
360
|
+
return false;
|
|
298
361
|
// WHOSE RESIDUE IS IN THIS STORE — read from storage, deliberately not
|
|
299
362
|
// from {@link current}. The two questions are different and take different
|
|
300
363
|
// authorities. `current()` answers "which key do I use right now", and
|
|
@@ -304,7 +367,8 @@ export const SessionUserScope = {
|
|
|
304
367
|
// to drop, and purging on the strength of an in-memory id would delete
|
|
305
368
|
// state that was written for the arriving user.
|
|
306
369
|
const persistedPrevious = readPersisted(SESSION_USER_STORAGE_KEY);
|
|
307
|
-
|
|
370
|
+
const ownerChanged = persistedPrevious !== null && persistedPrevious !== userId;
|
|
371
|
+
if (ownerChanged) {
|
|
308
372
|
purgePreviousUserClientState();
|
|
309
373
|
}
|
|
310
374
|
this._userId = userId;
|
|
@@ -312,6 +376,11 @@ export const SessionUserScope = {
|
|
|
312
376
|
// Unattributable legacy state, cleaned up on any adopt rather than only on
|
|
313
377
|
// a change — see LEGACY_ACTIVE_ORG_KEY.
|
|
314
378
|
removePersisted(LEGACY_ACTIVE_ORG_KEY);
|
|
379
|
+
// Last, so a listener that reads `current()` or the store sees the
|
|
380
|
+
// arriving owner and the swept storage, never a half-adopted state.
|
|
381
|
+
if (ownerChanged)
|
|
382
|
+
notifyOwnerChange();
|
|
383
|
+
return ownerChanged;
|
|
315
384
|
},
|
|
316
385
|
/**
|
|
317
386
|
* Forget the in-memory pointer. Test seam only — production has no reason to
|
package/dist/AuthContext.d.ts
CHANGED
|
@@ -33,8 +33,10 @@ export interface AuthContextValue {
|
|
|
33
33
|
/** Sign up with name, email, and password.
|
|
34
34
|
* Returns `{ requiresVerification: true }` when the server accepted the
|
|
35
35
|
* account but is gating sign-in on email verification; callers should
|
|
36
|
-
* show a "check your inbox" UI instead of navigating to a protected page.
|
|
37
|
-
|
|
36
|
+
* show a "check your inbox" UI instead of navigating to a protected page.
|
|
37
|
+
* `callbackURL` is where the email-verification link lands — see
|
|
38
|
+
* `SignUpData.callbackURL` for the shape the server accepts. */
|
|
39
|
+
signUp: (name: string, email: string, password: string, callbackURL?: string) => Promise<{
|
|
38
40
|
requiresVerification: boolean;
|
|
39
41
|
}>;
|
|
40
42
|
/** Sign out the current user */
|
|
@@ -141,11 +143,16 @@ export interface AuthContextValue {
|
|
|
141
143
|
isMembershipResolved: boolean;
|
|
142
144
|
/** Switch the active organization (workspace) */
|
|
143
145
|
switchOrganization: (orgId: string) => Promise<void>;
|
|
144
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Create a new organization. `timezone` is the creator's IANA zone, sent as
|
|
148
|
+
* the `timezone` query parameter, never in the body; see
|
|
149
|
+
* `AuthClient.createOrganization` (objectui#11908).
|
|
150
|
+
*/
|
|
145
151
|
createOrganization: (data: {
|
|
146
152
|
name: string;
|
|
147
153
|
slug: string;
|
|
148
154
|
logo?: string;
|
|
155
|
+
timezone?: string;
|
|
149
156
|
}) => Promise<AuthOrganization>;
|
|
150
157
|
/**
|
|
151
158
|
* Refresh the organizations list. Returns the freshly fetched list so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../src/AuthContext.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,cAAc,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzM,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,kCAAkC;IAClC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,wCAAwC;IACxC,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,iDAAiD;IACjD,aAAa,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,WAAW,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACvC,sCAAsC;IACtC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D
|
|
1
|
+
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../src/AuthContext.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,KAAK,EAAwB,mBAAmB,EAAE,kBAAkB,EAAyG,MAAM,YAAY,CAAC;AA4GvM,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EAAE,cAAc,EACtB,iBAAiB,EACjB,OAAc,EACd,WAAW,EACX,QAAQ,GACT,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,KAAK,EAAwB,mBAAmB,EAAE,kBAAkB,EAAyG,MAAM,YAAY,CAAC;AA4GvM,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EAAE,cAAc,EACtB,iBAAiB,EACjB,OAAc,EACd,WAAW,EACX,QAAQ,GACT,EAAE,iBAAiB,qBAq9BnB"}
|