Files
web-hosts/domains/coppertone.tech/docs/api/auth-service.md
2025-12-26 13:38:04 +01:00

825 lines
18 KiB
Markdown

# Auth Service API Documentation
**Base URL:** `/api/auth` (via gateway) or `http://localhost:8082` (direct)
**Service Port:** 8082
## Overview
The Auth Service handles all authentication and authorization for the Coppertone.tech platform. It supports:
- Multi-factor authentication (email/password + blockchain identity)
- Role-based access control (RBAC)
- Superuser hierarchy with protected initial superuser
- Identity linking/unlinking
## Role Hierarchy
| Role | Level | Description |
|------|-------|-------------|
| `SUPERUSER` | Highest | God-like permissions, full system access |
| `ADMIN` | High | Can manage STAFF/CLIENT, cannot touch SUPERUSERs |
| `STAFF` | Medium | Internal staff, can manage work orders |
| `CLIENT` | Low | External clients, limited to own resources |
### Special Flags
- **`is_initial_superuser`**: Marks the founding superuser. Cannot be deleted or demoted. Can only transfer status.
- **`is_protected`**: Protected users cannot be deleted from the system.
---
## Authentication
All protected endpoints require a JWT token in the Authorization header:
```
Authorization: Bearer <jwt_token>
```
### JWT Token Claims
```json
{
"user_id": 1,
"userId": 1,
"email": "user@example.com",
"roles": ["CLIENT", "ADMIN"],
"isInitialSuperuser": false,
"exp": 1699999999
}
```
---
## Public Endpoints
### Health Check
Check if the service is running.
```
GET /healthz
```
**Response:**
```
200 OK
ok
```
---
## Registration Endpoints
### Register with Email/Password
Create a new user account using email and password authentication.
```
POST /register-email-password
```
**Request Body:**
```json
{
"email": "user@example.com",
"password": "SecurePass123",
"name": "John Doe"
}
```
**Validation Rules:**
| Field | Rules |
|-------|-------|
| `email` | Required, valid email format, max 254 characters |
| `password` | Required, 8-72 characters, must contain uppercase, lowercase, and number |
| `name` | Required, max 100 characters |
**Response (201 Created):**
```json
{
"message": "User registered successfully",
"userId": 1
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Validation error | `{"field": "email", "message": "Invalid email format"}` |
| 409 | Email exists | `Email already registered` |
| 500 | Server error | `Failed to create user` |
**Special Behavior:**
- First user registered automatically becomes `SUPERUSER` with `is_initial_superuser = true`
- All subsequent users are assigned `CLIENT` role
- Role cannot be specified during registration (security measure)
---
### Register with Blockchain Address
Create a new user account using Ethereum blockchain identity.
```
POST /register-blockchain
```
**Request Body:**
```json
{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b2b1",
"signature": "0x...",
"message": "Sign this message to register: <timestamp>",
"name": "John Doe"
}
```
**Validation Rules:**
| Field | Rules |
|-------|-------|
| `address` | Required, valid Ethereum address (0x + 40 hex chars) |
| `signature` | Required, valid Ethereum signature (65 bytes) |
| `message` | Required, message that was signed |
| `name` | Required, max 100 characters |
**Signature Verification:**
- Uses Ethereum personal_sign format
- Message is prefixed with `\x19Ethereum Signed Message:\n<length>`
- Public key is recovered and compared to provided address
**Response (201 Created):**
```json
{
"message": "User registered successfully",
"userId": 1
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Missing fields | `Blockchain address is required` |
| 401 | Invalid signature | `Invalid signature` |
| 409 | Address exists | `Blockchain address already registered` |
| 500 | Server error | `Failed to create user` |
---
## Login Endpoints
### Login with Email/Password
Authenticate using email and password credentials.
```
POST /login-email-password
```
**Request Body:**
```json
{
"email": "user@example.com",
"password": "SecurePass123"
}
```
**Response (200 OK):**
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Invalid JSON | Error message |
| 401 | Wrong credentials | `Invalid credentials` |
| 500 | Server error | `Login failed` |
**Token Expiry:** 24 hours
---
### Login with Blockchain Signature
Authenticate using Ethereum wallet signature.
```
POST /login-blockchain
```
**Request Body:**
```json
{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b2b1",
"signature": "0x...",
"message": "Sign this message to login: <timestamp>"
}
```
**Response (200 OK):**
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 401 | Invalid signature | `Invalid signature` |
| 401 | Not registered | `Address not registered` |
| 500 | Server error | `Login failed` |
---
## Protected Endpoints
### Get User Profile
Retrieve the authenticated user's profile.
```
GET /profile
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** Any authenticated user
**Response (200 OK):**
```json
{
"id": 1,
"name": "John Doe",
"email": "user@example.com",
"roles": ["CLIENT"],
"isInitialSuperuser": false,
"isProtected": false,
"createdAt": "2024-01-15T10:30:00Z"
}
```
---
## Identity Management
### Get User Identities
List all authentication identities linked to the current user.
```
GET /identities
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** Any authenticated user
**Response (200 OK):**
```json
[
{
"id": 1,
"userId": 1,
"type": "email_password",
"identifier": "user@example.com",
"isPrimaryLogin": true,
"createdAt": "2024-01-15T10:30:00Z"
},
{
"id": 2,
"userId": 1,
"type": "blockchain_address",
"identifier": "0x742d35cc6634c0532925a3b844bc9e7595f1b2b1",
"isPrimaryLogin": false,
"createdAt": "2024-01-16T14:20:00Z"
}
]
```
---
### Link New Identity
Add a new authentication method to the current user's account.
```
POST /link-identity
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `CLIENT`, `STAFF`, or `ADMIN`
**Request Body (Email/Password):**
```json
{
"type": "email_password",
"email": "alternate@example.com",
"password": "SecurePass123"
}
```
**Request Body (Blockchain):**
```json
{
"type": "blockchain_address",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b2b1",
"signature": "0x...",
"message": "Link this wallet to my account: <timestamp>"
}
```
**Response (201 Created):**
```json
{
"message": "Identity linked successfully"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Invalid type | `Invalid identity type` |
| 400 | Validation error | `{"field": "email", "message": "..."}` |
| 401 | Invalid signature | `Invalid signature` |
| 409 | Already linked | `Identity already linked` |
---
### Unlink Identity
Remove an authentication method from the current user's account.
```
POST /unlink-identity
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `CLIENT`, `STAFF`, or `ADMIN`
**Request Body:**
```json
{
"identityId": 2
}
```
**Response (200 OK):**
```json
{
"message": "Identity unlinked successfully"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Last identity | `Cannot remove your last identity. You must have at least one login method.` |
| 403 | Not owner | `Forbidden: identity does not belong to you` |
| 404 | Not found | `Identity not found` |
**Notes:**
- Cannot remove the last identity (must have at least one login method)
- If unlinking primary identity, another identity is automatically promoted to primary
---
## Admin Endpoints
### Get All Users
Retrieve a list of all users in the system.
```
GET /admin/users
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `ADMIN` or `SUPERUSER`
**Response (200 OK):**
```json
[
{
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"roles": ["SUPERUSER"],
"isInitialSuperuser": true,
"isProtected": true,
"createdAt": "2024-01-01T00:00:00Z"
},
{
"id": 2,
"name": "John Doe",
"email": "john@example.com",
"roles": ["CLIENT"],
"isInitialSuperuser": false,
"isProtected": false,
"createdAt": "2024-01-15T10:30:00Z"
}
]
```
---
### Promote User Role
Grant a role to a user.
```
POST /admin/users/promote-role
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `ADMIN` or `SUPERUSER`
**Request Body:**
```json
{
"userId": 2,
"role": "STAFF"
}
```
**Valid Roles:** `CLIENT`, `STAFF`, `ADMIN`
**Response (200 OK):**
```json
{
"message": "Successfully granted STAFF role to user 2"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Invalid role | `Invalid role. Must be CLIENT, STAFF, or ADMIN` |
| 400 | SUPERUSER role | `Use /superuser/promote endpoint to promote to SUPERUSER` |
| 403 | ADMIN touching SUPERUSER | `Forbidden: ADMINs cannot modify SUPERUSER accounts` |
| 404 | User not found | `User not found` |
| 409 | Already has role | `User already has STAFF role` |
**Restrictions:**
- `ADMIN` users cannot promote users who have `SUPERUSER` role
- `SUPERUSER` promotion requires `/superuser/promote` endpoint
- Audit log entry is created for each promotion
---
### Demote User Role
Remove a role from a user.
```
POST /admin/users/demote-role
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `ADMIN` or `SUPERUSER`
**Request Body:**
```json
{
"userId": 2,
"role": "ADMIN"
}
```
**Valid Roles:** `CLIENT`, `STAFF`, `ADMIN`
**Response (200 OK):**
```json
{
"message": "Successfully removed ADMIN role from user 2"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Invalid role | `Invalid role. Must be CLIENT, STAFF, or ADMIN` |
| 400 | SUPERUSER role | `Use /superuser/demote endpoint to remove SUPERUSER role` |
| 400 | Only role | `Cannot remove user's only role. Assign a different role first.` |
| 403 | Self-demotion | `Cannot remove your own ADMIN role` |
| 403 | ADMIN touching SUPERUSER | `Forbidden: ADMINs cannot modify SUPERUSER accounts` |
| 404 | User not found | `User not found` |
| 404 | Doesn't have role | `User does not have ADMIN role` |
**Restrictions:**
- Cannot remove a user's only role
- `ADMIN` cannot demote themselves from `ADMIN` (unless they're also `SUPERUSER`)
- `ADMIN` users cannot demote users who have `SUPERUSER` role
- `SUPERUSER` demotion requires `/superuser/demote` endpoint
---
## Superuser Endpoints
### Promote to Superuser
Promote a user to SUPERUSER role.
```
POST /superuser/promote
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `SUPERUSER` only
**Request Body:**
```json
{
"userId": 3
}
```
**Response (200 OK):**
```json
{
"message": "Successfully promoted user 3 to SUPERUSER"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 403 | Not SUPERUSER | `Forbidden: insufficient permissions` |
| 404 | User not found | `User not found` |
| 409 | Already SUPERUSER | `User is already a SUPERUSER` |
**Notes:**
- Only existing `SUPERUSER` users can promote others to `SUPERUSER`
- New `SUPERUSER` does not become `is_initial_superuser`
- Audit log entry is created
---
### Demote from Superuser
Remove SUPERUSER role from a user.
```
POST /superuser/demote
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `SUPERUSER` only
**Request Body:**
```json
{
"userId": 3
}
```
**Response (200 OK):**
```json
{
"message": "Successfully removed SUPERUSER role from user 3"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 403 | Not SUPERUSER | `Forbidden: insufficient permissions` |
| 403 | Self-demotion | `Cannot demote yourself. Have another SUPERUSER do it.` |
| 403 | Initial SUPERUSER | `Cannot demote the INITIAL SUPERUSER. They must transfer their status first using /superuser/transfer` |
| 404 | User not found | `User not found` |
| 404 | Not a SUPERUSER | `User does not have SUPERUSER role` |
**Special Behavior:**
- Cannot demote the `is_initial_superuser` - they must transfer first
- Cannot demote yourself - another `SUPERUSER` must do it
- If user has no other roles, `CLIENT` role is automatically added before demotion
- Audit log entry is created
---
### Transfer Initial Superuser Status
Transfer the initial superuser status to another user. **Only the current initial superuser can call this endpoint.**
```
POST /superuser/transfer
```
**Headers:**
```
Authorization: Bearer <token>
```
**Required Role:** `SUPERUSER` with `is_initial_superuser = true`
**Request Body:**
```json
{
"newSuperuserId": 5,
"reason": "Retiring from the company, transferring ownership to new CTO"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `newSuperuserId` | integer | Yes | User ID to receive initial superuser status |
| `reason` | string | No | Reason for transfer (stored in audit log) |
**Response (200 OK):**
```json
{
"message": "Successfully transferred INITIAL SUPERUSER status to user 5 (Jane Smith)"
}
```
**Error Responses:**
| Status | Condition | Response |
|--------|-----------|----------|
| 400 | Self-transfer | `Cannot transfer to yourself` |
| 403 | Not initial SUPERUSER | `Forbidden: Only the INITIAL SUPERUSER can transfer their status` |
| 404 | Target not found | `Target user not found` |
**What Happens During Transfer:**
1. `is_initial_superuser` flag removed from current user
2. `is_initial_superuser` and `is_protected` flags set on new user
3. New user automatically granted `SUPERUSER` role (if not already)
4. Transfer recorded in `superuser_transfers` audit table
5. Original user retains `SUPERUSER` role (can be demoted by new initial SUPERUSER)
**Audit Trail:**
```sql
SELECT * FROM superuser_transfers;
-- id | from_user_id | to_user_id | transferred_at | reason
-- 1 | 1 | 5 | 2024-01-20... | Retiring from company...
```
---
## Error Response Format
All error responses follow this format:
**Simple Error:**
```
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Invalid request body
```
**Validation Error:**
```json
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"field": "email",
"message": "Invalid email format"
}
```
---
## CORS Configuration
The service supports CORS with the following configuration:
| Header | Value |
|--------|-------|
| `Access-Control-Allow-Origin` | Configured via `CORS_ALLOW_ORIGIN` env var (default: `http://localhost:8090`) |
| `Access-Control-Allow-Methods` | `GET, POST, PUT, DELETE, OPTIONS` |
| `Access-Control-Allow-Headers` | `Content-Type, Authorization` |
| `Access-Control-Allow-Credentials` | `true` |
---
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `JWT_SECRET` | Yes | - | Secret key for JWT signing (min 32 chars, recommend 64+) |
| `DB_HOST` | Yes | - | PostgreSQL host |
| `DB_USER` | Yes | - | PostgreSQL user |
| `DB_PASSWORD` | Yes | - | PostgreSQL password |
| `DB_NAME` | Yes | - | PostgreSQL database name |
| `DB_SSL_MODE` | No | `require` | SSL mode: `disable`, `require`, `verify-ca`, `verify-full` |
| `DB_SCHEMA` | No | `public` | Schema: `dev`, `testing`, `prod` |
| `CORS_ALLOW_ORIGIN` | No | `http://localhost:8090` | Allowed CORS origin |
| `DEFAULT_USER_ROLE` | No | `CLIENT` | Default role for new users |
---
## Database Tables
### users
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE,
is_initial_superuser BOOLEAN DEFAULT false,
is_protected BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### identities
```sql
CREATE TABLE identities (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
type identity_type NOT NULL, -- 'email_password', 'blockchain_address', 'did'
identifier VARCHAR(500) NOT NULL,
credential TEXT, -- Password hash or other credential
is_primary_login BOOLEAN DEFAULT false,
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(type, identifier)
);
```
### user_roles
```sql
CREATE TABLE user_roles (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role user_role NOT NULL, -- 'SUPERUSER', 'ADMIN', 'STAFF', 'CLIENT'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, role)
);
```
### superuser_transfers
```sql
CREATE TABLE superuser_transfers (
id SERIAL PRIMARY KEY,
from_user_id INTEGER NOT NULL REFERENCES users(id),
to_user_id INTEGER NOT NULL REFERENCES users(id),
transferred_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
reason TEXT
);
```
---
## Security Considerations
1. **Password Storage**: Passwords are hashed using bcrypt with default cost
2. **JWT Security**: Tokens expire after 24 hours, signed with HS256
3. **First User Protection**: Initial superuser cannot be deleted, only transferred
4. **Role Hierarchy**: ADMINs cannot modify SUPERUSERs
5. **Audit Logging**: All role changes and transfers are logged
6. **Input Validation**: All inputs are validated and sanitized
7. **SQL Injection**: All queries use parameterized statements