Per-user auth via Nextcloud Login Flow v2, Tailwind UI rewrite

- auth.py/auth_routes.py: Login Flow v2 (verified against real NC
  source/docs) - no OAuth2 client registration needed, backend polls
  server-side so no CORS issues. Sessions are hashed-token cookies;
  NC app passwords encrypted at rest (Fernet). Every /api/* route
  guarded by a blueprint-wide before_request, not per-route decorators,
  so future routes are protected by default.
- receipts/groups scoped per owner_nc_user_id; cross-user access 404s.
- nc_client/cospend_client take (username, app_password) per call
  instead of one shared global credential - each user's uploads/shares/
  bills now happen as themselves.
- Frontend: LoginGate component drives the login flow (open NC login
  in a new tab, poll our backend, done).
- Merged the old separate review step into the split screen, redesigned
  with Tailwind (was unstyled/broken), default-excluded-per-item
  splitting with one-click "include everyone" fixed, DD.MM.YYYY date
  field, receipt-icon branding.
- Dropped LLM bounding-box highlighting - unreliable on real receipts,
  plain photo upload instead.
- Only mention who a bill is split with in its title when there's more
  than one bill off the same receipt to disambiguate.
This commit is contained in:
2026-08-30 16:02:29 +02:00
parent d97aac0e17
commit 1b38396a2c
31 changed files with 1653 additions and 746 deletions
+20 -1
View File
@@ -1,5 +1,25 @@
CREATE TABLE IF NOT EXISTS users (
nc_user_id TEXT PRIMARY KEY,
-- Fernet-encrypted NC app password obtained via Login Flow v2. Never
-- returned by any API response.
nc_app_password_encrypted BLOB NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_login_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sessions (
-- SHA-256 hex digest of the actual session cookie value - never the raw
-- token, so a DB read alone can't yield a usable session (same
-- principle as password hashing).
session_id_hash TEXT PRIMARY KEY,
nc_user_id TEXT NOT NULL REFERENCES users(nc_user_id) ON DELETE CASCADE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS receipts (
id TEXT PRIMARY KEY,
owner_nc_user_id TEXT NOT NULL REFERENCES users(nc_user_id) ON DELETE CASCADE,
image_path TEXT NOT NULL,
image_width INTEGER,
image_height INTEGER,
@@ -14,7 +34,6 @@ CREATE TABLE IF NOT EXISTS receipts (
CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY,
receipt_id TEXT NOT NULL REFERENCES receipts(id) ON DELETE CASCADE,
name TEXT NOT NULL,
cospend_project_id TEXT NOT NULL,
payer_member_id TEXT NOT NULL,
member_ids_json TEXT NOT NULL, -- json list of cospend member ids (owers)