Files
Andrew S Erwin b9721ff13f
release gate / check (push) Failing after 8m35s
initial commit on Federted servers
2026-10-19 16:23:55 -05:00

54 lines
2.5 KiB
TypeScript

export const dynamic = 'force-dynamic'
import { NextResponse } from 'next/server'
import jwt from 'jsonwebtoken'
import { auth } from '@/auth'
import { apiBase } from '@/lib/api'
// Review CR-02: "sign out everywhere" is an explicit, observable action.
// The NextAuth signOut event is a fire-and-forget backstop; this route lets
// the UI establish whether Go's global revocation (tokenVersion bump, which
// kills Go cookie sessions and previously minted assertions) actually
// succeeded BEFORE the local session is discarded — so a backend outage
// degrades to an accurate "signed out here, other sessions may remain"
// outcome instead of a silent partial logout. The request is deadline-bound
// and logs without secrets.
export async function POST(req: Request) {
const expectedOrigin = new URL(process.env.NEXTAUTH_URL || req.url).origin
if (req.headers.get('origin') !== expectedOrigin) {
return NextResponse.json({ error: 'Invalid request origin' }, { status: 403 })
}
const session = await auth()
const secret = process.env.NEXTAUTH_SECRET || process.env.AUTH_SECRET
const id = typeof session?.user?.id === 'string' ? session.user.id : ''
const ver = typeof session?.user?.ver === 'number' ? session.user.ver : 0
if (!secret || !id || ver < 1) {
// No backend identity attached to this session — nothing to revoke.
return NextResponse.json({ revoked: false, reason: 'no-backend-session' })
}
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 5000)
try {
const res = await fetch(`${apiBase()}/api/auth/signout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Same short-lived assertion the data proxy signs (lib/api.ts).
Authorization: `Bearer ${jwt.sign({ uid: id, ver, typ: 'assertion' }, secret, { algorithm: 'HS256', issuer: 'computer', expiresIn: '60s' })}`,
},
cache: 'no-store',
signal: controller.signal,
})
if (!res.ok) {
console.error(`session revoke: Go signout answered ${res.status} for user ${id}`)
return NextResponse.json({ revoked: false, reason: 'backend-error' })
}
return NextResponse.json({ revoked: true })
} catch (error) {
const kind = error instanceof Error ? error.name : 'Error'
console.error(`session revoke: Go signout unreachable for user ${id} (${kind})`)
return NextResponse.json({ revoked: false, reason: 'unreachable' })
} finally {
clearTimeout(timer)
}
}