Skip to content

feat: JWT auth module, global guard, DEV_USER_ID removal - #8

Merged
codedsultan merged 10 commits into
mainfrom
develop
Jul 25, 2026
Merged

feat: JWT auth module, global guard, DEV_USER_ID removal#8
codedsultan merged 10 commits into
mainfrom
develop

Conversation

@codedsultan

Copy link
Copy Markdown
Owner
  • Add argon2, @nestjs/jwt, and @nestjs/passport auth dependencies
  • Database Schema: Migrate User table — add passwordHash, rename table to users, drop name
  • Auth Module: AuthService (register/login with argon2 hashing) and AuthController (POST /v1/auth/register, POST /v1/auth/login, both tagged with @Public())
  • Guards & Strategy: JwtStrategy (passport-jwt) and JwtAuthGuard registered as global APP_GUARD
  • Decorators: Added @Public() and @CurrentUser() parameter decorators
  • Replace per-controller AuthGuard with global JwtAuthGuard and remove AuthGuard from all module providers
  • DEV_USER_ID Removal: All controllers now use @CurrentUser(); DEV_USER_ID is fully removed
  • Ingestion/retrieval services: userId is now strictly required with no fallback
  • query.controller.ts + query-stream: pass user.sub down to retrieval
  • eval/seed.ts: switched to local EVAL_USER_ID constant instead of importing deleted constant
  • Tests:
    • auth-guard.e2e-spec.ts: JWT-based e2e test with route audit comment block
    • query.controller.spec.ts: updated for @CurrentUser() parameter signature
  • Config: JWT_SECRET added to AppModule config validation schema (min 32 chars required)

- Add argon2, @nestjs/jwt, @nestjs/passport auth dependencies
- Migrate User table: add passwordHash, rename to users, drop name
- AuthModule: AuthService (register/login with argon2), AuthController (POST /v1/auth/register, POST /v1/auth/login, both @public())
- JwtStrategy (passport-jwt), JwtAuthGuard (global APP_GUARD)
- @public() and @currentuser() param decorators
- Replace per-controller AuthGuard with global JwtAuthGuard
- Remove AuthGuard from all module providers
- All controllers use @currentuser() — DEV_USER_ID fully removed
- Ingestion/retrieval services: userId now required, no fallback
- query.controller.ts + query-stream: pass user.sub to retrieval
- eval/seed.ts: local EVAL_USER_ID constant instead of importing constant
- auth-guard.e2e-spec.ts: JWT-based test with route audit comment block
- query.controller.spec.ts: updated for @currentuser() param signature
- JWT_SECRET added to AppModule config validation (min 32 chars)
…ation tests

- Move env vars to module level before AppModule import so @nestjs/config v4
  forRoot() validation passes with DATABASE_URL, JWT_SECRET, etc.
- Replace full AppModule with minimal TestIntegrationModule (PrismaModule +
  RetrievalService + stub embedding provider) — avoids booting BullMQ, Auth,
  Queues, and the real Gemini embedding API
- Add pool.on('error', ...) listener in PrismaService to suppress pg-pool's
  unhandled 57P01 exceptions during container teardown
- Set ownership.integration env vars at module level too
@typescript-eslint/require-await flags embed() since it has no await.
Switch to Promise.resolve() instead.
In Next.js 16, cookies() returns a Promise. The old code called
.get() directly on the unresolved Promise, which threw. The catch
block silently returned { token: null }, causing initClientToken()
to cache null. Every API call then sent no Authorization header,
got 401, and the 401 handler called /api/auth/logout + redirect —
making any newly-registered user appear to be immediately signed out.
Root cause: Next.js 16 client-side router.push() could lose the freshly-set
auth_token cookie during navigation. Combined with a race where useQuery
fires listDocuments() during render before useEffect-based token init
completes, apiFetch sent requests without auth headers, triggering 401 →
cookie clear → redirect to login.

Changes:
- window.location.href instead of router.push() after auth to force a
  full page navigation that carries the cookie
- Module-level eager token fetch (before React render) so initPromise is
  set by the time useQuery fires during render
- Shared initPromise so getAuthToken() awaits in-progress init instead
  of returning null
handleLogout only called setLoading(false) in the catch block. After a
successful logout the button stayed stuck at "Logging out..." until the
auth-check useEffect re-ran and hid the button entirely.
@codedsultan
codedsultan merged commit 837ba88 into main Jul 25, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant