vault, extracted from the platform into its own repository
The fourth plugin out, after offscale, music and email. Two routers, a sidecar,
three tables and — new — a settings screen, since the vault never had a UI in
the platform.
What it is: a reverse-proxy to a Vaultwarden you already run, plus a credential
broker that keeps the vault session server-side so devices never hold one. It
stores no passwords and cannot read any; every byte through it is ciphertext.
Two things changed rather than moved:
• VAULTWARDEN_URL left .env for a one-row `vault_config` table with a panel
writing it. A plugin cannot ask the owner to edit a file and restart a
process, so the env var could not survive the move. Server-wide, decided
with the owner: one Vaultwarden per install. The sidecar reads it on a 10s
cache rather than per request, since it sits in front of every proxied byte.
• Core stopped importing clearVaultTokens/clearVaultUnlockKey from three auth
handlers. That is the security property behind the distress and panic
buttons, so it could not simply be dropped — the platform gained a small
account-lifecycle hook instead, and the severity mapping moved here where it
belongs. Core now names no table and no column of this plugin.
Two things were deleted rather than carried, both already dark since
2026-08-13 and both recorded in PLUGIN.md:
• The unauthenticated Bitwarden passthrough. A plugin's routes mount behind
Officer's account gate; top-level unauthenticated prefixes are not something
a manifest can declare. Its own comments already called it TEMPORARY,
pending a first-party browser extension that authenticates to Officer.
• The notifications websocket. No plugin owns a socket on this platform yet.
SignalR falls back to HTTP long-polling on its own, so clients still sync.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
// Generic proxy header hygiene for the platform-side /api/vault forwarder. The platform knows nothing
|
||||
// about Vaultwarden — it only forwards faithfully to the officer-vault sidecar (loopback). Bodies are
|
||||
// never touched; we only drop hop-by-hop headers and redact token-shaped query strings from logs.
|
||||
|
||||
const HOP_BY_HOP = new Set([
|
||||
'connection',
|
||||
'keep-alive',
|
||||
'proxy-authenticate',
|
||||
'proxy-authorization',
|
||||
'te',
|
||||
'trailer',
|
||||
'transfer-encoding',
|
||||
'upgrade',
|
||||
'host',
|
||||
]);
|
||||
|
||||
/** Copy headers, dropping only hop-by-hop / proxy-* ones. Authorization + device headers pass through. */
|
||||
export function stripHopByHop(src: Headers): Headers {
|
||||
const out = new Headers();
|
||||
src.forEach((value, key) => {
|
||||
const k = key.toLowerCase();
|
||||
if (HOP_BY_HOP.has(k) || k.startsWith('proxy-')) return;
|
||||
out.set(key, value);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
const REDACT = /([?&](access_token|refresh_token|code|token)=)[^&]*/gi;
|
||||
export const redactPath = (pathAndQuery: string): string => pathAndQuery.replace(REDACT, '$1<redacted>');
|
||||
Reference in New Issue
Block a user