d2e2ebe108
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.
27 lines
686 B
JSON
27 lines
686 B
JSON
{
|
|
"name": "recipescam-api",
|
|
"version": "1.0.0",
|
|
"private": true,
|
|
"description": "RecipesCam web API - accounts + user recipes (server never receives photos)",
|
|
"main": "dist/server.js",
|
|
"scripts": {
|
|
"build": "tsc",
|
|
"start": "node dist/server.js",
|
|
"dev": "tsx watch src/server.ts",
|
|
"test": "node test/security.mjs",
|
|
"test:immich": "node test/immich.mjs"
|
|
},
|
|
"dependencies": {
|
|
"better-sqlite3": "^12.11.1",
|
|
"fastify": "^5.12.5",
|
|
"nodemailer": "^7.0.13"
|
|
},
|
|
"devDependencies": {
|
|
"@types/better-sqlite3": "^9.6.0",
|
|
"@types/node": "^22.20.3",
|
|
"@types/nodemailer": "^8.0.2",
|
|
"tsx": "^4.23.13",
|
|
"typescript": "^5.9.3"
|
|
}
|
|
}
|