Skip to content
Merged
2 changes: 2 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.7",
"@radix-ui/react-label": "^2.1.3",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-portal": "^1.1.9",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.4",
"@radix-ui/react-select": "^2.1.7",
Expand Down
8 changes: 6 additions & 2 deletions src/core/server/auth/ory/oauth-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ export async function revokeOryOAuthSessionsForSubject(
return
}

await revokeConsentSessions(subject, clientId)
await revokeLoginSessions(subject)
// Independent Hydra revocations; run concurrently. Each call logs and
// swallows its own errors.
await Promise.all([
revokeConsentSessions(subject, clientId),
revokeLoginSessions(subject),
])
}

async function revokeConsentSessions(
Expand Down
15 changes: 8 additions & 7 deletions src/core/server/auth/ory/signout-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ export async function completeOrySignOut(origin = BASE_URL): Promise<string> {
)
}

if (userId) {
await revokeOryOAuthSessionsForSubject(userId)
}

if (identityId) {
await revokeKratosSessionsForIdentity(identityId)
}
// Hydra OAuth and Kratos session revocations are independent admin calls;
// run them concurrently to keep the sign-out action fast. Both helpers
// log-and-swallow their own errors, and the Kratos helper retries 429
// contention, so Promise.all never rejects here.
await Promise.all([
userId ? revokeOryOAuthSessionsForSubject(userId) : null,
identityId ? revokeKratosSessionsForIdentity(identityId) : null,
])

const logoutUrl = idToken ? buildOryLogoutUrl({ idToken, origin }) : null
return (logoutUrl ?? postLogoutUrl).toString()
Expand Down
19 changes: 18 additions & 1 deletion src/features/dashboard/sidebar/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { Portal } from '@radix-ui/react-portal'
import Link from 'next/link'
import { useState } from 'react'
import { PROTECTED_URLS } from '@/configs/urls'
Expand All @@ -20,6 +21,7 @@ import {
LogoutIcon,
UnpackIcon,
} from '@/ui/primitives/icons'
import { Loader } from '@/ui/primitives/loader'
import { SidebarMenuButton, SidebarMenuItem } from '@/ui/primitives/sidebar'
import { useDashboard } from '../context'
import { CreateTeamDialog } from './create-team-dialog'
Expand All @@ -29,9 +31,17 @@ import { TeamAvatar } from './team-avatar'
export default function DashboardSidebarMenu() {
const { team } = useDashboard()
const [createTeamOpen, setCreateTeamOpen] = useState(false)
// explicit state instead of useTransition: a sync transition callback
// settles immediately, so isPending would flip back to false while the
// sign-out action is still in flight; this stays true until the redirect
// navigates away, and only resets if the action fails before that
const [isLoggingOut, setIsLoggingOut] = useState(false)

const handleLogout = () => {
signOutAction()
setIsLoggingOut(true)
signOutAction().catch(() => {
setIsLoggingOut(false)
})
Comment thread
huv1k marked this conversation as resolved.
}

return (
Expand Down Expand Up @@ -94,6 +104,7 @@ export default function DashboardSidebarMenu() {
<DropdownMenuItem
variant="error"
className="h-9 gap-2.5 [&_svg]:size-5 font-sans prose-body-highlight"
disabled={isLoggingOut}
onSelect={handleLogout}
>
<LogoutIcon className="ml-0.5" /> Log out
Expand All @@ -106,6 +117,12 @@ export default function DashboardSidebarMenu() {
open={createTeamOpen}
onOpenChange={setCreateTeamOpen}
/>
{isLoggingOut && (
<Portal className="bg-bg/90 fixed inset-0 z-60 flex items-center justify-center gap-2.5">
<Loader variant="slash" size="sm" />
<span className="prose-body-highlight">Logging out...</span>
</Portal>
)}
</>
)
}
Loading