171 lines
8.3 KiB
Markdown
171 lines
8.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`
|
|
|
|
## Web UI (server-rendered)
|
|
|
|
The account UI is rendered by Django templates — no Node.js toolchain, no
|
|
separate frontend service, no build step.
|
|
|
|
| Route | View | Purpose |
|
|
|-------|------|---------|
|
|
| `/` | `identity.ui_views.home` | Landing page |
|
|
| `/register/` | `identity.ui_views.register` | Self-registration (reuses `RegisterSerializer`) |
|
|
| `/account/` | `identity.ui_views.account` | Profile summary and active sessions |
|
|
| `/account/profile/` | `identity.ui_views.profile` | Edit self-service profile fields |
|
|
| `/account/security/` | `identity.ui_views.security` | Email verification, TOTP MFA, recovery codes, passkeys |
|
|
| `/account/organizations/` | `identity.ui_views.organizations` | Workspaces the user belongs to + create |
|
|
| `/account/organizations/<slug>/` | `identity.ui_views.organization_detail` | Members and roles (owners/admins only) |
|
|
| `/account/developers/` | `identity.ui_views.developers` | OAuth clients; staff can register apps and rotate secrets |
|
|
| `/account/password/` | `identity.ui_views.password_change` | Change password |
|
|
| `/account/reset-password/` | `identity.ui_views.password_reset` | Request and confirm reset (`?token=`) |
|
|
| `/p/<token>/` | `passport.ui_views.public_passport` | Public share-link passport page |
|
|
| `/sso/login/` | `sso.views.SsoLoginView` | Central login (session cookie for `*.hamsoo.me`) |
|
|
| `/oauth/consent` | `oauth.views.ConsentView` | First-time authorization consent |
|
|
| `/admin/` | Django admin | Users, organizations, applications, sessions, events |
|
|
|
|
Templates live next to their apps (`apps/*/templates/`) and share
|
|
`identity/templates/base.html`. Styling is self-contained inline CSS in that
|
|
base template, so WhiteNoise/collectstatic is not on the critical path for the
|
|
account pages.
|
|
|
|
### Why no SPA
|
|
The platform is an IdP: its pages are credential entry points. Server rendering
|
|
keeps passwords and tokens out of client-side state, gives CSRF protection by
|
|
default, and removes a second deployable. Products (Hamsoo, Bermooda) keep
|
|
their own frontends and talk to the API/OIDC endpoints.
|
|
## Deployment
|
|
|
|
### Docker Compose (Production-Ready)
|
|
```yaml
|
|
services:
|
|
postgres: postgres:16-alpine
|
|
redis: redis:7-alpine
|
|
api: gunicorn (3 workers, serves API + account pages)
|
|
```
|
|
|
|
### Environment Separation
|
|
- `.env` for secrets (not committed)
|
|
- `.env.example` as template
|
|
- `DJANGO_ENV=production` enforces `DJANGO_SECRET_KEY`
|
|
|
|
### Static Files
|
|
- Django: WhiteNoise (`CompressedManifestStaticFilesStorage`)
|
|
- Account pages carry their CSS inline; `collectstatic` is only needed for the
|
|
Django admin assets.
|
|
|
|
## 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 |
|
|
| Web UI (Django) | Horizontal scale with the API workers |
|
|
|
|
## Future: Multi-Region
|
|
|
|
- Platform deployable per region with shared user namespace
|
|
- Cross-region session sync via Redis
|
|
- DNS-based routing to nearest region |