gh_UserManager/docs/MIGRATION.md
bermooda-company 54d5891edf user
2026-08-23 23:59:14 +03:30

7.2 KiB

Migration Guide

Overview

This document describes how products (Bermooda, Hamsoo, new products) connect to the Identity Platform, and how the platform evolved from a product-coupled UserManager to an independent identity service.

From UserManager → Central Identity Platform

Phase 1: Architecture Audit (DONE)

The audit confirmed:

  1. No global roles on User: The identity.User model has NO employer/job_seeker fields. Roles are contextual via Membership.role (owner/admin/member).
  2. No product-domain models in identity layer: No Workspace, BusinessProfile, Employee, Payroll, Project, Resume tables in the identity database.
  3. No product-specific fields in JWT: The JWT contains only user_id, user_status, identity_key, and optional product_key/organization_id (context, not domain).
  4. Organization is generic: identity.Organization has no Bermooda-specific fields (like industry, hiring settings).

The project was already architected correctly — it was built as an independent platform rather than a product-coupled service.

Phase 2-5: Domain Separation (ALREADY DONE)

All product-specific concepts were never tightly coupled — the platform was designed with clear boundaries from the start. No destructive changes are needed.

Phase 6: JWT Payload (DONE)

JWT contains only identity claims:

  • Always: user_id, user_status, identity_key, exp, iat, iss, jti
  • Optional context: product_key, organization_id (if context established at login)

No product-specific roles or permissions are embedded.

Phase 7-10: Versioning, Contracts, Security, Docs (IN PROGRESS)

  • API versioning: /v1/ namespace added (parallel to /api/v1/)
  • Product model added to identity layer
  • Active Context support in JWT
  • This documentation

How a Product Integrates

Step 1: Register Product

Products register themselves with the Identity Platform:

POST /v1/products/
Authorization: Bearer <admin_token>
Content-Type: application/json

{
  "key": "bermooda",
  "name": "Bermooda",
  "description": "ERP platform for teams"
}

Response: { "id": "uuid", "key": "bermooda", "name": "Bermooda", ... }

Step 2: Register Application (OAuth2 Client)

Products register OAuth2 clients:

POST /v1/applications/
Authorization: Bearer <admin_token>
Content-Type: application/json

{
  "product_key": "bermooda",
  "name": "Bermooda Web",
  "redirect_uris": ["https://bermooda.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scopes": ["openid", "profile", "email", "org:read"]
}

Response: { "client_id": "...", "client_secret": "..." } (secret shown once)

Step 3: Authenticate Users

Products redirect users to the Identity Platform login:

https://id.example.com/login?product_key=bermooda&organization_id=<uuid>

After login, the user is redirected back with a code. The product exchanges the code for tokens:

POST /v1/auth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "auth_code_from_platform",
  "client_id": "bermooda-client-id",
  "client_secret": "bermooda-client-secret",
  "redirect_uri": "https://bermooda.example.com/callback",
  "code_verifier": "..."
}

Response: { "access_token": "...", "refresh_token": "...", ... }

Step 4: Verify JWTs

Products verify access tokens using the platform's public key:

import jwt
import requests

jwks = requests.get("https://id.example.com/.well-known/jwks.json").json()
# Cache JWKS, extract public key by kid
decoded = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],
    audience="bermooda-client-id",
    issuer="https://id.example.com",
)
# decoded = {"user_id": "uuid", "user_status": "active", "identity_key": "usr_...", ...}

Important: The JWT sub/user_id is the SAME UUID across all products. Products use this UUID as their FK to identity.User — never creating their own user record.

Step 5: Resolve User Context

Products resolve the user's identity and context:

GET /v1/identity/
Authorization: Bearer <access_token>

Response:

{
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "phone": "+989123456789",
    "status": "active",
    "email_verified": true
  },
  "memberships": [
    { "organization": "org-uuid", "role": "admin", "status": "active" }
  ],
  "organizations": [
    { "id": "org-uuid", "name": "ACME", "slug": "acme" }
  ],
  "products": [
    { "key": "bermooda", "name": "Bermooda" }
  ]
}

The product then maps organization_id (identity) to its own concept:

  • Bermooda: Workspace.objects.get(org=org_uuid)
  • Hamsoo: BusinessProfile.objects.get(user=user_uuid)

Step 6: Subscribe to Events

Products subscribe to identity events via Redis Streams:

import redis

r = redis.Redis(host="redis", port=6379, db=0)
# Create consumer group (one per product service)
r.xgroup_create("events.user", "bermooda-sync", mkstream=True)

# Consume events
while True:
    events = r.xread(
        {"events.user": "$"},
        count=10,
        block=1000,
    )
    for stream, messages in events:
        for msg_id, msg in messages:
            process_event(msg)
            r.xack("events.user", "bermooda-sync", msg_id)

Backward Compatibility

The platform maintains backward compatibility:

  • Old endpoints (/api/v1/...) remain functional — they are NOT removed
  • New endpoints (/v1/...) mirror the same functionality with improved naming
  • Old tokens remain valid until expiry
  • Existing users retain their UUIDs — no data migration needed
Old Path New Path Status
/api/v1/auth/login /v1/auth/login Both work
/api/v1/users/ /v1/users/ Both work
/api/v1/organizations/ /v1/organizations/ Both work
/api/v1/applications/ /v1/applications/ Both work
/api/v1/sessions/ /v1/sessions/ Both work
/api/v1/security/events/ /v1/security/events/ Both work
(NEW) /v1/products/ New — product catalog
(NEW) /v1/health/ New — health check

Migration Checklist

  • Architecture audit complete
  • No product-domain models in identity layer
  • No global roles on User model
  • JWT contains only identity claims
  • Organization is generic (no product-specific fields)
  • Product model added to identity layer
  • Add Active Context to admin dashboard
  • Migrate product integrations to new endpoints (gradual)
  • Deprecation timeline for old endpoints (6+ months)
  • Full test coverage of migration scenarios

Gotchas

  1. Do NOT recreate users in products: Use the identity User UUID as FK
  2. Do NOT store passwords/tokens in products: Delegate to Identity Platform
  3. Do NOT embed product roles in JWT: Read from Membership API
  4. Do NOT hardcode product names: Use product_key from Product registration
  5. Do NOT assume user data is complete: Products extend with their own profiles
  6. UUIDs are immutable: Never change a user's UUID; use identity_key for external references

Support

  • API Issues: Internal ticket system
  • Security: security@identity-platform.internal
  • Documentation: /api/docs/ (Swagger UI)