# 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 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: ", "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` - 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: " } ``` **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 ``` **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 ``` **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 ``` **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: " } ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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