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

481 lines
10 KiB
Markdown

# 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:**
```json
{
"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:**
```json
{
"email": "user@example.com"
}
```
**Response:**
```json
{
"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:**
```javascript
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):**
```html
<!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:**
```json
{
"email": "user@example.com",
"name": "John Doe",
"public_key": "04a1b2c3d4e5f6..."
}
```
**Response:**
```json
{
"success": true,
"message": "Registration completed successfully",
"user_id": "uuid-user-id"
}
```
**Error Response:**
```json
{
"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:**
```json
{
"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:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
```
**Error Response:**
```json
{
"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:**
```json
{
"sub": "user-id",
"email": "user@example.com",
"name": "John Doe",
"public_key": "04a1b2c3d4e5f6...",
"created_at": "1640995200"
}
```
**Error Response:**
```json
{
"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
```rust
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
```rust
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
```rust
struct VerificationStatus {
email: String, // Email address
verified: bool, // Verification state
verification_token: String, // UUID token
}
```
## Authentication Flow
### 1. Challenge-Response Authentication
```mermaid
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:
```json
{
"error": "error_code",
"error_description": "Human readable description",
"error_uri": "https://docs.example.com/oauth/errors"
}
```
### Common Error Scenarios
1. **Email Not Verified**
```json
{
"success": false,
"message": "Email not verified"
}
```
2. **Invalid Token**
```json
{
"error": "invalid_token",
"error_description": "Invalid or expired token"
}
```
3. **User Not Found**
```json
{
"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:
```json
{
"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:
```rust
// 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:
```rust
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
```rust
#[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,
}
```