Id RFC 009 migration from zitadel

RFC-009 — Migration from Zitadel

  • *tatus:*Draft
  • *ate:*20260408
  • *uthor:*Koder Team
  • *epends on:*RFC001 through RFC008

Summary

Defines the strategy to migrate the current Zitadel-based Koder ID to the new implementation without downtime for existing users and integrated products (Flow, Talk, etc.).

Current State

  • *itadel*running on k.lin (127.0.0.1:8090 behind koder-jet)
  • *RL:*https:/d.koder.dev
  • *enants:*Koder, Crescer, Vivver (as Zitadel organizations)
  • *ntegrated products:*Koder Flow (Gitea), Koder Talk (in development)
  • *sers:*admin (root), platform-admin, and org users
  • *rotocols:*OIDC, OAuth2

Migration Strategy: Parallel Run

Phase 1: Build           Phase 2: Shadow          Phase 3: Cutover
──────────────────       ──────────────────       ──────────────────
New system in dev        New system receives       New system is
No production traffic    mirrored auth requests    primary
                         Zitadel still primary     Zitadel shutdown

Phase 1 — Build (current)

  1. Implement all microservices per RFCs 001-008
  2. Write comprehensive tests (unit, integration, e2e)
  3. Deploy on staging environment
  4. Run OIDC conformance tests

*xit criteria:*All OIDC conformance tests pass, e2e tests cover all integrated products.

Phase 2 — Data Migration & Shadow

  1. *xport from Zitadel:*
    • Users (profiles, email, status)
    • Organizations → tenants
    • OAuth2 clients (client_id, redirect URIs, scopes)
    • NOTE: password hashes cannot be exported from Zitadel (bcrypt internal)
  1. *assword migration strategy:*
    • Option A: Force password reset for all users on cutover
    • Option B: Proxy login to Zitadel during transition — on successful login, re-hash password with Argon2id in new system
    • *ecommended: Option B*— transparent to users, no disruption
  1. *hadow mode:*
    • Deploy new system alongside Zitadel
    • Gateway proxies auth requests to both systems
    • Zitadel responses are authoritative
    • New system responses are logged and compared (but not served to clients)
    • Fix discrepancies until shadow results match 100%

Phase 3 — Cutover

  1. Update DNS/proxy to route to new system
  2. Keep Zitadel running in read-only mode for 7 days (rollback safety net)
  3. Monitor error rates, auth success rates, latency
  4. After 7 days with no issues, decommission Zitadel

*utover is reversible:*if issues arise, switch DNS back to Zitadel within minutes.

Data Mapping

Zitadel → Koder ID v2

Zitadel Concept Koder ID v2 Table
Organization Tenant tenants
Human User User users
Machine User API Key api_keys
Application (OIDC) OAuth2 Client clients
Project (no direct equivalent)
Grant Consent consents
Session Session sessions
Personal Access Token API Key api_keys

Migration Script Outline

func MigrateFromZitadel(zitadelDB *sql.DB, koderID *AdminClient) error {
    // 1. Migrate organizations → tenants
    orgs, _ := exportZitadelOrgs(zitadelDB)
    for _, org := range orgs {
        koderID.CreateTenant(org.Name, org.Domain)
    }

    // 2. Migrate users (without passwords)
    users, _ := exportZitadelUsers(zitadelDB)
    for _, user := range users {
        koderID.CreateUser(user.Email, user.DisplayName, user.OrgID)
        // Password will be migrated on first login (Phase 2, Option B)
    }

    // 3. Migrate OIDC applications → OAuth2 clients
    apps, _ := exportZitadelApps(zitadelDB)
    for _, app := range apps {
        koderID.RegisterClient(app.Name, app.ClientID, app.RedirectURIs, app.Scopes)
    }

    return nil
}

Product Integration Updates

Each product needs minimal changes to point to the new OIDC endpoints:

Product Change Required
Koder Flow (Gitea) Update OIDC discovery URL (same if domain stays id.koder.dev)
Koder Talk Update OIDC config (in development, easy)
KDB Dashboard Update OIDC config

If the OIDC discovery URL remains https://id.koder.dev/.well-known/openid-configuration, most products require *ero changes*— they discover endpoints dynamically.

Rollback Plan

At any point during Phase 2 or 3:

  1. Stop new system's gateway
  2. Point DNS/proxy back to Zitadel
  3. Zitadel has all original data (read-only mode still running)
  4. Users may need to re-login (sessions won't carry over)

Timeline Estimate

Phase Duration
Phase 1 — Build Core development
Phase 2 — Shadow 2 weeks minimum
Phase 3 — Cutover 1 day + 7 days monitoring

Risks

Risk Mitigation
Password hashes not exportable Transparent re-hash on login (Option B)
OIDC spec compliance gaps Run official conformance test suite
KDB instability PostgreSQL fallback driver ready
Service discovery misconfiguration Health check endpoint catches issues early

Open Questions

  1. Should we keep Zitadel's user IDs or generate new ones? (keeping them avoids breaking external references)
  2. How to handle Zitadel's event sourcing data? (export aggregate state, not events)
  3. MFA devices — can TOTP secrets be exported from Zitadel, or do users need to re-register?

Source: ../home/koder/dev/koder/meta/docs/stack/rfcs/id-RFC-009-migration-from-zitadel.md