Skip to content

[1b] Google OAuth sign-in + JWT session#23

Open
andrmaz wants to merge 4 commits into
developfrom
cursor/google-oauth-jwt-session-7970
Open

[1b] Google OAuth sign-in + JWT session#23
andrmaz wants to merge 4 commits into
developfrom
cursor/google-oauth-jwt-session-7970

Conversation

@andrmaz

@andrmaz andrmaz commented Jun 13, 2026

Copy link
Copy Markdown
Owner

What was built

Implements the Google OAuth sign-in flow and JWT-based session management for the Cortex API.

Changes

New: apps/api/src/auth/

  • auth.types.ts — shared types: GoogleProfile, JwtPayload, AuthenticatedUser
  • auth.service.tsissueToken, verifyToken, decodeToken (wraps @nestjs/jwt)
  • auth.controller.ts — two controllers:
    • AuthController: GET /auth/google (OAuth redirect) and GET /auth/google/callback (upsert user, return JWT)
    • MeController: GET /api/me (returns authenticated user identity, protected by JwtAuthGuard)
  • user.service.tsfindByGoogleSub, findById, findOrCreate (first-time provisioning via PrismaService)
  • auth.module.ts — wires PassportModule, JwtModule (8 h expiry), strategies, guards, services
  • strategies/google.strategy.tspassport-google-oauth20 strategy; requires a verified email address
  • strategies/jwt.strategy.tspassport-jwt Bearer-token strategy; validates payload fields; fails fast when JWT_SECRET is absent
  • guards/jwt-auth.guard.ts — NestJS guard; throws stable public UnauthorizedException, logs raw error server-side
  • guards/google-auth.guard.ts — thin wrapper around AuthGuard("google")

New: apps/api/src/prisma/

  • prisma.service.tsPrismaClient wrapper with OnModuleInit/OnModuleDestroy lifecycle hooks
  • prisma.module.ts@Global() module so PrismaService is available everywhere

Unit tests (53 total, all passing)

  • auth.service.spec.ts — 10 tests: token issuance, verification, decode
  • strategies/jwt.strategy.spec.ts — 5 tests: payload validation + constructor throws when JWT_SECRET missing
  • user.service.spec.ts — 9 tests: existing user fast-path, create with org, reject no-org, bad email format (no PII), domain extraction, P2002 race recovery via upsert, non-P2002 error re-throw

Security hardening (CodeRabbit round 1)

  • JWT_SECRET fail-fast: JwtModule.registerAsync and JwtStrategy constructor both throw at startup when JWT_SECRET is unset
  • Stable public error: JwtAuthGuard always emits "Missing or invalid authentication token"; raw errors logged server-side via Logger
  • Verified email only: GoogleStrategy requires emails[].verified === true
  • FK-safe org resolution: UserService.findOrCreate throws UnauthorizedException when no org matches

Atomicity + correctness (CodeRabbit round 2)

  • Race-safe findOrCreate: prisma.user.create followed by prisma.user.upsert({ update: {} }) on P2002 — concurrent OAuth callbacks for the same googleSub are handled without duplicate-insert failures
  • BadRequestException for malformed email: replaces raw Error; message contains no PII
  • Organization.name @unique: schema constraint added so domain lookup uses findUnique (unambiguous, no multi-row hazard); migration 0002_organization_name_unique included

Supporting changes

  • packages/db/prisma/schema.prismaOrganization.name @unique
  • packages/db/prisma/migrations/0002_organization_name_unique/CREATE UNIQUE INDEX migration
  • apps/api/src/__mocks__/db-client.mock.ts — added user.upsert and organization.findUnique
  • app.module.ts — imports PrismaModule and AuthModule
  • turbo.jsonglobalEnv for auth env vars
  • .env.example — documents required environment variables

Acceptance criteria

  • A user can complete Google SSO and land on a success state (GET /auth/google/callback returns { accessToken })
  • A valid JWT/session token is issued and verifiable by the middleware (JwtStrategy + JwtAuthGuard)
  • /api/me returns authenticated user identity
  • Invalid or expired tokens are rejected with a structured error response (UnauthorizedException)
  • Unit tests cover token issuance and validation paths (53 tests, all passing)

Environment variables required

Set these before running the API (see .env.example):

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=http://localhost:4000/auth/google/callback
JWT_SECRET=
Open in Web Open in Cursor 

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented Google OAuth sign-in capability for user authentication
    • Added JWT-based token authentication for protected API endpoints
    • New user profile endpoint to retrieve authenticated user information
  • Chores

    • Added environment configuration templates for authentication and database setup

- Add AuthModule with GoogleStrategy (passport-google-oauth20) and
  JwtStrategy (passport-jwt) for two-factor identity verification
- Add AuthService with issueToken / verifyToken / decodeToken methods
- Add JwtAuthGuard that rejects missing/expired tokens with a structured
  UnauthorizedException
- Add AuthController: GET /auth/google (OAuth redirect) and
  GET /auth/google/callback (upsert user, issue JWT)
- Add MeController: GET /api/me (returns authenticated user identity)
- Add UserService for DB-backed user lookup / first-time provisioning
- Add PrismaService + PrismaModule (global) wrapping the generated client
- Declare JWT_SECRET, GOOGLE_* env vars in turbo.json globalEnv
- Add .env.example documenting required environment variables
- Add tsconfig paths for db/client so TS resolves the Prisma generated
  client from within the API workspace
- Write 14 unit tests covering token issuance, verification, expiry,
  tampering, and JWT strategy payload validation (all passing)
- Update app.module.spec.ts to mock GoogleStrategy + PrismaService so
  the existing integration tests remain green without real credentials

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cursor[bot], we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 13 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a399743d-ecf2-478c-98a0-b2a4f1fa36ca

📥 Commits

Reviewing files that changed from the base of the PR and between db54b8d and 5259073.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • apps/api/src/__mocks__/db-client.mock.ts
  • apps/api/src/auth/user.service.spec.ts
  • apps/api/src/auth/user.service.ts
  • packages/db/prisma/migrations/0002_organization_name_unique/migration.sql
  • packages/db/prisma/schema.prisma
📝 Walkthrough

Walkthrough

This PR implements a complete Google OAuth and JWT authentication system for the NestJS API. It adds authentication controllers for login and user retrieval, Passport strategies for Google OAuth and JWT validation, route-protecting guards, database user provisioning with organization linking, token issuance/verification services, and updates the application module to wire everything together.

Changes

Authentication system with Google OAuth and JWT

Layer / File(s) Summary
Authentication type contracts
apps/api/src/auth/auth.types.ts
Defines GoogleProfile, JwtPayload, and AuthenticatedUser interfaces used throughout the authentication system.
Prisma database integration
apps/api/src/prisma/prisma.service.ts, apps/api/src/prisma/prisma.module.ts, apps/api/src/__mocks__/db-client.mock.ts, apps/api/jest.config.js, apps/api/tsconfig.json
Establishes PrismaService lifecycle management, exports it via a global PrismaModule, sets up Jest mocks with moduleNameMapper, and adds TypeScript path aliases for db/client and db.
JWT token service and tests
apps/api/src/auth/auth.service.ts, apps/api/src/auth/auth.service.spec.ts
Implements AuthService with issueToken, verifyToken, and decodeToken methods; tests verify correct JWT encoding, expiration handling, signature validation, and decoding without verification.
User provisioning service
apps/api/src/auth/user.service.ts, apps/api/src/auth/user.service.spec.ts
Implements UserService with findOrCreate that validates email format, looks up organizations by domain, creates users with role: "member", and rejects unrecognized domains; tests cover existing users, creation, validation errors, and organization lookup.
Passport authentication strategies
apps/api/src/auth/strategies/google.strategy.ts, apps/api/src/auth/strategies/jwt.strategy.ts, apps/api/src/auth/strategies/jwt.strategy.spec.ts
Implements GoogleStrategy with verified email enforcement and JwtStrategy with required JWT_SECRET environment variable; tests verify JWT payload validation and constructor enforcement.
Route protection guards
apps/api/src/auth/guards/google-auth.guard.ts, apps/api/src/auth/guards/jwt-auth.guard.ts
Adds GoogleAuthGuard for OAuth flows and JwtAuthGuard with error handling that logs failures and throws UnauthorizedException for missing/invalid tokens.
Authentication endpoints and routes
apps/api/src/auth/auth.controller.ts
Implements AuthController with GET /auth/google (login initiation) and GET /auth/google/callback (user provisioning and token issuance); implements MeController with GET /api/me returning the authenticated user.
Auth module configuration and app integration
apps/api/src/auth/auth.module.ts, apps/api/src/app.module.ts
Configures AuthModule with PassportModule defaulting to "jwt", JwtModule.registerAsync requiring JWT_SECRET with 8h expiration, and providers for AuthService, UserService, GoogleStrategy, JwtStrategy; updates AppModule to import AuthModule and PrismaModule.
Test mocking, environment, and configuration
apps/api/src/app.module.spec.ts, .env.example, apps/api/package.json, turbo.json
Updates app.module.spec.ts to mock GoogleStrategy and PrismaService and set JWT_SECRET per test; adds .env.example with database and OAuth placeholders; adds @nestjs/jwt, Passport, and JSON Web Token packages; declares auth env vars in Turbo's globalEnv.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops of joy! Auth is here,
With Google logins, tokens clear,
JWTs and guards so tight,
Users provisioned just right! 🔐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title '[1b] Google OAuth sign-in + JWT session' accurately summarizes the main changes: implementing Google OAuth authentication and JWT-based session management, which are the primary features added across the auth domain and supporting infrastructure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/google-oauth-jwt-session-7970

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.

@cursor cursor Bot mentioned this pull request Jun 13, 2026
5 tasks
@andrmaz andrmaz changed the base branch from main to develop June 13, 2026 10:27
@andrmaz andrmaz self-assigned this Jun 13, 2026
@andrmaz andrmaz added the enhancement New feature or request label Jun 13, 2026
@andrmaz andrmaz linked an issue Jun 13, 2026 that may be closed by this pull request
5 tasks
@andrmaz andrmaz marked this pull request as ready for review June 13, 2026 10:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/auth/auth.controller.ts`:
- Around line 51-60: The code issues JWTs before ensuring dbUser.organizationId
is truthy, causing tokens that jwt.strategy.ts will immediately reject; update
the flow in auth.controller.ts so you only build AuthenticatedUser and call
this.authService.issueToken(...) after confirming a valid organizationId: check
dbUser.organizationId after this.userService.findOrCreate({ googleSub, email });
if it's falsy either (a) await a new/existing userService method to assign or
ensure an organization (e.g., ensureOrganizationAssigned(dbUser) or
assignDefaultOrganization(dbUser)) and refresh dbUser, or (b) reject the auth
callback / throw a clear error so no token is minted; in short, move the
AuthenticatedUser construction and issueToken call to occur only after
organization assignment/verification (or return an explicit failure) so tokens
are never self-invalidating.

In `@apps/api/src/auth/auth.module.ts`:
- Around line 14-15: The code currently falls back to a hard-coded JWT secret;
update the JwtModule registration (the secret property used in
JwtModule.register / JwtModule.registerAsync inside AuthModule) to remove the
"changeme-dev-secret" fallback and instead read process.env.JWT_SECRET and fail
fast when it's missing (e.g., throw a clear Error at startup or assert the env
var before passing it to JwtModule). Concretely: replace secret:
process.env["JWT_SECRET"] ?? "changeme-dev-secret" with logic that reads
process.env.JWT_SECRET, throws an Error("JWT_SECRET environment variable is
required") if falsy, and returns the value so the app will not start without a
configured secret.

In `@apps/api/src/auth/guards/jwt-auth.guard.ts`:
- Around line 14-18: handleRequest in the JwtAuthGuard currently exposes
err?.message in the UnauthorizedException; change it to always throw a stable
public message (e.g. new UnauthorizedException("Missing or invalid
authentication token")) and do NOT include err.message in the exception; instead
log the underlying error server-side—import and use Nest's Logger (or an
existing logger) inside handleRequest (e.g. Logger.error(err?.message, err) or
logger.error(err?.stack || err?.message)) so the raw verifier/passport details
are recorded internally but never sent to the client.

In `@apps/api/src/auth/strategies/google.strategy.ts`:
- Around line 27-33: The code currently accepts profile.emails?.[0]?.value
without ensuring the email is verified; update the Google strategy callback to
look for a verified email (check profile.emails array for an entry where .value
exists and .verified is true, not just taking index 0) and only call done(null,
user) with that verified address; if no verified email is found, call done(new
Error("No verified email returned from Google"), undefined) to prevent
provisioning. Ensure you reference the same profile.emails and done symbols so
UserService.findOrCreate never receives an unverified email.

In `@apps/api/src/auth/strategies/jwt.strategy.ts`:
- Around line 17-18: The JWT validation in JwtStrategy currently rejects falsy
organizationId which conflicts with UserService provisioning that sets
organizationId to an empty string; update the JwtStrategy.validate (or
equivalent) check to only fail when payload.organizationId is strictly undefined
or null (allowing an empty string) OR change UserService to provision a
non-empty organizationId so the existing check remains valid—pick one consistent
contract and apply it across JwtStrategy.validate and UserService
(user.service.ts) so token issuance and validation agree.
- Around line 12-13: The code currently falls back to a predictable secret
("changeme-dev-secret") via process.env["JWT_SECRET"] ?? "changeme-dev-secret",
which must be removed; instead ensure the JWT secret is required at startup by
throwing a clear error or failing fast when process.env["JWT_SECRET"] is
missing, and wire secretOrKey to that required value (or to a secure config
provider), e.g. compute const jwtSecret = process.env["JWT_SECRET"]; if
(!jwtSecret) throw new Error("JWT_SECRET must be set"); then set secretOrKey:
jwtSecret so the Jwt strategy uses a non-predictable secret.

In `@apps/api/src/auth/user.service.ts`:
- Line 38: The current domain extraction const domain =
input.email.split("@")[1] ?? "" can silently produce an empty domain for
malformed emails; update the code in the user service where input.email is
processed to validate the email first (e.g., ensure input.email is a non-empty
string containing exactly one "@" and both local and domain parts are non-empty,
or run a simple regex), and if invalid fail-fast by throwing a clear error or
returning a validation failure before using domain; replace the direct split
with a guarded extraction (check input.email.includes("@") or use email.match)
and only set domain when the validation passes.
- Around line 43-50: The code in create user flow sets organizationId: org?.id
?? "" which will violate the required FK when no organization is found; in
apps/api/src/auth/user.service.ts (look for the prisma.user.create call and the
org variable from findFirst) change the behavior so you do not insert an empty
string: check if org is missing and either (A) reject the request by throwing a
clear error (e.g., throw new Error('No organization found for email domain') or
a domain-specific HTTP error) before calling prisma.user.create and keep
organizationId set to org.id, or (B) implement an explicit fallback (create/use
a real “Unassigned” organization and use its id) or (C) change the schema to
make User.organizationId nullable and handle null downstream — pick one of these
approaches and update the prisma.user.create invocation to pass a valid
organizationId (org.id) instead of an empty string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d67b0968-c591-4249-98b5-3553a25ba8c6

📥 Commits

Reviewing files that changed from the base of the PR and between 6a78db7 and 0633af5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • .env.example
  • apps/api/jest.config.js
  • apps/api/package.json
  • apps/api/src/__mocks__/db-client.mock.ts
  • apps/api/src/app.module.spec.ts
  • apps/api/src/app.module.ts
  • apps/api/src/auth/auth.controller.ts
  • apps/api/src/auth/auth.module.ts
  • apps/api/src/auth/auth.service.spec.ts
  • apps/api/src/auth/auth.service.ts
  • apps/api/src/auth/auth.types.ts
  • apps/api/src/auth/guards/google-auth.guard.ts
  • apps/api/src/auth/guards/jwt-auth.guard.ts
  • apps/api/src/auth/strategies/google.strategy.ts
  • apps/api/src/auth/strategies/jwt.strategy.spec.ts
  • apps/api/src/auth/strategies/jwt.strategy.ts
  • apps/api/src/auth/user.service.ts
  • apps/api/src/prisma/prisma.module.ts
  • apps/api/src/prisma/prisma.service.ts
  • apps/api/tsconfig.json
  • turbo.json

Comment thread apps/api/src/auth/auth.controller.ts
Comment thread apps/api/src/auth/auth.module.ts Outdated
Comment thread apps/api/src/auth/guards/jwt-auth.guard.ts Outdated
Comment thread apps/api/src/auth/strategies/google.strategy.ts Outdated
Comment thread apps/api/src/auth/strategies/jwt.strategy.ts Outdated
Comment thread apps/api/src/auth/strategies/jwt.strategy.ts
Comment thread apps/api/src/auth/user.service.ts Outdated
Comment thread apps/api/src/auth/user.service.ts Outdated
@andrmaz

andrmaz commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

@cursoragent Fix all conversations

@cursor

cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

All 8 CodeRabbit review issues have been resolved. Here's a summary of what was fixed:

Security hardening:

  • JWT_SECRET fail-fast — both JwtModule.registerAsync (in auth.module.ts) and the JwtStrategy constructor now throw Error("JWT_SECRET environment variable is required") at startup; the "changeme-dev-secret" fallback is gone entirely
  • JwtAuthGuard now emits a stable public message ("Missing or invalid authentication token") and logs the raw passport/verifier error via NestJS Logger so internal details never reach clients
  • GoogleStrategy now requires emails[].verified === true; unverified email addresses cause an auth error

Contract correctness:

  • UserService.findOrCreate validates email format (throws Error for missing or leading @) and throws UnauthorizedException when no Organization is provisioned for the email domain — eliminating the empty-string organizationId sentinel that would have caused FK constraint failures and self-invalidating JWTs
  • The organizationId check in JwtStrategy.validate and UserService.findOrCreate are now fully consistent: a JWT is only ever issued after a valid org is confirmed

Tests: Added user.service.spec.ts (6 new tests) and a constructor-throws test in jwt.strategy.spec.ts. Total: 50 tests, all passing.

View PR

Open in Web Open in Cursor 

…ixes

- jwt-auth.guard.ts: replace err?.message with a stable public message
  ("Missing or invalid authentication token") and log the raw error
  server-side with NestJS Logger to prevent internal detail leakage

- auth.module.ts: switch to JwtModule.registerAsync; throw at startup
  when JWT_SECRET is absent instead of falling back to a hard-coded value

- jwt.strategy.ts: read JWT_SECRET eagerly in constructor and throw when
  it is missing; remove the "changeme-dev-secret" fallback

- google.strategy.ts: require a verified email (emails[].verified===true)
  before forwarding the profile; unverified addresses are now rejected

- user.service.ts:
  * validate email format (throw if no '@' or empty local-part)
  * throw UnauthorizedException when no Organization is provisioned for the
    email domain — eliminates the empty-string organizationId that would
    cause FK constraint failures and self-invalidating JWTs
  * remove the org?.id ?? "" sentinel that masked missing org assignment

- auth.controller.ts: update jsdoc to reflect that JWT issuance is now
  guarded by the service-level org check

- app.module.spec.ts: set JWT_SECRET in beforeAll so JwtModule.registerAsync
  and JwtStrategy can initialise without a real secret in tests

- jwt.strategy.spec.ts: add test asserting constructor throws when
  JWT_SECRET is unset

- user.service.spec.ts: new test suite (6 tests) covering findOrCreate —
  returns existing user, creates with valid org, rejects missing org,
  rejects malformed email (no '@', leading '@'), correct domain extraction

All 50 tests pass.

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/auth/user.service.ts`:
- Around line 45-47: The code uses prisma.organization.findFirst({ where: {
name: domain } }) which is ambiguous because Organization.name is not unique;
update the Prisma schema to add a uniqueness constraint on Organization.name
(add `@unique` to the name field) and run a migration, then replace the lookup
call in the user service (the this.prisma.organization.findFirst call) with a
unique lookup such as this.prisma.organization.findUnique({ where: { name:
domain } }) or this.prisma.organization.findUniqueOrThrow({ where: { name:
domain } }) and adjust error handling for the not-found case accordingly.
- Around line 31-34: The check-then-create in findOrCreate is racy: remove the
separate pre-read and make creation atomic by using an upsert or by doing a
guarded create-with-recovery; specifically, replace the pattern that calls
findByGoogleSub then create with either (a) a single upsert call that returns
the existing-or-new row, or (b) attempt create(...) inside findOrCreate and
catch the unique-constraint error and on that error call findByGoogleSub(...) to
return the already-created user; update symbols: findOrCreate, findByGoogleSub,
and create to follow one of these atomic approaches so concurrent requests
cannot both pass the read and trigger a failing insert.
- Around line 36-43: Replace the raw Error throws that interpolate user input
with Nest HTTP exceptions: in the code that validates input.email (the checks
using atIndex = input.email.indexOf("@") and domain = input.email.slice(...)),
throw new BadRequestException('Invalid email format') instead of new
Error(`Invalid email format: ${input.email}`); also import BadRequestException
from '`@nestjs/common`' and remove any usage of the raw email in the exception
message so no PII is exposed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b05d618-9a0c-45e9-9bc7-2f823305814e

📥 Commits

Reviewing files that changed from the base of the PR and between 0633af5 and db54b8d.

📒 Files selected for processing (9)
  • apps/api/src/app.module.spec.ts
  • apps/api/src/auth/auth.controller.ts
  • apps/api/src/auth/auth.module.ts
  • apps/api/src/auth/guards/jwt-auth.guard.ts
  • apps/api/src/auth/strategies/google.strategy.ts
  • apps/api/src/auth/strategies/jwt.strategy.spec.ts
  • apps/api/src/auth/strategies/jwt.strategy.ts
  • apps/api/src/auth/user.service.spec.ts
  • apps/api/src/auth/user.service.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/src/auth/strategies/jwt.strategy.spec.ts
  • apps/api/src/auth/guards/jwt-auth.guard.ts
  • apps/api/src/app.module.spec.ts
  • apps/api/src/auth/auth.controller.ts

Comment thread apps/api/src/auth/user.service.ts Outdated
Comment thread apps/api/src/auth/user.service.ts
Comment thread apps/api/src/auth/user.service.ts Outdated
@andrmaz

andrmaz commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

@cursoragent Fix all conversations

@cursor

cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Taking a look!

Open in Web Open in Cursor 

@cursor

cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

All three CodeRabbit findings from round 2 are resolved. Here's what was done:

Organization.name @unique (schema + migration)

  • Added @unique to Organization.name in packages/db/prisma/schema.prisma — domain-based org lookup is now unambiguous with no multi-row hazard
  • Created migration 0002_organization_name_unique with the CREATE UNIQUE INDEX statement
  • Regenerated the Prisma client so findUnique({ where: { name } }) is correctly typed

UserService.findOrCreate — three fixes in one:

  1. Atomic create-with-race-recovery: replaced findByGoogleSubcreate pattern with findUniquecreate → catch P2002upsert({ update: {} }). Concurrent OAuth callbacks for the same googleSub no longer race to a duplicate-insert failure; the second caller gets the upsert's no-op and the existing row is returned.
  2. BadRequestException instead of raw Error: malformed email now throws a NestJS HTTP exception that maps to 400; message is "Invalid email format" with no PII.
  3. findUnique for org lookup: since Organization.name is now @unique, findFirst was replaced with findUnique.

Tests: expanded user.service.spec.ts to 9 tests covering the new P2002 race recovery, non-P2002 error re-throw, PII-free exception messages, and the updated findUnique org call. All 53 tests pass.

View PR

Open in Web Open in Cursor 

cursoragent and others added 2 commits June 13, 2026 10:59
…ueness

Organization.name uniqueness (schema + migration):
- Add @unique to Organization.name in packages/db/prisma/schema.prisma so
  domain-based org lookup is unambiguous and free of multi-row races
- Add migration 0002_organization_name_unique with the corresponding
  CREATE UNIQUE INDEX statement
- Regenerate Prisma client (findUnique now accepts { where: { name } })

user.service.ts — three fixes:
1. Race-safe findOrCreate: attempt prisma.user.create; on P2002 unique-
   constraint collision (concurrent OAuth callback), fall back to
   prisma.user.upsert({ update: {} }) which atomically returns the row
   already inserted by the racing request
2. BadRequestException instead of raw Error for malformed email format;
   error message contains no PII (email value removed)
3. Replace prisma.organization.findFirst with prisma.organization.findUnique
   (correct since Organization.name is now @unique)

db-client.mock.ts:
- Add user.upsert and organization.findUnique to the Jest mock so new
  code paths are exercisable in unit tests

user.service.spec.ts:
- Update organization lookup assertions to use findUnique
- Add test: existing user skips org lookup and create/upsert entirely
- Add test: P2002 race recovery resolves via upsert
- Add test: non-P2002 DB errors are re-thrown unchanged
- Add test: BadRequestException message contains no PII
All 53 tests pass.

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>
The PR targets the develop branch but ci.yml only fired on push/PR
events targeting main, so CI was silently skipped on all feature PRs
merging into develop. Add develop to both push and pull_request branch
filters.

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>
@andrmaz

andrmaz commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

@cursoragent CI test did not run. CI test is currently on develop

@cursor

cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

CI is now fully green. The fix from the previous turn — adding develop to both the push and pull_request branch filters in .github/workflows/ci.yml — resolved the issue. All checks are passing:

  • Lint & Type Check
  • Build
  • Validate Prisma Schema
  • CodeQL
  • CodeRabbit

The root cause was that the workflow only listened for events on main, but this PR targets develop. GitHub's pull_request trigger fires based on the target (base) branch, so the CI was silently skipped. The one-line change to include develop in both filters was enough to unblock it.

View PR

Open in Web Open in Cursor 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[1b] Google OAuth sign-in + JWT session

2 participants