auth_api/
auth-api.rs

1//! Auth API Example
2//! Run:
3//!   cargo run --example auth-api
4
5use axum::{middleware::from_fn_with_state, response::IntoResponse};
6use rapid_rs::auth::{
7    auth_routes_with_store, create_token_pair, hash_password,
8    middleware::{inject_auth_config, RequireRoles},
9    models::AuthUserInfo,
10    AuthAppState, AuthConfig, AuthResponse, AuthUser, CreateUserData, InMemoryUserStore,
11    RegisterRequest, UserStore,
12};
13use rapid_rs::prelude::*;
14
15/// Protected route - requires any valid JWT
16async fn protected_route(user: AuthUser) -> impl IntoResponse {
17    Json(serde_json::json!({
18        "message": format!("Hello, {}! You are authenticated.", user.email),
19        "user_id": user.id,
20        "roles": user.roles,
21    }))
22}
23
24/// Admin-only route - requires "admin" role
25async fn admin_route(user: AuthUser) -> Result<impl IntoResponse, ApiError> {
26    user.require_role("admin")
27        .map_err(|_| ApiError::Forbidden)?;
28
29    Ok(Json(serde_json::json!({
30        "message": "Welcome to the admin panel!",
31        "admin_id": user.id,
32    })))
33}
34
35/// Public route - no authentication required
36async fn public_route() -> impl IntoResponse {
37    Json(serde_json::json!({
38        "message": "This is a public endpoint. Anyone can access it!",
39    }))
40}
41
42/// This handler logic is only reached if the Middleware allows it
43async fn middleware_admin() -> impl IntoResponse {
44    Json(serde_json::json!({
45        "status": "success",
46        "message": "You have the ADMIN role, so you can see this!",
47    }))
48}
49
50/// This handler logic is only reached if the Middleware allows it
51async fn middleware_user() -> impl IntoResponse {
52    Json(serde_json::json!({
53        "status": "success",
54        "message": "You have the USER role, so you can see this!",
55    }))
56}
57
58//  Hack: Special endpoint to register an ADMIN for testing purposes
59async fn register_admin(
60    State(state): State<AuthAppState<InMemoryUserStore>>,
61    ValidatedJson(payload): ValidatedJson<RegisterRequest>,
62) -> Result<Json<AuthResponse>, ApiError> {
63    let password_hash = hash_password(&payload.password, &state.config)?;
64
65    let user = state
66        .user_store
67        .create(CreateUserData {
68            email: payload.email,
69            name: payload.name,
70            password_hash,
71        })
72        .await?;
73
74    let token_pair = create_token_pair(
75        &user.id,
76        &user.email,
77        vec!["admin".to_string()],
78        &state.config,
79    )?;
80
81    Ok(Json(AuthResponse {
82        access_token: token_pair.access_token,
83        refresh_token: token_pair.refresh_token,
84        token_type: token_pair.token_type,
85        expires_in: token_pair.expires_in,
86        user: AuthUserInfo {
87            id: user.id,
88            email: user.email,
89            name: user.name,
90            roles: vec!["admin".to_string()],
91        },
92    }))
93}
94
95#[tokio::main]
96async fn main() {
97    // Make sure AUTH_JWT_SECRET is set in environment
98    // For development, you can use the default, but set it in production!
99    let mut auth_config = AuthConfig::from_env();
100    auth_config.jwt_secret =
101        "rapid-rs-dev-secret-change-me-in-production-make-it-at-least-32-chars".to_string();
102
103    let user_store = InMemoryUserStore::new();
104
105    let app_state = AuthAppState {
106        config: auth_config.clone(),
107        user_store: user_store.clone(),
108    };
109
110    let admin_middleware_routes = Router::new()
111        .route("/middleware/admin", get(middleware_admin))
112        .layer(RequireRoles::any(vec!["admin"]));
113
114    let user_middleware_routes = Router::new()
115        .route("/middleware/user", get(middleware_user))
116        .layer(RequireRoles::any(vec!["user"]));
117
118    // Build routes
119    let protected_routes = Router::new()
120        .route("/protected", get(protected_route))
121        .route("/admin", get(admin_route))
122        .route("/public", get(public_route));
123
124    // Admin Registration Route
125    let auth_extras = Router::new()
126        .route("/auth/register-admin", post(register_admin))
127        .with_state(app_state);
128
129    println!("🔐 Auth API Example");
130    println!("==================");
131    println!();
132    println!("📝 Register a user:");
133    println!("   curl -X POST http://localhost:3000/auth/register -H \"Content-Type: application/json\" -d \"{{\\\"email\\\":\\\"user@example.com\\\",\\\"password\\\":\\\"SecurePass123\\\",\\\"name\\\":\\\"John Doe\\\"}}\"");
134    println!();
135    println!("🔑 Login:");
136    println!("   curl -X POST http://localhost:3000/auth/login -H \"Content-Type: application/json\" -d \"{{\\\"email\\\":\\\"user@example.com\\\",\\\"password\\\":\\\"SecurePass123\\\"}}\"");
137    println!();
138    println!("🔒 Access protected route:");
139    println!("   curl -X GET http://localhost:3000/protected -H \"Authorization: Bearer <access_token>\"");
140    println!();
141    println!("Scenario 1: Regular User");
142    println!("> Register a regular User:");
143    println!("  curl -X POST http://localhost:3000/auth/register -H \"Content-Type: application/json\" -d \"{{\\\"email\\\":\\\"user@example.com\\\",\\\"password\\\":\\\"SecurePass123\\\",\\\"name\\\":\\\"Basic Joe\\\"}}\"");
144    println!();
145    println!("> Test Admin Route (Should fail with code 403 Forbidden):");
146    println!("  curl -X GET http://localhost:3000/middleware/admin -H \"Authorization: Bearer <access_token>\"");
147    println!();
148    println!("Scenario 2: Admin User");
149    println!("> Register an ADMIN User:");
150    println!("  curl -X POST http://localhost:3000/auth/register-admin -H \"Content-Type: application/json\" -d \"{{\\\"email\\\":\\\"admin@example.com\\\",\\\"password\\\":\\\"SecurePass123\\\",\\\"name\\\":\\\"Admin Joe\\\"}}\"");
151    println!();
152    println!("> Test Admin Route (Should succed):");
153    println!("  curl -X GET http://localhost:3000/middleware/admin -H \"Authorization: Bearer <access_token>\"");
154
155    println!();
156
157    let api_routes = Router::new()
158        .merge(protected_routes)
159        .merge(admin_middleware_routes)
160        .merge(user_middleware_routes)
161        .merge(auth_extras)
162        .layer(from_fn_with_state(auth_config.clone(), inject_auth_config));
163
164    App::new()
165        .auto_configure()
166        .mount(auth_routes_with_store(auth_config, user_store))
167        .mount(api_routes)
168        .run()
169        .await
170        .unwrap();
171}