# 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: ```bash POST /v1/products/ Authorization: Bearer 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: ```bash POST /v1/applications/ Authorization: Bearer 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= ``` After login, the user is redirected back with a code. The product exchanges the code for tokens: ```bash 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: ```python 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: ```bash GET /v1/identity/ Authorization: Bearer ``` Response: ```json { "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: ```python 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 - [x] Architecture audit complete - [x] No product-domain models in identity layer - [x] No global roles on User model - [x] JWT contains only identity claims - [x] Organization is generic (no product-specific fields) - [x] 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)