restore the Bitwarden door, as one declared unauthenticated path
A stock Bitwarden client speaks Vaultwarden's protocol and nothing else: it gets its own bearer token from /identity/connect/token and has nowhere to put a platform JWT, so the authenticated /api/vault router cannot serve it. This was dropped during the extraction because a plugin's routes mount behind the account gate. It comes back through the platform's own mechanism rather than an exception: `unauthenticatedPaths: ['/bw/*']` in the manifest, resolved against this plugin's mount and re-checked for containment at boot. ONE prefix, not the four root mounts the platform version needed. A Bitwarden client takes `https://<officer>/api/vault/bw` as its server URL and derives /api, /identity, /icons and /notifications beneath it, so the whole protocol fits under a single declared path. The router is mounted BEFORE this file's middleware, deliberately: Hono collects handlers in registration order, so a route registered ahead of a `.use()` answers without it. Verified rather than assumed, because it is an auth boundary — /api/vault/bw/* reaches the passthrough with no session while /api/vault/config still goes through userMiddleware and the owner gate. Not new exposure: everything here was already reachable at the Vaultwarden URL it replaces, guarded by the same master password and the same Vaultwarden auth. Officer adds no security to a Bitwarden client because it has no credential to check. What it buys is that Vaultwarden need not be exposed at all — on this install it listens on 127.0.0.1 only, and Officer, already behind the tailnet and already terminating TLS, becomes the single address any client needs. Net reduction in surface. Still temporary, and still deleted rather than adjusted once a first-party extension authenticates to Officer against the /session/login broker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { mountPrefix } from '@@/plugins/manifest';
|
||||
import { onAccountLifecycle } from '@@/account-lifecycle';
|
||||
import { manifest } from '../manifest';
|
||||
import { getVaultServerUrl } from './sidecar-server';
|
||||
import { publicVaultRouter } from './public-router';
|
||||
import { stripHopByHop, redactPath } from './proxy-util';
|
||||
import { passwordGrant, pick, expiryFrom, type PasswordGrantInput } from './broker';
|
||||
import { getValidAccessToken, refreshAccess } from './token-store';
|
||||
@@ -42,6 +43,21 @@ const PREFIX = `/api${mountPrefix({ appName: 'vault', manifest })}`;
|
||||
|
||||
export const router = createRouter();
|
||||
|
||||
/*
|
||||
The unauthenticated Bitwarden door, mounted FIRST — before the middleware below, deliberately.
|
||||
|
||||
Hono collects matching handlers in registration order, so a route registered ahead of a `.use()` answers
|
||||
without that middleware ever running. Verified rather than assumed, because it is an auth boundary: a
|
||||
request to `/api/vault/bw/*` reaches the passthrough with no session, while `/api/vault/config` still
|
||||
goes through `userMiddleware` and the owner gate.
|
||||
|
||||
The platform is told about this through `unauthenticatedPaths` in the manifest, which is what makes it
|
||||
skip `userMiddleware` and the permission gate for exactly this prefix. Declaring it is not optional —
|
||||
without the declaration the platform gates it like anything else and a Bitwarden client gets a 401 it
|
||||
cannot answer.
|
||||
*/
|
||||
router.route('/bw', publicVaultRouter);
|
||||
|
||||
/**
|
||||
* Wipe this account's vault state when its session ends. Registered as a side effect of the router being
|
||||
* imported, which the platform does once when the plugin's routes are mounted — so the subscription's
|
||||
|
||||
Reference in New Issue
Block a user