Files
loyaly_cutomerweb/src/app/api/auth/logout/route.ts
2026-09-25 16:31:10 +05:30

75 lines
3.0 KiB
TypeScript

import {NextResponse} from 'next/server';
import {authApi} from '@/services/api/authApi';
import {sessionCookieOptions} from '@/features/auth/services/sessionToken';
import {
sessionCookieFor,
tokenCookieFor,
} from '@/features/auth/services/tabScope';
import {resolveTabId} from '@/features/auth/services/tabScopeRequest';
import {peekAccessToken} from '@/features/auth/services/upstreamSession';
export const dynamic = 'force-dynamic';
/**
* POST /api/auth/logout — sign THIS TAB out.
*
* Revokes the session upstream first, then clears that tab's two cookies. The
* order is deliberate, and so is the fact that an upstream failure does NOT
* abort the local clear: if the platform is unreachable, the least bad outcome
* is that this tab is signed out immediately and the server-side session lapses
* on its own expiry. Leaving somebody apparently signed in because a revoke
* call failed is the one outcome nobody expects from pressing Sign out.
*
* No refresh attempt: the token is about to be thrown away, so spending a
* refresh token to revoke it is pure waste.
*
* ── Only this tab, and the upstream revoke is still real ─────────────────
* `peekAccessToken` resolves through the tab scope, so the token revoked
* upstream is THIS tab's session and no other. Signing out of the manager tab
* ends the manager's platform session — genuinely, server-side, as before — and
* leaves the admin and staff tabs holding their own untouched sessions in their
* own cookies. Nothing here weakens server-side invalidation; it narrows what
* gets invalidated to what the person actually asked to sign out of.
*
* A request with no resolvable tab clears nothing and still answers 200. There
* is no session to end, and guessing at one would sign out a tab that never
* asked.
*/
export async function POST() {
const accessToken = await peekAccessToken();
if (accessToken) {
try {
await authApi.logout(accessToken);
} catch {
// Already-expired, revoked, or unreachable — all fine. The cookies below
// are what actually ends this tab's session.
}
}
const res = NextResponse.json(
{data: {ok: true}, meta: {generatedAt: new Date().toISOString()}},
{headers: {'cache-control': 'no-store'}},
);
const tabId = await resolveTabId();
if (tabId) {
// Overwrite with an expired cookie rather than only deleting: a delete that
// misses on `path` leaves a live session behind.
res.cookies.set(sessionCookieFor(tabId), '', sessionCookieOptions(0));
res.cookies.set(tokenCookieFor(tabId), '', {
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
path: '/',
maxAge: 0,
});
}
// The pointer is deliberately left alone. It names a tab, not a session, and
// the signed-out tab rewrites it on its next load anyway — clearing it here
// would only blank the server-rendered first paint of whichever OTHER tab the
// person switches to next.
return res;
}