use super::{write_file, Scaffold, ScaffoldArgs, ScaffoldResult};
use anyhow::Result;
pub struct AuthScaffold;
impl Scaffold for AuthScaffold {
fn name(&self) -> &'static str {
"auth"
}
fn description(&self) -> &'static str {
"Full auth system: login/register/logout, password reset, email verify, TOTP"
}
fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
let mut r = ScaffoldResult::default();
let d = args.dry_run;
write_file(
&mut r,
"src/app/controllers/auth_controller.rs",
AUTH_CONTROLLER,
d,
)?;
write_file(
&mut r,
"src/app/controllers/password_reset_controller.rs",
PASSWORD_RESET,
d,
)?;
write_file(
&mut r,
"src/app/controllers/email_verify_controller.rs",
EMAIL_VERIFY,
d,
)?;
write_file(
&mut r,
"src/app/controllers/totp_controller.rs",
TOTP_CONTROLLER,
d,
)?;
write_file(
&mut r,
"src/app/middleware/auth_rate_limit.rs",
AUTH_RATE_LIMIT,
d,
)?;
write_file(&mut r, "tests/auth_flow_test.rs", AUTH_TEST, d)?;
r.warnings
.push("Register auth routes in src/routes/mod.rs".into());
r.warnings
.push("Add JWT_SECRET, MAIL_* vars to .env".into());
Ok(r)
}
}
const AUTH_CONTROLLER: &str = r#"use axum::{extract::State, response::IntoResponse, Json};
use rok_auth::axum::{Ctx, Response};
use rok_validate::Valid;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct RegisterRequest {
pub email: String,
pub password: String,
pub name: String,
}
pub async fn login(Valid(Json(req)): Valid<Json<LoginRequest>>) -> impl IntoResponse {
// TODO: verify credentials, issue JWT
Response::json(serde_json::json!({"message": "Login not yet implemented"}))
}
pub async fn register(Valid(Json(req)): Valid<Json<RegisterRequest>>) -> impl IntoResponse {
// TODO: create user, send verification email
Response::json(serde_json::json!({"message": "Register not yet implemented"}))
}
pub async fn logout(ctx: Ctx) -> impl IntoResponse {
// TODO: blacklist token
Response::json(serde_json::json!({"message": "logged out"}))
}
pub async fn refresh(ctx: Ctx) -> impl IntoResponse {
// TODO: issue new access token
Response::json(serde_json::json!({"message": "Refresh not yet implemented"}))
}
"#;
const PASSWORD_RESET: &str = r#"use axum::{response::IntoResponse, Json};
use rok_auth::axum::Response;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ForgotRequest { pub email: String }
#[derive(Deserialize)]
pub struct ResetRequest { pub token: String, pub password: String }
pub async fn forgot(Json(req): Json<ForgotRequest>) -> impl IntoResponse {
// TODO: send reset email via rok-mail
Response::json(serde_json::json!({"message": "Reset email sent"}))
}
pub async fn reset(Json(req): Json<ResetRequest>) -> impl IntoResponse {
// TODO: verify token and update password
Response::json(serde_json::json!({"message": "Password updated"}))
}
"#;
const EMAIL_VERIFY: &str = r#"use axum::{extract::Query, response::IntoResponse};
use rok_auth::axum::Response;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct VerifyQuery { pub token: String }
pub async fn verify(Query(q): Query<VerifyQuery>) -> impl IntoResponse {
// TODO: verify email token and mark user as verified
Response::json(serde_json::json!({"message": "Email verified"}))
}
pub async fn resend(ctx: rok_auth::axum::Ctx) -> impl IntoResponse {
// TODO: resend verification email
Response::json(serde_json::json!({"message": "Verification email resent"}))
}
"#;
const TOTP_CONTROLLER: &str = r#"use axum::{response::IntoResponse, Json};
use rok_auth::axum::{Ctx, Response};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct TotpVerifyRequest { pub code: String }
pub async fn setup(ctx: Ctx) -> impl IntoResponse {
// TODO: generate TOTP secret, return QR code URI
Response::json(serde_json::json!({"qr_uri": "otpauth://totp/..."}))
}
pub async fn enable(ctx: Ctx, Json(req): Json<TotpVerifyRequest>) -> impl IntoResponse {
// TODO: verify code and enable TOTP
Response::json(serde_json::json!({"message": "TOTP enabled"}))
}
pub async fn disable(ctx: Ctx, Json(req): Json<TotpVerifyRequest>) -> impl IntoResponse {
// TODO: verify code and disable TOTP
Response::json(serde_json::json!({"message": "TOTP disabled"}))
}
"#;
const AUTH_RATE_LIMIT: &str = r#"use axum::{extract::Request, middleware::Next, response::Response};
use rok_problem::Problem;
pub async fn auth_rate_limit(req: Request, next: Next) -> Result<Response, Problem> {
// TODO: integrate with rok-cache for sliding window rate limiting
// Example: 10 requests per minute per IP for auth endpoints
Ok(next.run(req).await)
}
"#;
const AUTH_TEST: &str = r#"#[cfg(test)]
mod tests {
#[tokio::test]
async fn test_login_invalid_credentials() {
// TODO: set up test app, POST /auth/login with wrong password, expect 401
}
#[tokio::test]
async fn test_register_creates_user() {
// TODO: POST /auth/register, verify user in DB, expect 201
}
#[tokio::test]
async fn test_logout_blacklists_token() {
// TODO: login, logout, retry with old token, expect 401
}
}
"#;