vault: session-gated notifications WS + lifecycle cleanup

- token-store: shared "give me a valid Vaultwarden access token" (proactive
  refresh) used by both the HTTP proxy and the WS; router refactored onto it.
- notifications WS: validates the platform session in `open` (deferred, owner
  only), injects the stored Vaultwarden token into the upstream, and buffers
  client frames during the async setup so the SignalR handshake isn't dropped.
  The device connects with its platform JWT (?access_token=), never a vault one.
- lifecycle: logout drops the vault token set (keeps the protector); distress
  (/auth/revoke) and panic wipe both token set and protector, forcing a
  one-time master-password re-setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 02:44:55 +00:00
co-authored by Claude Opus 4.8
parent ccc86cca6d
commit aae0fbd0ea
6 changed files with 138 additions and 69 deletions
+5 -2
View File
@@ -1,11 +1,14 @@
import type { Handler } from 'hono';
import { blacklistToken, cleanupExpiredTokens } from 'officerdb';
import { blacklistToken, cleanupExpiredTokens, clearVaultTokens } from 'officerdb';
export const signoutHandler: Handler = async (ctx) => {
const user = ctx.get('user') as { jti: string; exp: number };
const user = ctx.get('user') as { id: number; jti: string; exp: number };
await blacklistToken(user.jti, user.exp);
// Drop the brokered vault session on logout (the protector key stays, so re-login is frictionless).
clearVaultTokens(user.id).catch(() => {});
// Opportunistic cleanup of expired tokens (non-blocking)
cleanupExpiredTokens().catch(() => {});