- 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?;
481 lines
10 KiB
Markdown
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,
|
|
}
|
|
```
|