feat(immich): read Immich through a per-user read-only proxy
The browser cannot talk to Immich directly: the key must stay out of it, COEP blocks the origin, and the app has no place to keep a key per user. So the backend keeps it. `src/immich.ts` holds the whole surface — the user's servers live in a JSON column on `users` (additive migration), and every route reads the key from there and never takes a URL from the browser except when probing one. Albums, a page of assets, a thumbnail and an original, all behind the normal session check. `probe` is the only route that touches a URL the client named, and it validates it first (http/https only, no credentials, no path, no query, no hash) so the browser cannot turn the backend into a proxy to an arbitrary host. The key is masked down to its last four characters everywhere it comes back out, and no log line carries it. The share-link path is the same routes with `type: 'share'`, whose key travels as `?key=`, so there is one code path per call rather than two. test/immich.mjs runs a fake Immich on loopback — two keys with different albums, one of them without `asset.download` — and checks 59 things including that neither the responses nor the log leak a key.
This commit is contained in:
@@ -8,7 +8,8 @@
|
||||
"build": "tsc",
|
||||
"start": "node dist/server.js",
|
||||
"dev": "tsx watch src/server.ts",
|
||||
"test": "node test/security.mjs"
|
||||
"test": "node test/security.mjs",
|
||||
"test:immich": "node test/immich.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^12.11.1",
|
||||
|
||||
Reference in New Issue
Block a user