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

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

{
  "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"
  }
]

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

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:

  • 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:

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

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

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

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

  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:

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

  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