Skip to content

docs: local authentication mode design spec#1354

Merged
ggfevans merged 4 commits intomainfrom
docs/1117-local-auth-design
Feb 28, 2026
Merged

docs: local authentication mode design spec#1354
ggfevans merged 4 commits intomainfrom
docs/1117-local-auth-design

Conversation

@ggfevans
Copy link
Collaborator

@ggfevans ggfevans commented Feb 28, 2026

User description

Summary

  • Adds design spec for AUTH_MODE=local (feat: Local authentication mode (username/password) #1117) — username/password authentication for homelabbers without an IdP
  • Covers credential bootstrap, Argon2id hashing, login API, rate limiting, login form architecture, nginx integration, security considerations, and migration paths
  • Addresses CodeRabbit feedback: simplified timing-safe username comparison

Closes #1117

Test plan

  • Review design spec for completeness against issue feat: Local authentication mode (username/password) #1117 acceptance criteria
  • Verify security considerations are adequate for single-admin homelab context
  • Confirm nginx integration approach is compatible with existing auth proxy contract

🤖 Generated with Claude Code


CodeAnt-AI Description

Design spec for local username/password authentication (AUTH_MODE=local)

What Changed

  • Adds a complete design spec for running Rackula with a single local admin account using username/password authentication instead of an external IdP.
  • Describes required environment variables and startup behavior (username/password bootstrap, password hashing with Argon2id, and container-restart password changes), plus optional Docker secret support.
  • Defines user-visible login flow: a standalone login page served at GET /auth/login and a POST /api/auth/login endpoint that returns a session cookie on success and 401/429 responses on failure or rate limit.
  • Specifies observable protections: per-IP rate limiting (5 attempts/60s), constant-time username comparison, generic failure messages, CSRF exemption for login, and HTTPS cookie warnings.
  • Documents migration behavior: how sessions and logins behave when switching between local and OIDC modes and what remains compatible.
  • Lists acceptance tests and E2E scenarios to verify login, logout, session behavior, rate limiting, and that credentials are not logged.

Impact

✅ Shorter homelab setup for single-admin deployments
✅ Clearer login flow when no external IdP is available
✅ Fewer successful brute-force attempts due to built-in rate limiting

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

ggfevans and others added 2 commits February 27, 2026 18:00
Design spec for AUTH_MODE=local covering credential bootstrap,
Argon2id hashing, login API, rate limiting, login form architecture,
nginx integration, and security considerations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace SHA-256 hash-based timing-safe comparison with direct
timingSafeEqual on padded buffers — simpler and equally secure
since usernames are not secret values.

Addresses CodeRabbit review feedback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codeant-ai
Copy link
Contributor

codeant-ai bot commented Feb 28, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

Adds a detailed design for a new local authentication mode (AUTH_MODE=local), describing credential bootstrap, Argon2id hashing, login endpoints and UI, in-memory rate limiting, session handling, Nginx serving changes, environment variables, and testing/migration guidance.

Changes

Cohort / File(s) Summary
Local Auth Design Documentation
docs/plans/2026-02-27-local-auth-design.md
New comprehensive design specifying AUTH_MODE=local: credential bootstrap (env/file), Argon2id hashing/PHC format, GET/POST login routes, in-memory per-IP rate limiting, session cookie behavior, separate Vite/Svelte login UI, Nginx routing changes, security considerations, and test plan.
Frontend README header
README.md
Replaces header link app.racku.lacount.racku.la and swaps static dark-mode image for a color-scheme-aware <picture> element selecting dark/light assets with a fallback <img>.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Nginx
  participant Frontend as Login Page
  participant API as Rackula API
  participant Auth as Local Auth Module
  participant Store as Session Store

  User->>Nginx: GET /auth/login
  Nginx->>Frontend: serve login page (static)
  User->>Frontend: submit credentials (POST /api/auth/login)
  Frontend->>API: POST /api/auth/login (credentials)
  API->>Auth: validate credentials, check rate-limit
  Auth->>Auth: verify Argon2id hash (timing-safe)
  alt success
    Auth->>Store: create session, set cookie
    API-->>Frontend: 200 + Set-Cookie
    Frontend-->>User: redirect to app
  else failure
    Auth-->>API: error (401 or 429)
    API-->>Frontend: error response
    Frontend-->>User: show error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • #1182: Implementation PR that builds upon this design to implement local authentication mode, handlers, hashing, rate limiting, and login UI.
  • #1339: Related PR concerning auth endpoint and Nginx routing adjustments for the login flow.

Suggested labels

size:XL

Poem

🐰 A tiny rabbit hops with glee,
I wrote a plan for local key,
Argon2 to guard the gate,
Rate limits keep the pace sedate,
Cookies snug — now welcome, dev and me! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive README.md changes appear to address a logo/branding update unrelated to the local auth design spec scope; unclear if this is a necessary ancillary change or out-of-scope. Clarify whether the README logo update is necessary for this PR or should be split into a separate change to keep the scope focused on the auth design spec.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'docs: local authentication mode design spec' clearly and concisely describes the main change—adding a design specification for local authentication.
Description check ✅ Passed The description is well-structured, providing a comprehensive summary of the design spec covering credential bootstrap, authentication flow, rate limiting, and security considerations related to the changeset.
Linked Issues check ✅ Passed The design spec comprehensively addresses all acceptance criteria from #1117: defines AUTH_MODE=local config, bootstrap flow, Argon2id hashing, login/logout/session behavior, anonymous access blocking, and migration paths between modes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch docs/1117-local-auth-design

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Feb 28, 2026
@codeant-ai
Copy link
Contributor

codeant-ai bot commented Feb 28, 2026

CodeAnt AI finished reviewing your PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/plans/2026-02-27-local-auth-design.md`:
- Around line 79-86: The fenced code block showing Argon2id parameters is
missing a language identifier, which affects rendering; update the block that
contains "type: argon2id" / "memoryCost: 65536" etc. to include a language tag
such as `text` or `properties` after the opening triple backticks (e.g.,
```text) so the configuration-style content renders correctly.
- Around line 282-286: Clarify and enforce session invalidation when switching
RACKULA_AUTH_MODE from "oidc" to "local": update the migration docs to recommend
invalidating existing sessions by rotating the session secret or expiring
session cookies and/or provide a clear command/step to purge server-side session
store after setting RACKULA_LOCAL_USERNAME and RACKULA_LOCAL_PASSWORD and
changing RACKULA_AUTH_MODE, and add a note that automated tooling could
optionally trigger this rotation to avoid subject mismatches (e.g.,
"user@example.com" vs "admin") that break authorization or audit trails.
- Line 39: Implement Docker secrets support by checking for environment
variables with a _FILE suffix (e.g., RACKULA_LOCAL_PASSWORD_FILE and analogous
session secret file var) and reading the secret from that file when present,
falling back to the plain env var if not; update the local auth initialization
logic that currently reads RACKULA_LOCAL_PASSWORD to prefer file-based values
when RACKULA_LOCAL_PASSWORD_FILE is set and apply the same pattern to the
session secret handling so both secrets use the secure Docker secrets flow.
- Around line 374-380: Add unit tests that assert the application fails fast
during credential bootstrap by exercising the validation logic (e.g., the
function/method responsible for bootstrap validation such as
validateBootstrapCredentials or bootstrapStartup in the auth/init code): include
tests that (1) supplying invalid credentials causes startup to throw with a
clear message, (2) omitting required env vars RACKULA_LOCAL_USERNAME or
RACKULA_LOCAL_PASSWORD triggers startup failure, (3) providing a password
shorter than 12 characters fails validation, and (4) if you implement Docker
secrets file reading, include a test that missing/invalid secret file content
causes startup failure; make assertions on thrown error types/messages so the
tests guarantee the fail-fast behavior described in section 1.
- Around line 143-151: The fenced code block showing the credential verification
flow is missing a language identifier; update the opening fence for that
numbered list (the credential verification flow block: "1. Rate limit check ...
7. On success -> reset rate limit counter, create signed session token") to
include a language identifier such as text or pseudocode (e.g., change ``` to
```text) so the block is treated as plain/pseudo code by renderers.
- Around line 354-369: Update the "## 13. Files to Modify" table to include the
documentation files referenced elsewhere in the plan: add rows for
docs/deployment/AUTHENTICATION.md (new/modified — migration steps, local mode
setup), and consider adding docs/ARCHITECTURE.md and docs/reference/SPEC.md
where appropriate; ensure the table entries mirror the style used for other rows
(file name in first column, brief change description in second) so the plan's
mention of AUTHENTICATION.md (line 289) is reflected in this modification list.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 719a094 and ec43413.

📒 Files selected for processing (1)
  • docs/plans/2026-02-27-local-auth-design.md

4. Discard plaintext password from process memory
5. If either missing or invalid -> **fail startup** with clear error

**Docker secrets support** (stretch goal): `RACKULA_LOCAL_PASSWORD_FILE=/run/secrets/local_password` reads from file instead of env. Same pattern documented (but not yet implemented) for session secret.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider prioritizing Docker secrets support.

While the environment variable approach is simpler for MVP, marking Docker secrets as a "stretch goal" may be too conservative given the security implications. The plaintext password visibility in docker inspect is a real risk (acknowledged in the Devil's Advocate section at line 325). Since Docker secrets support is relatively straightforward to implement (read from file if _FILE suffix exists, pattern already documented for session secret), consider including it in the MVP scope rather than deferring it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-27-local-auth-design.md` at line 39, Implement Docker
secrets support by checking for environment variables with a _FILE suffix (e.g.,
RACKULA_LOCAL_PASSWORD_FILE and analogous session secret file var) and reading
the secret from that file when present, falling back to the plain env var if
not; update the local auth initialization logic that currently reads
RACKULA_LOCAL_PASSWORD to prefer file-based values when
RACKULA_LOCAL_PASSWORD_FILE is set and apply the same pattern to the session
secret handling so both secrets use the secure Docker secrets flow.

Comment on lines 374 to 380
### Unit Tests

- Argon2id hash/verify round-trip
- Rate limiter: 5 failures trigger lockout, lockout expires, success resets counter, separate IPs tracked independently
- Login endpoint: valid creds -> 200 + cookie, invalid -> 401, missing fields -> 400, rate limited -> 429
- Timing-safe username comparison

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add startup validation tests.

The testing strategy is comprehensive, but consider adding unit tests for credential bootstrap validation:

  • Invalid credentials cause startup failure with clear error messages
  • Missing required env vars (RACKULA_LOCAL_USERNAME, RACKULA_LOCAL_PASSWORD) cause startup failure
  • Password below 12 characters causes startup failure
  • Docker secrets file reading (if implemented in MVP)

These tests ensure the fail-fast behavior described in section 1 works correctly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-27-local-auth-design.md` around lines 374 - 380, Add unit
tests that assert the application fails fast during credential bootstrap by
exercising the validation logic (e.g., the function/method responsible for
bootstrap validation such as validateBootstrapCredentials or bootstrapStartup in
the auth/init code): include tests that (1) supplying invalid credentials causes
startup to throw with a clear message, (2) omitting required env vars
RACKULA_LOCAL_USERNAME or RACKULA_LOCAL_PASSWORD triggers startup failure, (3)
providing a password shorter than 12 characters fails validation, and (4) if you
implement Docker secrets file reading, include a test that missing/invalid
secret file content causes startup failure; make assertions on thrown error
types/messages so the tests guarantee the fail-fast behavior described in
section 1.

ggfevans and others added 2 commits February 27, 2026 18:50
- Add language tags to fenced code blocks (text)
- Recommend session secret rotation on OIDC->local migration
- Add credential bootstrap fail-fast tests to testing strategy
- Add AUTHENTICATION.md to files-to-modify table

Skipped: Docker secrets implementation (explicitly deferred as
stretch goal in the design spec).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
docs/plans/2026-02-27-local-auth-design.md (1)

355-371: 🧹 Nitpick | 🔵 Trivial

Consider updates to ARCHITECTURE.md and SPEC.md.

The files list includes docs/deployment/AUTHENTICATION.md but doesn't mention whether docs/reference/ARCHITECTURE.md or docs/reference/SPEC.md need updates. As per coding guidelines, ARCHITECTURE.md provides the high-level overview and SPEC.md is the authoritative technical specification. Adding a new authentication mode likely requires updates to both.

📋 Suggested additions to the table
 | `docs/deployment/AUTHENTICATION.md`   | Add local auth setup guide, migration steps between modes                      |
+| `docs/reference/ARCHITECTURE.md`      | Document AUTH_MODE=local in authentication architecture overview                |
+| `docs/reference/SPEC.md`              | Add local auth specification (credential flow, endpoints, session handling)     |

Based on learnings: Start with docs/ARCHITECTURE.md for codebase overview and consult docs/reference/SPEC.md as the authoritative technical specification. Organize documentation by purpose: ARCHITECTURE.md for high-level overview, guides/ for testing and accessibility patterns, reference/ for specifications and technical details, planning/ for roadmap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-27-local-auth-design.md` around lines 355 - 371, Update
the documentation to include the new local-auth mode: add a high-level overview
of local authentication and where it fits into the system in
docs/reference/ARCHITECTURE.md (referencing the new files like src/login.ts,
src/lib/components/LoginForm.svelte, and api/src/auth-logger.ts), and add
concrete technical requirements, env var definitions (RACKULA_LOCAL_*), API
behavior (api/src/app.ts / api/src/security.ts conditional routes and config
parsing), and deployment/config steps to docs/reference/SPEC.md so it aligns
with the new docs/deployment/AUTHENTICATION.md guide; ensure SPEC.md includes
precise env names, expected request/response shapes for /auth/login, and
migration notes for deploy/docker-compose.persist.yml and
deploy/nginx.conf.template.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/plans/2026-02-27-local-auth-design.md`:
- Line 284: The document uses mixed British/American spelling for
"authorization" (e.g., "authorisation" near the RACKULA_AUTH_SESSION_SECRET
recommendation), so search the file for all occurrences of "authorisation" and
replace them with the American spelling "authorization" to standardize
terminology; verify headings, inline text, code comments, and any related
phrases (e.g., "authorize"/"authorise") are consistent and update them as needed
to "authorize"/"authorization".
- Line 145: Update the spec to describe the simplified timing-safe username
comparison: replace the padded-buffer technique with computing a fixed-length
cryptographic hash (e.g., SHA-256) of each username and comparing those hashes
with Node.js crypto.timingSafeEqual; mention the need to normalize usernames
before hashing (case/whitespace rules), that hashes are equal-length so no
manual padding is required, and reference the use of crypto.timingSafeEqual (or
equivalent) as the comparison primitive so readers can implement
verifyUsernameHash() or similar consistently.

---

Duplicate comments:
In `@docs/plans/2026-02-27-local-auth-design.md`:
- Around line 355-371: Update the documentation to include the new local-auth
mode: add a high-level overview of local authentication and where it fits into
the system in docs/reference/ARCHITECTURE.md (referencing the new files like
src/login.ts, src/lib/components/LoginForm.svelte, and api/src/auth-logger.ts),
and add concrete technical requirements, env var definitions (RACKULA_LOCAL_*),
API behavior (api/src/app.ts / api/src/security.ts conditional routes and config
parsing), and deployment/config steps to docs/reference/SPEC.md so it aligns
with the new docs/deployment/AUTHENTICATION.md guide; ensure SPEC.md includes
precise env names, expected request/response shapes for /auth/login, and
migration notes for deploy/docker-compose.persist.yml and
deploy/nginx.conf.template.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec43413 and 186282d.

📒 Files selected for processing (2)
  • README.md
  • docs/plans/2026-02-27-local-auth-design.md


```text
1. Rate limit check (per-IP sliding window)
2. Timing-safe username comparison (padded buffers + timingSafeEqual)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Clarify timing-safe username comparison implementation.

The specification mentions "padded buffers + timingSafeEqual" but this approach adds complexity. According to the PR summary, there was a CodeRabbit reviewer change to "simplify timing-safe username comparison."

Consider documenting the final simplified approach here. If using Node.js crypto.timingSafeEqual, note that it requires equal-length buffers, so padding is necessary. However, a simpler approach might be to compare fixed-length hashes of the usernames rather than padding the raw strings.

💡 Simpler alternative approach
-2. Timing-safe username comparison (padded buffers + timingSafeEqual)
+2. Timing-safe username comparison (compare SHA-256 hashes of usernames)

This avoids manual buffer padding and leverages cryptographic hashing for constant-time comparison.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-27-local-auth-design.md` at line 145, Update the spec to
describe the simplified timing-safe username comparison: replace the
padded-buffer technique with computing a fixed-length cryptographic hash (e.g.,
SHA-256) of each username and comparing those hashes with Node.js
crypto.timingSafeEqual; mention the need to normalize usernames before hashing
(case/whitespace rules), that hashes are equal-length so no manual padding is
required, and reference the use of crypto.timingSafeEqual (or equivalent) as the
comparison primitive so readers can implement verifyUsernameHash() or similar
consistently.

@ggfevans ggfevans merged commit 9e37056 into main Feb 28, 2026
12 checks passed
@ggfevans ggfevans deleted the docs/1117-local-auth-design branch February 28, 2026 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Local authentication mode (username/password)

1 participant