# JWT Validation Integration Guide

## Overview

This guide describes how to integrate the complete JWT validation system into the syntaxis-api server.

## Components

### 1. AuthConfig (in shared-api-lib)

Located in `shared/rust-api/shared-api-lib/src/config/mod.rs`

```toml
# configs/features/auth.toml
api_keys = ["key1", "key2"]
jwt_secret = "your-secret-key-at-least-32-chars"
jwt_enabled = true
jwt_expiration = 3600  # 1 hour
```

### 2. AppState Updates

Added to `syntaxis/crates/syntaxis-api/src/state.rs`:
```rust
pub struct AppState {
    pub db: Arc<PersistenceLayer>,
    pub jwt_provider: Option<Arc<JwtProvider>>,  // NEW
    // ... other fields
}
```

### 3. Middleware Integration

File: `syntaxis/crates/syntaxis-api/src/middleware/auth.rs`

```rust
pub async fn auth_middleware(
    State(state): State<AppState>,
    headers: HeaderMap,
    mut req: Request,
    next: Next,
) -> Result<Response, ApiError>
```

### 4. Auth Handlers

Updated in `syntaxis/crates/syntaxis-api/src/handlers/auth.rs`:

- **register()**: Creates JWT token for new user
- **login()**: Generates JWT token on successful auth
- **logout()**: Adds token to revoked_tokens table
- **current_user()**: Extracts user from JWT claims

## Integration Steps

### Step 1: Initialize JwtProvider in main.rs

```rust
// In main.rs, during app startup:

let jwt_provider = if cfg.features.auth.jwt_enabled && cfg.auth.jwt_secret.is_some() {
    let secret = cfg.auth.jwt_secret.as_ref().unwrap();
    Some(Arc::new(JwtProvider::new(secret, cfg.auth.jwt_expiration)?))
} else {
    None
};

let app_state = AppState::new(
    db,
    metrics,
    rate_limiter,
    auth,
    jwt_provider,  // NEW
    events,
);
```

### Step 2: Apply Middleware to Protected Routes

```rust
// In main.rs, when building routes:

let protected_routes = Router::new()
    .route("/user/current", get(handlers::auth::current_user))
    .route("/user/logout", post(handlers::auth::logout))
    .route("/projects", get(handlers::projects::list))  // Protected
    .route("/projects/:id", get(handlers::projects::get))  // Protected
    .layer(axum::middleware::from_fn_with_state(
        app_state.clone(),
        middleware::auth::auth_middleware,
    ));

let public_routes = Router::new()
    .route("/auth/register", post(handlers::auth::register))
    .route("/auth/login", post(handlers::auth::login))
    .route("/health", get(handlers::health));

let app = Router::new()
    .merge(public_routes)
    .merge(protected_routes)
    .with_state(app_state);
```

### Step 3: Extract Claims in Handlers

```rust
// In any protected handler:

pub async fn my_handler(
    State(state): State<AppState>,
    axum::Extension(claims): axum::Extension<Claims>,  // Claims from middleware
) -> ApiResult<Json<Response>> {
    // Use claims.sub for user ID
    // Use claims.roles for authorization
    // Use claims.tenant_id for multi-tenancy
    Ok(Json(response))
}
```

### Step 4: Implement RBAC Guards

```rust
// Helper function for role-based access:

fn require_role(claims: &Claims, required_role: &str) -> ApiResult<()> {
    if claims.roles.contains(&required_role.to_string()) {
        Ok(())
    } else {
        Err(ApiError::Unauthorized(
            format!("Required role: {}", required_role)
        ))
    }
}

// In handler:
pub async fn admin_handler(
    axum::Extension(claims): axum::Extension<Claims>,
) -> ApiResult<Json<Response>> {
    require_role(&claims, "admin")?;
    // ... handler code
}
```

## Configuration

### Development

```toml
# syntaxis-api-config.toml
[server]
host = "127.0.0.1"
port = 3001
database_path = "data/workspace.db"

[server.features]
auth = { enabled = true }
```

```toml
# configs/features/auth.toml
api_keys = ["dev-key-123"]
jwt_secret = "development-secret-key-minimum-32-characters-long"
jwt_enabled = true
jwt_expiration = 3600  # 1 hour
```

### Production

```toml
# Ensure jwt_secret has at least 32 characters
# Use environment variables for sensitive values:
jwt_secret = "${JWT_SECRET}"  # Will be replaced at runtime

# Or set at runtime:
# export JWT_SECRET="your-production-secret-key-min-32-chars"
```

## Token Lifecycle

### Token Creation (Login/Register)

1. User credentials validated
2. JwtProvider creates signed JWT with claims:
   - `sub` (user ID)
   - `roles` (user roles)
   - `tenant_id` (optional, for multi-tenancy)
   - `exp` (expiration timestamp)
   - `iat` (issued at)
   - `aud` (audience: "syntaxis-api")

3. Token returned to client

### Token Usage (Protected Routes)

1. Client sends request with: `Authorization: Bearer <token>`
2. Middleware extracts Bearer token
3. JwtProvider.verify_token() validates:
   - Token signature
   - Expiration time
   - Audience claim
4. Claims inserted into request extensions
5. Handlers access via `Extension(claims)`

### Token Revocation (Logout)

1. User calls logout endpoint with Bearer token
2. Token added to `revoked_tokens` table with:
   - `token_jti`: Unique identifier
   - `user_id`: User who owned token
   - `reason`: "logout"
   - `revoked_at`: Timestamp
   - `expires_at`: When to cleanup DB

3. Future requests with revoked token return 401

## Security Considerations

### Secret Management

- Store `jwt_secret` in environment variables, never in code
- Minimum 32 characters (requirement for HS256)
- Rotate secrets periodically
- Use different secrets for different environments

### Token Expiration

- Default: 1 hour
- Configurable via `jwt_expiration` in config
- Expired tokens automatically rejected by middleware
- Cleanup of revoked tokens older than expiration

### HTTPS Only

- Always use HTTPS in production
- Tokens sent in Authorization header (secure)
- Never send tokens in URLs

### Validation

- Signature validated every request
- Claims validated (exp, aud, etc.)
- Revocation checked (TODO: implement in middleware)

## Testing

### Unit Tests

```rust
#[tokio::test]
async fn test_jwt_token_creation() {
    let provider = JwtProvider::new("secret-key-min-32-chars-long", 3600).unwrap();
    let token = provider.create_token(
        "user123",
        vec!["user".to_string()],
        None
    ).unwrap();

    let claims = provider.verify_token(&token).unwrap();
    assert_eq!(claims.sub, "user123");
}
```

### Integration Tests

```rust
#[tokio::test]
async fn test_login_returns_jwt() {
    // Setup app with real auth enabled
    // POST /auth/login with credentials
    // Verify response contains valid JWT token
    // Extract user ID from token
}

#[tokio::test]
async fn test_protected_route_requires_jwt() {
    // Setup app
    // GET /user/current without Authorization header
    // Verify returns 401 Unauthorized
    // GET /user/current with invalid token
    // Verify returns 401
    // GET /user/current with valid token
    // Verify returns 200 with user data
}

#[tokio::test]
async fn test_logout_revokes_token() {
    // Login to get token
    // POST /user/logout with token
    // Verify returns 204 No Content
    // POST /user/logout with same token again
    // Verify returns 401 Unauthorized (token revoked)
}
```

## Troubleshooting

### "JWT provider not configured"

- Ensure `jwt_enabled = true` in `configs/features/auth.toml`
- Ensure `jwt_secret` is set and has at least 32 characters
- Check that JwtProvider is initialized in AppState

### "Invalid Authorization header format"

- Ensure header is: `Authorization: Bearer <token>`
- Check for typos (case-sensitive "Bearer")
- Verify no extra spaces

### "JWT validation failed"

- Check token signature (may be expired)
- Verify token uses correct secret
- Check if token has been revoked
- Verify audience matches "syntaxis-api"

### Token expiration issues

- Increase `jwt_expiration` if needed
- Client should refresh tokens before expiration
- Implement token refresh endpoint (optional)

## Future Enhancements

1. **Token Refresh**: Implement /auth/refresh endpoint
2. **Revocation Check**: Complete TODO in middleware
3. **Custom Claims**: Add additional claims as needed
4. **Rate Limiting**: Combine with rate limiter for auth endpoints
5. **Audit Logging**: Log all auth events
6. **Multi-Factor Auth**: Add 2FA support
