Skip to content

Conversation

@johnny603
Copy link
Contributor

@johnny603 johnny603 commented Oct 21, 2025

User description

Description

Once a user has signed up, the users are able to sign up with different auth modes. For example, if they have signed up using Google OAuth, and now they are trying to sign in using email and OTP, we should allow them to do that. Also, we should add EMAIL_OTP as an authentication method in their profile section.

Fixes #1187 over 6 steps

Plan

  1. Where to look (locations / searches)
  • Search the repo for the single identifier to find all places that will need changing:
    • git grep -n "authProvider" || rg "authProvider" -S
  • Backend / API
    • backend/, api/, server/, services/, src/services/auth/, src/controllers/auth/, src/routes/auth/
    • user model files: pkg/models/user., models/user., src/models/user.*, prisma/schema.prisma, schema files for ORM (TypeORM, Sequelize, Prisma)
    • database migration folder: db/migrations/, migrations/, prisma/migrations/
    • signup / login handlers: controllers or handlers that call user creation/upsert
    • tests: test/, tests/, packages/*/test, integration tests touching auth
  • Auth logic and providers
    • OAuth handlers: google/github/apple provider code (src/services/oauth/, src/lib/passport/)
    • OTP/email code: src/services/email-otp/, src/lib/otp/
    • Session / token issuance: src/services/session/, src/services/jwt/
  • Schema package
    • packages/schema or @keyshade/schema — look for the user response body definition, types, OpenAPI / JSON schema files
  • Frontend / Platform
    • platform/, web/, packages/platform, src/components/Profile/, src/pages/profile, src/components/Auth/ — profile UI, account settings, linked auth providers UI
  • Docs & API references
    • docs/, docs/api/, README.md, openapi.yaml / swagger.yaml, docs/auth.md
  • CI / deployment
    • .github/workflows/**, Dockerfiles, infra/migrations scripts
  1. Backend-first plan (high level)
    A. Design decisions & compatibility
  • authProviders will be an array (string enum values like "GOOGLE", "EMAIL_OTP", "GITHUB", etc.)
  • Keep API backwards compatibility for a transition period: read old authProvider string if present; write new authProviders array on update.
  • New sign-in methods should append to authProviders rather than overwrite.
  • Ensure deduplication (no duplicates), and always canonicalize names (e.g., uppercase).

B. Tasks

  1. Add/modify DB column
    • If using Postgres: add auth_providers JSONB (or text[]). Default to [].
    • Migration example (Postgres JSONB):
      • ALTER TABLE users ADD COLUMN auth_providers JSONB NOT NULL DEFAULT '[]'::jsonb;
      • UPDATE users SET auth_providers = jsonb_build_array(auth_provider) WHERE auth_provider IS NOT NULL;
      • (optional) DROP COLUMN auth_provider; or keep it for compatibility and remove in a later release.
    • If ORM (Prisma/TypeORM/Sequelize): update model schema and generate migration.
  2. Update user model / repository layer
    • Replace single-field usage with array-field usage. Provide helper methods: addAuthProvider(provider), hasAuthProvider(provider).
    • When loading user records, if auth_providers empty and auth_provider (legacy) present, handle both.
  3. Update sign-up / sign-in flows
    • On successful signup/sign-in via a provider, append that provider to the user's auth_providers (if not present).
    • For email OTP sign-in: when they verify an email/OTP and a user exists with same email, append "EMAIL_OTP".
    • For OAuth flows: when OAuth user matches an existing user (e.g., email match), link provider rather than creating new user.
  4. Ensure security checks
    • When linking new providers, verify ownership (e.g., require OAuth verification or email ownership) to prevent account takeover.
  5. Update JWT/session payloads if they contain authProvider
    • Replace single value with array or omit if unnecessary.
  6. Update tests
    • Unit tests for user model helpers.
    • Integration tests: signup via OAuth then email OTP sign-in should link (not create duplicate).
  7. Add logging and metrics around linking events for debugging.

C. Migration plan / rollout

  • Stage 1: Add new column auth_providers, keep old auth_provider.
  • Stage 2: Update backend to read from auth_providers when present; if not present, fallback to auth_provider and load it into auth_providers on first write.
  • Stage 3: Run data-migration to populate auth_providers from auth_provider for existing users (can be done in the DB migration or an ad-hoc job).
  • Stage 4: After verifying usage and telemetry, remove legacy column in a later migration and update code accordingly.
  1. @keyshade/schema changes
  • Update user response schema to include authProviders: string[] (enum values).
  • Keep previous response backward-compatible if you need to (e.g., add authProviders while keeping authProvider during transition). If breaking change, bump the version.
  • Update TypeScript types, OpenAPI schemas, and generated clients.
  • Run tests that consume the schema package.
  1. Platform / frontend changes
  • Update profile/account settings screen to:
    • Show list of linked auth providers (authProviders array).
    • Show UI affordances for linking/unlinking providers (careful: disallow unlinking the only provider without adding another).
    • Add "Link email OTP" or "Add OTP" flows (or instructions) if desired.
  • Update login flow UI where you show sign-in options; optionally inform users that signing in via a new method will link to their existing account if emails match.
  • Update client-side types/interfaces to accept authProviders in user object.
  • Update tests for frontend components.
  1. Documentation
  • Update docs/auth.md:
    • Explain multiple auth modes are supported, what providers are available, how linking works, and security implications.
  • Update API docs:
    • User response object examples showing authProviders.
    • Note migration/version changes if API changed.
  • Add migration notes to CHANGELOG.
  • Update developer docs for database migrations and how to add new providers.
  1. QA & Testing checklist
  • Unit tests for:
    • addAuthProvider/hasAuthProvider, dedupe behavior.
    • Sign-up/sign-in flows append correctly.
  • Integration tests:
    • OAuth signup then email OTP sign-in — verify single account, authProviders contains both.
    • Email OTP signup then Google sign-in — verify linking.
    • Attempt linking provider without verifying ownership — expect rejection.
  • Manual tests:
    • Existing users (with only auth_provider) can still sign in.
    • New users get auth_providers populated.
  • Load test small subset of user migrations before mass migration.
  • Security review: ensure linking cannot be abused for account takeover.
  1. Example migration SQL (Postgres, concise)
  • Add column and backfill:
    • ALTER TABLE users ADD COLUMN auth_providers JSONB NOT NULL DEFAULT '[]'::jsonb;
    • UPDATE users SET auth_providers = CASE WHEN auth_provider IS NOT NULL THEN jsonb_build_array(auth_provider) ELSE '[]'::jsonb END;
    • (defer DROP COLUMN auth_provider until after rollout)
  1. Suggested commits / PRs
  • PR 1 (backend): Add auth_providers column + model changes + basic append logic + unit tests. (Non-breaking)
  • PR 2 (migration): Data backfill script and migration run plan.
  • PR 3 (auth flows): OAuth/email OTP linking behavior and integration tests.
  • PR 4 (schema): Update @keyshade/schema types + OpenAPI + version bump.
  • PR 5 (platform): Update profile UI + tests + docs.
  • PR 6 (docs): Update developer and API docs, CHANGELOG.
  1. PR checklist
  • DB migration + backfill included or referenced
  • Unit + integration tests added/updated
  • Schema changes in @keyshade/schema and generated clients updated
  • Frontend updated to display authProviders
  • Security checks for linking implemented
  • CHANGELOG and docs updated
  • Verified on staging and smoke tested

If you want, I can:

  • Draft the DB migration SQL for your DB/ORM (tell me which one you use: Prisma, TypeORM, Sequelize, raw Postgres).
  • Draft the model helper methods (example TypeScript/Go/Python) to add/has auth provider.
  • Draft the @keyshade/schema user response change (JSON schema / TypeScript interface).
    Which of those would you like next?

PR Type

Enhancement


Description

  • Add authProviders array field to User model for multiple auth support

  • Implement UserAuthProviderService with provider management methods

  • Update auth flow to link new providers instead of rejecting mismatches

  • Add database migration and helper functions for backward compatibility

  • Include comprehensive unit tests for provider management logic


Diagram Walkthrough

flowchart LR
  A["User Signs In"] --> B["Check Auth Provider"]
  B --> C["Provider Already Linked?"]
  C -->|Yes| D["Allow Sign In"]
  C -->|No| E["Add Provider to authProviders"]
  E --> D
  F["Legacy authProvider Field"] -.->|Fallback| G["authProviders Array"]
  G --> H["User Profile Updated"]
Loading

File Walkthrough

Relevant files
Database schema
1 files
schema.prisma
Add authProviders array field to User model                           
+1/-0     
Database migration
1 files
migration.sql
Migrate legacy authProvider to new array field                     
+9/-0     
Enhancement
4 files
user-auth-provider.service.ts
New service for managing multiple auth providers                 
+203/-0 
user.ts
Helper functions for auth provider management                       
+82/-1   
auth.service.ts
Update auth flow to link new providers dynamically             
+33/-30 
index.ts
Add authProviders array to user schema validation               
+2/-1     
Tests
2 files
user-auth-provider.service.spec.ts
Comprehensive tests for auth provider service                       
+259/-0 
user.spec.ts
Unit tests for auth provider helper functions                       
+130/-0 
Configuration changes
1 files
auth.module.ts
Register UserAuthProviderService in auth module                   
+2/-0     

…roviders (PR 1 of keyshade-xyz#1187)

- Add  array to User model while keeping legacy  for backward compatibility
- Introduce  for managing multiple providers
- Update  to link new OAuth providers to existing users
- Add helper functions and tests for provider management and migration
- Preserve CLI mode behavior and ensure existing users can still sign in
- Sets the stage for future PRs: frontend updates, full migration, and removal of legacy field
@codiumai-pr-agent-free
Copy link
Contributor

codiumai-pr-agent-free bot commented Oct 21, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟡
🎫 #1187
🟢 Update authProvider to authProviders in the user model
Append new auth providers when users sign up using a new one
Update response body in user schema
Update user profile section to list all different kinds of auth providers linked to the
account
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@codiumai-pr-agent-free
Copy link
Contributor

codiumai-pr-agent-free bot commented Oct 21, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate duplicated auth provider logic

Consolidate the duplicated authentication provider helper functions from
apps/api/src/common/user.ts into the UserAuthProviderService to eliminate
redundancy and create a single source of truth.

Examples:

apps/api/src/common/user.ts [43-60]
export function hasAuthProvider(
  user: any,
  authProvider: AuthProvider
): boolean {
  const currentProviders = user.authProviders || []

  // Check new array first
  if (currentProviders.includes(authProvider)) {
    return true
  }

 ... (clipped 8 lines)
apps/api/src/common/user-auth-provider.service.ts [93-113]
  hasAuthProvider(user: any, authProvider: AuthProvider): boolean {
    let currentProviders: AuthProvider[] = []

    try {
      currentProviders = user.authProviders || []
    } catch {
      currentProviders = []
    }

    // Check new array first

 ... (clipped 11 lines)

Solution Walkthrough:

Before:

// In apps/api/src/common/user.ts
export function hasAuthProvider(user, provider) {
  // ... logic to check authProviders and fallback to authProvider
}
export function getPrimaryAuthProvider(user) {
  // ... logic to get first provider or fallback
}
// ... and other helpers

// In apps/api/src/common/user-auth-provider.service.ts
@Injectable()
export class UserAuthProviderService {
  hasAuthProvider(user, provider) {
    // ... nearly identical logic to check providers
  }
  getPrimaryAuthProvider(user) {
    // ... nearly identical logic to get first provider
  }
  // ... and other helpers, some with DB access
}

After:

// In apps/api/src/common/user.ts
// (Helper functions are removed)

// In apps/api/src/common/user-auth-provider.service.ts
@Injectable()
export class UserAuthProviderService {
  // Centralized logic for checking providers
  hasAuthProvider(user, provider) {
    // ... single implementation
  }

  // Centralized logic for getting primary provider
  getPrimaryAuthProvider(user) {
    // ... single implementation
  }

  // Other service methods remain
  async addAuthProvider(userId, provider) {
    // ... DB update logic
  }
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies significant logic duplication for handling auth providers across user.ts and user-auth-provider.service.ts, which should be consolidated to improve maintainability.

Medium
Possible issue
Update user object after modification

Update the local user variable with the return value from
this.userAuthProviderService.addAuthProvider to prevent using a stale object.

apps/api/src/auth/service/auth.service.ts [503-516]

 try {
-  // Add the new auth provider to the user's account
-  await this.userAuthProviderService.addAuthProvider(
+  // Add the new auth provider to the user's account and update the local user object
+  user = await this.userAuthProviderService.addAuthProvider(
     user.id,
     authProvider
   )
   this.logger.log(
     `Successfully linked ${authProvider} to user ${email}'s account`
   )
 } catch (error) {
   this.logger.warn(
     `Could not link auth provider ${authProvider} to user ${email}: ${error.message}. This may be expected if the migration hasn't run yet.`
   )
 }

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the local user object becomes stale by not capturing the return value of addAuthProvider, which could lead to subtle bugs.

Low
  • Update

Comment on lines +503 to +506
try {
// Add the new auth provider to the user's account
await this.userAuthProviderService.addAuthProvider(
user.id,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Update user object after modification

Suggested change
try {
// Add the new auth provider to the user's account
await this.userAuthProviderService.addAuthProvider(
user.id,
try {
// Add the new auth provider to the user's account and update the local user object
user = await this.userAuthProviderService.addAuthProvider(
user.id,

…e PR and the user auth provider service file
@johnny603 johnny603 requested a review from rajdip-b October 23, 2025 01:59
@github-actions
Copy link
Contributor

@johnny603, please resolve all open reviews!

@github-actions
Copy link
Contributor

@johnny603, please resolve all open reviews; otherwise this PR will be closed after Tue Oct 28 2025 02:16:11 GMT+0000 (Coordinated Universal Time)!

@rajdip-b
Copy link
Member

@johnny603 could you please drop a video of this feature?

@johnny603
Copy link
Contributor Author

KeyshadeMultipleAuthModesIdea

So the feature would look something like this, I pulled this from FAFSA's 2FA page.

From profile settings you would be able to toggle on 2FA

Once you login again, you will be asked to input one of the 2FA methods you specified from the settings

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple auth modes for a user

2 participants