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

11 KiB

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:

{
  "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).

Param Example
search ?search=john (icontains on name/email)
ordering ?ordering=-created_at (prefix - for desc)
field=value ?status=active (exact match)

Error Format

{
  "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:

{
  "email": "user@acme.com",
  "password": "secret123"
}

Response (200):

{
  "access": "eyJ...",
  "refresh": "eyJ...",
  "token_type": "bearer",
  "expires_in": 900
}

POST /auth/refresh

Rotate access token.

Request:

{
  "refresh": "eyJ..."
}

Response (200):

{
  "access": "eyJ...",
  "refresh": "eyJ...",  // rotated
  "token_type": "bearer",
  "expires_in": 900
}

POST /auth/logout

Revoke session + refresh token.

Request:

{
  "refresh": "eyJ..."  // optional; if omitted, revokes current session only
}

Response (204): No content.


Users

GET /users/me

Current authenticated user.

Response (200):

{
  "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):

{
  "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:

{
  "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):

{
  "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:

{
  "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):

{
  "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:

{
  "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):

{
  "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):

{
  "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):

{
  "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.

{
  "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)