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?;
This commit is contained in:
Timur Gordon
2025-11-03 16:16:18 +01:00
parent be061409af
commit f970f3fb58
33 changed files with 8947 additions and 449 deletions

434
docs/openid-compliance.md Normal file
View File

@@ -0,0 +1,434 @@
# OpenID Connect Compliance
## Overview
Self implements OpenID Connect (OIDC) compatible endpoints while maintaining its self-sovereign identity principles. The implementation provides standard OAuth 2.0 and OIDC flows that can integrate with existing identity providers and relying party applications.
## OpenID Connect Implementation
### Supported Flows
#### 1. Client Credentials Flow with Cryptographic Assertions
Self implements a modified client credentials flow using cryptographic signatures instead of client secrets:
```mermaid
sequenceDiagram
participant C as Client
participant S as Self Server
participant RP as Relying Party
Note over C,RP: Authentication Flow
C->>S: POST /oauth/token (with signature)
S->>S: Verify cryptographic signature
S->>C: Return access_token (JWT)
C->>S: GET /oauth/userinfo (Bearer token)
S->>C: Return user claims
C->>RP: Present identity claims
```
#### 2. Authorization Code Flow (Future)
Planned implementation for web applications:
```mermaid
sequenceDiagram
participant U as User
participant RP as Relying Party
participant S as Self Server
U->>RP: Access protected resource
RP->>S: Redirect to /oauth/authorize
S->>U: Present consent screen
U->>S: Approve/deny consent
S->>RP: Redirect with authorization code
RP->>S: POST /oauth/token (exchange code)
S->>RP: Return access_token + id_token
```
### Endpoint Compliance
#### Token Endpoint - `/oauth/token`
**OAuth 2.0 Compliance:**
- ✅ Supports `client_credentials` grant type
- ✅ Returns standard token response format
- ✅ Implements proper error responses
- ✅ Validates client authentication
- 🔄 Planned: `authorization_code` grant type
- 🔄 Planned: `refresh_token` support
**Request Format:**
```json
{
"grant_type": "client_credentials",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": "signed-jwt-containing-challenge-response",
"public_key": "client-identifier",
"challenge": "server-provided-challenge",
"scope": "openid profile"
}
```
**Response Format:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile"
}
```
#### UserInfo Endpoint - `/oauth/userinfo`
**OpenID Connect Compliance:**
- ✅ Requires Bearer token authentication
- ✅ Returns standard OIDC claims
- ✅ Supports CORS for cross-origin requests
- ✅ Validates JWT token signatures
- ✅ Returns appropriate error responses
**Response Format:**
```json
{
"sub": "user-unique-identifier",
"email": "user@example.com",
"name": "John Doe",
"public_key": "04a1b2c3d4e5f6...",
"created_at": "1640995200"
}
```
### JWT Token Structure
#### Access Token Claims
```rust
struct Claims {
// Standard OIDC claims
sub: String, // Subject identifier (public key)
iss: String, // Issuer ("self-sovereign-identity")
aud: String, // Audience ("identity-server")
exp: usize, // Expiration time
iat: usize, // Issued at time
// OAuth 2.0 claims
scope: String, // Granted scopes
// Self-specific claims
challenge: Option<String>, // Authentication challenge
}
```
#### ID Token (Future Implementation)
```rust
struct IdTokenClaims {
// Required OIDC claims
sub: String, // Subject identifier
iss: String, // Issuer
aud: String, // Audience (client_id)
exp: usize, // Expiration time
iat: usize, // Issued at time
// Optional OIDC claims
email: Option<String>,
name: Option<String>,
picture: Option<String>,
// Authentication context
auth_time: Option<usize>,
nonce: Option<String>,
}
```
## Discovery Document
### OpenID Configuration Endpoint
**Planned Implementation:** `/.well-known/openid-configuration`
```json
{
"issuer": "https://identity.example.com",
"authorization_endpoint": "https://identity.example.com/oauth/authorize",
"token_endpoint": "https://identity.example.com/oauth/token",
"userinfo_endpoint": "https://identity.example.com/oauth/userinfo",
"jwks_uri": "https://identity.example.com/.well-known/jwks.json",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token"
],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["HS256", "RS256"],
"scopes_supported": [
"openid",
"profile",
"email"
],
"claims_supported": [
"sub",
"iss",
"aud",
"exp",
"iat",
"email",
"name",
"public_key",
"created_at"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_assertion"
],
"token_endpoint_auth_signing_alg_values_supported": ["HS256", "RS256"]
}
```
### JWKS Endpoint
**Planned Implementation:** `/.well-known/jwks.json`
```json
{
"keys": [
{
"kty": "oct",
"use": "sig",
"kid": "self-signing-key-1",
"alg": "HS256",
"k": "base64url-encoded-secret"
}
]
}
```
## Scope and Claims Mapping
### Supported Scopes
| Scope | Description | Claims Returned |
|-------|-------------|-----------------|
| `openid` | OpenID Connect authentication | `sub`, `iss`, `aud`, `exp`, `iat` |
| `profile` | Basic profile information | `name`, `public_key`, `created_at` |
| `email` | Email address | `email` |
### Claim Definitions
| Claim | Type | Description | Source |
|-------|------|-------------|--------|
| `sub` | string | Subject identifier (unique user ID) | User record ID |
| `email` | string | Email address | User registration |
| `name` | string | Display name | User registration |
| `public_key` | string | Cryptographic public key | Key generation |
| `created_at` | string | Account creation timestamp | Registration time |
## Integration Examples
### Relying Party Integration
#### JavaScript Client
```javascript
class SelfOIDCClient {
constructor(issuer, clientId) {
this.issuer = issuer;
this.clientId = clientId;
}
async authenticate(publicKey, signature, challenge) {
const tokenResponse = await fetch(`${this.issuer}/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: signature,
public_key: publicKey,
challenge: challenge,
scope: 'openid profile email'
})
});
const tokens = await tokenResponse.json();
// Get user info
const userInfoResponse = await fetch(`${this.issuer}/oauth/userinfo`, {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
return await userInfoResponse.json();
}
}
```
#### Node.js Server
```javascript
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// Middleware to verify Self tokens
function verifySelfToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' });
}
const token = authHeader.substring(7);
try {
// Verify token with Self's public key or shared secret
const decoded = jwt.verify(token, process.env.SELF_JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
// Protected route
app.get('/protected', verifySelfToken, (req, res) => {
res.json({
message: 'Access granted',
user: req.user
});
});
```
### Identity Provider Integration
#### SAML Bridge
```rust
// Future implementation: SAML assertion generation
pub struct SamlAssertion {
pub subject: String,
pub issuer: String,
pub audience: String,
pub attributes: HashMap<String, String>,
pub signature: String,
}
impl SamlAssertion {
pub fn from_oidc_claims(claims: &Claims) -> Self {
let mut attributes = HashMap::new();
attributes.insert("email".to_string(), claims.email.clone());
attributes.insert("name".to_string(), claims.name.clone());
attributes.insert("public_key".to_string(), claims.public_key.clone());
SamlAssertion {
subject: claims.sub.clone(),
issuer: claims.iss.clone(),
audience: "saml-service-provider".to_string(),
attributes,
signature: "".to_string(), // Generate SAML signature
}
}
}
```
## Security Considerations
### Token Security
1. **JWT Signing**: Tokens signed with HMAC-SHA256 or RSA-SHA256
2. **Token Expiration**: Short-lived tokens (1 hour default)
3. **Scope Validation**: Strict scope checking for claims
4. **Audience Validation**: Tokens bound to specific audiences
### OIDC Security Best Practices
1. **PKCE Support**: Planned for authorization code flow
2. **State Parameter**: Anti-CSRF protection
3. **Nonce Validation**: Replay attack prevention
4. **HTTPS Only**: All endpoints require HTTPS in production
### Self-Sovereign Considerations
1. **No Central Authority**: Users control their own identity
2. **Cryptographic Authentication**: No passwords or shared secrets
3. **Local Key Storage**: Private keys never transmitted
4. **Minimal Data Collection**: Only necessary claims stored
## Compliance Status
### OAuth 2.0 RFC 6749
| Requirement | Status | Notes |
|-------------|--------|-------|
| Authorization Endpoint | 🔄 Planned | For authorization code flow |
| Token Endpoint | ✅ Implemented | Client credentials flow |
| Error Responses | ✅ Implemented | Standard error format |
| Access Token Format | ✅ Implemented | JWT tokens |
| Scope Parameter | ✅ Implemented | openid, profile, email |
### OpenID Connect Core 1.0
| Requirement | Status | Notes |
|-------------|--------|-------|
| ID Token | 🔄 Planned | JWT format with required claims |
| UserInfo Endpoint | ✅ Implemented | Standard claims format |
| Discovery | 🔄 Planned | .well-known/openid-configuration |
| Authentication | ✅ Implemented | Cryptographic challenge-response |
| Claims | ✅ Implemented | Standard and custom claims |
### OpenID Connect Discovery 1.0
| Requirement | Status | Notes |
|-------------|--------|-------|
| Configuration Endpoint | 🔄 Planned | Metadata document |
| JWKS Endpoint | 🔄 Planned | Public key distribution |
| Dynamic Registration | ❌ Not Planned | Self-sovereign model |
## Future Enhancements
### Planned Features
1. **Authorization Code Flow**: Full web application support
2. **ID Tokens**: Standard OIDC ID token implementation
3. **Refresh Tokens**: Long-lived session management
4. **Discovery Document**: Standard OIDC discovery
5. **JWKS Endpoint**: Public key distribution
6. **PKCE Support**: Enhanced security for public clients
### Advanced Features
1. **Federation**: Trust relationships with other identity providers
2. **Delegation**: Temporary identity delegation
3. **Multi-Factor**: Additional authentication factors
4. **Device Flow**: Support for device authentication
5. **Logout**: Single logout implementation
### Standards Compliance
1. **RFC 7636**: PKCE implementation
2. **RFC 7662**: Token introspection
3. **RFC 7009**: Token revocation
4. **RFC 8693**: Token exchange
5. **OpenID Connect Session Management**: Session handling