Skip to content

fix(security): remove all password-based authentication#861

Merged
2witstudios merged 8 commits intomasterfrom
pu/remove-password-auth
Apr 9, 2026
Merged

fix(security): remove all password-based authentication#861
2witstudios merged 8 commits intomasterfrom
pu/remove-password-auth

Conversation

@2witstudios
Copy link
Copy Markdown
Owner

@2witstudios 2witstudios commented Apr 9, 2026

Summary

  • Completely removes password auth from the entire PageSpace codebase — zero traces remain
  • Passwords are obsolete and represent a credential stuffing threat vector
  • Passkeys (WebAuthn) and magic links are already fully implemented as replacements
  • 74 files changed: 7,144 lines deleted, 99 lines added

What changed

Deleted (15 files)

  • 5 password API routes: /api/auth/login, /api/auth/signup, /api/auth/mobile/login, /api/auth/mobile/signup, /api/account/password
  • PasswordLoginForm.tsx component
  • 7 password-specific test files
  • Load test infrastructure (seed-loadtest-user.ts, auth-baseline.k6.js)

Schema & Core

  • Removed password column from users table
  • Removed 'both' from AuthProvider enum (data migration for existing provider='both' users)
  • Removed BCRYPT_COST constant, 'password_reset' verification type
  • Removed bcryptjs from all 3 package.json files

OAuth Callbacks (5 routes)

  • Removed user.password ? 'both' : '<provider>' logic from Google callback, one-tap, native + Apple callback, native

Frontend

  • On-prem signin page: PasswordLoginFormMagicLinkForm
  • Account settings: removed entire password change section
  • Admin CreateUserForm: removed password fields
  • useAuth hook: removed login() function (called deleted endpoint)

Scripts & Control Plane

  • setup-onprem-admin.ts: rewritten without bcrypt/passwords — creates admin with email/name/role only
  • Control plane admin seeder + provisioning engine: removed password generation/hashing
  • TenantProvisioningCompleteEmail: removed temporary password section

Documentation

  • Updated marketing auth docs, security sections, privacy page
  • Annotated password_change in activity enums as @deprecated (historical audit data)

Migration

  • 0094_wakeful_champions.sql: migrates provider='both' → OAuth provider, recreates enum without 'both', drops password column

Verification

All grep patterns return zero hits:

bcrypt, BCRYPT_COST, hasPassword, PasswordLoginForm, provider 'both', temporaryPassword

TypeScript compiles. Build passes.

Breaking changes

  • On-prem: Password auth removed. Magic link works if SMTP configured. Full passkey UX deferred to On-prem: implement local passkey auth to replace password login #860
  • Mobile apps: /api/auth/mobile/login and /api/auth/mobile/signup deleted — mobile needs passkey/magic link update
  • Database: Migration drops password column and removes 'both' enum value — run migration before deploying

Test plan

  • Verify OAuth login (Google, Apple) still works — provider should be set to 'google' or 'apple' (never 'both')
  • Verify magic link signin works on cloud and on-prem
  • Verify passkey registration and login works
  • Verify account settings page no longer shows password section
  • Verify admin can create users without password fields
  • Run pnpm typecheck — passes
  • Run pnpm test — verify no password-related test failures
  • Verify migration applies cleanly on staging

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Changed Features

    • Switched to passwordless auth: magic links and passkeys replace email/password signup and login.
    • Removed in-app password management and password-change flows; account settings and admin create-user UI no longer collect passwords.
    • On-prem admin setup now issues one-time magic-link sign-in.
  • Documentation

    • Docs and UI copy updated to reflect magic-link/passkey routes and session behavior.
  • Other

    • OAuth/passkey flows unified for a passwordless experience.

Completely removes password auth from the entire codebase. Passwords are
obsolete and represent a credential stuffing threat vector — passkeys
(WebAuthn) and magic links are already fully implemented as replacements.

- Delete 5 password API routes (login, signup, mobile login/signup, password change)
- Delete PasswordLoginForm component and all password-related test files
- Remove password column from users schema + 'both' from AuthProvider enum
- Remove bcryptjs dependency from all 3 packages
- Update 5 OAuth callbacks to remove 'user.password ? both : provider' logic
- Update on-prem signin to use magic link instead of password form
- Remove password change UI from account settings
- Remove password fields from admin user creation
- Rewrite setup-onprem-admin.ts and control-plane admin seeder (no passwords)
- Remove useAuth login function (called deleted /api/auth/login endpoint)
- Update marketing docs and privacy page
- Add data migration for provider='both' users → their OAuth provider
- Annotate password_change in activity enums as @deprecated (historical)

72 files changed, 99 insertions, 7144 deletions. Zero password auth traces
remain (verified via grep). On-prem passkey UX follow-up tracked in #860.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pagespace-master-plan Ready Ready Preview, Comment Apr 9, 2026 4:31am

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 17 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 17 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03eb8618-9979-4263-81f3-17c7ccf2d209

📥 Commits

Reviewing files that changed from the base of the PR and between ba5dfb1 and e5225a6.

📒 Files selected for processing (3)
  • packages/db/drizzle/0094_wakeful_champions.sql
  • packages/db/src/__tests__/migrate.test.ts
  • packages/db/src/migrate.ts
📝 Walkthrough

Walkthrough

This PR removes password-based authentication across the codebase: deletes bcrypt usage and BCRYPT constants, drops the users.password column and related tests/routes/UI, and migrates admin/setup, provisioning, and docs to passwordless magic-link/passkey flows.

Changes

Cohort / File(s) Summary
Dependency & Config
apps/control-plane/package.json, apps/web/package.json, packages/db/package.json, knip.json
Removed bcryptjs and @types/bcryptjs references; updated knip ignore list.
DB Schema & Migrations
packages/db/drizzle/0094_wakeful_champions.sql, packages/db/drizzle/meta/_journal.json, packages/db/src/schema/auth.ts
Added migration to reassign provider values and drop users.password; removed 'both' from AuthProvider enum; journal updated.
Schema Types & Auth Constants
packages/lib/src/auth/constants.ts, packages/lib/src/auth/oauth-types.ts, packages/lib/src/auth/verification-utils.ts
Removed BCRYPT_COST; removed 'both' provider from types; replaced password_reset verification type with webauthn_signup and unified token expiry default.
Auth Routes Removed / Reduced
apps/web/src/app/api/auth/login/route.ts, apps/web/src/app/api/auth/signup/route.ts, apps/web/src/app/api/auth/mobile/login/route.ts, apps/web/src/app/api/auth/mobile/signup/route.ts, apps/web/src/app/api/account/password/route.ts
Deleted password-based login/signup/mobile routes and password-change route (all bcrypt/validation/tokenVersion/revocation logic removed).
Account/Admin API Adjustments
apps/web/src/app/api/account/route.ts, apps/web/src/app/api/admin/users/create/route.ts, apps/web/src/app/api/account/__tests__/*
Removed password/hasPassword selection and response; admin-create no longer accepts/hashes password; tests updated/removed.
Seeder & Provisioning
apps/control-plane/src/services/admin-seeder.ts, apps/control-plane/src/services/provisioning-engine.ts, apps/control-plane/src/services/__tests__/*
Seeder stopped generating/hashing temporary passwords; SeedResult/provisioning email payloads no longer include temporaryPassword; tests adjusted.
Email Templates & Activity
packages/lib/src/email-templates/TenantProvisioningCompleteEmail.tsx, packages/lib/src/monitoring/activity-logger.ts
Removed temporary-password content from provisioning email; marked password_change activity deprecated.
Client UI / Hooks
apps/web/src/app/auth/signin/page.tsx, apps/web/src/app/settings/account/page.tsx, apps/web/src/components/auth/PasswordLoginForm.tsx, apps/web/src/components/admin/CreateUserForm.tsx, apps/web/src/hooks/useAuth.ts
Removed password login form and actions.login; settings/admin UIs no longer expose password fields; signin on‑prem now shows passkey/magic-link flows.
OAuth & Provider Logic
apps/web/src/app/api/auth/*/route.ts, packages/lib/src/auth/oauth-utils.ts
Provider-update logic simplified to avoid deriving 'both'; updates preserve or set OAuth provider ('google'/'apple') and no longer depend on password presence.
Tests: Fixtures & Suites
many apps/web/src/app/api/auth/__tests__/*, packages/lib/src/auth/__tests__/*, apps/control-plane/.../__tests__/*, apps/processor/...
Extensive removal of password/login/signup test suites; many test fixtures updated to omit password and assertions adjusted for passwordless behavior.
Factories, Scripts & Tools
packages/db/src/test/factories.ts, scripts/__tests__/setup.ts, scripts/setup-onprem-admin.ts, scripts/seed-loadtest-user.ts (deleted), scripts/tenant-export.ts, scripts/check-fetch-auth.js
Removed test-side bcrypt hashing and fixture passwords; on‑prem admin CLI no longer accepts --password and prints magic-link; load-test seeder deleted; tenant export stopped including password; fetch-auth checks updated.
Docs & Marketing
apps/marketing/src/app/docs/*, apps/marketing/src/app/privacy/page.tsx, apps/marketing/src/components/sections/SecurityPageSections.tsx
Docs and marketing updated from email/password to magic links/passkeys; API samples now show POST /api/auth/magic-link/send instead of signup/login.
Load Tests
tests/load/auth-baseline.k6.js
Deleted legacy k6 baseline load test that exercised password auth flows.
Misc Tests Cleanup
apps/web/src/app/api/account/__tests__/get-patch-route.test.ts, various auth callback/one-tap/google/apple test files
Removed password fields from many test fixtures and deleted or simplified tests that asserted behaviors dependent on stored passwords or provider:'both'.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ProvisioningEngine
  participant AdminSeeder
  participant DB
  participant EmailService

  Client->>ProvisioningEngine: provision(tenant, adminEmail, adminName)
  ProvisioningEngine->>AdminSeeder: seedAdmin(adminEmail, adminName)
  AdminSeeder->>DB: INSERT users (id, email, role, name)  -- no password
  DB-->>AdminSeeder: inserted / alreadyExisted result
  AdminSeeder-->>ProvisioningEngine: { email, alreadyExisted }
  ProvisioningEngine->>EmailService: sendProvisioningEmail({ email })  -- no temporaryPassword
  EmailService-->>Client: email delivered (async)
  ProvisioningEngine-->>Client: return { tenantId, email }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through code and waved goodbye,
Old bcrypt crumbs fell from my eye.
Magic links now light the way,
Passkeys hum at break of day.
The rabbit twitched — passwords away! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(security): remove all password-based authentication' accurately and clearly describes the main change in the changeset: comprehensive removal of password authentication across the codebase.

✏️ 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 pu/remove-password-auth

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.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (4)
apps/web/src/lib/auth/device-auth-helpers.ts (1)

12-12: Consider whether 'email' is the most semantically accurate default.

While 'email' is a reasonable default now that password authentication is removed (and magic links use email), you might consider alternatives like 'magic_link', 'passwordless', or 'unknown' if you want the log message to be more semantically precise when the provider is not explicitly passed.

That said, since all production callers pass the provider explicitly (per the relevant code snippets) and this value only affects logging, the current choice is acceptable. This is more of a semantic consistency consideration than a functional issue.

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

In `@apps/web/src/lib/auth/device-auth-helpers.ts` at line 12, The default
parameter value provider = 'email' in device-auth-helpers.ts is semantically
imprecise now that password auth is gone; change the default to a more accurate
token such as 'magic_link' or 'passwordless' (or 'unknown' if you prefer a
neutral sentinel) wherever the provider parameter is defined, and update any
related log messages that rely on this default (search for uses of the provider
parameter and logging in device-auth-helpers.ts) so logs reflect the new default
string; ensure callers that pass provider explicitly remain unchanged and adjust
tests or fixtures expecting 'email' as the implicit default.
packages/db/drizzle/0094_wakeful_champions.sql (1)

1-13: Hand-written migration is appropriate here, but document the edge case.

Per coding guidelines, SQL files should be generated via pnpm db:generate. However, this migration requires custom data migration logic (converting 'both' to specific providers) that Drizzle cannot auto-generate, making hand-authoring necessary.

Edge case to document: Users who have both googleId AND appleId will be assigned to 'google' (line 2 matches first). Consider adding a comment explaining this prioritization:

 -- Migrate users with provider='both' to their OAuth provider before enum change
+-- Note: Users with both googleId and appleId are assigned to 'google' (first linked provider takes priority)
 UPDATE "users" SET "provider" = 'google' WHERE "provider" = 'both' AND "googleId" IS NOT NULL;--> statement-breakpoint
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/drizzle/0094_wakeful_champions.sql` around lines 1 - 13, Add an
explicit comment in this hand-written migration documenting the edge case where
users with both googleId and appleId are assigned to 'google' due to the
ordering of the UPDATE statements (the UPDATE setting provider = 'google' runs
before the one for 'apple'); mention the prioritization rationale (or note if
this is intentional) next to the first UPDATE ("UPDATE \"users\" SET
\"provider\" = 'google'...") and optionally describe how to change handling if
different behavior is desired; also mention that this was hand-written because
Drizzle cannot auto-generate the data-migration for the AuthProvider enum
change.
apps/control-plane/src/services/__tests__/provisioning-engine.test.ts (1)

363-363: Rename stale test description (“with credentials”).

The test no longer validates credential data, so the title is outdated and can cause confusion during triage.

✏️ Suggested rename
-    test('given a new admin user, should send provisioning email with credentials', async () => {
+    test('given a new admin user, should send provisioning email', async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/control-plane/src/services/__tests__/provisioning-engine.test.ts` at
line 363, Rename the stale test title string "given a new admin user, should
send provisioning email with credentials" to reflect actual assertions (e.g.,
"given a new admin user, should send provisioning email" or "given a new admin
user, should send provisioning email to the admin") inside the test block in
provisioning-engine.test.ts so the description no longer mentions credentials
that are not asserted; update only the test name string used in the test(...)
call.
apps/control-plane/src/services/admin-seeder.ts (1)

14-17: Verify the hardcoded admin name matches organizational expectations.

The name column is hardcoded to 'Admin' (line 41). This is a minor consideration—confirm this generic name is acceptable for all tenant admin users, or if it should derive from the owner email or be configurable.

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

In `@apps/control-plane/src/services/admin-seeder.ts` around lines 14 - 17, The
admin name is hardcoded to 'Admin' in the seeding logic (update where the admin
user record is created in admin-seeder.ts); change it so the name is either
derived from the owner email (e.g., local-part or capitalized display name) or
passed in as a configurable parameter to the seeding function (e.g., add a name
param to the existing seed function or use a config value), and update any call
sites to supply that value; ensure the code that constructs the user record
replaces the literal 'Admin' with the derived or injected name while preserving
the existing SeedResult behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/api/auth/apple/native/route.ts`:
- Line 104: The code currently forces provider: 'apple' which will overwrite an
existing user's provider and can downgrade linked accounts; change the
assignment so that when an existing user object is present you preserve
user.provider and only set provider='apple' for newly created users (e.g., check
if user exists via the user variable or createOrUpdateUser/upsertUser flow and
set provider = user?.provider ?? 'apple'). Ensure any call sites that write the
provider field (the object containing provider) use this conditional logic so
existing user.provider is not overwritten.

In `@apps/web/src/app/api/auth/google/callback/route.ts`:
- Line 138: The hardcoded provider: 'google' risks overwriting an existing
user's provider; change the assignment to preserve any existing provider by
using the current user object (e.g., provider: user?.provider ?? 'google') or
merge logic that prefers user.provider when present; update the provider field
in the OAuth callback/update logic (the object containing provider, and the user
variable used to load the existing account) so existing linked-provider state is
retained.

In `@apps/web/src/app/api/auth/google/native/route.ts`:
- Line 115: The route currently unconditionally sets provider: 'google' which
overwrites an existing linked provider; change the assignment so it derives
newProvider from the existing user record instead of hardcoding 'google' (e.g.,
compute newProvider = user?.provider ?? 'google' or otherwise apply your
deterministic merge policy) and use that newProvider wherever the code currently
sets provider; update references to provider, newProvider, user (or
existingUser) in this route handler to preserve previously linked providers
rather than forcing 'google'.

In `@apps/web/src/app/api/auth/google/one-tap/route.ts`:
- Line 138: The code unconditionally sets provider: 'google' which can overwrite
an existing user's linked-provider; change the logic in the one-tap route
handler that constructs the user/create-or-update payload (the object with
provider: 'google') to compute provider from the existing user record if present
(e.g., provider = existingUser?.provider ?? 'google') and use that computed
value when calling the user upsert/update function so we preserve an
already-linked provider instead of always overwriting with 'google'.

In `@packages/db/src/schema/auth.ts`:
- Line 138: The VerificationType union in
packages/lib/src/auth/verification-utils.ts must be expanded to include
'webauthn_signup' so it matches the DB schema and usages; update the exported
type alias VerificationType to "export type VerificationType =
'email_verification' | 'magic_link' | 'webauthn_signup';" and then run
TypeScript checks to ensure callers like the code in passkey-service.ts that
call db.insert(..., type: 'webauthn_signup', ...) no longer bypass the type
system.

In `@scripts/setup-onprem-admin.ts`:
- Around line 6-7: Update the on-prem deployment docs to remove the stale
--password flag from the setup:admin example: replace the example invocation
that includes --password "SecurePassword123!" with the current passwordless
invocation for the setup:admin script (showing only --email and --name flags),
ensuring the docs reflect the CLI contract change and the exact snippet matches
the example used in scripts/setup-onprem-admin.ts.

---

Nitpick comments:
In `@apps/control-plane/src/services/__tests__/provisioning-engine.test.ts`:
- Line 363: Rename the stale test title string "given a new admin user, should
send provisioning email with credentials" to reflect actual assertions (e.g.,
"given a new admin user, should send provisioning email" or "given a new admin
user, should send provisioning email to the admin") inside the test block in
provisioning-engine.test.ts so the description no longer mentions credentials
that are not asserted; update only the test name string used in the test(...)
call.

In `@apps/control-plane/src/services/admin-seeder.ts`:
- Around line 14-17: The admin name is hardcoded to 'Admin' in the seeding logic
(update where the admin user record is created in admin-seeder.ts); change it so
the name is either derived from the owner email (e.g., local-part or capitalized
display name) or passed in as a configurable parameter to the seeding function
(e.g., add a name param to the existing seed function or use a config value),
and update any call sites to supply that value; ensure the code that constructs
the user record replaces the literal 'Admin' with the derived or injected name
while preserving the existing SeedResult behavior.

In `@apps/web/src/lib/auth/device-auth-helpers.ts`:
- Line 12: The default parameter value provider = 'email' in
device-auth-helpers.ts is semantically imprecise now that password auth is gone;
change the default to a more accurate token such as 'magic_link' or
'passwordless' (or 'unknown' if you prefer a neutral sentinel) wherever the
provider parameter is defined, and update any related log messages that rely on
this default (search for uses of the provider parameter and logging in
device-auth-helpers.ts) so logs reflect the new default string; ensure callers
that pass provider explicitly remain unchanged and adjust tests or fixtures
expecting 'email' as the implicit default.

In `@packages/db/drizzle/0094_wakeful_champions.sql`:
- Around line 1-13: Add an explicit comment in this hand-written migration
documenting the edge case where users with both googleId and appleId are
assigned to 'google' due to the ordering of the UPDATE statements (the UPDATE
setting provider = 'google' runs before the one for 'apple'); mention the
prioritization rationale (or note if this is intentional) next to the first
UPDATE ("UPDATE \"users\" SET \"provider\" = 'google'...") and optionally
describe how to change handling if different behavior is desired; also mention
that this was hand-written because Drizzle cannot auto-generate the
data-migration for the AuthProvider enum change.
🪄 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: b4130d07-ed21-4490-a319-8615d303213e

📥 Commits

Reviewing files that changed from the base of the PR and between b7671e8 and ab8dae0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (73)
  • apps/control-plane/package.json
  • apps/control-plane/src/services/__tests__/admin-seeder.test.ts
  • apps/control-plane/src/services/__tests__/provisioning-engine.test.ts
  • apps/control-plane/src/services/admin-seeder.ts
  • apps/control-plane/src/services/provisioning-engine.ts
  • apps/marketing/src/app/docs/security/authentication/page.tsx
  • apps/marketing/src/app/privacy/page.tsx
  • apps/marketing/src/components/sections/SecurityPageSections.tsx
  • apps/processor/src/services/__tests__/user-validator.test.ts
  • apps/web/package.json
  • apps/web/src/app/api/account/__tests__/get-patch-route.test.ts
  • apps/web/src/app/api/account/password/__tests__/route.test.ts
  • apps/web/src/app/api/account/password/route.ts
  • apps/web/src/app/api/account/route.ts
  • apps/web/src/app/api/admin/users/[userId]/gift-subscription/__tests__/route.security.test.ts
  • apps/web/src/app/api/admin/users/create/route.ts
  • apps/web/src/app/api/auth/__tests__/login-redirect.test.ts
  • apps/web/src/app/api/auth/__tests__/login.test.ts
  • apps/web/src/app/api/auth/__tests__/me.test.ts
  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/mobile-signup.test.ts
  • apps/web/src/app/api/auth/__tests__/signup-redirect.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
  • apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts
  • apps/web/src/app/api/auth/apple/callback/route.ts
  • apps/web/src/app/api/auth/apple/native/__tests__/route.test.ts
  • apps/web/src/app/api/auth/apple/native/route.ts
  • apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts
  • apps/web/src/app/api/auth/google/__tests__/open-redirect-protection.test.ts
  • apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts
  • apps/web/src/app/api/auth/google/callback/route.ts
  • apps/web/src/app/api/auth/google/native/__tests__/route.test.ts
  • apps/web/src/app/api/auth/google/native/route.ts
  • apps/web/src/app/api/auth/google/one-tap/__tests__/route.test.ts
  • apps/web/src/app/api/auth/google/one-tap/route.ts
  • apps/web/src/app/api/auth/login/__tests__/desktop-login.test.ts
  • apps/web/src/app/api/auth/login/route.ts
  • apps/web/src/app/api/auth/mobile/login/route.ts
  • apps/web/src/app/api/auth/mobile/signup/route.ts
  • apps/web/src/app/api/auth/signup/route.ts
  • apps/web/src/app/auth/signin/page.tsx
  • apps/web/src/app/settings/account/page.tsx
  • apps/web/src/components/admin/CreateUserForm.tsx
  • apps/web/src/components/auth/PasswordLoginForm.tsx
  • apps/web/src/components/auth/index.ts
  • apps/web/src/hooks/__tests__/useAuth.test.ts
  • apps/web/src/hooks/useAuth.ts
  • apps/web/src/lib/auth/__tests__/admin-role-version.test.ts
  • apps/web/src/lib/auth/device-auth-helpers.ts
  • knip.json
  • packages/db/drizzle/0094_wakeful_champions.sql
  • packages/db/drizzle/meta/0094_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/package.json
  • packages/db/src/schema/auth.ts
  • packages/db/src/test/factories.ts
  • packages/lib/src/auth/__tests__/constants.test.ts
  • packages/lib/src/auth/__tests__/oauth-utils-unit.test.ts
  • packages/lib/src/auth/__tests__/session-service.test.ts
  • packages/lib/src/auth/__tests__/verification-utils.test.ts
  • packages/lib/src/auth/account-lockout.ts
  • packages/lib/src/auth/constants.ts
  • packages/lib/src/auth/magic-link-service.test.ts
  • packages/lib/src/auth/oauth-types.ts
  • packages/lib/src/auth/oauth-utils.ts
  • packages/lib/src/auth/passkey-service.test.ts
  • packages/lib/src/auth/verification-utils.ts
  • packages/lib/src/email-templates/TenantProvisioningCompleteEmail.tsx
  • packages/lib/src/monitoring/activity-logger.ts
  • scripts/__tests__/setup.ts
  • scripts/seed-loadtest-user.ts
  • scripts/setup-onprem-admin.ts
  • tests/load/auth-baseline.k6.js
💤 Files with no reviewable changes (39)
  • apps/control-plane/package.json
  • packages/db/package.json
  • apps/web/src/app/api/auth/google/tests/open-redirect-protection.test.ts
  • apps/web/package.json
  • apps/web/src/app/api/admin/users/[userId]/gift-subscription/tests/route.security.test.ts
  • apps/processor/src/services/tests/user-validator.test.ts
  • apps/web/src/app/api/auth/google/tests/google-callback-redirect.test.ts
  • packages/lib/src/auth/tests/session-service.test.ts
  • packages/lib/src/auth/passkey-service.test.ts
  • apps/web/src/app/api/auth/google/callback/tests/route.test.ts
  • packages/lib/src/auth/magic-link-service.test.ts
  • packages/lib/src/auth/constants.ts
  • apps/web/src/app/api/auth/google/one-tap/tests/route.test.ts
  • packages/lib/src/auth/tests/constants.test.ts
  • apps/web/src/app/api/account/route.ts
  • apps/web/src/hooks/tests/useAuth.test.ts
  • packages/db/src/test/factories.ts
  • apps/web/src/app/api/account/tests/get-patch-route.test.ts
  • apps/web/src/app/api/auth/apple/native/tests/route.test.ts
  • apps/web/src/app/api/auth/google/native/tests/route.test.ts
  • apps/web/src/lib/auth/tests/admin-role-version.test.ts
  • scripts/seed-loadtest-user.ts
  • apps/web/src/app/api/auth/tests/login-redirect.test.ts
  • apps/web/src/app/api/account/password/tests/route.test.ts
  • apps/control-plane/src/services/provisioning-engine.ts
  • apps/web/src/app/api/auth/login/tests/desktop-login.test.ts
  • apps/web/src/app/api/auth/signup/route.ts
  • apps/web/src/app/api/account/password/route.ts
  • scripts/tests/setup.ts
  • tests/load/auth-baseline.k6.js
  • apps/web/src/app/api/auth/login/route.ts
  • apps/web/src/components/auth/PasswordLoginForm.tsx
  • apps/web/src/app/api/auth/tests/mobile-signup.test.ts
  • apps/web/src/app/api/auth/mobile/signup/route.ts
  • apps/web/src/app/api/auth/tests/signup.test.ts
  • apps/web/src/app/api/auth/tests/signup-redirect.test.ts
  • apps/web/src/app/api/auth/mobile/login/route.ts
  • apps/web/src/app/api/auth/tests/mobile-login.test.ts
  • apps/web/src/app/api/auth/tests/login.test.ts

P1 fixes:
- Add passkey login button to on-prem signin page alongside magic link,
  preventing lockout for users who registered passkeys
- Generate one-time magic link in setup-onprem-admin.ts, enabling
  initial sign-in without SMTP configuration

P2/Critical fixes:
- Remove password column from tenant-export USER_COLUMNS
- Remove password from scripts/__tests__/setup.ts SQL fixture
- Remove password from tenant-import test fixture

High:
- Update marketing API auth docs: remove deleted /api/auth/signup and
  /api/auth/login endpoints, add magic link endpoint
- Remove deleted /api/account/password from users API docs

Medium:
- Fix stale middleware comment mentioning "email/password"
- Remove dead login/signup routes from middleware public route list
- Remove dead CSRF exemptions for /api/auth/login and /api/auth/signup
- Remove deleted routes from check-fetch-auth.js

Low:
- Fix privacy page: "JWT tokens" → "opaque session tokens"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/middleware.ts (2)

31-39: ⚠️ Potential issue | 🔴 Critical

On-prem/tenant route block conflicts with new passwordless auth paths

Because Line 49 applies this prefix block first, keeping /api/auth/magic-link/ and /api/auth/passkey/ in CLOUD_ONLY_ROUTE_PREFIXES will return 404 for the exact auth routes now required after password removal.

Suggested fix
 const CLOUD_ONLY_ROUTE_PREFIXES = [
   '/api/stripe/',
   '/api/auth/google/',
   '/api/auth/apple/',
-  '/api/auth/magic-link/',
-  '/api/auth/passkey/',
   '/api/auth/signup',
   '/api/auth/mobile/signup',
 ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/middleware.ts` around lines 31 - 39, The CLOUD_ONLY_ROUTE_PREFIXES
array currently contains '/api/auth/magic-link/' and '/api/auth/passkey/' which
causes the middleware’s cloud-only prefix check to short-circuit and 404 the new
passwordless auth endpoints; update the middleware by removing those two entries
from CLOUD_ONLY_ROUTE_PREFIXES (or otherwise ensure the tenant/on-prem route
check runs before this cloud-only check) so that the passwordless routes
(handled by the auth handlers) are not blocked; verify the affected symbols
CLOUD_ONLY_ROUTE_PREFIXES and the middleware route-check logic (the function
that iterates/uses CLOUD_ONLY_ROUTE_PREFIXES) are updated accordingly and
consider normalizing prefixes without/with trailing slashes to match actual
route paths.

100-112: ⚠️ Potential issue | 🔴 Critical

Missing public bypass for login bootstrap endpoints causes 401 for signed-out users

/api/auth/login-csrf and magic-link send endpoints are part of unauthenticated sign-in initiation, but this bypass list doesn’t include them. They will hit the session-cookie check and fail for logged-out users.

Suggested fix
     if (
+      pathname.startsWith('/api/auth/login-csrf') ||
       pathname.startsWith('/api/auth/csrf') ||
+      pathname.startsWith('/api/auth/magic-link/') ||
       pathname.startsWith('/api/auth/google') ||
       pathname.startsWith('/api/auth/passkey/authenticate') ||
       pathname.startsWith('/api/auth/device/') ||
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/middleware.ts` around lines 100 - 112, The pathname bypass condition
that checks multiple public API routes currently omits unauthenticated sign-in
initiation endpoints, causing 401s for signed-out users; update the if-condition
that uses pathname.startsWith(...) to also allow '/api/auth/login-csrf' and the
magic-link send endpoint(s) (e.g. add pathname.startsWith('/api/auth/magic') or
pathname.startsWith('/api/auth/magic/send') as appropriate) so these endpoints
skip the session-cookie check and remain accessible without a session.
apps/marketing/src/app/docs/api/auth/page.tsx (1)

45-53: ⚠️ Potential issue | 🟡 Minor

Provider response docs still include removed both value

Line 52 still documents "provider": "email | google | both", but this PR removes both from the enum. Please update this response shape to avoid stale auth contract docs.

Suggested doc fix
-  "provider": "email | google | both",
+  "provider": "email | google",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/marketing/src/app/docs/api/auth/page.tsx` around lines 45 - 53, The
Response example still lists the removed enum value "both" for the provider
field; update the Response JSON example in page.tsx so "provider" only documents
"email | google" (or the concrete string values) and remove any other
occurrences of "both" in nearby auth response docs or examples (search for the
"provider" field in the Response block and the Response JSON snippet to locate
and fix).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/marketing/src/app/docs/api/auth/page.tsx`:
- Around line 18-27: The docs in page.tsx list the endpoint as POST
/api/auth/magic-link but the implemented flow actually calls POST
/api/auth/magic-link/send; update the documentation to use the exact implemented
path (replace "/api/auth/magic-link" with "/api/auth/magic-link/send") or
alternatively change the implementation to accept the shorter route—search for
the endpoint string in page.tsx and align the path and any body/response text to
match the actual handler name used by the auth flow.

---

Outside diff comments:
In `@apps/marketing/src/app/docs/api/auth/page.tsx`:
- Around line 45-53: The Response example still lists the removed enum value
"both" for the provider field; update the Response JSON example in page.tsx so
"provider" only documents "email | google" (or the concrete string values) and
remove any other occurrences of "both" in nearby auth response docs or examples
(search for the "provider" field in the Response block and the Response JSON
snippet to locate and fix).

In `@apps/web/middleware.ts`:
- Around line 31-39: The CLOUD_ONLY_ROUTE_PREFIXES array currently contains
'/api/auth/magic-link/' and '/api/auth/passkey/' which causes the middleware’s
cloud-only prefix check to short-circuit and 404 the new passwordless auth
endpoints; update the middleware by removing those two entries from
CLOUD_ONLY_ROUTE_PREFIXES (or otherwise ensure the tenant/on-prem route check
runs before this cloud-only check) so that the passwordless routes (handled by
the auth handlers) are not blocked; verify the affected symbols
CLOUD_ONLY_ROUTE_PREFIXES and the middleware route-check logic (the function
that iterates/uses CLOUD_ONLY_ROUTE_PREFIXES) are updated accordingly and
consider normalizing prefixes without/with trailing slashes to match actual
route paths.
- Around line 100-112: The pathname bypass condition that checks multiple public
API routes currently omits unauthenticated sign-in initiation endpoints, causing
401s for signed-out users; update the if-condition that uses
pathname.startsWith(...) to also allow '/api/auth/login-csrf' and the magic-link
send endpoint(s) (e.g. add pathname.startsWith('/api/auth/magic') or
pathname.startsWith('/api/auth/magic/send') as appropriate) so these endpoints
skip the session-cookie check and remain accessible without a session.
🪄 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: 81b4fb71-f596-4cd2-b7a7-822e66a45e8e

📥 Commits

Reviewing files that changed from the base of the PR and between ab8dae0 and 8a6b455.

📒 Files selected for processing (11)
  • apps/marketing/src/app/docs/api/auth/page.tsx
  • apps/marketing/src/app/docs/api/users/page.tsx
  • apps/marketing/src/app/privacy/page.tsx
  • apps/web/middleware.ts
  • apps/web/src/app/auth/signin/page.tsx
  • apps/web/src/lib/auth/auth-fetch.ts
  • scripts/__tests__/setup.ts
  • scripts/__tests__/tenant-import.test.ts
  • scripts/check-fetch-auth.js
  • scripts/setup-onprem-admin.ts
  • scripts/tenant-export.ts
💤 Files with no reviewable changes (3)
  • apps/marketing/src/app/docs/api/users/page.tsx
  • scripts/check-fetch-auth.js
  • apps/web/src/lib/auth/auth-fetch.ts
✅ Files skipped from review due to trivial changes (1)
  • scripts/tests/tenant-import.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/marketing/src/app/privacy/page.tsx
  • scripts/tests/setup.ts
  • scripts/setup-onprem-admin.ts
  • apps/web/src/app/auth/signin/page.tsx

- Split migration 0094 into two files to fix PostgreSQL enum transaction
  error (new enum values can't be used in the same transaction they're created)
- Fix provider downgrade in 4 OAuth routes: preserve existing provider
  instead of unconditionally overwriting (apple/native, google/callback,
  google/native, google/one-tap)
- Remove magic-link and passkey from CLOUD_ONLY_ROUTE_PREFIXES so on-prem
  users can actually sign in after password auth removal
- Add login-csrf and magic-link to public route bypass in middleware
- Add 'webauthn_signup' to VerificationType union
- Fix stale --password flag in on-prem deployment docs
- Fix magic-link endpoint path in marketing API docs (/send suffix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@2witstudios
Copy link
Copy Markdown
Owner Author

Addressing review feedback (a3ea4e8)

Critical middleware fixes (from CodeRabbit's outside-diff comments)

  1. CLOUD_ONLY_ROUTE_PREFIXES (middleware.ts:31-39): Removed /api/auth/magic-link/ and /api/auth/passkey/ from the block list. With password auth removed, these are now required for on-prem/tenant sign-in.

  2. Public route bypass (middleware.ts:100-112): Added /api/auth/login-csrf and /api/auth/magic-link/ to the unauthenticated bypass list so signed-out users can initiate magic-link login.

All inline review comments addressed

  • Provider downgrade (4 OAuth routes): Now preserves existing user.provider instead of unconditionally overwriting. Only 'email' accounts get upgraded to the OAuth provider.
  • VerificationType: Added 'webauthn_signup' to the union type.
  • Stale --password flag: Removed from on-prem deployment docs.
  • Magic-link endpoint path: Fixed to /api/auth/magic-link/send in marketing docs.

CI blocker fix

  • Migration 0094: Split into two migration files (0094 + 0095) to fix PostgreSQL error unsafe use of new value "apple" of enum type "AuthProvider". New enum values must be committed before use — the ALTER COLUMN ... TYPE ... USING cast now runs in a separate transaction.

- Add comment documenting edge case in migration where users with both
  googleId and appleId are assigned to 'google'
- Rename stale test description that referenced credentials

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PostgreSQL error 55P04: "unsafe use of new value 'apple' of enum type
AuthProvider — new enum values must be committed before they can be used."

Root cause: Drizzle's migrate() runs ALL pending migrations in a single
transaction, so splitting into two migration files doesn't help.

Fix: Skip the enum recreation entirely. The data migration cleans out all
'both' values, and the TypeScript schema already excludes 'both' so the
app code won't produce it. The unused 'both' value stays harmlessly in
the DB enum.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
apps/web/middleware.ts (1)

31-37: Unify OAuth path matchers between cloud-only shield and public bypass for consistency.

The CLOUD_ONLY_ROUTE_PREFIXES includes '/api/auth/google/' and '/api/auth/apple/' (with trailing slash), while the public bypass checks use pathname.startsWith('/api/auth/google') (no trailing slash) on line 102. Although no exact-path OAuth routes currently exist (only nested routes like /callback, /signin, /one-tap), the matcher inconsistency is a maintenance risk. Reuse the same prefix constants in both places to prevent future misconfigurations and keep the policy logic easier to audit.

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

In `@apps/web/middleware.ts` around lines 31 - 37, The CLOUD_ONLY_ROUTE_PREFIXES
array is inconsistent with hardcoded checks like
pathname.startsWith('/api/auth/google') — update the middleware to reuse the
same prefix constants instead of hardcoding strings: replace the individual
pathname.startsWith calls with a check that iterates over
CLOUD_ONLY_ROUTE_PREFIXES (or extract a shared AUTH prefixes constant used by
both the cloud-only shield and the public bypass) so both policy branches use
the identical prefix values; reference CLOUD_ONLY_ROUTE_PREFIXES and the
existing pathname.startsWith('/api/auth/google') checks when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/middleware.ts`:
- Around line 31-37: The CLOUD_ONLY_ROUTE_PREFIXES array is inconsistent with
hardcoded checks like pathname.startsWith('/api/auth/google') — update the
middleware to reuse the same prefix constants instead of hardcoding strings:
replace the individual pathname.startsWith calls with a check that iterates over
CLOUD_ONLY_ROUTE_PREFIXES (or extract a shared AUTH prefixes constant used by
both the cloud-only shield and the public bypass) so both policy branches use
the identical prefix values; reference CLOUD_ONLY_ROUTE_PREFIXES and the
existing pathname.startsWith('/api/auth/google') checks when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b425f0a-37a2-44fc-a13b-d94e87dfd47c

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6b455 and ba5dfb1.

📒 Files selected for processing (10)
  • apps/control-plane/src/services/__tests__/provisioning-engine.test.ts
  • apps/marketing/src/app/docs/api/auth/page.tsx
  • apps/web/middleware.ts
  • apps/web/src/app/api/auth/apple/native/route.ts
  • apps/web/src/app/api/auth/google/callback/route.ts
  • apps/web/src/app/api/auth/google/native/route.ts
  • apps/web/src/app/api/auth/google/one-tap/route.ts
  • docs/on-prem-deployment.md
  • packages/db/drizzle/0094_wakeful_champions.sql
  • packages/lib/src/auth/verification-utils.ts
💤 Files with no reviewable changes (1)
  • docs/on-prem-deployment.md
✅ Files skipped from review due to trivial changes (2)
  • apps/web/src/app/api/auth/google/callback/route.ts
  • apps/control-plane/src/services/tests/provisioning-engine.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/src/app/api/auth/google/one-tap/route.ts
  • apps/web/src/app/api/auth/google/native/route.ts
  • packages/db/drizzle/0094_wakeful_champions.sql
  • apps/marketing/src/app/docs/api/auth/page.tsx
  • packages/lib/src/auth/verification-utils.ts

Two fixes for PostgreSQL 55P04 "new enum values must be committed before use":

1. Custom migration runner: each migration now runs in its own transaction
   instead of Drizzle's default single-transaction approach. This fixes the
   issue where migration 0049 adds 'apple' to AuthProvider enum and migration
   0094 tries to use it.

2. Skip enum recreation in 0094: even with per-migration transactions, CREATE
   TYPE within a single migration creates "new" values that can't be used in
   the same transaction. Leave 'both' as harmless unused DB enum value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…runner

The migrate.ts was changed from using Drizzle's built-in migrate() to a
custom runner. Updated tests to mock readMigrationFiles, db.execute, and
db.transaction instead of the old drizzle-orm/node-postgres/migrator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@2witstudios 2witstudios merged commit 3817aab into master Apr 9, 2026
12 checks passed
@2witstudios 2witstudios deleted the pu/remove-password-auth branch April 9, 2026 05:07
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