chore: migrate user identity from auth0_sub to UUID #219

Merged
egullickson merged 10 commits from issue-206-migrate-user-identity-uuid into main 2026-02-16 20:55:41 +00:00
Owner

Summary

  • Migrate all database foreign keys from auth0_sub VARCHAR(255) to user_profiles.id UUID as the primary user identifier across 17 feature tables, admin system, and audit logs
  • Update auth plugin to set userContext.userId to UUID (from profile lookup) instead of raw JWT sub claim
  • Refactor admin system for UUID identity: admin_users gains id UUID PK + user_profile_id UUID FK, admin creation now requires existing user_profiles entry
  • Update all frontend admin UI to use user.id (UUID) instead of user.auth0Sub for API calls

Fixes #206
Fixes #211
Fixes #212
Fixes #213
Fixes #214
Fixes #215
Fixes #216
Fixes #217

Commits

Commit Milestone Description
6011888 M1: DB migration SQL 5-phase migration: add UUID columns, backfill, admin restructure, constraints, drop/rename
1321440 M2: Auth plugin + admin guard userContext.userId = UUID; admin guard queries user_profile_id
fd9d1ad M3: Admin system refactor Admin types/repo/service/controller use id + userProfileId
b418a50 M4: User profile repository getById() for UUID lookups; admin joins on user_profiles.id
3b1112a M6: Supporting code Audit log, backup, OCR, user-profile controller/service updated
754639c M7: Tests + frontend Test fixtures use UUID; frontend admin UI uses user.id

Architecture Change

Before: JWT sub ("auth0|xxx") -> userContext.userId -> WHERE user_id = auth0_sub
After:  JWT sub ("auth0|xxx") -> profile lookup -> userContext.userId (UUID) -> WHERE user_id = UUID
  • auth0_sub remains only in user_profiles table for JWT-to-profile resolution
  • All other tables reference user_profiles.id UUID via user_id FK
  • admin_users has its own id UUID PK + user_profile_id FK (not renamed to avoid ambiguity)

Test plan

  • TypeScript compiles cleanly (backend + frontend, zero errors)
  • ESLint passes (zero errors)
  • All 27 UUID-migration-affected unit tests pass
  • Run migration SQL against staging database and verify row counts
  • Verify admin login flow works end-to-end (UUID identity in admin guard)
  • Verify user profile CRUD operations with UUID userId
  • Test admin user management (tier changes, deactivate, promote) on desktop + mobile
  • Verify OCR endpoints use correct userId from userContext
  • Verify audit logs display correct user emails (JOIN on UUID)

Generated with Claude Code

## Summary - Migrate all database foreign keys from `auth0_sub VARCHAR(255)` to `user_profiles.id UUID` as the primary user identifier across 17 feature tables, admin system, and audit logs - Update auth plugin to set `userContext.userId` to UUID (from profile lookup) instead of raw JWT `sub` claim - Refactor admin system for UUID identity: `admin_users` gains `id UUID PK` + `user_profile_id UUID FK`, admin creation now requires existing `user_profiles` entry - Update all frontend admin UI to use `user.id` (UUID) instead of `user.auth0Sub` for API calls Fixes #206 Fixes #211 Fixes #212 Fixes #213 Fixes #214 Fixes #215 Fixes #216 Fixes #217 ## Commits | Commit | Milestone | Description | |--------|-----------|-------------| | `6011888` | M1: DB migration SQL | 5-phase migration: add UUID columns, backfill, admin restructure, constraints, drop/rename | | `1321440` | M2: Auth plugin + admin guard | `userContext.userId` = UUID; admin guard queries `user_profile_id` | | `fd9d1ad` | M3: Admin system refactor | Admin types/repo/service/controller use `id` + `userProfileId` | | `b418a50` | M4: User profile repository | `getById()` for UUID lookups; admin joins on `user_profiles.id` | | `3b1112a` | M6: Supporting code | Audit log, backup, OCR, user-profile controller/service updated | | `754639c` | M7: Tests + frontend | Test fixtures use UUID; frontend admin UI uses `user.id` | ## Architecture Change ``` Before: JWT sub ("auth0|xxx") -> userContext.userId -> WHERE user_id = auth0_sub After: JWT sub ("auth0|xxx") -> profile lookup -> userContext.userId (UUID) -> WHERE user_id = UUID ``` - `auth0_sub` remains only in `user_profiles` table for JWT-to-profile resolution - All other tables reference `user_profiles.id` UUID via `user_id` FK - `admin_users` has its own `id` UUID PK + `user_profile_id` FK (not renamed to avoid ambiguity) ## Test plan - [ ] TypeScript compiles cleanly (backend + frontend, zero errors) - [ ] ESLint passes (zero errors) - [ ] All 27 UUID-migration-affected unit tests pass - [ ] Run migration SQL against staging database and verify row counts - [ ] Verify admin login flow works end-to-end (UUID identity in admin guard) - [ ] Verify user profile CRUD operations with UUID userId - [ ] Test admin user management (tier changes, deactivate, promote) on desktop + mobile - [ ] Verify OCR endpoints use correct userId from userContext - [ ] Verify audit logs display correct user emails (JOIN on UUID) Generated with [Claude Code](https://claude.com/claude-code)
egullickson added 6 commits 2026-02-16 16:32:08 +00:00
Multi-phase SQL migration converting all user_id columns from
VARCHAR(255) auth0_sub to UUID referencing user_profiles.id.
Restructures admin_users with UUID PK and user_profile_id FK.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auth plugin now uses profile.id (UUID) as userContext.userId instead
of raw JWT sub. Admin guard queries admin_users by user_profile_id.
Auth0 Management API calls continue using auth0Sub from JWT.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updated user-profile.repository.ts to use UUID instead of auth0_sub:
- Added getById(id) method for UUID-based lookups
- Changed all methods (except getByAuth0Sub, getOrCreate) to accept userId (UUID) instead of auth0Sub
- Updated SQL WHERE clauses from auth0_sub to id for UUID-based queries
- Fixed cross-table joins in listAllUsers and getUserWithAdminStatus to use user_profile_id
- Updated hardDeleteUser to use UUID for all DELETE statements
- Updated auth.plugin.ts to call updateEmail and updateEmailVerified with userId (UUID)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate admin controller, routes, validation, and users controller
from auth0Sub identifiers to UUID. Admin CRUD now uses admin UUID id,
user management routes use user_profiles UUID. Clean up debug logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- audit-log: JOIN on user_profiles.id instead of auth0_sub
- backup: use userContext.userId instead of auth0Sub
- ocr: use request.userContext.userId instead of request.user.sub
- user-profile controller: use getById() with UUID instead of getOrCreateProfile()
- user-profile service: accept UUID userId for all admin-focused methods
- user-profile repository: fix admin JOIN aliases from auth0_sub to id

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chore: update test fixtures and frontend for UUID identity (refs #217)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 6m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Failing after 4m7s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 9s
754639c86d
Backend test fixtures:
- Replace auth0|xxx format with UUID in all test userId values
- Update admin tests for new id/userProfileId schema
- Add missing deletionRequestedAt/deletionScheduledFor to auth test mocks
- Fix admin integration test supertest usage (app.server)

Frontend:
- AdminUser type: auth0Sub -> id + userProfileId
- admin.api.ts: all user management methods use userId (UUID) params
- useUsers/useAdmins hooks: auth0Sub -> userId/id in mutations
- AdminUsersPage + AdminUsersMobileScreen: user.auth0Sub -> user.id
- Remove encodeURIComponent (UUIDs don't need encoding)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
egullickson added 1 commit 2026-02-16 16:56:09 +00:00
fix: handle mixed user_id formats in UUID migration backfill (refs #206)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 3m36s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 53s
Deploy to Staging / Verify Staging (pull_request) Failing after 8s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 8s
7fc80ab49f
user_preferences had rows where user_id already contained user_profiles.id
(UUID) instead of auth0_sub. Added second backfill pass matching UUID-format
values directly, and cleanup for 2 orphaned rows with no matching profile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
egullickson added 1 commit 2026-02-16 17:03:38 +00:00
fix: deduplicate user_preferences before unique constraint (refs #206)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
28165e4f4a
Users with both auth0_sub and UUID rows in user_preferences get the same
user_profile_id after backfill, causing unique constraint violation on
rename. Keep the newest row per user_profile_id.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Bug report created: #220 - documents all changes from this PR for debugging session.

Bug report created: #220 - documents all changes from this PR for debugging session.
egullickson added 1 commit 2026-02-16 17:39:14 +00:00
fix: migrate remaining controllers from Auth0 sub to UUID identity (refs #220)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m40s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 24s
Deploy to Staging / Verify Staging (pull_request) Successful in 10s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
dd3b58e061
16 controllers still used request.user.sub (Auth0 ID) instead of
request.userContext.userId (UUID) after the user_id column migration,
causing 500 errors on all authenticated endpoints including dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
egullickson added 1 commit 2026-02-16 17:50:34 +00:00
fix: replace remaining auth0_sub references with UUID identity (refs #220)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m40s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
e9093138fa
Vehicles service and subscriptions code still queried user_profiles by
auth0_sub after the UUID migration, causing 500 errors on GET /api/vehicles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
egullickson merged commit 7712ec6661 into main 2026-02-16 20:55:41 +00:00
egullickson deleted branch issue-206-migrate-user-identity-uuid 2026-02-16 20:55:42 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#219