NeahNew/COURRIER_USER_MANAGEMENT.md
2026-01-01 19:05:25 +01:00

9.0 KiB

Courrier User Management with Prisma

Overview

Important: Courrier (the email system) does NOT create User records in Prisma. It only manages email account credentials (MailCredentials) for users that already exist in the database.

User Creation Flow

1. User Creation in Keycloak (Primary Source)

Users are created in Keycloak first, which is the primary authentication system:

Location: app/api/users/route.ts (POST method)

Process:

  1. User is created in Keycloak via Admin API
  2. Roles are assigned to the user
  3. User may be created in external systems:
    • Leantime (project management tool)
    • Dolibarr (if user has "Mediation" or "Expression" roles)

Key Code:

// Create user in Keycloak
const createResponse = await fetch(
  `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      username: data.username,
      enabled: true,
      emailVerified: true,
      firstName: data.firstName,
      lastName: data.lastName,
      email: data.email,
      credentials: [{ type: "password", value: data.password, temporary: false }],
    }),
  }
);

2. User Sync to Prisma Database

After creation in Keycloak, users need to be synced to the Prisma database. This happens via:

Option A: Manual Sync Script

  • scripts/sync-users.ts or scripts/sync-users.js
  • Fetches users from Keycloak API
  • Creates/updates User records in Prisma

Option B: API Endpoint

  • app/api/sync-users/route.ts (GET method)
  • Can be called to sync users programmatically

Prisma User Creation:

await prisma.user.create({
  data: {
    id: user.id, // Use the Keycloak ID as primary ID
    email: user.email,
    password: tempPassword, // Temporary password (not used for auth)
    createdAt: new Date(),
    updatedAt: new Date(),
  },
});

Important Notes:

  • The Prisma User id field uses the Keycloak user ID (UUID)
  • The password field in Prisma is not used for authentication (Keycloak handles that)
  • Users must exist in Prisma before they can use Courrier

3. Prisma Schema

User Model (prisma/schema.prisma):

model User {
  id        String    @id @default(uuid())
  email     String    @unique
  password  String
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  mailCredentials MailCredentials[]  // One-to-many relationship
  // ... other relations
}

MailCredentials Model:

model MailCredentials {
  id        String   @id @default(uuid())
  userId    String
  email     String
  password  String?  // Optional (for OAuth accounts)
  host      String
  port      Int
  secure    Boolean  @default(true)
  use_oauth Boolean  @default(false)
  refresh_token String?
  access_token String?
  token_expiry DateTime?
  smtp_host String?
  smtp_port Int?
  smtp_secure Boolean? @default(false)
  display_name String?
  color     String?  @default("#0082c9")
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([userId, email])  // One email account per user
  @@index([userId])
}

Courrier's Role: Adding Email Accounts

How Courrier Adds Email Accounts

Location: app/api/courrier/account/route.ts (POST method)

Process:

  1. Authentication Check: Verifies user session exists
  2. User Existence Check: Verifies user exists in Prisma database
  3. Connection Test: Tests IMAP connection before saving
  4. Save Credentials: Creates/updates MailCredentials record

Key Code Flow:

// 1. Check if user exists in database
const userExistsInDB = await userExists(session.user.id);
if (!userExistsInDB) {
  return NextResponse.json({
    error: 'User not found in database',
    details: `The user ID from your session (${session.user.id}) doesn't exist in the database.`
  }, { status: 400 });
}

// 2. Test connection
const testResult = await testEmailConnection(credentials);
if (!testResult.imap) {
  return NextResponse.json({
    error: `Connection test failed: ${testResult.error}`
  }, { status: 400 });
}

// 3. Save credentials
await saveUserEmailCredentials(session.user.id, email, credentials);

Saving Email Credentials

Location: lib/services/email-service.tssaveUserEmailCredentials()

Process:

  1. Prepares database credentials object (excluding OAuth tokens)
  2. Uses upsert to create or update MailCredentials
  3. Caches full credentials (including OAuth tokens) in Redis

Key Code:

// Save to database using upsert
await prisma.mailCredentials.upsert({
  where: {
    // Finds existing record by userId + email
    userId_email: {
      userId: userId,
      email: credentials.email
    }
  },
  update: dbCredentials,
  create: {
    userId,
    ...dbCredentials
  }
});

// Cache full credentials (including OAuth) in Redis
await cacheEmailCredentials(userId, accountId, fullCreds);

Important Notes:

  • OAuth tokens (access_token, refresh_token) are stored in Redis only, not in Prisma
  • The Prisma MailCredentials table stores IMAP/SMTP settings
  • The password field is optional (for OAuth accounts like Microsoft)

Microsoft OAuth Flow

Location: app/api/courrier/microsoft/callback/route.ts

For Microsoft accounts, the flow is:

  1. User authorizes via Microsoft OAuth
  2. Access token and refresh token are obtained
  3. Credentials are saved with use_oauth: true
  4. OAuth tokens are cached in Redis (not in Prisma)

Data Flow Diagram

┌─────────────┐
│  Keycloak   │  ← Primary user creation
└──────┬──────┘
       │
       │ Sync
       ↓
┌─────────────┐
│   Prisma    │  ← User record created
│    User     │
└──────┬──────┘
       │
       │ User adds email account
       ↓
┌─────────────┐
│   Prisma    │  ← MailCredentials created
│MailCredentials│
└──────┬──────┘
       │
       │ OAuth tokens (if applicable)
       ↓
┌─────────────┐
│    Redis    │  ← OAuth tokens cached
└─────────────┘

Key Files Reference

User Creation

  • app/api/users/route.ts - Creates users in Keycloak
  • scripts/sync-users.ts - Syncs users from Keycloak to Prisma
  • app/api/sync-users/route.ts - API endpoint for syncing users

Courrier Email Management

  • app/api/courrier/account/route.ts - Add/update/delete email accounts
  • lib/services/email-service.ts - Core email service functions
    • saveUserEmailCredentials() - Saves email credentials to Prisma
    • getUserEmailCredentials() - Retrieves credentials from Prisma
    • testEmailConnection() - Tests IMAP/SMTP connection

Database Schema

  • prisma/schema.prisma - Prisma schema definitions
  • lib/prisma.ts - Prisma client instance

Authentication

  • app/api/auth/options.ts - NextAuth configuration
  • lib/auth.ts - Authentication helpers

Auto-Creation of Users

As of recent updates, Courrier now automatically creates User records in Prisma if they don't exist when:

  • Adding an email account (/api/courrier/account POST)
  • Checking session status (/api/courrier/session GET)

This handles cases where:

  • The database was reset/lost but users still exist in Keycloak
  • Users were created in Keycloak but never synced to Prisma

The auto-creation uses session data from Keycloak to populate:

  • id: Keycloak user ID (UUID)
  • email: User's email from session
  • password: Temporary random password (not used for auth, Keycloak handles authentication)

Common Issues & Solutions

Issue: "User not found in database" when adding email account

Cause: User exists in Keycloak but not in Prisma database

Solution:

  • Automatic: The system now auto-creates users when needed
  • Manual: Run the sync script to create users in Prisma:
npm run sync-users
# or
node scripts/sync-users.js

Issue: Email credentials not saving

Check:

  1. User exists in Prisma: prisma.user.findUnique({ where: { id: userId } })
  2. Connection test passes before saving
  3. Unique constraint [userId, email] is not violated

Issue: OAuth tokens not persisting

Note: OAuth tokens are stored in Redis, not Prisma. Check:

  • Redis connection and TTL settings
  • Redis cache functions in lib/redis.ts

Summary

  1. Users are created in Keycloak first (via app/api/users/route.ts)
  2. Users are synced to Prisma (via sync scripts or API)
  3. Courrier adds email accounts by creating MailCredentials records linked to existing Users
  4. OAuth tokens are cached in Redis, not stored in Prisma
  5. Users must exist in Prisma before they can add email accounts via Courrier

Courrier is a credentials management system for existing users, not a user creation system.