Files
self/docs/server-api.md
Timur Gordon f970f3fb58 Add SelfFreezoneClient wrapper for Self components
- Created SelfFreezoneClient in Self components
- Wraps SDK FreezoneScriptClient for Self-specific operations
- Implements send_verification_email method
- Uses Rhai script template for email verification
- Includes template variable substitution
- Added serde-wasm-bindgen dependency

Usage:
  let client = SelfFreezoneClient::builder()
      .supervisor_url("http://localhost:8080")
      .secret("my-secret")
      .build()?;

  client.send_verification_email(
      "user@example.com",
      "123456",
      "https://verify.com/abc"
  ).await?;
2025-11-03 16:16:18 +01:00

10 KiB

Server API Documentation

Overview

The Self identity server provides a RESTful API for email verification, user registration, and OAuth 2.0 compatible authentication. The server is built with Rust and Axum, providing high performance and security.

Base Configuration

  • Default Port: 8080
  • Protocol: HTTP/HTTPS
  • Content-Type: application/json
  • CORS: Enabled for cross-origin requests

API Endpoints

Health Check

GET /health

Health check endpoint for monitoring and load balancers.

Response:

{
  "status": "healthy",
  "service": "self-server"
}

Status Codes:

  • 200 OK: Service is healthy

Email Verification

POST /api/send-verification

Initiates email verification process for new user registration.

Request Body:

{
  "email": "user@example.com"
}

Response:

{
  "success": true,
  "message": "Verification email sent",
  "verification_url": "http://localhost:8080/api/verify/uuid-token"
}

Status Codes:

  • 200 OK: Verification email sent successfully
  • 400 Bad Request: Invalid email format
  • 500 Internal Server Error: Server error

Notes:

  • In development mode, verification URL is logged to console
  • Production should integrate with SMTP service
  • Verification tokens are UUID v4 format

GET /api/verification-status/{email}

Server-Sent Events stream for real-time verification status updates.

Parameters:

  • email: URL-encoded email address

Response: SSE stream with events:

data: verified

Event Types:

  • verified: Email has been successfully verified
  • keep-alive: Periodic keep-alive message

Usage Example:

const eventSource = new EventSource('/api/verification-status/user@example.com');
eventSource.onmessage = function(event) {
  if (event.data === 'verified') {
    // Update UI to show verified status
  }
};

GET /api/verify/{token}

Email verification callback endpoint. Users click this link from their email.

Parameters:

  • token: Verification token from email link

Response: HTML page confirming verification

Status Codes:

  • 200 OK: Email verified successfully
  • 400 Bad Request: Invalid or expired token

HTML Response (Success):

<!DOCTYPE html>
<html>
<head><title>Email Verified</title></head>
<body>
  <div class="container">
    <div class="success"></div>
    <h1>Email Verified Successfully!</h1>
    <p>Your email address has been verified.</p>
  </div>
</body>
</html>

User Registration

POST /api/register

Completes user registration after email verification.

Request Body:

{
  "email": "user@example.com",
  "name": "John Doe",
  "public_key": "04a1b2c3d4e5f6..."
}

Response:

{
  "success": true,
  "message": "Registration completed successfully",
  "user_id": "uuid-user-id"
}

Error Response:

{
  "success": false,
  "message": "Email not verified",
  "user_id": null
}

Status Codes:

  • 200 OK: Registration successful
  • 400 Bad Request: Email not verified or invalid data
  • 500 Internal Server Error: Server error

Validation:

  • Email must be verified before registration
  • Public key must be valid hex format
  • Name cannot be empty

OAuth 2.0 Authentication

POST /oauth/token

OAuth 2.0 token endpoint for cryptographic authentication.

Request Body:

{
  "grant_type": "client_credentials",
  "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
  "client_assertion": "signed-jwt-token",
  "public_key": "04a1b2c3d4e5f6...",
  "challenge": "server-challenge",
  "scope": "openid profile"
}

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Error Response:

{
  "error": "invalid_client",
  "error_description": "User not found"
}

Status Codes:

  • 200 OK: Authentication successful
  • 401 Unauthorized: Invalid credentials or signature
  • 400 Bad Request: Malformed request

OAuth 2.0 Error Codes:

  • invalid_client: Public key not found
  • invalid_grant: Invalid signature or challenge
  • unsupported_grant_type: Grant type not supported
  • server_error: Internal server error

GET /oauth/userinfo

OpenID Connect UserInfo endpoint. Returns user profile information.

Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response:

{
  "sub": "user-id",
  "email": "user@example.com",
  "name": "John Doe",
  "public_key": "04a1b2c3d4e5f6...",
  "created_at": "1640995200"
}

Error Response:

{
  "error": "invalid_token",
  "error_description": "Missing Authorization header"
}

Status Codes:

  • 200 OK: User info returned successfully
  • 401 Unauthorized: Invalid or missing token

Data Models

User Model

struct User {
    id: String,           // UUID v4
    email: String,        // Verified email address
    public_key: String,   // Hex-encoded public key
    name: String,         // Display name
    created_at: String,   // Unix timestamp
}

JWT Claims

struct Claims {
    sub: String,        // Subject (public key)
    iss: String,        // Issuer ("self-sovereign-identity")
    aud: String,        // Audience ("identity-server")
    exp: usize,         // Expiration time (Unix timestamp)
    iat: usize,         // Issued at time (Unix timestamp)
    scope: String,      // Granted scopes
}

Verification Status

struct VerificationStatus {
    email: String,              // Email address
    verified: bool,             // Verification state
    verification_token: String, // UUID token
}

Authentication Flow

1. Challenge-Response Authentication

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: POST /oauth/token (with signature)
    S->>S: Verify signature against public key
    S->>S: Generate JWT token
    S->>C: Return access_token
    C->>S: GET /oauth/userinfo (with Bearer token)
    S->>S: Validate JWT token
    S->>C: Return user profile

2. JWT Token Validation

  1. Extract Bearer token from Authorization header
  2. Decode JWT using server secret
  3. Validate issuer, audience, and expiration
  4. Extract public key from subject claim
  5. Look up user by public key
  6. Return user information

Error Handling

Standard HTTP Status Codes

  • 200 OK: Request successful
  • 400 Bad Request: Invalid request format or data
  • 401 Unauthorized: Authentication required or failed
  • 404 Not Found: Resource not found
  • 500 Internal Server Error: Server error

OAuth 2.0 Error Format

All OAuth errors follow RFC 6749 format:

{
  "error": "error_code",
  "error_description": "Human readable description",
  "error_uri": "https://docs.example.com/oauth/errors"
}

Common Error Scenarios

  1. Email Not Verified

    {
      "success": false,
      "message": "Email not verified"
    }
    
  2. Invalid Token

    {
      "error": "invalid_token",
      "error_description": "Invalid or expired token"
    }
    
  3. User Not Found

    {
      "error": "invalid_client",
      "error_description": "User not found"
    }
    

Rate Limiting

Currently not implemented but recommended for production:

  • Email Verification: 5 requests per email per hour
  • Authentication: 10 attempts per public key per minute
  • Registration: 3 registrations per IP per hour

Security Headers

The server should include security headers in production:

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'

Monitoring and Logging

Log Levels

  • INFO: Normal operations (requests, registrations)
  • WARN: Invalid tokens, failed verifications
  • ERROR: Server errors, database failures

Metrics to Track

  • Request count by endpoint
  • Authentication success/failure rates
  • Registration completion rates
  • Email verification rates
  • Response times
  • Error rates

Health Check Details

The /health endpoint can be extended for detailed health information:

{
  "status": "healthy",
  "service": "self-server",
  "version": "1.0.0",
  "uptime": 3600,
  "checks": {
    "database": "healthy",
    "email_service": "healthy",
    "jwt_signing": "healthy"
  }
}

Production Considerations

Database Integration

Replace in-memory storage with persistent database:

// Example with SQLx
async fn store_user(pool: &PgPool, user: &User) -> Result<(), sqlx::Error> {
    sqlx::query!(
        "INSERT INTO users (id, email, public_key, name, created_at) VALUES ($1, $2, $3, $4, $5)",
        user.id, user.email, user.public_key, user.name, user.created_at
    )
    .execute(pool)
    .await?;
    Ok(())
}

SMTP Integration

Replace console logging with actual email sending:

use lettre::{SmtpTransport, Transport, Message};

async fn send_verification_email(email: &str, verification_url: &str) -> Result<(), Box<dyn std::error::Error>> {
    let message = Message::builder()
        .from("noreply@yourapp.com".parse()?)
        .to(email.parse()?)
        .subject("Verify your email address")
        .body(format!("Click here to verify: {}", verification_url))?;

    let mailer = SmtpTransport::relay("smtp.gmail.com")?
        .credentials(Credentials::new("username".to_string(), "password".to_string()))
        .build();

    mailer.send(&message)?;
    Ok(())
}

Environment Configuration

#[derive(Parser, Debug)]
struct Args {
    #[arg(short, long, default_value_t = 8080)]
    port: u16,
    
    #[arg(long, env = "DATABASE_URL")]
    database_url: String,
    
    #[arg(long, env = "JWT_SECRET")]
    jwt_secret: String,
    
    #[arg(long, env = "SMTP_HOST")]
    smtp_host: String,
    
    #[arg(long, env = "SMTP_USERNAME")]
    smtp_username: String,
    
    #[arg(long, env = "SMTP_PASSWORD")]
    smtp_password: String,
}