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

172 lines
7.3 KiB
Markdown

# Architecture
## Overview
Identity Platform follows an **Adobe-style holding model**: the Platform is the central hub/reference, and products (Bermooda, Hamsoo, future apps) are sub-products that connect to it. The Platform owns identity; products own their domain data.
```
┌──────────────────────────────────────────────────────────────┐
│ IDENTITY PLATFORM │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Identity │ │ Authentication│ │ Organization │ │
│ │ (User) │ │ (Session) │ │ (Membership)│ │
│ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │
│ └────────────────┴────────────────┘ │
│ PRIVATE KEY (RS256) — NEVER LEAVES PLATFORM │
└────────────────────────────┬─────────────────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Bermooda │ │ Hamsoo │ │ Future │
│ (ERP) │ │ (Network) │ │ Apps │
└───────────┘ └───────────┘ └───────────┘
▲ ▲ ▲
│ PUBLIC KEY (verify only) │
└──────────────┴──────────────┘
```
## Data Ownership Boundary
| Data | Owner | Notes |
|------|-------|-------|
| **User ID (UUID)** | Identity Platform | Immutable, globally unique |
| **Email & Phone** | Identity Platform | Verified, used for auth |
| **Password Hash** | Identity Platform | Never exposed |
| **Authentication & Sessions** | Identity Platform | Full lifecycle |
| **Organizations** | Identity Platform | Shared across products |
| **Memberships & Roles** | Identity Platform | Owner/Admin/Member |
| **Applications (OAuth Clients)** | Identity Platform | Per-product credentials |
| **Service Credentials** | Identity Platform | Client credentials flow |
| **Security Events / Audit Log** | Identity Platform | Immutable, queryable |
| **Employee & Payroll** | Bermooda | Domain data |
| **Profile & Resume** | Hamsoo | Domain data |
| **Projects & Listings** | Product | Domain data |
**Rule:** Identity Platform never stores domain data (payroll, resumes, projects). Products never store auth data (passwords, sessions, tokens).
## Modular Monolith (Backend)
The Django backend is a **modular monolith** with 11 apps:
| App | Responsibility |
|-----|----------------|
| `common` | Base models, permissions, pagination, rate limiting, exceptions |
| `identity` | Custom User model (UUID PK), managers, serializers |
| `authentication` | Login, refresh, logout, MFA-ready, rate limiting |
| `organization` | Organization CRUD, slug generation |
| `membership` | Membership model (User↔Org), roles, statuses |
| `application` | OAuth clients (confidential/public), scopes |
| `session` | Session tracking, device info, revocation |
| `security` | SecurityEvent model, audit log |
| `oauth` | OIDC discovery, JWKS, authorization server stubs |
| `config` | Settings, URLs, WSGI/ASGI |
| `admin` | Django admin registrations |
## API Versioning
All endpoints under `/api/v1/`. Breaking changes → `/api/v2/`.
OpenAPI schema via `drf-spectacular` at:
- JSON: `/api/schema/`
- Swagger UI: `/api/docs/`
- ReDoc: `/api/redoc/`
## Key Design Decisions
### UUID Primary Keys
- User PK = UUID (immutable, unguessable)
- All references use UUID — no integer IDs exposed
### RS256 Asymmetric Signing
- Platform holds **private key** (signing tokens)
- Products hold **public key** (verifying tokens via JWKS)
- Private key **never** leaves platform — products cannot forge tokens
### Session Model
- Each login creates a `Session` record (type: browser/api/device)
- Refresh token stored hashed; rotatable; revocable
- Device fingerprinting for detection
### Organization Model
- Single `Organization` model reusable as company/team/workspace
- `Membership` links User→Org with role (owner/admin/member) and status
- Products query memberships for authorization
### Rate Limiting
- Per-user and per-IP via Django cache (Redis in prod)
- Configurable windows; brute-force triggers lockout
- Endpoint-level decorators
### Security Events
- Immutable `SecurityEvent` model
- Types: `login_success`, `login_failed`, `logout`, `token_refreshed`, `session_revoked`, `password_changed`, `mfa_changed`, `suspicious_activity`
- Severity: `low`, `medium`, `high`, `critical`
## Frontend Architecture
Next.js 14 App Router, TypeScript, Tailwind.
```
apps/web/
├── app/
│ ├── layout.tsx # Root layout, fonts, providers
│ ├── page.tsx # Landing page (all sections)
│ └── globals.css # Tailwind + custom components
├── components/
│ ├── ui/ # Button, Badge, Section primitives (CVA)
│ ├── sections/ # 10 landing sections
│ ├── navbar.tsx # Sticky, lang toggle, links
│ ├── footer.tsx
│ └── language-provider.tsx # RTL/LTR, fa/en, localStorage
└── lib/
├── utils.ts # cn() helper
├── content.ts # Products, Features, Steps, Protocols
└── i18n.ts # Full fa/en dictionary (370 lines)
```
### RTL-First, Bilingual
- Default: Persian (fa), RTL
- Toggle persists to `localStorage`
- `<html dir="rtl" lang="fa">` set by provider
- Fonts: `Inter` (Latin), `Vazirmatn` (Arabic/Persian)
### Brand Config via Env
- `NEXT_PUBLIC_SITE_NAME` used in navbar, footer, metadata
- Default fallback: "Identity Platform"
- No hardcoded brand strings
## Deployment
### Docker Compose (Production-Ready)
```yaml
services:
postgres: postgres:16-alpine
redis: redis:7-alpine
api: gunicorn (3 workers)
web: next start (standalone output)
```
### Environment Separation
- `.env` for secrets (not committed)
- `.env.example` as template
- `DJANGO_ENV=production` enforces `DJANGO_SECRET_KEY`
### Static Files
- Django: WhiteNoise (`CompressedManifestStaticFilesStorage`)
- Next.js: `output: 'standalone'` in Docker
## Scaling Considerations
| Component | Horizontal Scale |
|-----------|------------------|
| API (Django) | Stateless — add workers/containers behind LB |
| PostgreSQL | Read replicas for query-heavy workloads |
| Redis | Cluster mode for cache/sessions |
| Frontend | Static CDN + edge functions |
## Future: Multi-Region
- Platform deployable per region with shared user namespace
- Cross-region session sync via Redis
- DNS-based routing to nearest region