Skip to content

Unified Email Accounts - Allow same email across OAuth and Local providers #1120

@kaumudpa

Description

@kaumudpa

🔖 Feature description

Add support for unified email accounts where users can access their account through any authentication provider (Google, GitHub, email/password, etc.) as long as they use the same email address. This feature is controlled by a new environment variable UNIFIED_EMAIL_ACCOUNTS and is opt-in to maintain backward compatibility.

Key capabilities:

  • OAuth users (Google, GitHub, etc.) can sign in to existing email/password accounts
  • Email/password users can sign in via OAuth providers
  • OAuth-only users can add a password to their account (with email verification for security)
  • Password reset works for any account that has a password set
  • Auto-activation when OAuth provider verifies the email

🎤 Why is this feature needed ?

Problem Statement

Currently, Postiz treats each provider + email combination as a separate account. This creates friction:

  1. User registered with email/password, later tries Google OAuth: Gets redirected to sign-up page instead of logging in, even though the email is the same
  2. User registered with Google OAuth, wants to add password: Cannot add password login capability
  3. Self-hosted instances with registration disabled: OAuth users with matching emails cannot access their existing accounts

Use Cases

Use Case 1: Enterprise SSO Migration
A company using email/password wants to add Google Workspace SSO. Current behavior forces users to create new accounts or maintain two separate accounts.

Use Case 2: Flexibility for End Users
Users may prefer different login methods at different times (OAuth on mobile, password on shared computers). Unified accounts allow this seamlessly.

Use Case 3: Account Recovery
If an OAuth provider has issues, users with unified accounts can still access via password (if set).

Security Considerations

  • Email verification is required when an OAuth user adds a password (prevents account hijacking)
  • Password hash is stored in JWT token during verification flow (never exposed, uses bcrypt with salt)
  • Feature is opt-in via environment variable to prevent unexpected behavior changes

✌️ How do you aim to achieve this?

Implementation Approach

1. New Environment Variable

UNIFIED_EMAIL_ACCOUNTS=true  # Default: false (disabled)

2. Database Layer

  • New method getUserByEmailAnyProvider(email) - finds user by email regardless of provider
  • New method setPassword(id, password) - sets password for any account
  • New method setPasswordHash(id, hash) - sets pre-hashed password (for verification flow)

3. Authentication Flows Modified

Scenario Flag OFF (Original) Flag ON (New)
OAuth login, email exists as LOCAL Redirect to sign-up Log in to existing account
LOCAL registration, email exists as OAuth Error: "Email exists" Send verification email to add password
Password reset for OAuth-only user Silent fail (no email sent) Return message explaining OAuth-only account
OAuth login for unactivated LOCAL user Redirect to sign-up Auto-activate and log in

4. Secure Password Addition Flow

1. OAuth user tries to register with email/password
2. System finds existing OAuth account with same email
3. JWT created with: { id, email, passwordHash (bcrypt), type: 'add-password' }
4. Verification email sent with activation link
5. User clicks link → password saved to database
6. User can now log in via email/password OR OAuth

5. Files Modified

  • auth.service.ts - Core authentication logic with flag checks
  • auth.controller.ts - Uses service's activationRequired response
  • users.repository.ts - New database methods
  • users.service.ts - Exposed new repository methods
  • .env.example - Documentation for new variable

🔄️ Additional Information

Backward Compatibility

  • 100% backward compatible when UNIFIED_EMAIL_ACCOUNTS is not set or set to false
  • All existing flows work exactly as before
  • No database migrations required
  • No breaking changes to API contracts

Alternative Solutions Considered

  1. Automatic account linking without verification: Rejected due to security risk (anyone knowing an email could hijack OAuth accounts)

  2. Separate "link accounts" UI flow: More complex, requires frontend changes, and still needs the backend capability we're implementing

  3. Always-on unified emails: Rejected to maintain backward compatibility and let self-hosters choose their security model

Testing Recommendations

  • Test with UNIFIED_EMAIL_ACCOUNTS=false to verify no regression
  • Test with UNIFIED_EMAIL_ACCOUNTS=true:
    • OAuth → existing LOCAL account
    • LOCAL registration → existing OAuth account (verify email flow)
    • Password reset for OAuth-only user
    • Password reset for account with password
    • Auto-activation for unactivated LOCAL user via OAuth

Related

  • Useful for self-hosted instances with multiple auth providers
  • Complements existing OAuth provider support (Google, GitHub, Generic OAuth)

👀 Have you spent some time to check if this feature request has been raised before?

  • I checked and didn't find similar issue

Are you willing to submit PR?

Yes I am willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions