Skip to content

feat(auth): implement local admin setup, login, and auth UI (#30)#56

Merged
steilerDev merged 1 commit into
betafrom
feat/30-local-admin-auth
Feb 9, 2026
Merged

feat(auth): implement local admin setup, login, and auth UI (#30)#56
steilerDev merged 1 commit into
betafrom
feat/30-local-admin-auth

Conversation

@steilerDev
Copy link
Copy Markdown
Owner

Summary

  • Implements Story 1.2: Local Admin Authentication — Initial Setup #30: Local Admin Authentication — Initial Setup & Login
  • Backend: auth endpoints (GET /api/auth/me, POST /api/auth/setup, POST /api/auth/login) with argon2 password hashing and timing-attack prevention
  • Frontend: SetupPage and LoginPage with accessible forms, client-side validation, and error handling
  • Auth API client module with typed request/response interfaces
  • 65 new tests (userService unit tests + auth routes integration tests)

Changes

Backend

  • server/src/services/userService.ts — user CRUD functions (createLocalUser, verifyPassword, findByEmail, countUsers, countActiveUsers, toUserResponse)
  • server/src/routes/auth.ts — GET /me, POST /setup, POST /login with JSON schema validation
  • server/src/plugins/config.ts — SESSION_DURATION and SECURE_COOKIES config
  • server/src/app.ts — register auth routes
  • server/package.json — added argon2@0.43.0
  • .env.example — new env vars documented

Frontend

  • client/src/lib/authApi.ts — typed API client for auth endpoints
  • client/src/pages/SetupPage/ — admin setup form (email, display name, password, confirm)
  • client/src/pages/LoginPage/ — login form (email, password)
  • client/src/App.tsx — /setup and /login routes outside AppShell
  • client/webpack.config.cjs — extensionAlias for ESM .js → .ts resolution

Tests

  • server/src/services/userService.test.ts — 31 unit tests
  • server/src/routes/auth.test.ts — 34 integration tests

Test plan

  • All 281 tests pass across 22 suites
  • Lint: 0 errors
  • Format: all clean
  • Typecheck: all 3 workspaces clean
  • Build: shared → client → server succeeds
  • npm audit: 0 vulnerabilities
  • CI: Quality Gates + Docker checks

Fixes #30

🤖 Generated with Claude Code

Add backend authentication endpoints (GET /api/auth/me, POST /api/auth/setup,
POST /api/auth/login) with argon2 password hashing and timing-attack prevention.
Create frontend SetupPage and LoginPage with client-side validation, accessible
forms, and error handling. Add auth API client module and update routing.

Backend:
- userService with createLocalUser, verifyPassword, findByEmail, countUsers
- Auth routes with JSON schema validation (email format, password min 12 chars)
- Config: SESSION_DURATION and SECURE_COOKIES environment variables
- argon2@0.43.0 for OWASP-recommended password hashing

Frontend:
- SetupPage: centered card form for initial admin account creation
- LoginPage: centered card form for email/password login
- authApi.ts: typed API client for auth endpoints
- Routes /setup and /login outside AppShell (no sidebar)
- Webpack extensionAlias for ESM .js -> .ts resolution

Tests: 65 new tests (userService unit + auth routes integration)
All quality gates pass, 281 tests total across 22 suites.

Fixes #30

Co-Authored-By: Claude backend-developer (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[product-owner] PR #56 Review for Story #30: Local Admin Authentication -- Initial Setup & Login


Acceptance Criteria Verification

# Criterion Verdict Evidence
1 POST /api/auth/setup creates first admin user (email, displayName, password) PASS auth.ts line 64-89: endpoint creates user with role admin, auth_provider local. Returns 201 with user object. Integration test creates first admin user successfully (201) confirms.
2 Setup returns 403 SETUP_COMPLETE when users already exist PASS auth.ts line 72-74: checks countUsers > 0 before creation. Error code SETUP_COMPLETE. Integration test returns 403 SETUP_COMPLETE when users already exist confirms.
3 Password stored as argon2 hash (never plain text) PASS userService.ts line 48: argon2.hash(password). Unit test creates user with hashed password (not plain text) verifies hash starts with $argon2id$. Response never includes passwordHash (tested in both setup and login response tests).
4 POST /api/auth/login authenticates with email/password PASS auth.ts line 99-134: finds user by email, verifies password via argon2.verify(), returns 200 with user profile. Integration test succeeds with valid credentials (200) confirms.
5 Login returns 401 INVALID_CREDENTIALS for wrong credentials (generic message) PASS auth.ts line 116 and 127: both non-existent user and wrong password return same error code and message Invalid email or password. Tests for wrong password and non-existent email both verify INVALID_CREDENTIALS with identical message.
6 Login returns 401 ACCOUNT_DEACTIVATED for deactivated users PASS auth.ts line 120-122: checks deactivatedAt before password verification, throws ACCOUNT_DEACTIVATED. Integration test fails for deactivated user (401 ACCOUNT_DEACTIVATED) confirms.
7 GET /api/auth/me returns setupRequired:true when no users, false otherwise PASS auth.ts lines 38-55: returns 200 with setupRequired: true/false based on user count. Two integration tests verify both states.
8 Timing-attack prevention (constant-time comparison for non-existent users) PASS auth.ts lines 110-115: when user not found or has no passwordHash, a dummy argon2 hash is verified before returning error. This ensures consistent response time whether the email exists or not. Tests verify this for both non-existent users and OIDC users without passwords.
9 Password minimum 12 characters enforced PASS auth.ts line 13: JSON schema validation minLength: 12. SetupPage client-side validation also enforces 12 chars. Integration tests verify boundary cases: 12 chars accepted, 11 chars rejected.
10 Setup page with form validation PASS SetupPage.tsx: form with email, displayName, password, confirmPassword fields. Client-side validation for required fields, password min length, password confirmation match. Error display with role="alert" and aria-invalid attributes. Accessible labels with htmlFor.
11 Login page with form validation and error display PASS LoginPage.tsx: form with email and password fields. Client-side validation for required fields. API error display via errorBanner with role="alert". Accessible labels, aria-invalid, aria-describedby for error association.

Result: 11/11 acceptance criteria PASS.


UAT Scenario Coverage

# Scenario Status
1 Setup endpoint available when no users exist Covered by AC #7 test
2 First admin creation via setup Covered by AC #1 test
3 Setup endpoint locked after first admin Covered by AC #2 test
4 Setup page shown when no users exist Manual (frontend page exists at /setup)
5 Password hashing with secure algorithm Covered by AC #3 test
6 Invalid email format rejected Covered by integration test
7 Password too short rejected Covered by AC #9 tests
8 Empty display name rejected Covered by integration test
9 Successful login with valid credentials Covered by AC #4 test
10 Login page shown for returning users Manual (frontend page exists at /login)
11 Invalid email returns generic error Covered by AC #5 test
12 Invalid password returns generic error Covered by AC #5 test
13 Deactivated user cannot log in Covered by AC #6 test
14-15 Session persistence across reload Deferred to Story #32 (noted in code comments)
16-17 Timing attack mitigation Covered by AC #8 tests

Agent Responsibilities Check

Responsibility Agent Status
Backend implementation backend-developer DONE (Co-Authored-By in commit)
Frontend implementation frontend-developer DONE (Co-Authored-By in commit)
Tests (65 new, 281 total) qa-integration-tester DONE (Co-Authored-By in commit)
UAT scenarios uat-validator DONE (posted on issue #30)
Architecture sign-off product-architect PENDING -- no review on PR yet
Security review security-engineer PENDING -- no review on PR yet

Quality Gates

  • CI (Quality Gates): PASS
  • CI (Docker): PASS
  • Dependency pinning: argon2 pinned at exact 0.43.0 -- PASS
  • Accessibility: Focus styles on inputs and buttons, role="alert" on errors, aria-invalid/aria-describedby -- PASS
  • Conventions: Conventional commit, Fixes #30, proper agent attribution -- PASS
  • Scope discipline: No scope creep detected. Session creation correctly deferred to Story #32 with // NOTE: comments.

Non-Blocking Observations

  1. Deactivated user error code reveals account state: The ACCOUNT_DEACTIVATED error code and message "Account has been deactivated" discloses that the account exists and is deactivated. The AC explicitly requires this code (AC #6), so this is correct per requirements. However, the UAT Scenario 13 expected a "generic error message (no indication that account is deactivated)". The security-engineer should evaluate whether this information leakage is acceptable in the security review, and if not, a follow-up story should be created to align the ACs. Not blocking for this PR since the implementation matches the story's acceptance criteria.

  2. additionalProperties: false behavior: The JSON schemas set additionalProperties: false but Fastify/AJV defaults strip extra properties silently rather than rejecting them. The tests correctly assert 201/200 for payloads with extra fields. This is acceptable behavior but worth documenting if stricter validation is desired later.

  3. Login page successMessage placeholder text: The login success message includes "Session management will be implemented in Story #32." -- this is a developer-facing note that should be removed when Story #32 is implemented. Acceptable as a temporary placeholder.


Decision

CONDITIONALLY APPROVED -- All 11 acceptance criteria pass, 65 tests cover the UAT scenarios comprehensively, CI is green, dependency is properly pinned, and test authorship is verified. The PR correctly scopes session management to Story #32.

Approval is conditional on:

  • product-architect must review and approve (architecture compliance, code quality)
  • security-engineer must review and approve (especially the timing-attack prevention, argon2 configuration, and the deactivated-user error code information leakage noted above)

Both reviews are required per CLAUDE.md before merge.

Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[product-architect] Architecture review of PR #56 — Story #30: Local Admin Authentication


Verdict: APPROVE (with observations for refinement)

CI is green (Quality Gates + Docker pass). The implementation is well-structured, follows established patterns, and correctly implements the API contract for the three endpoints in scope. The observations below are minor and appropriate for the refinement phase.


Architecture Compliance

Passes. The implementation follows the established Fastify plugin pattern and aligns with the plugin registration order documented in the Architecture wiki:

  • Auth routes registered after db plugin (correct, needs fastify.db)
  • Uses { prefix: '/api/auth' } for route mounting (correct convention)
  • Route handlers throw AppError instances, never construct error responses directly (correct per ADR-009)
  • Service layer (userService.ts) is properly separated from route handlers (good separation of concerns)

The registration order in app.ts is: config -> errorHandler -> compress -> db -> authRoutes -> health -> static. This is consistent with the Architecture wiki's documented order (auth routes go between db and static).

API Contract Alignment

Passes. All three endpoints match the API Contract wiki specification:

Endpoint Contract Implementation Match
GET /api/auth/me 200 OK, {user, setupRequired, oidcEnabled} Correct Yes
POST /api/auth/setup 201 Created, {user}, 403 SETUP_COMPLETE Correct Yes
POST /api/auth/login 200 OK, {user}, 401 INVALID_CREDENTIALS/ACCOUNT_DEACTIVATED Correct Yes

Validation schemas match the contract:

  • Setup: email (format: email), displayName (1-100 chars), password (min 12)
  • Login: email (string), password (string)
  • Both use additionalProperties: false (good; stricter than the test comments suggest about "additionalProperties ignored" — note: Fastify may strip extra properties rather than rejecting, depending on AJV removeAdditional setting)

UserResponse shape correctly excludes passwordHash and oidcSubject as specified. The toUserResponse() function uses explicit field picking (not spread/delete), which is the secure pattern for sensitive field exclusion.

Schema Alignment

Passes. userService.ts operates on the Drizzle users schema from server/src/db/schema.ts, which matches the Wiki Schema page and migration 0001_create_users_and_sessions.sql. Column names (snake_case DB, camelCase TS) follow conventions.

Shared Types Usage

Passes. UserResponse imported from @cornerstone/shared — the returned shape from toUserResponse() matches the interface. ErrorCode is properly typed through AppError constructor usage (e.g., 'SETUP_COMPLETE', 'INVALID_CREDENTIALS', 'ACCOUNT_DEACTIVATED').

Security Review Items

  1. Timing attack prevention (GOOD): Login route hashes a dummy password when user is not found or has no passwordHash. This prevents email enumeration via response time differences.

  2. Generic error messages (GOOD): INVALID_CREDENTIALS always returns "Invalid email or password" regardless of whether the email exists or password was wrong. Matches the security requirement in the API Contract.

  3. Deactivation check ordering: In auth.ts lines ~96-101, the deactivation check happens before password verification. This means a deactivated user gets ACCOUNT_DEACTIVATED without their password being checked. This is functionally correct per the API Contract (deactivated users get a distinct error), but note it does reveal that the email exists and the account was deactivated. The API Contract explicitly specifies this distinction, so this is intentional.

  4. Sensitive field exclusion (GOOD): toUserResponse() uses positive field selection (explicitly lists safe fields), not negative selection (delete sensitive fields). This is the correct pattern — new sensitive columns added later will NOT accidentally leak.

Code Quality

  1. ESM imports: All .js extensions present in import paths (correct for ESM Node.js).
  2. Naming conventions: Files use camelCase (userService.ts, authApi.ts), React components use PascalCase (SetupPage.tsx, LoginPage.tsx). Matches convention.
  3. Type safety: No unjustified any types in production code. Test files use as any with eslint-disable comments only for verifying that sensitive fields are absent — acceptable.
  4. request.body as { ... } type assertion: Used in route handlers. This is standard Fastify practice since the JSON schema validation guarantees the shape at runtime.

Test Coverage

65 new tests (31 userService unit + 34 auth route integration) covering:

  • All happy paths for all 3 endpoints
  • Validation boundary cases (password min length 11 vs 12, empty displayName, missing fields)
  • Error cases (SETUP_COMPLETE, INVALID_CREDENTIALS, ACCOUNT_DEACTIVATED)
  • Sensitive field exclusion verification
  • Timing attack prevention verification
  • Case-sensitivity for email lookup
  • OIDC user attempting local login (returns INVALID_CREDENTIALS)

The coverage appears thorough for the 95% target. QA agent will verify exact coverage numbers.

Observations for Refinement

These are non-blocking items appropriate for the epic refinement phase:

  1. additionalProperties: false test description mismatch: Tests at lines ~1289-1305 and ~1532-1550 are named "accepts payload even with additional properties (additionalProperties ignored)" but the schema actually sets additionalProperties: false. Fastify with additionalProperties: false may strip or ignore extra properties rather than rejecting them (depends on AJV configuration). The test passes, but the comment/name is misleading — consider updating the test name to reflect actual behavior.

  2. Config test updates are minimal: The 4 existing config tests now include sessionDuration and secureCookies in expected output. However, there are no new tests for invalid SESSION_DURATION (e.g., non-numeric, negative) or invalid SECURE_COOKIES (e.g., "yes", "1"). The validation logic is present in config.ts but untested. QA should add coverage.

  3. Client-side autoComplete attribute: LoginPage has autoComplete="email" and autoComplete="current-password" (good for password managers). SetupPage is missing autoComplete attributes on its inputs — minor UX improvement for the refinement pass.

  4. Hardcoded dummy hash: auth.ts line ~1836 contains a hardcoded argon2 hash string for timing attack prevention. This works correctly but could be made more maintainable by generating the dummy hash once at module load time (e.g., const DUMMY_HASH = await argon2.hash('dummy-password-for-timing');). Not a security issue, just a readability improvement.

  5. setupSchema and loginSchema are route-local: These JSON schemas are defined at the top of auth.ts. As the number of routes grows, consider extracting schemas to a shared location (e.g., server/src/schemas/). Not needed now, but worth noting for future organization.

  6. CSS duplication between SetupPage and LoginPage: The two CSS Module files are nearly identical (~290 lines combined with ~95% overlap). Consider extracting a shared AuthPage.module.css during refinement.

  7. Session not created on setup/login: Both endpoints correctly note // NOTE: Session creation will be added in Story #32. The success messages in the frontend components also reference Story #32. This is properly scoped and well-documented.


Summary: This is a solid implementation that faithfully follows the API Contract, Schema, and Architecture documentation. The error handling pattern, sensitive field exclusion, and timing attack prevention are all correctly implemented. All observations above are minor refinement items. Approve.

Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[security-engineer]

Security Review: PR #56 (Story #30 - Local Admin Authentication)

I have completed a comprehensive security review of PR #56. This PR implements the initial setup and login endpoints with local password authentication using Argon2.

Summary

Overall Assessment: APPROVED with recommendations for future stories

This PR implements secure password authentication with proper hashing, timing attack prevention, and sensitive data protection. No critical or high-severity vulnerabilities were identified. All security controls appropriate for this phase (initial setup + login without session management) are correctly implemented.

Findings

✅ Password Hashing (SECURE)

Status: No issues found

The implementation uses argon2@0.43.0 with secure default parameters:

  • Algorithm: argon2id (OWASP-recommended hybrid mode)
  • Memory cost: 65536 KiB (64 MiB) — exceeds OWASP minimum of 15 MiB
  • Time cost: 3 iterations — meets OWASP minimum of 2
  • Parallelism: 4 threads — exceeds OWASP minimum of 1

Evidence:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/services/userService.ts:48await argon2.hash(password) uses secure defaults
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/services/userService.test.ts:165 — test confirms argon2id format
  • npm audit — 0 vulnerabilities, argon2@0.43.0 has no known CVEs

✅ Timing Attack Prevention (SECURE)

Status: No issues found

The login endpoint implements proper timing attack prevention by always hashing a dummy password when the user is not found or has no password hash (OIDC users).

Evidence:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.ts:110-116 — Dummy password verification when user not found or passwordHash is null:
    if (!user || !user.passwordHash) {
      // Hash a dummy password to prevent timing attacks
      await userService.verifyPassword(
        '$argon2id$v=19$m=65536,t=3,p=4$aGVsbG93b3JsZA$cZn5d+rFz8E4HMhH+3e6Ug',
        password,
      );
      throw new AppError('INVALID_CREDENTIALS', 401, 'Invalid email or password');
    }
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.test.ts:614-668 — Tests confirm timing attack prevention for both non-existent users and OIDC users

This prevents attackers from using response time differences to enumerate valid email addresses.


✅ Input Validation (SECURE)

Status: No issues found

JSON schema validation is properly implemented with appropriate constraints:

Evidence:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.ts:6-17 — Setup schema:
    • email: { type: 'string', format: 'email' } — validates RFC 5322 email format via AJV
    • displayName: { type: 'string', minLength: 1, maxLength: 100 } — prevents empty and excessively long names
    • password: { type: 'string', minLength: 12 } — enforces strong password minimum length
    • additionalProperties: false — rejects unexpected fields (NOTE: Fastify default AJV ignores this, see informational finding below)
  • Client-side validation mirrors server-side constraints in /Users/franksteiler/Documents/Sandboxes/cornerstone/client/src/pages/SetupPage/SetupPage.tsx:23-48

No SQL injection risk — all database queries use Drizzle ORM's parameterized queries.


✅ Sensitive Data Protection (SECURE)

Status: No issues found

Password hashes and OIDC subjects are correctly excluded from all API responses.

Evidence:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/services/userService.ts:17-28toUserResponse() function explicitly excludes passwordHash and oidcSubject
  • All auth routes (/api/auth/setup, /api/auth/login, /api/auth/me) use toUserResponse() before returning user data
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.test.ts:323-365 — Tests confirm passwordHash and oidcSubject are never in API responses

TypeScript type safety ensures developers cannot accidentally leak sensitive fields — the UserResponse type excludes these fields at compile time.


✅ Error Message Security (SECURE)

Status: No issues found

Error messages follow secure patterns:

  • Login failures return generic INVALID_CREDENTIALS for both "user not found" and "wrong password" (prevents account enumeration)
  • ACCOUNT_DEACTIVATED reveals account state, but this is acceptable UX — users need to know why they can't login
  • SETUP_COMPLETE reveals setup state, but this is acceptable — prevents duplicate admin creation attempts

Evidence:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.ts:116 — Generic error for non-existent users
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.ts:127 — Generic error for wrong password
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.test.ts:463-477 — Test confirms same error code for both cases

✅ Frontend Security (SECURE)

Status: No issues found

Client code follows secure practices:

  • No XSS vectors: No use of dangerouslySetInnerHTML, innerHTML, or eval() in any client code
  • Proper form handling: All user input is handled via controlled React components
  • Accessibility: ARIA attributes (aria-invalid, aria-describedby, role="alert") used correctly
  • Password hygiene: Passwords are cleared from state after submission (lines 66-70 in SetupPage.tsx, lines 51-53 in LoginPage.tsx)
  • Autocomplete: Proper autoComplete attributes (email, current-password) for password manager compatibility

Evidence:

  • Grep search for XSS patterns returned no matches
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/client/src/pages/LoginPage/LoginPage.tsx:97,119 — Proper autocomplete attributes

✅ Dependency Security (SECURE)

Status: No issues found

All dependencies are secure:

  • npm audit0 vulnerabilities (0 fixable)
  • argon2@0.43.0 — No known CVEs
  • All other dependencies inherited from previous PRs (already audited)

📋 Informational: Fastify additionalProperties Behavior

Severity: Informational
OWASP Category: A04 - Insecure Design (minor configuration note)

The JSON schema specifies additionalProperties: false, but Fastify's default AJV configuration does not reject extra properties — they are silently ignored.

Evidence:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.test.ts:305-321 — Test confirms extra fields are ignored, not rejected

Why this is acceptable:

  • Extra fields are ignored, not processed — no security impact
  • Fail-open for unknown fields is a reasonable design choice
  • This is Fastify's documented default behavior
  • For a small self-hosted application (1-5 users), strict OpenAPI validation is not critical

If stricter validation is desired in the future:
Configure Fastify with AJV removeAdditional: true or removeAdditional: 'all' in app.ts:

const app = Fastify({
  ajv: {
    removeAdditional: true, // Strips extra properties
    // OR: removeAdditional: 'all' // Strips ALL extra properties including nested
  },
});

Risk if unaddressed: None. This is a design choice, not a vulnerability.


Recommendations for Future Stories

The following security controls are missing from this PR but are expected to be addressed in Story #32 (Session Management) or later stories:

1. Rate Limiting (MEDIUM Priority)

Add rate limiting to /api/auth/login and /api/auth/setup endpoints to prevent brute-force attacks and credential stuffing.

Recommendation: Use @fastify/rate-limit with appropriate thresholds:

import rateLimit from '@fastify/rate-limit';

await app.register(rateLimit, {
  max: 5, // 5 requests
  timeWindow: '15 minutes', // per 15 minutes
  skipOnError: false,
});

2. CSRF Protection (HIGH Priority for Session Management)

When session cookies are implemented in Story #32, ensure they use SameSite=Strict or SameSite=Lax to prevent CSRF attacks.

Recommendation: Set cookie flags in session configuration:

reply.setCookie('session', sessionToken, {
  httpOnly: true,
  secure: fastify.config.secureCookies,
  sameSite: 'strict', // or 'lax' if external OAuth redirect needed
  maxAge: fastify.config.sessionDuration,
});

3. Security Headers (MEDIUM Priority)

Add @fastify/helmet to set security headers (CSP, HSTS, X-Frame-Options, etc.).

Recommendation:

npm install @fastify/helmet

Then in app.ts:

import helmet from '@fastify/helmet';

await app.register(helmet, {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"], // CSS Modules may need unsafe-inline
      imgSrc: ["'self'", 'data:'],
    },
  },
});

4. Account Lockout Mechanism (LOW Priority)

Consider adding temporary account lockout after N failed login attempts to further prevent credential stuffing.


Conclusion

This PR implements secure authentication with no critical or high-severity vulnerabilities. Password hashing uses OWASP-recommended Argon2id with secure parameters, timing attack prevention is correctly implemented, sensitive data is properly excluded from API responses, and all input validation is appropriate.

The missing security controls (rate limiting, CSRF protection, security headers) are expected to be addressed in future stories and do not block approval of this PR.

Recommendation: APPROVED — No blocking issues. Proceed with merge after product-owner and product-architect reviews are complete.


Review Completed: 2026-02-09
Reviewed By: security-engineer agent (Claude Sonnet 4.5)
Files Reviewed:

  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/services/userService.ts
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/routes/auth.ts
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/client/src/pages/SetupPage/SetupPage.tsx
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/client/src/pages/LoginPage/LoginPage.tsx
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/client/src/lib/authApi.ts
  • /Users/franksteiler/Documents/Sandboxes/cornerstone/server/src/plugins/config.ts
  • All test files for auth routes and user service

@steilerDev steilerDev merged commit fc77c6d into beta Feb 9, 2026
3 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 9, 2026

🎉 This PR is included in version 1.7.0-beta.2 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.7.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@steilerDev steilerDev deleted the feat/30-local-admin-auth branch February 19, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants