use crate::errors::ApiError; pub use crate::services::register::*; use api::http::{Json, State}; use api::middleware::RequestId; use api::rest::{AppResponseData, NoData}; use session::Session; #[utoipa::path( post, path = "/auth/register/code", tag = "auth", operation_id = "auth_register_code", summary = "发送注册验证码", description = "校验工作量证明(payload = 邮箱的 UTF-8 字节)与邮箱格式后,把 6 位验证码写入缓存并寄出。缓存键 `auth:code:register:{email}`,有效期由 `verify_code_ttl_secs` 决定,发码限流键 `auth:rate:register:{email}`。", request_body = RegisterCodeRequest, responses( ( status = 200, description = "验证码已写入缓存并寄出", body = AppResponseData, example = json!({"code":200,"message":"success","data":{"expires_in_secs":600}}) ), ( status = 400, description = "`invalid_email`(邮箱格式)/ `expired`(挑战过期)/ `invalid_payload`(payload 与邮箱不一致)/ `invalid_proof`(未满足难度)", body = AppResponseData, example = json!({"code": "invalid_email", "message": "email is invalid"}) ), ( status = 409, description = "`email_taken`(邮箱已注册)", body = AppResponseData, example = json!({"code": "email_taken", "message": "email is already taken"}) ), ( status = 429, description = "`rate_limited`(同一邮箱发码过于频繁)", body = AppResponseData, example = json!({"code": "rate_limited", "message": "too many requests, retry after 60s"}) ), ( status = 500, description = "`email`(投递失败)/ `cache` / `session` / `serialization` / `internal`", body = AppResponseData, example = json!({"code": "internal", "message": "internal server error"}) ) ) )] pub async fn register_code( State(svc): State, session: Session, body: Json, request_id: RequestId, ) -> Result, ApiError> { let response = svc .send_register_code(session, body.into_inner(), request_id.0) .await?; Ok(AppResponseData::ok(response)) } #[utoipa::path( post, path = "/auth/register", tag = "auth", operation_id = "auth_register", summary = "提交注册", description = "校验工作量证明(payload = 用户名的 UTF-8 字节)、用户名/展示名/出生日期/邮箱、邮箱验证码(键 `auth:code:register:{email}`,尝试上限 `max_verify_attempts`)后,Argon2 重哈希口令并创建用户与初始档案。成功后写会话(user_id、security_version、renew)并记审计事件。", request_body = UserRegisterRequest, responses( ( status = 200, description = "注册成功且已登录", body = AppResponseData, example = json!({"code":200,"message":"success","data":{"user_id":1234567,"username":"alice","displayname":"Alice"}}) ), ( status = 400, description = "`invalid_username` / `invalid_displayname` / `invalid_email` / `invalid_birth_date` / `invalid_password` / `invalid_email_code`(验证码错误或已过期)/ `expired` / `invalid_payload` / `invalid_proof`", body = AppResponseData, example = json!({"code": "invalid_email_code", "message": "email verification code is invalid"}) ), ( status = 403, description = "`under_minimum_age`(未满最小年龄)", body = AppResponseData, example = json!({"code": "under_minimum_age", "message": "you must be at least 13 years old"}) ), ( status = 409, description = "`username_taken` / `email_taken`", body = AppResponseData, example = json!({"code": "username_taken", "message": "username is already taken"}) ), ( status = 429, description = "`email_code_attempts_exceeded`(验证码尝试次数用尽)", body = AppResponseData, example = json!({"code": "email_code_attempts_exceeded", "message": "too many invalid email verification attempts"}) ), ( status = 500, description = "`password_hash`(口令重哈希失败)/ `database` / `cache` / `session` / `serialization` / `internal`", body = AppResponseData, example = json!({"code": "internal", "message": "internal server error"}) ) ) )] pub async fn register( State(svc): State, session: Session, body: Json, request_id: RequestId, ) -> Result, ApiError> { let user_agent = session.user_agent()?; let ip_addr = session.client_ip()?; let response = svc .register( session, body.into_inner(), user_agent, ip_addr, request_id.0, ) .await?; Ok(AppResponseData::ok(response)) }