# LoreLoom First-Run Admin Setup

## Purpose

This document designs the first-run admin setup flow for LoreLoom authentication.

It is a planning document only. It does not implement auth yet.

The goal is to add a minimal local account setup flow that works well for:

- a single-user self-hosted instance
- a small household instance
- Raspberry Pi and LAN deployment

This flow should stay simple, understandable, and local-first.

## Goals

The first-run auth setup should:

- create the first local admin account
- gate setup so it can only happen once
- preserve existing project data during migration
- fit LoreLoom’s self-hosted and Raspberry Pi friendly model
- avoid public-SaaS complexity

## First-Run State

### How Setup Is Required

The backend should decide whether setup is required based on durable server state, not frontend assumptions.

Recommended rule:

- setup is required when `users` contains zero rows

This is the clearest and least surprising rule.

Optional supporting instance state can still exist, for example in `instance_settings`, but the primary truth should remain:

- `COUNT(users) = 0`

### Setup Availability

The setup page and setup API should only be usable until the first admin account is successfully created.

Recommended behavior:

- if no users exist:
  - setup endpoints are available
  - login screen should redirect to setup
- if at least one user exists:
  - setup endpoints return a clear `409` or `403`
  - setup page should redirect to login

This prevents accidental second setup and avoids ambiguous instance state.

### Why This Simplicity Matters

LoreLoom does not need a flexible installer workflow for v1 auth.

A single rule is enough:

- no users means setup
- any user means setup is complete

That is easy to inspect, easy to recover, and easy to reason about on a self-hosted machine.

## Admin Creation

### Required Fields

The first admin creation form should include:

- `username`
- `password`
- `passwordConfirmation`

### Optional Fields

- `displayName`

### Fixed Values

The first created account should receive:

- `role = admin`

There should be no role picker in the setup UI.

This is safer and simpler:

- the first account is always the admin
- the system does not need to explain roles during setup

### Username Rules

Recommended minimal rules:

- required
- unique
- trimmed
- lowercase normalization recommended
- short and predictable character set

Suggested allowed shape:

- letters
- numbers
- `_`
- `-`

This keeps the login model simple and reduces surprising edge cases.

### Password Rules

Recommended minimal rules:

- required
- confirmation must match
- minimum length of 6 characters
- include show/hide controls in the frontend so users can verify what they typed on local or LAN HTTP deployments

Avoid trying to build a complicated password-strength meter in the first version.

The goal is clear setup, not a heavyweight onboarding flow.

## Security Model

### Password Hashing

Recommended later implementation:

- Node's built-in `scrypt` for the first self-hosted implementation

Possible later upgrade only if the extra dependency is justified:

- `argon2id`

Why:

- password hashes must never be stored in plaintext
- LoreLoom should rely on established platform cryptography primitives, not custom crypto

Suggested stored fields:

- password hash string
- timestamps

Do not store:

- plaintext passwords
- reversible encrypted passwords

### Session Cookie Approach

The first auth model should use server-side sessions with a secure cookie.

Recommended shape:

- session row stored in SQLite
- opaque session ID in cookie
- server resolves the current user from that session ID

Recommended cookie defaults:

- `HttpOnly`
- `SameSite=Lax`
- `Path=/`

Cookie `Secure` behavior:

- `false` on plain HTTP local/LAN deployments
- `true` when running behind HTTPS or a reverse proxy that terminates TLS

This fits LoreLoom better than a token-heavy client auth model.

### Session Expiry

Recommended first-pass expiry:

- relatively long but finite, for example 14 days

Optional behavior:

- rolling session refresh on active use

Why:

- long enough for a home writing tool
- short enough to avoid effectively permanent sessions

If a session expires:

- the backend returns `401`
- the frontend sends the user back to login
- local browser data should not be deleted automatically

### Logout

Logout should:

- delete the active session server-side
- clear the session cookie
- return the frontend to the login screen

Logout should not destroy local project data by surprise.

Instead, frontend cache handling should become user-scoped later so logging out does not leak data across users.

### CSRF Considerations

CSRF matters if LoreLoom uses cookie-based auth for state-changing requests.

For the first version, reasonable protection can stay simple:

- rely on same-origin default deployment
- keep CORS disabled by default
- use `SameSite=Lax` cookies

If authenticated mutation routes expand later, add explicit CSRF protection such as:

- double-submit token
- or an Origin/Referer validation strategy for same-origin requests

For v1 auth, the most important practical protections are:

- same-origin default
- no public cross-origin use by default
- no permissive CORS

### Secure Defaults For LAN And Self-Hosted Use

Recommended defaults:

- cookie sessions
- no cross-origin browser access by default
- admin setup only when no users exist
- conservative logging
- no password recovery by email

This matches the expected deployment shape:

- localhost
- private LAN
- home reverse proxy later if desired

### What Changes With HTTPS Or A Reverse Proxy

When LoreLoom is placed behind HTTPS:

- set session cookie `Secure=true`
- ensure proxy headers are trusted correctly
- preserve same-origin behavior unless there is a strong reason not to

Nothing about the basic login model needs to change.

That is a good sign. The same local account system should work on:

- localhost
- LAN HTTP
- HTTPS behind a private reverse proxy

## API Routes

These route drafts assume the existing Fastify + SQLite architecture.

### `GET /api/setup/status`

Purpose:

- tell the frontend whether first-run setup is still required

Response summary:

- `setupRequired: boolean`
- `hasUsers: boolean`
- optional small instance state if useful later

Examples:

- no users:
  - `setupRequired: true`
  - `hasUsers: false`
- users exist:
  - `setupRequired: false`
  - `hasUsers: true`

### `POST /api/setup/admin`

Purpose:

- create the first admin account exactly once

Request summary:

- `username`
- `password`
- `passwordConfirmation`
- `displayName?`

Behavior:

- validate payload
- confirm no users exist
- create first user with `role = admin`
- create authenticated session
- assign legacy existing projects to this user during migration

Success response summary:

- authenticated user info
- setup complete confirmation

Failure cases:

- invalid payload
- passwords do not match
- setup already completed

### `POST /api/auth/login`

Purpose:

- authenticate an existing local user

Request summary:

- `username`
- `password`

Behavior:

- normalize username
- verify password
- create session
- set session cookie

Success response summary:

- current user info

Failure cases:

- bad credentials
- disabled account
- setup not completed yet

### `POST /api/auth/logout`

Purpose:

- end the current session

Behavior:

- delete current session if present
- clear session cookie

Success response summary:

- `ok: true`

This route should be idempotent enough that logging out twice is not a serious error path.

### `GET /api/auth/me`

Purpose:

- return the current authenticated user for frontend session hydration

Response summary:

- authenticated user info if session is valid
- `401` if not logged in

Suggested user payload shape:

- `id`
- `username`
- `displayName`
- `role`
- `diagnosticsOptIn`

Avoid returning internal auth-sensitive fields such as password hashes or raw session data.

## Frontend UX

The frontend should remain calm and practical. Auth should feel like a simple gate, not a product layer of its own.

### First-Run Setup Screen

When `GET /api/setup/status` says setup is required:

- the app should show a dedicated first-run setup screen
- normal app routes should not be usable yet

The setup screen should explain, plainly:

- this instance has no users yet
- create the first local admin account
- this account will own existing projects on this instance

The form should contain:

- username
- display name optional
- password
- confirm password
- create admin button

### Login Screen

When setup is complete and there is no valid session:

- show a minimal login screen

The login screen should be intentionally small:

- username
- password
- sign in

No social login buttons.
No marketing copy.
No signup flow.

### Logged-In Shell State

When a session is valid:

- the normal app shell loads
- the current user is lightly visible in the shell

Possible UI:

- username in header
- small account menu
- logout action

This should stay subtle. LoreLoom is still primarily a writing app.

### Logout Action

Logout should be available from the shell in a predictable place.

Recommended behavior:

- click logout
- current session ends
- app returns to login screen

### What Happens If Setup Is Incomplete

If setup is incomplete:

- frontend should route to setup
- login screen should not be the primary path
- API project routes should remain inaccessible until setup finishes

### What Happens If Session Expires

If the session expires during use:

- the next authenticated API request returns `401`
- frontend should preserve local unsynced state where possible
- user is returned to login
- after login, sync can resume

The important behavior is:

- do not silently discard local work
- do not pretend the user is still authenticated

## Data Ownership

### Existing Projects During Migration

For v1 auth:

- all existing projects should be assigned to the first admin account during migration

This matches the current no-auth reality, where there is effectively one implicit user.

### No Sharing Yet

For the first auth release:

- no project sharing
- no shared editing
- no per-project invites

Every project belongs to exactly one owner.

### No Public Signup

There should be no public signup flow.

Account creation beyond the first admin can be designed later, likely as an admin-only action for household instances.

## Non-Goals

The first-run admin setup should explicitly not include:

- OAuth
- email reset
- public signup
- multi-user sharing
- invites
- MFA
- external identity providers

These all add real complexity and are not required for LoreLoom’s first local account model.

## Phased Implementation Plan

### Phase 1: Schema And Utilities

Add the backend foundations needed for auth:

- `users` table
- `sessions` table
- `instance_settings` table if used
- password hashing utility
- session utility

Also add:

- project ownership column on `projects`

### Phase 2: Setup And Auth Routes

Implement:

- `GET /api/setup/status`
- `POST /api/setup/admin`
- `POST /api/auth/login`
- `POST /api/auth/logout`
- `GET /api/auth/me`

Keep behavior deliberately small and local-account only.

### Phase 3: Frontend Setup/Login Gate

Add frontend session gating:

- setup screen when no users exist
- login screen when setup is complete but user is not authenticated
- authenticated shell state
- logout action

Do not redesign the whole app. Add only the minimum gates needed.

### Phase 4: Project Ownership Migration

Apply migration behavior for existing data:

- assign all existing projects to the first admin
- ensure project APIs are owner-scoped
- keep no-auth legacy data from becoming orphaned

### Phase 5: Diagnostics Opt-In Later

Only after the core auth flow is stable:

- add per-user diagnostics opt-in UX
- scope richer diagnostics history to the authenticated user

This should not block the first auth milestone.

## Recommended Defaults Summary

If we compress the whole design down to the essentials, the first auth setup for LoreLoom should be:

- one-time first admin creation when no users exist
- username + password login
- optional display name
- admin role assigned automatically
- cookie-based server session
- same-origin default deployment
- projects assigned to the first admin during migration
- no public signup
- no sharing

That gives LoreLoom a practical local account foundation without pulling it away from its current values or deployment model.
