- 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?;
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 successfully400 Bad Request: Invalid email format500 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 verifiedkeep-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 successfully400 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 successful400 Bad Request: Email not verified or invalid data500 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 successful401 Unauthorized: Invalid credentials or signature400 Bad Request: Malformed request
OAuth 2.0 Error Codes:
invalid_client: Public key not foundinvalid_grant: Invalid signature or challengeunsupported_grant_type: Grant type not supportedserver_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 successfully401 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
- Extract Bearer token from Authorization header
- Decode JWT using server secret
- Validate issuer, audience, and expiration
- Extract public key from subject claim
- Look up user by public key
- Return user information
Error Handling
Standard HTTP Status Codes
200 OK: Request successful400 Bad Request: Invalid request format or data401 Unauthorized: Authentication required or failed404 Not Found: Resource not found500 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
-
Email Not Verified
{ "success": false, "message": "Email not verified" } -
Invalid Token
{ "error": "invalid_token", "error_description": "Invalid or expired token" } -
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,
}