18 KiB
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
{
"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:
{
"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):
{
"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
SUPERUSERwithis_initial_superuser = true - All subsequent users are assigned
CLIENTrole - 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:
{
"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):
{
"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:
{
"email": "user@example.com",
"password": "SecurePass123"
}
Response (200 OK):
{
"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:
{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b2b1",
"signature": "0x...",
"message": "Sign this message to login: <timestamp>"
}
Response (200 OK):
{
"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):
{
"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):
[
{
"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):
{
"type": "email_password",
"email": "alternate@example.com",
"password": "SecurePass123"
}
Request Body (Blockchain):
{
"type": "blockchain_address",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1b2b1",
"signature": "0x...",
"message": "Link this wallet to my account: <timestamp>"
}
Response (201 Created):
{
"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:
{
"identityId": 2
}
Response (200 OK):
{
"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):
[
{
"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:
{
"userId": 2,
"role": "STAFF"
}
Valid Roles: CLIENT, STAFF, ADMIN
Response (200 OK):
{
"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:
ADMINusers cannot promote users who haveSUPERUSERroleSUPERUSERpromotion requires/superuser/promoteendpoint- 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:
{
"userId": 2,
"role": "ADMIN"
}
Valid Roles: CLIENT, STAFF, ADMIN
Response (200 OK):
{
"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
ADMINcannot demote themselves fromADMIN(unless they're alsoSUPERUSER)ADMINusers cannot demote users who haveSUPERUSERroleSUPERUSERdemotion requires/superuser/demoteendpoint
Superuser Endpoints
Promote to Superuser
Promote a user to SUPERUSER role.
POST /superuser/promote
Headers:
Authorization: Bearer <token>
Required Role: SUPERUSER only
Request Body:
{
"userId": 3
}
Response (200 OK):
{
"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
SUPERUSERusers can promote others toSUPERUSER - New
SUPERUSERdoes not becomeis_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:
{
"userId": 3
}
Response (200 OK):
{
"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
SUPERUSERmust do it - If user has no other roles,
CLIENTrole 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:
{
"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):
{
"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:
is_initial_superuserflag removed from current useris_initial_superuserandis_protectedflags set on new user- New user automatically granted
SUPERUSERrole (if not already) - Transfer recorded in
superuser_transfersaudit table - Original user retains
SUPERUSERrole (can be demoted by new initial SUPERUSER)
Audit Trail:
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:
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
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
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
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
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
- Password Storage: Passwords are hashed using bcrypt with default cost
- JWT Security: Tokens expire after 24 hours, signed with HS256
- First User Protection: Initial superuser cannot be deleted, only transferred
- Role Hierarchy: ADMINs cannot modify SUPERUSERs
- Audit Logging: All role changes and transfers are logged
- Input Validation: All inputs are validated and sanitized
- SQL Injection: All queries use parameterized statements