Before the extraction this worked by pasting https://officer.example.com into a Bitwarden client and logging in normally. The extraction moved the door to /api/vault/bw, which needs five URLs configured by hand — technically the same thing, practically nobody's setup. Declares what it actually needs, now that the platform can express it: unauthenticatedRootPaths /identity, /notifications, /icons, /events — the four a Bitwarden client derives from a bare server URL. None is a name Officer serves, so the path alone decides and no sniffing is involved. protocolHeaders bitwarden-client-name, bitwarden-client-version, device-type — for /api/*, the one namespace both servers claim, where the path cannot separate Vaultwarden's /api/sync from Officer's /api/chat. `publicRouter` is exported for the platform to mount above the gate. One router serves all three doors: it strips /api/vault/bw when reached that way, and otherwise the path is already Vaultwarden's own. /bw stays. It costs nothing and remains the explicit address for a client that would rather be told than infer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
3.4 KiB
TypeScript
74 lines
3.4 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
/**
|
|
* Vault — a reverse-proxy to a self-hosted Vaultwarden, with Officer holding the credential.
|
|
*
|
|
* ── What this plugin is, and what it deliberately is not ──
|
|
*
|
|
* It is NOT a password manager. It stores no vault items, renders no cipher list, and cannot decrypt
|
|
* anything: the vault is end-to-end encrypted and every route here is a dumb pass-through. What it owns is
|
|
* the CREDENTIAL problem — Officer brokers the Vaultwarden token, keeps it server-side, and injects it on
|
|
* the way through, so a device that has an Officer session never holds a vault token at all.
|
|
*
|
|
* It also does not install Vaultwarden. You point it at one you already run. That is the whole
|
|
* configuration surface, and it lives in the database rather than in `.env` — see `db/schema.ts`.
|
|
*
|
|
* ── No `osDependencies` ──
|
|
*
|
|
* Nothing here shells out. The sidecar is a Bun HTTP/WS proxy and the upstream is reached with `fetch`.
|
|
*/
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '1.0.0',
|
|
platform: '>=1.0.0',
|
|
|
|
label: 'Vault',
|
|
summary: 'Your Vaultwarden, behind an Officer session — passwords for the apps that speak Bitwarden',
|
|
// A lucide glyph rather than artwork: this ships no assets/icon.png, and `KeyRound` is what the app
|
|
// store catalogue already used for it.
|
|
icon: 'KeyRound',
|
|
color: '#175ddc',
|
|
|
|
permissions: [
|
|
{
|
|
key: 'vault',
|
|
label: 'Vault',
|
|
description: 'The password vault, and the Officer-held session that opens it',
|
|
},
|
|
],
|
|
|
|
/*
|
|
The Bitwarden door: served without an Officer session, because a stock Bitwarden client has nowhere to
|
|
put one. It carries its own Vaultwarden bearer token, which Vaultwarden itself judges — see
|
|
`api/public-router.ts` for why this is the same exposure through one door rather than a new one, and
|
|
why it is temporary.
|
|
|
|
ONE prefix, not four. A Bitwarden client is given `https://<officer>/api/vault/bw` as its server URL
|
|
and derives `/api`, `/identity`, `/icons` and `/notifications` beneath it, so everything the protocol
|
|
needs sits under a single declared path. The platform resolves this against the plugin's own mount and
|
|
re-checks containment at boot, so it can only ever mean `/api/vault/bw/*`.
|
|
*/
|
|
unauthenticatedPaths: ['/bw/*'],
|
|
|
|
/*
|
|
The four paths a Bitwarden client derives from a bare server URL, claimed at the ROOT so that URL can
|
|
be the bare Officer one. This is what makes the setup "paste https://officer.example.com and log in"
|
|
rather than five fields copied into an advanced pane — which is the difference between a feature and
|
|
a thing nobody uses.
|
|
|
|
None of them is a name Officer serves: they belong to Vaultwarden's protocol and to nothing else here,
|
|
so the path alone decides and no sniffing is involved.
|
|
*/
|
|
unauthenticatedRootPaths: ['/identity', '/notifications', '/icons', '/events'],
|
|
|
|
/*
|
|
`/api/*` is the one namespace BOTH servers claim — Vaultwarden has /api/sync, /api/ciphers; Officer
|
|
has /api/chat, /api/settings. The path cannot separate them, so the client says who it is: every
|
|
Bitwarden client stamps one of these, and older ones `device-type`.
|
|
|
|
Routing, not authentication. A forged header reaches Vaultwarden, which demands its own credential
|
|
exactly as it would have; nothing of Officer's is authorised by it.
|
|
*/
|
|
protocolHeaders: ['bitwarden-client-name', 'bitwarden-client-version', 'device-type'],
|
|
};
|