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

518 lines
11 KiB
Markdown

# API Reference
## Base URL
Versioned identity API:
```
https://id.example.com/v1/
```
OAuth 2.0 / OIDC protocol endpoints live at the API root (not under `/v1/`):
```
https://id.example.com/oauth/authorize
https://id.example.com/oauth/token
https://id.example.com/oauth/jwks
https://id.example.com/.well-known/openid-configuration
```
## Authentication
All endpoints (except auth, oauth, discovery) require:
```
Authorization: Bearer <access_token>
```
Access tokens are JWTs signed with **HS256** (HMAC) using the platform
`JWT_SIGNING_KEY`. They are verified by a shared secret, not via JWKS — the
JWKS endpoint (`/oauth/jwks`) currently returns an empty key set.
## Pagination
List endpoints return:
```json
{
"count": 123,
"next": "https://id.example.com/api/v1/endpoint?page=2",
"previous": null,
"results": [...]
}
```
Default page size: 20. Override with `?page_size=50` (max 100).
## Filtering & Search
| Param | Example |
|-------|---------|
| `search` | `?search=john` (icontains on name/email) |
| `ordering` | `?ordering=-created_at` (prefix `-` for desc) |
| `field=value` | `?status=active` (exact match) |
## Error Format
```json
{
"detail": "Human-readable message",
"code": "ERROR_CODE",
"fields": {
"email": ["Enter a valid email."]
}
}
```
HTTP status codes: `200`, `201`, `400`, `401`, `403`, `404`, `429`, `500`.
---
## Authentication
### POST `/auth/login`
Obtain token pair.
**Request:**
```json
{
"email": "user@acme.com",
"password": "secret123"
}
```
**Response (200):**
```json
{
"access": "eyJ...",
"refresh": "eyJ...",
"token_type": "bearer",
"expires_in": 900
}
```
### POST `/auth/refresh`
Rotate access token.
**Request:**
```json
{
"refresh": "eyJ..."
}
```
**Response (200):**
```json
{
"access": "eyJ...",
"refresh": "eyJ...", // rotated
"token_type": "bearer",
"expires_in": 900
}
```
### POST `/auth/logout`
Revoke session + refresh token.
**Request:**
```json
{
"refresh": "eyJ..." // optional; if omitted, revokes current session only
}
```
**Response (204):** No content.
---
## Users
### GET `/users/me`
Current authenticated user.
**Response (200):**
```json
{
"id": "uuid",
"email": "user@acme.com",
"phone": "+989123456789",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_staff": false,
"date_joined": "2024-01-15T10:30:00Z",
"last_login": "2024-01-20T08:15:00Z",
"organizations": [
{ "id": "uuid", "name": "Acme Corp", "role": "owner" }
]
}
```
### GET `/users/{id}`
Admin only. User detail by UUID.
---
## Organizations
### GET `/organizations`
List organizations user belongs to.
**Response (200):**
```json
{
"count": 2,
"results": [
{
"id": "uuid",
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Main organization",
"is_active": true,
"created_at": "2024-01-10T12:00:00Z",
"owner": { "id": "uuid", "email": "owner@acme.com" },
"member_count": 15,
"user_role": "owner"
}
]
}
```
### POST `/organizations`
Create organization (caller becomes owner).
**Request:**
```json
{
"name": "New Company",
"description": "Optional description"
}
```
**Response (201):** Organization object.
### GET `/organizations/{id}`
Organization detail.
### PATCH `/organizations/{id}`
Update (owner/admin only).
### DELETE `/organizations/{id}`
Delete (owner only).
---
## Memberships
### GET `/organizations/{org_id}/memberships`
List members of an organization.
**Response (200):**
```json
{
"count": 15,
"results": [
{
"id": "uuid",
"user": { "id": "uuid", "email": "user@acme.com", "name": "John Doe" },
"role": "member",
"status": "active",
"joined_at": "2024-01-15T10:30:00Z",
"invited_by": { "id": "uuid", "email": "owner@acme.com" }
}
]
}
```
### POST `/organizations/{org_id}/memberships`
Invite user (owner/admin).
**Request:**
```json
{
"email": "new@user.com",
"role": "member"
}
```
### PATCH `/organizations/{org_id}/memberships/{membership_id}`
Update role/status (owner/admin).
### DELETE `/organizations/{org_id}/memberships/{membership_id}`
Remove member (owner) or leave (self).
---
## Applications (OAuth Clients)
### GET `/applications`
List applications (admin).
**Response (200):**
```json
{
"count": 3,
"results": [
{
"id": "uuid",
"name": "Bermooda",
"client_id": "bermooda-client-id",
"client_type": "confidential",
"redirect_uris": ["https://bermooda.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"scopes": ["openid", "profile", "email", "org:read"],
"is_active": true,
"created_at": "2024-01-10T12:00:00Z"
}
]
}
```
### POST `/applications`
Register new application.
**Request:**
```json
{
"name": "My App",
"client_type": "confidential",
"redirect_uris": ["https://app.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scopes": ["openid", "profile", "email"]
}
```
**Response (201):** Application with `client_secret` (shown once).
### GET `/applications/{id}`
### PATCH `/applications/{id}`
### DELETE `/applications/{id}`
### POST `/applications/{id}/rotate-secret`
Rotate client secret.
---
## Sessions
### GET `/sessions`
List current user's active sessions.
**Response (200):**
```json
{
"count": 3,
"results": [
{
"id": "uuid",
"device_name": "Chrome on macOS",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"type": "browser",
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"last_activity": "2024-01-20T08:15:00Z",
"current": true
}
]
}
```
### DELETE `/sessions/{id}`
Revoke session (also revokes its refresh token).
**Response (204):** No content.
---
## Security Events
### GET `/events`
List security events (admin, or user's own events).
**Query params:** `user_id`, `event_type`, `severity`, `date_from`, `date_to`
**Response (200):**
```json
{
"count": 50,
"results": [
{
"id": "uuid",
"user": { "id": "uuid", "email": "user@acme.com" },
"event_type": "login_success",
"severity": "low",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"metadata": { "session_id": "uuid" },
"created_at": "2024-01-20T08:15:00Z"
}
]
}
```
**Event Types:**
- `login_success`, `login_failed`
- `logout`, `token_refreshed`
- `session_revoked`, `password_changed`
- `mfa_changed`, `mfa_enrolled`, `mfa_removed`
- `suspicious_activity`, `brute_force_detected`
**Severity:** `low`, `medium`, `high`, `critical`
---
## OAuth 2.0 / OIDC
Authorization Code + Refresh Token grants with PKCE support.
### GET `/oauth/authorize`
Start the authorization code flow.
**Query parameters:**
| Parameter | Required | Description |
|-----------|----------|-------------|
| `client_id` | yes | Application (OAuth client) identifier |
| `response_type` | yes | Must be `code` |
| `redirect_uri` | yes | Must match a registered redirect URI for the client |
| `scope` | yes | Space-delimited scopes (e.g. `openid profile`) |
| `state` | no | Opaque value echoed back to the client |
| `nonce` | no | Binds authorisation to the token |
| `code_challenge` | no | PKCE code challenge |
| `code_challenge_method` | no | `S256` (default `plain`) |
| `token` | no | Bearer JWT access token may be passed as a query parameter instead of a header |
If the user is not authenticated (or inactive), responds `401` with a `login_url`.
Otherwise redirects (`302`) to the registered `redirect_uri` with `code` + optional `state`:
```
https://bermooda.com/callback?code=<raw_code>&state=abc123
```
On validation failure (invalid `client_id`, `redirect_uri` mismatch, unsupported
`response_type`), responds `400` with `error` (`unauthorized_client`,
`invalid_request`, `unsupported_response_type`, `invalid_scope`).
### POST `/oauth/token`
Exchange an authorization code for tokens, or refresh an access token.
**Content-Type:** `application/x-www-form-urlencoded`
**Grant: `authorization_code`**
```
grant_type=authorization_code
code=<raw_code>
redirect_uri=https://bermooda.com/callback
client_id=<client_id>
client_secret=<client_secret>
code_verifier=<pkce_verifier> # required only if code_challenge was used
```
**Grant: `refresh_token`**
```
grant_type=refresh_token
refresh_token=<refresh_token>
client_secret=<client_secret>
```
**Response (200):**
```json
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "<raw_refresh_token>", // rotated on refresh
"scope": "openid profile"
}
```
Authorization codes are single-use. Refresh tokens are rotated on each use and
the previous refresh token is revoked.
Errors: `400 invalid_client`, `400 invalid_grant`, `400 invalid_request`,
`401 invalid_client`, `400 unsupported_grant_type`.
### GET `/oauth/jwks`
Returns the JSON Web Key Set. Currently returns an empty key set `{"keys": []}`
because tokens are signed with HMAC (HS256), not an asymmetric key pair.
### GET `/oauth/scopes/`
Lists the non-system OAuth scopes available to applications.
---
## OIDC Discovery
### GET `/.well-known/openid-configuration`
Standard OpenID Provider configuration.
```json
{
"issuer": "https://id.example.com",
"authorization_endpoint": "https://id.example.com/oauth/authorize",
"token_endpoint": "https://id.example.com/oauth/token",
"jwks_uri": "https://id.example.com/oauth/jwks",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["public"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"id_token_signing_alg_values_supported": ["HS256"],
"claims_supported": ["sub", "name", "email", "email_verified", "preferred_username", "locale", "picture"],
"code_challenge_methods_supported": ["S256", "plain"]
}
```
## Admin Endpoints
### GET `/admin/users/` — List all users (staff)
### GET `/admin/organizations/` — All organizations
### GET `/admin/applications/` — All OAuth clients
### GET `/admin/security/events/` — Full audit log
Access: Django admin or staff API token.
---
## Rate Limits
| Scope | Limit |
|-------|-------|
| Per-user (authenticated) | 100 req/min |
| Per-IP (unauthenticated) | 20 req/min |
| `/auth/login` | 5/min/IP, 10/min/user |
| `/auth/refresh` | 20/min/user |
Headers on 429:
```
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
```
---
## SDKs / Clients
| Language | Package |
|----------|---------|
| Python | `pip install identity-platform-sdk` (planned) |
| JavaScript | `npm install @identity-platform/client` (planned) |
| Go | `go get github.com/identity-platform/go-sdk` (planned) |
Until SDKs exist, use standard HTTP clients with the endpoints above.
---
## Changelog
| Version | Date | Changes |
|---------|------|---------|
| v1.0.0 | 2024-01 | Initial release: auth, users, orgs, memberships, apps, sessions, events, OIDC discovery |
---
## Support
- API Issues: Internal ticket system
- Security: `security@identity-platform.internal`
- Docs: `/api/docs/` (Swagger UI)