import { getVaultTokens, updateVaultAccess, type VaultTokenSet } from '../db/queries'; import { refreshGrant, pick, expiryFrom } from './broker'; // Read-side of the brokered Vaultwarden token: hand out a currently-valid access token, refreshing it // server-side (with the stored refresh token) when it's near expiry. Shared by the HTTP proxy and the // notifications-WS proxy so both inject a fresh token. The device never sees any of this. const REFRESH_SKEW_MS = 60_000; // refresh a bit before expiry to avoid racing a 401 /** Renew the access token from the stored refresh token; persist and return it, or null on failure. */ export async function refreshAccess(userId: number, ts: VaultTokenSet): Promise { const r = await refreshGrant(ts.refreshToken, ts.clientId ?? 'mobile'); if (!r.ok) return null; const access = pick(r.data, 'access_token'); const refresh = pick(r.data, 'refresh_token'); if (typeof access !== 'string') return null; await updateVaultAccess( userId, access, typeof refresh === 'string' ? refresh : ts.refreshToken, expiryFrom(r.data, Date.now()), ); return access; } /** A currently-valid Vaultwarden access token for the owner, or null if none is stored. */ export async function getValidAccessToken(userId: number): Promise { const ts = await getVaultTokens(userId); if (!ts) return null; if (ts.expiresAt && ts.expiresAt.getTime() < Date.now() + REFRESH_SKEW_MS) { return (await refreshAccess(userId, ts)) ?? ts.accessToken; } return ts.accessToken; }