openvibers/openvibe.locker README.md

README.md

main 9.8 KB · 132 lines · markdown

1# OpenVibe.Locker
2
3A front door for private files, passwords and expiring Sends. Node 22, Express/CommonJS and PostgreSQL via `openvibe-sdk/db`.
4The server stores ciphertext and opaque access metadata. It never seals, opens, indexes, previews, transcodes or
5runs AI on content. The cryptographic format and operations are the existing `locker.envelope.v1` and
6`openvibe-sdk/locker`; the device UI and Share sheet are Shared's `ov-locker.js`, `ov-locker-core.js` and `ov-share.js`.
7
8**Live site:** https://openvibe.locker · **API docs:** https://openvibe.services · **For AI:** https://openvibe.locker/llms.txt
9**Run it:** `npm ci && npm start` (Node 22 or newer) · **Tests:** `npm test`
10
11## Run and verify
12
13`npm ci`, then `npm start`. Without database settings, development uses PGlite in `data/pglite`.
14Network SSO and private Media storage need the service credentials configured by the operator; there is no fake
15production backend or sample content. `npm test` uses isolated PGlite databases, real WebCrypto and local adapters.
16`npm run test:browser` starts a local test instance, exercises the real browser worker and saves 390/1280 screenshots
17in both themes to `artifacts/`. `npm run check` checks syntax. Browser checks use Playwright's Chromium or local Chrome.
18
19Production requires PostgreSQL, a separate migration role, the Locker Network principal and the private Media
20Locker tenant. [Launch handoff](deploy/LAUNCH.md) names the exact configuration, port 5145, drill port 15145,
21`ov_locker`, `openvibe-locker.service` and `/etc/openvibe/locker.env`. No host or DNS changes are made here.
22
23## Pages
24
25- `/`: device lock status and the front door.
26- `/drive`: encrypted files, folders, rename and delete. Names appear only after the browser decrypts them.
27- `/passwords`: browser-encrypted logins, password generation, TOTP, local search and CSV import. Each breach check
28 asks before sending only the first five SHA-1 hex characters to Have I Been Pwned. Password logins cannot be shared.
29- `/files/:id`: open, download, rename, the people Share sheet, and Report.
30- `/sends`: create a link-locked file or note, choose 1 hour to 30 days and an optional download-attempt limit.
31- `/s/:id#k=<base64url>`: anonymous opening; the key never leaves the fragment. Each content request consumes a claim,
32 including disconnected downloads. GET metadata and HEAD do not consume one.
33- `/recovery`: Shared setup, passphrase or supported device passkey, and a one-time 24-word kit with print/save.
34- `/staff`: Network staff can review reports, disable items and restrict uploads.
35- `/docs`: Locked items on OpenVibe, the SDK example and API links. See [OpenAPI](public/openapi.json) and [llms.txt](public/llms.txt).
36
37If you lose the password (or your locker passphrase and recovery kit), nobody can open this, including OpenVibe.
38
39## Private payload
40
41The whole payload below is sealed by the existing SDK. This is an application payload inside the envelope, not a
42new cryptographic format. The server never parses it:
43
441. Four bytes, unsigned big-endian, giving the JSON metadata length (at most 65536).
452. UTF-8 JSON `{ version: 1, name, type, size, folder? }` (original filename, MIME type and true byte count).
463. Exactly `size` content bytes. A folder has zero content bytes. Text Sends use UTF-8 and `text/plain`.
47
48`public/js/payload.js` is usable from Node and a browser. `prefix(meta)` gives the framing bytes; `pack(meta, stream)`
49and `unpack(stream)` work with WHATWG streams. Feed the packed stream to `locker.sealStream` with its exact plaintext
50size. The worker processes SDK chunks of 1 MiB into OPFS; no whole large file is buffered. Opening streams to an
51atomic file writer; final verification must succeed before the writable is committed. The bounded Blob fallback
52is 32 MiB. Opening a list label reads only its first authenticated chunk and cancels the remaining content stream.
53
54## Storage and access
55
56Single-request ciphertext routes accept `application/octet-stream` with Content-Length up to **90 MiB**. The server counts
57actual received bytes, bounds the header at 64 KiB and uses `openvibe-sdk/locker/server.inspectEnvelopePrefix` to
58validate shape and the exact expected ciphertext length. Drive starts with the owner's people slot only. Sends
59are link locks. Unknown plaintext metadata is rejected. The browser enforces the true file limit; the server can
60only enforce the corresponding padded size, since true sizes are encrypted.
61
62Browser uploads use resumable sessions, so every request fits Cloudflare's 100 MB limit:
63
641. `POST /api/v1/uploads` with JSON `{ kind: "file" | "folder" | "login" | "send", bytes, parent?, expires_in?, download_limit? }`.
65 `bytes` is the entire encrypted envelope size; this reserves quota and returns `{ id, part_size: 33554432 }`.
662. `PUT /api/v1/uploads/:id/parts/:n` with an octet-stream slice, numbered from zero. Each part is exactly
67 32 MiB except the last, which is exactly the remaining bytes. Received parts are immutable and retryable.
683. `GET /api/v1/uploads/:id` lists `received_parts`. Resume only the missing parts, including after a server restart.
694. `POST /api/v1/uploads/:id/complete` checks all parts and the envelope, stores it in Media and returns the item.
70 Completion retries return the same result for 24 hours; GET also returns that result with `state: "complete"`.
71 A session expires after one hour without a new part. Expired staging files and reservations are removed by the due job.
72
73Use `kind: "replace"` or `"reseal"` with `item_id` and the item's `If-Match` revision on initiation **and completion**.
74Replace returns the updated item; reseal returns the receipt for the Share API below. Completion rechecks standing,
75revision and recipients. The worker retries lost part/completion responses and queries received parts before retrying.
76The encrypted OPFS file and Send key remain with that worker; closing the page requires starting a new browser upload.
77A server interruption during Media completion leaves the reservation for expiry cleanup; retry a new session after expiry.
78
79Accounts must be signed in, at least seven days old and not banned/deleted in Network's current
80`/internal/identity/resolve?subject_id=…&trust=1` response. Local moderation restrictions always apply.
81`LOCKER_ALLOWLIST_ONLY` can additionally restrict the launch cohort. PostgreSQL locks the account row to reserve
82quota before accepting an upload, including the full size of each staged replacement. Renaming or changing sharing needs enough free quota for the incoming encrypted copy until it commits. Defaults: 5 GiB per account, 2 GiB per file,
8360 uploads/hour, 20 Sends/hour. Server metadata contains the owner, IDs, padded/ciphertext sizes, times and access
84rules, never names or MIME types. Media sees only a private `file`, `application/octet-stream` and `locked.ovlk`.
85
86For sharing a large file, seal a new envelope in the browser for the owner and all remaining recipients, then:
87
881. Complete a resumable `reseal` session with `item_id` and `If-Match: "<item revision>"`.
89 Envelopes up to 90 MiB can also use `POST /api/v1/files/:id/reseals`.
902. `PUT /api/v1/files/:id/share` with `{ members: [{ subject, role: "viewer", key_slot }], envelope_ref }`, preferably
91 with the Share GET's ETag in `If-Match`. The SDK checks recipients and fresh-key removal; Locker checks that the
92 staged upload belongs to this owner, file and revision. The Media reference, quota and ACL commit together.
933. An unconsumed receipt expires after one hour. Old objects and abandoned uploads are queued for erasure.
94
95The nginx vhost limits ordinary JSON requests to 64 KiB. Use staged receipts for sharing; only tiny envelopes fit inline. Large reseals carry a
96short receipt, never a giant JSON body. Names and every content byte are re-sealed locally; a departing member's
97old key cannot open the new envelope. Recipients may still keep a prior copy. Folder membership is organizational;
98sharing a folder does not grant its children. Transfer and the password-manager phase are outside v1.
99
100Expiry, user deletion and replacement queue durable Media erasure. A bounded due-based job retries failures; access
101ends at the due time even during a Media outage. `DELETE ?purge=1` is reserved to the Locker principal in Media and
102has no restore window. Reports carry a reason code, never a content key. Staff changes are recorded locally.
103
104ADR-033 export provides an opaque manifest with IDs and download routes; each envelope is downloadable separately
105while the account exists. Network owns the wrapped-key export. Account deletion denies all access immediately,
106queues ciphertext erasure, cancels pending deliveries and removes memberships. Append-only access/moderation
107history and an account deletion marker are retained as security evidence; they contain no plaintext or keys.
108
109### Companion password access
110
111`/api/v1/companion` accepts only a Bearer token which Network validates live at
112`GET /api/v1/me/keys/companion` (the Companion needs `locker.login.manage`). Deploy
113that Network scope/endpoint first; existing Companion grants require a new sign-in.
114No extension origin CORS wildcard or cookie authentication is added.
115
116- `GET /keys`: the caller's subject and wrapped key backups, never plaintext keys.
117- `GET /logins?before=…`: owner-only encrypted login metadata with the normal cursor.
118- `GET /logins/:id/content`: only that owner's `kind=login` ciphertext.
119- `POST /logins`: an SDK envelope, `application/octet-stream`, at most 2 MiB; the
120 item kind is fixed to `login`. Site, username, password, TOTP and notes belong in
121 the sealed payload. Normal upload quota, age and envelope validation apply.
122
123The scoped token cannot reach the general file API, key updates or blind fill.
124Every request revalidates Network's live token family, including content downloads.
125Run `node --test test/companion.test.js` for isolation and ciphertext-only checks.
126
127<!-- versions:start -->
128- openvibe-contracts: v0.419.0
129- openvibe-sdk: v0.161.0
130- openvibe-shared: v4.241.0
131<!-- versions:end -->
132