Add Local Authentication Option #36

Closed
opened 2026-02-22 04:45:39 +00:00 by shaun · 0 comments
Owner

Objective

Implement local username/password authentication in the trips app while preserving existing Synology OIDC login. Users must be able to choose an auth source on /login.

Scope

  • In scope:
    • Add local credentials auth provider.
    • Keep Synology OIDC provider working unchanged.
    • Replace /login auto-redirect with source picker + local login form.
    • Add secure password storage and verification.
    • Add basic brute-force protections.
    • Add tests for new auth logic and login route behavior.
  • Out of scope:
    • DB identity normalization (auth_identities, auth_sources tables).
    • Admin-configurable auth sources.
    • Account linking across providers.
    • Password reset email flows.

Current State (for agent context)

  • Auth config: trips/src/auth.ts (single provider: synology OIDC).
  • Login page server load: trips/src/routes/login/+page.server.ts (returns Synology sign-in URL only).
  • Login UI: trips/src/routes/login/+page.svelte (auto-submits hidden form to Synology).
  • User persistence: trips/src/lib/server/users.ts (upsertUserFromAuth).
  • Admin auth check: trips/src/lib/server/admin/auth.ts (env-based ADMIN_USER_IDS).

Functional Requirements

  1. /login must show:
    • “Sign in with Synology” action.
    • Local login form with identifier + password.
  2. Synology sign-in flow must keep working exactly as before.
  3. Local login must:
    • Accept identifier as username or email.
    • Verify password hash using Argon2id.
    • Create authenticated session via Auth.js provider callback.
  4. On successful local login:
    • Session must include session.user.id.
    • User must be redirected to app root/protected flow consistently with existing behavior.
  5. On local login failure:
    • Return generic error (“Invalid credentials”) without revealing whether user exists.
  6. Brute-force protection:
    • Per-identifier and per-IP attempt throttling with time-window lockout.
    • Do not permanently lock account.
  7. Local auth feature toggle:
    • Controlled by env var.
    • If disabled, local form hidden and endpoint rejects local auth attempts.
  8. Password policy:
    • Enforce minimum length (12+ chars) for any seeded or created local credentials.
    • No plaintext password storage or logging.

Non-Functional Requirements

  • Use constant-time hash verification path.
  • Keep auth code modular for future multi-source expansion.
  • Do not break existing tests unrelated to auth.
  • Keep TypeScript types strict and explicit.

Data Model Changes (Phase 1 minimal)

  • Add table local_credentials:
    • user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE
    • password_hash TEXT NOT NULL
    • created_at TEXT NOT NULL DEFAULT (datetime('now'))
    • updated_at TEXT NOT NULL DEFAULT (datetime('now'))
  • Add migration in trips/src/lib/server/db/migrations.ts using CREATE TABLE IF NOT EXISTS.
  • No changes to existing users schema in Phase 1.

Environment Variables

  • Add to trips/.env.example:
    • LOCAL_AUTH_ENABLED=false
    • LOCAL_AUTH_ARGON2_MEMORY_KB=65536
    • LOCAL_AUTH_ARGON2_TIME_COST=3
    • LOCAL_AUTH_ARGON2_PARALLELISM=1
    • LOCAL_AUTH_MAX_ATTEMPTS=5
    • LOCAL_AUTH_WINDOW_SECONDS=900
    • LOCAL_AUTH_LOCKOUT_SECONDS=900
  • Keep existing Synology env vars unchanged.

Implementation Tasks

  1. Update trips/src/auth.ts:
    • Keep Synology provider.
    • Add Auth.js credentials provider with id local.
    • Implement authorize() by looking up user by username/email, verifying Argon2id hash, and returning { id, name, email }.
    • Reuse existing session callback behavior (session.user.id).
  2. Add server module trips/src/lib/server/local-auth.ts:
    • verifyLocalCredentials(identifier, password, ip) function.
    • Centralize rate-limit checks and password verification.
  3. Add server module trips/src/lib/server/local-credentials.ts:
    • DB access for credential lookup and updates.
    • Optional helper to seed/update local password hashes.
  4. Update login load/action:
    • trips/src/routes/login/+page.server.ts should return available methods and Synology URL.
  5. Replace login UI:
    • trips/src/routes/login/+page.svelte should render both options.
    • Keep Synology submit via POST to Auth.js signin endpoint.
    • Local form posts to Auth.js signin/local.
  6. Add tests:
    • Unit tests for credential verification and rate limit behavior.
    • Route/UI tests for /login available options and error state.
  7. Update docs:
    • Add local auth setup section in trips/README.md.

Security Requirements

  • Use Argon2id hashing library with parameterized config.
  • Never return user existence details.
  • Never include secret/hash values in logs/errors.
  • Validate and trim all inputs.
  • Enforce CSRF-safe sign-in flow via Auth.js conventions.

Acceptance Criteria

  1. Existing Synology login still works end-to-end.
  2. With LOCAL_AUTH_ENABLED=true, /login shows both auth options.
  3. Valid local credentials create a usable session and allow access to protected routes.
  4. Invalid local credentials do not authenticate and show generic error.
  5. Rate limiting blocks repeated failures within configured window.
  6. New/updated tests pass.
  7. Lint/typecheck pass.
  8. .env.example and README include local-auth configuration.

Deliverables

  • Code changes in auth, login route/UI, server auth helpers, migrations, tests, docs.
  • Short PR summary with:
    • files changed
    • security choices
    • test results
    • known limitations carried into Phase 2
# Objective Implement local username/password authentication in the `trips` app while preserving existing Synology OIDC login. Users must be able to choose an auth source on `/login`. # Scope - In scope: - Add local credentials auth provider. - Keep Synology OIDC provider working unchanged. - Replace `/login` auto-redirect with source picker + local login form. - Add secure password storage and verification. - Add basic brute-force protections. - Add tests for new auth logic and login route behavior. - Out of scope: - DB identity normalization (`auth_identities`, `auth_sources` tables). - Admin-configurable auth sources. - Account linking across providers. - Password reset email flows. **Current State (for agent context)** - Auth config: `trips/src/auth.ts` (single provider: `synology` OIDC). - Login page server load: `trips/src/routes/login/+page.server.ts` (returns Synology sign-in URL only). - Login UI: `trips/src/routes/login/+page.svelte` (auto-submits hidden form to Synology). - User persistence: `trips/src/lib/server/users.ts` (`upsertUserFromAuth`). - Admin auth check: `trips/src/lib/server/admin/auth.ts` (env-based `ADMIN_USER_IDS`). **Functional Requirements** 1. `/login` must show: - “Sign in with Synology” action. - Local login form with identifier + password. 2. Synology sign-in flow must keep working exactly as before. 3. Local login must: - Accept identifier as username or email. - Verify password hash using Argon2id. - Create authenticated session via Auth.js provider callback. 4. On successful local login: - Session must include `session.user.id`. - User must be redirected to app root/protected flow consistently with existing behavior. 5. On local login failure: - Return generic error (“Invalid credentials”) without revealing whether user exists. 6. Brute-force protection: - Per-identifier and per-IP attempt throttling with time-window lockout. - Do not permanently lock account. 7. Local auth feature toggle: - Controlled by env var. - If disabled, local form hidden and endpoint rejects local auth attempts. 8. Password policy: - Enforce minimum length (12+ chars) for any seeded or created local credentials. - No plaintext password storage or logging. **Non-Functional Requirements** - Use constant-time hash verification path. - Keep auth code modular for future multi-source expansion. - Do not break existing tests unrelated to auth. - Keep TypeScript types strict and explicit. **Data Model Changes (Phase 1 minimal)** - Add table `local_credentials`: - `user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE` - `password_hash TEXT NOT NULL` - `created_at TEXT NOT NULL DEFAULT (datetime('now'))` - `updated_at TEXT NOT NULL DEFAULT (datetime('now'))` - Add migration in `trips/src/lib/server/db/migrations.ts` using `CREATE TABLE IF NOT EXISTS`. - No changes to existing `users` schema in Phase 1. **Environment Variables** - Add to `trips/.env.example`: - `LOCAL_AUTH_ENABLED=false` - `LOCAL_AUTH_ARGON2_MEMORY_KB=65536` - `LOCAL_AUTH_ARGON2_TIME_COST=3` - `LOCAL_AUTH_ARGON2_PARALLELISM=1` - `LOCAL_AUTH_MAX_ATTEMPTS=5` - `LOCAL_AUTH_WINDOW_SECONDS=900` - `LOCAL_AUTH_LOCKOUT_SECONDS=900` - Keep existing Synology env vars unchanged. **Implementation Tasks** 1. Update `trips/src/auth.ts`: - Keep Synology provider. - Add Auth.js `credentials` provider with id `local`. - Implement `authorize()` by looking up user by username/email, verifying Argon2id hash, and returning `{ id, name, email }`. - Reuse existing session callback behavior (`session.user.id`). 2. Add server module `trips/src/lib/server/local-auth.ts`: - `verifyLocalCredentials(identifier, password, ip)` function. - Centralize rate-limit checks and password verification. 3. Add server module `trips/src/lib/server/local-credentials.ts`: - DB access for credential lookup and updates. - Optional helper to seed/update local password hashes. 4. Update login load/action: - `trips/src/routes/login/+page.server.ts` should return available methods and Synology URL. 5. Replace login UI: - `trips/src/routes/login/+page.svelte` should render both options. - Keep Synology submit via POST to Auth.js signin endpoint. - Local form posts to Auth.js `signin/local`. 6. Add tests: - Unit tests for credential verification and rate limit behavior. - Route/UI tests for `/login` available options and error state. 7. Update docs: - Add local auth setup section in `trips/README.md`. **Security Requirements** - Use Argon2id hashing library with parameterized config. - Never return user existence details. - Never include secret/hash values in logs/errors. - Validate and trim all inputs. - Enforce CSRF-safe sign-in flow via Auth.js conventions. **Acceptance Criteria** 1. Existing Synology login still works end-to-end. 2. With `LOCAL_AUTH_ENABLED=true`, `/login` shows both auth options. 3. Valid local credentials create a usable session and allow access to protected routes. 4. Invalid local credentials do not authenticate and show generic error. 5. Rate limiting blocks repeated failures within configured window. 6. New/updated tests pass. 7. Lint/typecheck pass. 8. `.env.example` and README include local-auth configuration. **Deliverables** - Code changes in auth, login route/UI, server auth helpers, migrations, tests, docs. - Short PR summary with: - files changed - security choices - test results - known limitations carried into Phase 2
shaun added the ai-agent label 2026-02-22 04:45:39 +00:00
ai-agent was assigned by cicd 2026-02-22 04:45:41 +00:00
shaun closed this issue 2026-02-22 19:09:49 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: campbellwireless/trips#36