Skip to content

Commit c322421

Browse files
fix(console): after a sign-out, the next person to sign in lands on their own home, not the previous person's page (#30490)
domain:ui#5 · os-sales · session_01L6bhgUtwT9qwYbCjRDVu5Y <!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuHF6g83xPLnHoDsxEw6wKQ6)_ Part of #30125 (item 4 fixed here; item 3 measured and not reproduced on main, no change; items 1, 2 and 5 stay on the card). Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - 改了什么:在控制台里点「退出登录」之后,登录页不再带 `?redirect=` 回到上一个人打开的页面,下一个人登录后落到自己的首页。会话过期或被吊销(不是主动退出)时仍保留原路径,同一个人重新登录后回到原处。 - 为什么改:管理员在「系统设置」页面退出后,新成员登录直接落到那个页面,看到「你没有访问此应用的权限」(卡片 item 4,main 的生产构建上已复现)。 - 风险与回滚:只改登录跳转的一个分支;退出时少带一个参数,其它跳转(邀请链接、深链接、过期)不变;回滚即还原本 PR,无数据迁移。 - 先看:`ui/packages/auth/src/AuthProvider.tsx`(`sessionEndedBySignOut` 的升起与落下)、`ui/apps/console/src/components/ProtectedRoute.tsx` 的 `LoginRedirect`、测试文件 `ProtectedRoute.signOutListState-29174.test.tsx` 新增的 objectstack#30125 describe。 - 两分钟验证:`pnpm --dir ui exec vitest run apps/console/src/components/ProtectedRoute.signOutListState-29174.test.tsx`,预期 7 个用例全绿。 - 需要你做的:无,席位落地。 ## What changes Card #30125, **item 4**: after a sign-out, the next person lands on the previous person's page. - **`@object-ui/auth`**: `AuthContextValue.sessionEndedBySignOut` (new, boolean). `AuthProvider.signOut()` raises it in the same batch that clears `user`/`session`, so the first render without a session already reads it; it is lowered during render as soon as a session exists again, whoever it belongs to. A session that ends any other way (`loadSession` re-resolving to nothing: expiry, revocation, a sign-out in another tab) leaves it `false`. The outside-provider default in `useAuth.ts` answers `false`. - **`@object-ui/console`**: `LoginRedirect` sends the visitor to `/login` with no `?redirect=` when `sessionEndedBySignOut` is true. Otherwise it is unchanged: the path and params are kept, list `uf_*` state stripped (#29174). - All three sign-out call sites (`AppHeader`, `UserMenu`, `RemediationOverlay`) go through `AuthProvider.signOut()`, so the change is at that one source; `AppHeader.tsx` is not touched. - **Changeset**: `.changeset/30125-sign-out-no-return-link.md`, `@object-ui/auth` and `@object-ui/console` patch. Branch: the harness-assigned `claude/project-thread-guv7bd`, not `claude/issue-30125-*`; the card is #30125. ## Tests In the existing harness file `ProtectedRoute.signOutListState-29174.test.tsx` (real `AuthProvider` over a scripted client, real `ProtectedRoute`, real Console `LoginPage`), a new top-level describe for #30125, 3 cases: - A signs out of a Setup page; the login URL is exactly `/login`, no `redirect`; B signs in and lands on `/`. - A's session expires on the same page (the provider's own `refreshSession` re-resolves to no session); the login URL keeps the path; A signs back in and lands on it. - The sign-out is forgotten once someone signs in: A signs out, B signs in in the same tab, B's session then expires on the page, and the path is kept again. The #29174 block's first case now ends A's session by **expiry** instead of by sign-out: an explicit sign-out carries no return link at all, so the list-state stripping it pins is only reachable on the expiry path. Its assertions are otherwise unchanged (moved pin, declared). ## Acceptance notes - **Item 3 (time zone dialog over a toast): not reproduced on a production build of main, no change.** Measured with `os start` (Mode: production) on `examples/app-todo`, console built by `pnpm console:build` at `989ae42d`, Chromium 1440x900, zh-CN, Asia/Shanghai, a freshly registered first administrator. The dialog opened 0.2 to 0.3 s after the first entry into any `/apps/...` route (Setup), before any action was possible; after it was answered, inviting a member raised the invitation toast and no dialog opened over it. The prompt is mounted only under `/apps/:appName/*` and asks once per org and user, so on main it cannot first appear after an in-app action. Residual, not measured: an action OUTSIDE `/apps` that raises a toast and then navigates into an app within the toast's 4 s could still open the dialog over it; no such path was found in the first-run flow. - Items 1, 2 and 5 are out of this PR (server and product call / held by #30157 PR #30243 / devx template) and stay on the card. - `AuthContextValue` gains a required member. Code that builds its own value of that type (a test double typed as `AuthContextValue`) must add it; in this tree the only such builders are `useAuth.ts` (updated) and `inbox-arrival-preview.tsx`, which casts through `unknown`. Declared `Clause-②: yes (widening)` for this, as PR #29338 did for its `AuthContextValue` / `AuthClient` members. - Two browser tabs: a sign-out in tab 1 reaches tab 2 as a session that re-resolved to nothing, so tab 2 keeps its path. That is the expiry reading, by design: this provider cannot tell who signed out over there. ## Verification Tests, typecheck and the derived gates ran on `aa800e72` (the fix) or earlier as noted; the head adds evidence commits and their removals, the tree is byte-identical to `aa800e72` (`git diff aa800e7 HEAD` is empty). Base `989ae42d`. <details><summary>Live rig and screenshots (production mode, zh-CN, 1440 wide)</summary> `pnpm build` + `pnpm console:build`, then `objectstack start -p PORT --home TMP --database file:TMP` in `examples/app-todo` (banner: `Mode: production`). First administrator registered on `/register`; a member invited from Setup, Invitations, then signed up and accepted through the auth API. Item 4, before (main `989ae42d`): the administrator signs out of `/apps/com.objectstack.setup/sys_user`; the login page is `/login?redirect=%2Fapps%2Fcom.objectstack.setup%2Fsys_user`; the member signs in and lands on that page: 你没有访问此应用的权限. ![before: login URL](https://github.com/objectstack-ai/objectstack/blob/9dc2fc0669fdacd5c2c6236233037598532fbc6e/evidence/30125/item4-before-1-login-after-signout-1440.png?raw=true) ![before: member landing](https://github.com/objectstack-ai/objectstack/blob/9dc2fc0669fdacd5c2c6236233037598532fbc6e/evidence/30125/item4-before-2-member-landing-1440.png?raw=true) Item 4, after (console rebuilt at `aa800e72`): the login page is `/login`; the member lands on `/`, which opens the member's own default app. ![after: login URL](https://github.com/objectstack-ai/objectstack/blob/4e455f051f9101c8b48538e24d1178e05e98b383/evidence/30125/item4-after-1-login-after-signout-1440.png?raw=true) ![after: member landing](https://github.com/objectstack-ai/objectstack/blob/4e455f051f9101c8b48538e24d1178e05e98b383/evidence/30125/item4-after-2-member-landing-1440.png?raw=true) Item 3 (main `989ae42d`): the dialog at first entry into Setup, and the invitation toast uncovered after it was answered. ![item 3: dialog at first app entry](https://github.com/objectstack-ai/objectstack/blob/9dc2fc0669fdacd5c2c6236233037598532fbc6e/evidence/30125/item3-main-prod-prompt-at-first-app-entry-1440.png?raw=true) ![item 3: invitation toast uncovered](https://github.com/objectstack-ai/objectstack/blob/9dc2fc0669fdacd5c2c6236233037598532fbc6e/evidence/30125/item3-main-prod-invite-toast-uncovered-1440.png?raw=true) </details> <details><summary>Tests, ablation, typecheck, lint</summary> - The harness file: Test Files 1 passed (1), Tests 7 passed (7), lock VERDICT command-exit 0. - `pnpm --dir ui exec vitest run --maxWorkers=3 packages/auth/ apps/console/ packages/app-shell/src/providers/__tests__/MetadataProvider.crossPrincipalSeed.test.tsx` on `aa800e72`: Test Files 341 passed (341), Tests 3315 passed (3315), exit 0. - Ablation 1 (`scripts/ablation-replace.mjs`, anchor x1 to x0): `LoginRedirect`'s sign-out branch disabled: 2 failed / 5 passed (the sign-out case and the forgotten-sign-out case). Restored, blob equals HEAD, `git diff HEAD` empty. - Ablation 2, on `aa800e72`: the flag's reset in `AuthProvider` disabled: 1 failed / 6 passed (the forgotten-sign-out case). Restored, blob equals HEAD. - `pnpm --filter @object-ui/auth run type-check`, `@object-ui/app-shell`, `@object-ui/console` (after rebuilding `@object-ui/auth`): each exit 0. `pnpm --dir ui run check:spec-symbols`: exit 0. - eslint on the five changed files: 0 errors; the three remaining warnings are on `AuthProvider.tsx` lines this PR does not change. </details> <details><summary>Derived gates (35/35 exit 0)</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 35 families; the list is identical at the final head. Every one ran and exited 0; the sweep ran while the head moved from `0ddd2bd3` to `7a94e786`, and the later source commits change only lines inside `ui/packages/auth/src` that no family reads. `--ran` reconciliation: 35 derived, 35 run, 0 NOT-MEASURED (a derived zero, every line carries an exit code). Also `pnpm --dir ui run check:new-line-citations` and `check:control-bytes`: exit 0. CI runs the rest. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fa9c0aa commit c322421

6 files changed

Lines changed: 153 additions & 19 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/auth': patch
3+
'@object-ui/console': patch
4+
---
5+
6+
fix(console,auth): after an explicit sign-out, whoever signs in next lands on their own home, not on the page the previous person had open
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **`@object-ui/auth`: `AuthContextValue.sessionEndedBySignOut`**, `true` from a `signOut()` until a session is established again in the tab, whoever it belongs to; `false` on load, while signed in, and after a session that ended any other way (expired, revoked, signed out in another tab). It is a new required member of the exported `AuthContextValue` type: code that builds its own value of that type (a test double, a custom provider) must add `sessionEndedBySignOut: false` (or its own reading) to type-check.
12+
- **`@object-ui/console`: `LoginRedirect`** sends the visitor to `/login` with no `?redirect=` after an explicit sign-out. A new member signing in after the administrator signed out of a Setup page used to land on that page and read "you do not have access to this app". A session that expires or is revoked under the person still keeps the path (list state stripped as before), so that person signs back in where they were.

‎ui/apps/console/src/components/ProtectedRoute.signOutListState-29174.test.tsx‎

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
* `LoginRedirect`), and the real Console `LoginPage` with its post-login
1919
* hand-off. Only the shell's provider stack is a pass-through.
2020
*
21-
* - A signs out of a list carrying every kind of list state; the login page's
22-
* URL carries none of it, and B lands on the same path with the other
23-
* params kept (the card's reproduction, steps 4 to 7);
21+
* - A's session expires on a list carrying every kind of list state; the
22+
* login page's URL carries none of it, and B lands on the same path with
23+
* the other params kept (the card's reproduction, steps 4 to 7, with the
24+
* session ending by expiry: an explicit sign-out now carries no return link
25+
* at all, pinned in the objectstack#30125 block below);
2426
* - an invitation redirect still lands on the invitation;
2527
* - a signed-out arrival on a deep link with no list state keeps it byte for
2628
* byte;
@@ -34,10 +36,10 @@
3436

3537
import '@testing-library/jest-dom/vitest';
3638
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
37-
import { render, screen, waitFor, cleanup } from '@testing-library/react';
39+
import { render, screen, waitFor, cleanup, act } from '@testing-library/react';
3840
import userEvent from '@testing-library/user-event';
39-
import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom';
40-
import type { ReactNode } from 'react';
41+
import { MemoryRouter, Routes, Route, useLocation, useNavigate } from 'react-router-dom';
42+
import { useEffect, type ReactNode } from 'react';
4143
import { AuthProvider, useAuth } from '@object-ui/auth';
4244
import type { AuthClient } from '@object-ui/auth';
4345

@@ -100,24 +102,43 @@ function createClient(): AuthClient {
100102

101103
/** Every location the router settles on, oldest first. */
102104
const seen: string[] = [];
105+
/** The router's own navigate, for an in-app move between sign-ins. */
106+
const navigateTo: { current: ((to: string) => void) | null } = { current: null };
103107
function LocationProbe() {
104108
const location = useLocation();
109+
const navigate = useNavigate();
110+
useEffect(() => {
111+
navigateTo.current = navigate;
112+
}, [navigate]);
105113
seen.push(location.pathname + location.search);
106114
return null;
107115
}
108116

109-
/** The list page, reduced to the account menu's sign-out. */
117+
/**
118+
* The list page, reduced to the account menu's sign-out and a session
119+
* re-resolution: the provider's own loader, which is how a session that
120+
* expired or was revoked under the page reaches it.
121+
*/
110122
function SignedInList() {
111-
const { user, signOut } = useAuth();
123+
const { user, signOut, refreshSession } = useAuth();
112124
return (
113125
<div data-testid="list-page" data-user={user?.id ?? ''}>
114126
<button type="button" onClick={() => void signOut()}>
115127
Sign out
116128
</button>
129+
<button type="button" onClick={() => void refreshSession()}>
130+
Re-check session
131+
</button>
117132
</div>
118133
);
119134
}
120135

136+
/** The server ends the session on its own (expiry, revocation), and the page learns it. */
137+
async function expireSession() {
138+
serverUser = null;
139+
await userEvent.setup().click(screen.getByRole('button', { name: 'Re-check session' }));
140+
}
141+
121142
function renderConsole(entry: string, client: AuthClient) {
122143
return render(
123144
<AuthProvider authUrl="/api/v1/auth" client={client}>
@@ -176,7 +197,7 @@ afterEach(() => {
176197
});
177198

178199
describe('signing out leaves the list state behind (objectstack#29174)', () => {
179-
it('A signs out of a filtered, searched, sorted list; B signs in and lands on the path with none of it', async () => {
200+
it("A's session expires on a filtered, searched, sorted list; B signs in and lands on the path with none of it", async () => {
180201
serverUser = USER_A;
181202
const client = createClient();
182203
renderConsole(A_LIST_URL, client);
@@ -185,8 +206,8 @@ describe('signing out leaves the list state behind (objectstack#29174)', () => {
185206
expect((await screen.findByTestId('list-page')).dataset.user).toBe(USER_A.id);
186207
expect(seen[seen.length - 1]).toBe(A_LIST_URL);
187208

188-
await userEvent.setup().click(screen.getByRole('button', { name: 'Sign out' }));
189-
expect(client.signOut).toHaveBeenCalledTimes(1);
209+
await expireSession();
210+
expect(client.signOut).not.toHaveBeenCalled();
190211

191212
// The login page's own URL carries none of it: not the params, not a value.
192213
const { url, redirect } = await loginRedirect();
@@ -236,3 +257,70 @@ describe('signing out leaves the list state behind (objectstack#29174)', () => {
236257
expect(redirect).toBe(`${LIST_PATH}?recordId=r1`);
237258
});
238259
});
260+
261+
/**
262+
* objectstack#30125 — after a sign-out, the next person lands on the previous
263+
* person's page. A new member's first screen was "you do not have access to
264+
* this app" for the Setup page the administrator had open.
265+
*
266+
* An explicit sign-out sends the visitor to `/login` with no return link, so
267+
* whoever signs in next lands on their own home. A session that ends under the
268+
* person (expired, revoked) still keeps the path, so that person signs back in
269+
* where they were. Same real composition as the block above.
270+
*/
271+
describe('an explicit sign-out leaves no return link (objectstack#30125)', () => {
272+
const SETUP_PAGE = '/apps/com.objectstack.setup/sys_user/view/all?recordId=u1';
273+
274+
it('A signs out of a Setup page; B signs in and lands on home, not on that page', async () => {
275+
serverUser = USER_A;
276+
const client = createClient();
277+
renderConsole(SETUP_PAGE, client);
278+
expect((await screen.findByTestId('list-page')).dataset.user).toBe(USER_A.id);
279+
280+
await userEvent.setup().click(screen.getByRole('button', { name: 'Sign out' }));
281+
expect(client.signOut).toHaveBeenCalledTimes(1);
282+
283+
const { url, redirect } = await loginRedirect();
284+
expect(url).toBe('/login');
285+
expect(redirect).toBeNull();
286+
287+
await signInAs(USER_B.email);
288+
expect(await landing()).toBe(withConsoleBase('/'));
289+
});
290+
291+
it("A's session expires on the same page; A signs back in and lands on it", async () => {
292+
serverUser = USER_A;
293+
const client = createClient();
294+
renderConsole(SETUP_PAGE, client);
295+
expect((await screen.findByTestId('list-page')).dataset.user).toBe(USER_A.id);
296+
297+
await expireSession();
298+
299+
const { url, redirect } = await loginRedirect();
300+
expect(url).toBe(`/login?redirect=${encodeURIComponent(SETUP_PAGE)}`);
301+
expect(redirect).toBe(SETUP_PAGE);
302+
303+
await signInAs(USER_A.email);
304+
expect(await landing()).toBe(withConsoleBase(SETUP_PAGE));
305+
});
306+
307+
it('the sign-out is forgotten once someone signs in: a later expiry keeps the path again', async () => {
308+
serverUser = USER_A;
309+
const client = createClient();
310+
renderConsole(SETUP_PAGE, client);
311+
expect((await screen.findByTestId('list-page')).dataset.user).toBe(USER_A.id);
312+
313+
// A signs out; B signs in on the same tab, without a page load.
314+
await userEvent.setup().click(screen.getByRole('button', { name: 'Sign out' }));
315+
expect((await loginRedirect()).redirect).toBeNull();
316+
await signInAs(USER_B.email);
317+
await landing();
318+
319+
// B opens the page in-app, and B's session then expires under it.
320+
act(() => navigateTo.current!(SETUP_PAGE));
321+
expect((await screen.findByTestId('list-page')).dataset.user).toBe(USER_B.id);
322+
await expireSession();
323+
324+
expect((await loginRedirect()).redirect).toBe(SETUP_PAGE);
325+
});
326+
});

‎ui/apps/console/src/components/ProtectedRoute.tsx‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import type { ReactNode } from 'react';
1414
import { useLocation } from 'react-router-dom';
15-
import { AuthGuard } from '@object-ui/auth';
15+
import { AuthGuard, useAuth } from '@object-ui/auth';
1616
import {
1717
ConnectedShell,
1818
RequireOrganization,
@@ -29,15 +29,25 @@ import {
2929
* every embedded deployment, so a window-derived path would carry the `/_console`
3030
* mount into a value that `LoginPage` then re-prefixes with it (objectui#4168).
3131
*
32-
* It never carries the list's `uf_*` state (objectstack#29174). Whoever signs in
33-
* on that page may not be the person whose session just ended, and a filter
34-
* value can be sensitive; this guard cannot tell a sign-out from an expiry, so
35-
* it drops the state for both. The path and every other param are kept, byte
36-
* for byte when there was no list state, and the list then opens on the
37-
* signing-in user's own stored state or the view's default.
32+
* After an explicit sign-out it carries no return link at all
33+
* (objectstack#30125): the person left, so whoever signs in next is nobody in
34+
* particular and lands on their own home, not on the page the previous person
35+
* had open (a Setup page a new member may not open, say). `AuthProvider` tells
36+
* the two apart with `sessionEndedBySignOut`.
37+
*
38+
* Otherwise (the session expired or was revoked under the person, or a
39+
* signed-out visitor followed a deep link) the path and its params are kept, so
40+
* the person signs back in where they were. Even then it never carries the
41+
* list's `uf_*` state (objectstack#29174): whoever signs in after an expiry may
42+
* still be someone else, and a filter value can be sensitive. The
43+
* path and every other param are kept, byte for byte when there was no list
44+
* state, and the list then opens on the signing-in user's own stored state or
45+
* the view's default.
3846
*/
3947
export function LoginRedirect() {
4048
const location = useLocation();
49+
const { sessionEndedBySignOut } = useAuth();
50+
if (sessionEndedBySignOut) return <RedirectWithSplash to="/login" replace />;
4151
const params = new URLSearchParams(location.search);
4252
const kept = withoutListStateParams(params).toString();
4353
const returnSearch = kept === params.toString() ? location.search : kept ? `?${kept}` : '';

‎ui/packages/auth/src/AuthContext.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ export interface AuthContextValue {
1616
session: AuthClientSession | null;
1717
/** Whether the user is authenticated */
1818
isAuthenticated: boolean;
19+
/**
20+
* objectstack#30125 — `true` when this tab's last session was ended by
21+
* `signOut()` and no session has been established since; `false` on load,
22+
* while signed in, and after a session that ended any other way (expired,
23+
* revoked, signed out in another tab). Lets a sign-in redirect tell "the
24+
* person left", where whoever signs in next is nobody in particular, from
25+
* "the session lapsed under the person", who comes back to the same page.
26+
*/
27+
sessionEndedBySignOut: boolean;
1928
/**
2029
* Whether real authentication is enabled in this environment.
2130
* - `true`: real auth backend is in use; sign-in / sign-out are meaningful.

‎ui/packages/auth/src/AuthProvider.tsx‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ export function AuthProvider({
191191
// finds anything else drops it for good, so every later run loads as before.
192192
const bootSessionFor = useRef<string | null>(bootSession ? authUrl : null);
193193
const [error, setError] = useState<Error | null>(null);
194+
// objectstack#30125 — the session in this tab was ended by `signOut()`, and
195+
// nobody has signed in since. Raised in the same batch that clears `user`, so
196+
// the first render without a session already reads it; lowered as soon as a
197+
// session exists again (below), whoever it belongs to. A session
198+
// that ends any other way (expired, revoked, signed out in another tab)
199+
// leaves it `false`.
200+
const [sessionEndedBySignOut, setSessionEndedBySignOut] = useState(false);
194201
const [remediationRequired, setRemediationRequired] = useState<{ code: string; message: string } | null>(null);
195202
// objectstack#29105 — a password sign-in waiting for its second factor.
196203
const [twoFactorChallenge, setTwoFactorChallenge] = useState<TwoFactorChallenge | null>(null);
@@ -215,6 +222,11 @@ export function AuthProvider({
215222
? user !== null && session !== null
216223
: true;
217224

225+
// Any session, a new sign-in or a re-resolved one, ends "signed out".
226+
// Adjusted during render (React's "storing information from previous
227+
// renders"), so no render ever pairs a session with a raised flag.
228+
if (sessionEndedBySignOut && user !== null) setSessionEndedBySignOut(false);
229+
218230
// True while `loadSession` is awaiting the server. Read by the rotation
219231
// subscription below: a rotation the server performs INSIDE our own
220232
// `getSession` is already reflected in the answer we are about to apply, and
@@ -482,6 +494,7 @@ export function AuthProvider({
482494
await client.signOut();
483495
setUser(null);
484496
setSession(null);
497+
setSessionEndedBySignOut(true);
485498
setError(null);
486499
setTwoFactorChallenge(null);
487500
// The organization block is the previous principal's data too: the list
@@ -1141,6 +1154,7 @@ export function AuthProvider({
11411154
user,
11421155
session,
11431156
isAuthenticated,
1157+
sessionEndedBySignOut,
11441158
isAuthEnabled,
11451159
isLoading,
11461160
error,
@@ -1196,7 +1210,7 @@ export function AuthProvider({
11961210
listUserInvitations,
11971211
}),
11981212
[
1199-
user, session, isAuthenticated, isAuthEnabled, isLoading, error, isPreviewMode, previewMode,
1213+
user, session, isAuthenticated, sessionEndedBySignOut, isAuthEnabled, isLoading, error, isPreviewMode, previewMode,
12001214
signIn, twoFactorChallenge, verifySignInTwoFactor, cancelTwoFactorChallenge, signUp, signOut, refreshSession, updateUser, forgotPassword, sendVerificationEmail, resetPassword, changePassword, setInitialPassword, hasLocalPassword, getAuthConfig, signInWithProvider,
12011215
sendPhoneOtp, signInWithPhoneOtp, signInWithPhonePassword, requestPhonePasswordReset, resetPasswordWithPhoneOtp,
12021216
remediationRequired, enrollTotp, verifyTotp,

‎ui/packages/auth/src/useAuth.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export function useAuth(): AuthContextValue {
3131
user: null,
3232
session: null,
3333
isAuthenticated: false,
34+
sessionEndedBySignOut: false,
3435
isAuthEnabled: false,
3536
isLoading: false,
3637
error: null,

0 commit comments

Comments
 (0)