72 lines
3.4 KiB
TypeScript
72 lines
3.4 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
/**
|
|
* Linkwarden — your bookmarks, behind an Officer session.
|
|
*
|
|
* ── The model, which is Vault's ──
|
|
*
|
|
* One Linkwarden per Officer install, pointed at by the owner. Officer does not install it and does not
|
|
* store bookmarks: Linkwarden owns the data, and this plugin owns the CREDENTIAL and the door. Where
|
|
* Vault brokers one account, this maps EVERY Officer user onto a Linkwarden account of their own — see
|
|
* `db/schema.ts`, and `plugins/USER_PROVISIONING.md` for the contract that governs creating one.
|
|
*
|
|
* ── What is different from Vault, and it is not small ──
|
|
*
|
|
* Vaultwarden speaks a host-agnostic bearer protocol, so its sidecar can be a dumb pass-through and a
|
|
* Bitwarden client does not care what address it is reached at. Linkwarden is a Next.js app using
|
|
* NextAuth: it issues host-scoped session cookies, checks CSRF against the host, and hands clients
|
|
* ABSOLUTE urls built from `NEXTAUTH_URL`.
|
|
*
|
|
* Measured on the live instance rather than inferred — a sign-in posted to `100.64.0.1:3000` answers
|
|
* with `{"url":"https://linkwarden.home.pastilhas.eu/api/v1/auth/signin?csrf=true"}`. So serving the
|
|
* browser extension through Officer requires that `NEXTAUTH_URL` name the address Officer serves it at.
|
|
* That is a change to the container, not to this plugin, and it is a prerequisite rather than a
|
|
* preference: without it the extension is bounced out of the proxy mid-login.
|
|
*
|
|
* The API half is unaffected. `/api/v1/*` accepts `Authorization: Bearer <access token>` and never
|
|
* touches NextAuth, so the panel and anything an agent does work regardless of the above.
|
|
*
|
|
* ── No `osDependencies` ──
|
|
*
|
|
* Nothing shells out. The sidecar is a Bun HTTP proxy and the upstream is reached with `fetch`.
|
|
*/
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '2026.09.05.03',
|
|
platform: '>=1.0.0',
|
|
|
|
label: 'Linkwarden',
|
|
summary: 'Your bookmarks, behind an Officer session — saved, archived and searchable',
|
|
tags: ['Personal', 'Degoogling'],
|
|
icon: 'Bookmark',
|
|
color: '#0ea5e9',
|
|
|
|
permissions: [
|
|
{
|
|
key: 'linkwarden',
|
|
label: 'Linkwarden',
|
|
description: 'Your saved links, collections and their archived pages',
|
|
/*
|
|
Reading and saving links is every user's; pointing the install at a server and creating accounts
|
|
on it is not. `/config` holds the URL the whole install uses and `/accounts` provisions against
|
|
an admin credential — both are prefix-matched, so the trees beneath them are covered.
|
|
*/
|
|
ownerOnly: ['/config', '/accounts'],
|
|
},
|
|
],
|
|
|
|
/*
|
|
The extension's door, served without an Officer session.
|
|
|
|
Same reasoning as Vault's `/bw`: a stock Linkwarden extension carries its own credential and has
|
|
nowhere to put an Officer JWT. It is the same exposure through one address instead of two, and it is
|
|
what lets Linkwarden stop being reachable on its own.
|
|
|
|
ONE prefix and no root claims, which is where this is simpler than Vault. A Bitwarden client derives
|
|
`/api`, `/identity`, `/icons` and `/notifications` from a bare server URL and therefore needed four
|
|
root-level mounts; the Linkwarden extension is given a base and appends `/api/v1/...` beneath it, so
|
|
everything the protocol needs already sits under one declared path.
|
|
*/
|
|
unauthenticatedPaths: ['/lw/*'],
|
|
};
|