Skip to content

Harden admin auth: constant-time compare, session cookie, rate limiting, and one shared guard #38

Description

@royalpinto007

Problem

Admin authentication is a single shared password sent as a plaintext x-admin-password header on every request, and the check is copy-pasted into six route files:

  • app/api/admin/posts/route.ts (checkAdminAuth)
  • app/api/admin/posts/[id]/route.ts
  • app/api/admin/posts/[id]/edit/route.ts
  • app/api/admin/posts/[id]/resend-token/route.ts
  • app/api/admin/comments/route.ts
  • app/api/newsletter/send/route.ts

Three concrete weaknesses:

  1. The comparison is auth === expected, a non constant-time string compare.
  2. There is no rate limiting or lockout on the admin endpoints, even though the project already has consumeSharedRateLimit in lib/rate-limit/shared.ts and uses it on public write routes. The password can be brute forced against GET /api/admin/posts with no friction.
  3. The client (app/admin/page.tsx) keeps the raw password in React state and a ref (authedPasswordRef) and replays it on every fetch, so it is present in memory, in every request, and in any proxy or edge log that records headers.

Why it matters

These endpoints can approve, edit, reject, and delete arbitrary submissions, resend edit tokens, and trigger newsletter sends. That is the highest value authz boundary in the app and it is currently the weakest.

Suggested approach

  1. Add lib/auth/admin.ts exporting a single requireAdmin(req) helper, and delete the six copies.
  2. Compare with a constant-time function (crypto.timingSafeEqual over equal-length buffers, or hash both sides first).
  3. Add a POST /api/admin/session login route that verifies the password once and sets a signed, HttpOnly, Secure, SameSite=Strict session cookie with an expiry. Routes then validate the cookie, not the password.
  4. Rate limit the login route through consumeSharedRateLimit keyed on hashed IP, and log failures with logEvent at warn.
  5. Update app/admin/page.tsx to stop storing the password and to add an explicit logout.

Done when

  • No route file contains its own checkAdminAuth.
  • Password material is never sent on requests after login.
  • Failed logins are rate limited and logged.
  • Unit tests cover: valid login, invalid login, missing cookie, expired cookie, and rate limit trip.

If you want to take this on, comment on the issue to claim it and it will be assigned. Please keep to a maximum of 2 open claims per person at a time so other contributors get a chance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions