# OIDC Account Linking Implementation

## Overview

This feature implements secure account linking between password-based accounts and OIDC authentication. When a user attempts to log in via OIDC with an email that already exists as a password-only account, the system requires password verification before linking the accounts.

## Architecture

### Key Components

#### 1. Security Fix: `lib/accounts/user.ex`

**Change**: The `sign_in_with_oidc` action now filters by `oidc_id` instead of `email`.

```elixir
read :sign_in_with_oidc do
  argument :user_info, :map, allow_nil?: false
  argument :oauth_tokens, :map, allow_nil?: false
  prepare AshAuthentication.Strategy.OAuth2.SignInPreparation
  # SECURITY: Filter by oidc_id, NOT by email!
  filter expr(oidc_id == get_path(^arg(:user_info), [:sub]))
end
```

**Why**: Prevents OIDC users from bypassing password authentication and taking over existing accounts.

#### 2. Custom Error: `lib/accounts/user/errors/password_verification_required.ex`

Custom error raised when OIDC login conflicts with existing password account.

**Fields**:

- `user_id`: ID of the existing user
- `oidc_user_info`: OIDC user information for account linking

#### 3. Validation: `lib/accounts/user/validations/oidc_email_collision.ex`

Validates email uniqueness during OIDC registration.

**Scenarios**:

1. **User exists with matching `oidc_id`**: Allow (upsert)
2. **User exists without `oidc_id`** (password-protected OR passwordless): Raise `PasswordVerificationRequired`
   - The `LinkOidcAccountLive` will auto-link passwordless users without password prompt
   - Password-protected users must verify their password
3. **User exists with different `oidc_id`**: Hard error (cannot link multiple OIDC providers)
4. **No user exists**: Allow (new user creation)

#### 4. Account Linking Action: `lib/accounts/user.ex`

```elixir
update :link_oidc_id do
  description "Links an OIDC ID to an existing user after password verification"
  accept []
  argument :oidc_id, :string, allow_nil?: false
  argument :oidc_user_info, :map, allow_nil?: false
  # ... implementation
end
```

**Features**:

- Links `oidc_id` to existing user
- Updates email if it differs from OIDC provider
- Syncs email changes to linked member

#### 5. Controller: `lib/mv_web/controllers/auth_controller.ex`

Refactored for better complexity and maintainability.

**Key improvements**:

- Reduced cyclomatic complexity from 11 to below 9
- Better separation of concerns with helper functions
- Comprehensive documentation

**Flow**:

1. Detects `PasswordVerificationRequired` error
2. Stores OIDC info in session
3. Redirects to account linking page

#### 6. LiveView: `lib/mv_web/live/auth/link_oidc_account_live.ex`

Interactive UI for password verification and account linking.

**Flow**:

1. Retrieves OIDC info from session
2. **Auto-links passwordless users** immediately (no password prompt)
3. Displays password verification form for password-protected users
4. Verifies password using AshAuthentication
5. Links OIDC account on success
6. Redirects to complete OIDC login
7. **Logs all security-relevant events** (successful/failed linking attempts)

### Locale Persistence

**Problem**: Locale was lost on logout (session cleared).

**Solution**: Store locale in persistent cookie (1 year TTL) with security flags.

**Changes**:

- `lib/mv_web/locale_controller.ex`: Sets locale cookie with `http_only` and `secure` flags
- `lib/mv_web/router.ex`: Reads locale from cookie if session empty

**Security Features**:
- `http_only: true` - Cookie not accessible via JavaScript (XSS protection)
- `secure: true` - Cookie only transmitted over HTTPS in production
- `same_site: "Lax"` - CSRF protection

## Security Considerations

### 1. OIDC ID Matching

- **Before**: Matched by email (vulnerable to account takeover)
- **After**: Matched by `oidc_id` (secure)

### 2. Account Linking Flow

- Password verification required before linking (for password-protected users)
- Passwordless users are auto-linked immediately (secure, as they have no password)
- OIDC info stored in session (not in URL/query params)
- CSRF protection on all forms
- All linking attempts logged for audit trail

### 3. Email Updates

- Email updates from OIDC provider are applied during linking
- Email changes sync to linked member (if exists)

### 4. Error Handling

- Internal errors are logged but not exposed to users (prevents information disclosure)
- User-friendly error messages shown in UI
- Security-relevant events logged with appropriate levels:
  - `Logger.info` for successful operations
  - `Logger.warning` for failed authentication attempts
  - `Logger.error` for system errors

## Usage Examples

### Scenario 1: New OIDC User

```elixir
# User signs in with OIDC for the first time
# → New user created with oidc_id
```

### Scenario 2: Existing OIDC User

```elixir
# User with oidc_id signs in via OIDC
# → Matched by oidc_id, email updated if changed
```

### Scenario 3: Password User + OIDC Login

```elixir
# User with password account tries OIDC login
# → PasswordVerificationRequired raised
# → Redirected to /auth/link-oidc-account
# → User enters password
# → Password verified and logged
# → oidc_id linked to account
# → Successful linking logged
# → Redirected to complete OIDC login
```

### Scenario 4: Passwordless User + OIDC Login

```elixir
# User without password (invited user) tries OIDC login
# → PasswordVerificationRequired raised
# → Redirected to /auth/link-oidc-account
# → System detects passwordless user
# → oidc_id automatically linked (no password prompt)
# → Auto-linking logged
# → Redirected to complete OIDC login
```

## API

### Custom Actions

#### `link_oidc_id`

Links an OIDC ID to existing user after password verification.

**Arguments**:

- `oidc_id` (required): OIDC sub/id from provider
- `oidc_user_info` (required): Full OIDC user info map

**Returns**: Updated user with linked `oidc_id`

**Side Effects**:

- Updates email if different from OIDC provider
- Syncs email to linked member (if exists)

## References

- [AshAuthentication Documentation](https://hexdocs.pm/ash_authentication)
- [OIDC Specification](https://openid.net/specs/openid-connect-core-1_0.html)
- [Security Best Practices for Account Linking](https://cheatsheetseries.owasp.org/cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.html)
