From fd2c82da84589003272d13123db493e79bd460cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 23 Jul 2026 00:59:19 +0000 Subject: [PATCH] log token revoke as a security event Co-Authored-By: Claude Opus 4.8 --- src/servers/api/auth/auth.ts | 6 ++++-- src/servers/api/auth/revoke-handler.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/servers/api/auth/revoke-handler.ts diff --git a/src/servers/api/auth/auth.ts b/src/servers/api/auth/auth.ts index a77321f4..a66a718d 100644 --- a/src/servers/api/auth/auth.ts +++ b/src/servers/api/auth/auth.ts @@ -20,6 +20,7 @@ import { resetPasswordHandler } from './reset-password'; import { bootstrapHandler } from './bootstrap'; import { usersMe } from './users-me'; import { panicHandler } from './panic-handler'; +import { revokeHandler } from './revoke-handler'; import { passkeyRouter } from './passkey-router'; export const authRouter = createRouter(); @@ -33,8 +34,9 @@ authRouter.use('/', async (ctx) => ctx.json({ officerAuthServer: 'ok' })); authRouter.get('/me', userMiddleware, usersMe); authRouter.post('/signin', signinRateLimiter, signinHandler); authRouter.post('/signout', userMiddleware, signoutHandler); -// Revoke the current token — blacklists this JWT (same effect as signout). The app's "distress". -authRouter.post('/revoke', userMiddleware, signoutHandler); +// Revoke the current token — blacklists this JWT (same effect as signout), logged as a security +// event. The app's "distress". +authRouter.post('/revoke', userMiddleware, revokeHandler); // Trigger the panic lockdown — authenticated, no password in the body. authRouter.post('/panic', userMiddleware, panicHandler); authRouter.post('/signup', signupRateLimiter, signupHandler); diff --git a/src/servers/api/auth/revoke-handler.ts b/src/servers/api/auth/revoke-handler.ts new file mode 100644 index 00000000..970c3bb4 --- /dev/null +++ b/src/servers/api/auth/revoke-handler.ts @@ -0,0 +1,18 @@ +import type { Handler } from 'hono'; +import { blacklistToken, cleanupExpiredTokens } from 'officerdb'; +import { clientIp } from './client-ip'; + +// Distress: blacklist the current token (same as signout) but log it as a security event. +export const revokeHandler: Handler = async (ctx) => { + const user = ctx.get('user') as { jti: string; exp: number; email?: string }; + const origin = ctx.get('origin') as string | undefined; + + console.warn( + `[SECURITY] token revoked (distress) — user=${user.email || '?'}, origin=${origin || '-'}, ip=${clientIp(ctx)} — ${new Date().toISOString()}`, + ); + + await blacklistToken(user.jti, user.exp); + cleanupExpiredTokens().catch(() => {}); + + return ctx.json({ ok: true }); +};