feat: auth functionality
This commit is contained in:
Generated
+16
@@ -1765,12 +1765,14 @@ dependencies = [
|
||||
"jsonwebtoken",
|
||||
"lettre",
|
||||
"log",
|
||||
"rand 0.9.0",
|
||||
"redis",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"surrealdb",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
]
|
||||
@@ -4143,6 +4145,20 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
|
||||
@@ -19,6 +19,8 @@ lettre = { version = "0.11.12", features = ["tokio1-native-tls"] }
|
||||
surrealdb = { version = "2.2.1", features = ["protocol-http"] }
|
||||
thiserror = "2.0.11"
|
||||
anyhow = "1.0.97"
|
||||
rand = "0.9.0"
|
||||
tower-http = { version = "0.6.2", features = ["cors"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
+31
-2
@@ -1,5 +1,9 @@
|
||||
use crate::{AppState, RedisClient, SurrealClient};
|
||||
use axum::{Extension, Router};
|
||||
use crate::{AppState, Env, RedisClient, SurrealClient};
|
||||
use axum::{
|
||||
http::{header, HeaderValue, Method},
|
||||
Extension, Router,
|
||||
};
|
||||
use tower_http::cors::CorsLayer;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
pub mod v1;
|
||||
@@ -7,9 +11,34 @@ pub mod v2;
|
||||
|
||||
pub async fn apps(surrealdb: SurrealClient, redisdb: RedisClient) -> Router {
|
||||
let state = AppState { surrealdb, redisdb };
|
||||
let env = Env::new();
|
||||
let cors_origins = match env.rust_env.as_str() {
|
||||
"development" => vec!["http://localhost:5173"],
|
||||
"production" => {
|
||||
vec!["https://gacha.imphnen.dev", "https://imphnen.dev"]
|
||||
}
|
||||
_ => vec![
|
||||
"http://localhost:5173",
|
||||
"https://gacha.imphnen.dev",
|
||||
"https://imphnen.dev",
|
||||
],
|
||||
};
|
||||
|
||||
let allowed_origins: Vec<HeaderValue> = cors_origins
|
||||
.into_iter()
|
||||
.filter_map(|origin| origin.parse::<HeaderValue>().ok())
|
||||
.collect();
|
||||
|
||||
let cors_middleware = CorsLayer::new()
|
||||
.allow_origin(allowed_origins)
|
||||
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
|
||||
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE])
|
||||
.allow_credentials(true);
|
||||
|
||||
Router::new()
|
||||
.nest("/v1", v1::routes().await)
|
||||
.nest("/v2", v2::routes().await)
|
||||
.merge(SwaggerUi::new("/docs").url("/openapi.json", v1::docs_router()))
|
||||
.layer(cors_middleware)
|
||||
.layer(Extension(state))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::{AuthLoginRequestDto, AuthRegisterRequestDto, AuthService};
|
||||
use super::{
|
||||
AuthLoginRequestDto, AuthRegisterRequestDto, AuthResendOtpRequestDto, AuthService,
|
||||
AuthVerifyEmailRequestDto,
|
||||
};
|
||||
use crate::{v1::AuthLoginResponsetDto, AppState};
|
||||
use axum::{response::IntoResponse, Extension, Json};
|
||||
|
||||
use crate::{MessageResponseDto, ResponseSuccessDto};
|
||||
use axum::{response::IntoResponse, Extension, Json};
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
@@ -10,7 +12,7 @@ use crate::{MessageResponseDto, ResponseSuccessDto};
|
||||
request_body = AuthLoginRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = ResponseSuccessDto<AuthLoginResponsetDto>),
|
||||
(status = 401, description = "Unauthorized", body = MessageResponseDto)
|
||||
(status = 401, description = "Login failed", body = MessageResponseDto)
|
||||
),
|
||||
tag = "Authentication"
|
||||
)]
|
||||
@@ -26,8 +28,8 @@ pub async fn post_login(
|
||||
path = "/v1/auth/register",
|
||||
request_body = AuthRegisterRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = MessageResponseDto),
|
||||
(status = 401, description = "Unauthorized", body = MessageResponseDto)
|
||||
(status = 200, description = "Register successful", body = MessageResponseDto),
|
||||
(status = 401, description = "Register failed", body = MessageResponseDto)
|
||||
),
|
||||
tag = "Authentication"
|
||||
)]
|
||||
@@ -37,3 +39,54 @@ pub async fn post_register(
|
||||
) -> impl IntoResponse {
|
||||
AuthService::mutation_register(payload, &state).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/auth/verify",
|
||||
request_body = AuthVerifyEmailRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Verify email successful", body = MessageResponseDto),
|
||||
(status = 401, description = "Verify email failed", body = MessageResponseDto)
|
||||
),
|
||||
tag = "Authentication"
|
||||
)]
|
||||
pub async fn post_verify_email(
|
||||
Extension(state): Extension<AppState>,
|
||||
Json(payload): Json<AuthVerifyEmailRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
AuthService::mutation_verify_email(payload, &state).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/auth/resend",
|
||||
request_body = AuthResendOtpRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Resend otp successful", body = MessageResponseDto),
|
||||
(status = 401, description = "Resend otp failed", body = MessageResponseDto)
|
||||
),
|
||||
tag = "Authentication"
|
||||
)]
|
||||
pub async fn post_resend_otp(
|
||||
Extension(state): Extension<AppState>,
|
||||
Json(payload): Json<AuthResendOtpRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
AuthService::mutation_resend_otp(payload, &state).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/auth/forgot",
|
||||
request_body = AuthResendOtpRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Forgot password request successful", body = MessageResponseDto),
|
||||
(status = 401, description = "Forgot password request failed", body = MessageResponseDto)
|
||||
),
|
||||
tag = "Authentication"
|
||||
)]
|
||||
pub async fn post_forgot_password(
|
||||
Extension(state): Extension<AppState>,
|
||||
Json(payload): Json<AuthResendOtpRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
AuthService::mutation_forgot_password(payload, &state).await
|
||||
}
|
||||
|
||||
@@ -34,6 +34,29 @@ pub struct AuthActiveInactiveRequestDto {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AuthVerifyEmailRequestDto {
|
||||
pub email: String,
|
||||
pub otp: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AuthResendOtpRequestDto {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AuthNewPasswordRequestDto {
|
||||
pub token: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AuthSetNewPasswordRequestDto {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AuthQueryByEmailResponseDto {
|
||||
pub email: String,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::{AuthActiveInactiveRequestDto, AuthRegisterRequestDto};
|
||||
use super::{
|
||||
AuthActiveInactiveRequestDto, AuthRegisterRequestDto, AuthSetNewPasswordRequestDto,
|
||||
};
|
||||
use crate::{
|
||||
v1::{users_schema::UsersSchema, UsersItemDto},
|
||||
AppState, RedisKeyEnum, ResourceEnum,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use redis::Commands;
|
||||
|
||||
pub struct AuthRepository<'a> {
|
||||
@@ -45,12 +47,62 @@ impl<'a> AuthRepository<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_get_stored_otp(&self, email: String) -> Result<u32> {
|
||||
let redis_key = format!("{}:{}", RedisKeyEnum::Otp, email);
|
||||
let mut conn = match self.state.redisdb.get_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("Failed to get Redis connection: {}", e))
|
||||
}
|
||||
};
|
||||
let data: Option<String> = match conn.get(&redis_key) {
|
||||
Ok(data) => data,
|
||||
Err(e) => return Err(anyhow::anyhow!("Failed to get data from Redis: {}", e)),
|
||||
};
|
||||
match data {
|
||||
Some(otp_str) => match otp_str.parse::<u32>() {
|
||||
Ok(otp) => Ok(otp),
|
||||
Err(e) => Err(anyhow::anyhow!("Failed to parse OTP as u64: {}", e)),
|
||||
},
|
||||
None => Err(anyhow::anyhow!("No stored OTP found")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_store_otp(&self, email: String, otp: u32) -> Result<String> {
|
||||
let redis_key: String = format!("{}:{}", RedisKeyEnum::Otp, email);
|
||||
let mut conn = match self.state.redisdb.get_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => return Err(anyhow!("Failed to get Redis connection: {}", e)),
|
||||
};
|
||||
let otp_str: String = otp.to_string();
|
||||
match conn.set_ex::<_, _, ()>(&redis_key, &otp_str, 300) {
|
||||
Ok(_) => Ok("Success store otp".to_string()),
|
||||
Err(e) => Err(anyhow!("Failed to store OTP in Redis: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_delete_stored_otp(&self, email: String) -> Result<String> {
|
||||
let redis_key = format!("{}:{}", RedisKeyEnum::Otp, email);
|
||||
let mut conn = match self.state.redisdb.get_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => return Err(anyhow!("Failed to get Redis connection: {}", e)),
|
||||
};
|
||||
match conn.del::<_, ()>(&redis_key) {
|
||||
Ok(_) => Ok("Successfully deleted OTP".to_string()),
|
||||
Err(e) => Err(anyhow!("Failed to delete OTP from Redis: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_user_by_email(&self, email: String) -> Result<UsersSchema> {
|
||||
let db = &self.state.surrealdb;
|
||||
let result = db.select((ResourceEnum::Users.to_string(), email)).await?;
|
||||
let result = db
|
||||
.select((ResourceEnum::Users.to_string(), email.clone()))
|
||||
.await?;
|
||||
match result {
|
||||
Some(response) => Ok(response),
|
||||
None => bail!("User not found"),
|
||||
None => {
|
||||
bail!("User not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,13 +131,34 @@ impl<'a> AuthRepository<'a> {
|
||||
data: AuthActiveInactiveRequestDto,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb;
|
||||
let record: Option<UsersItemDto> = db
|
||||
let record: Option<AuthActiveInactiveRequestDto> = db
|
||||
.update((ResourceEnum::Users.to_string(), &data.email))
|
||||
.content(data)
|
||||
.merge(AuthActiveInactiveRequestDto {
|
||||
email: data.email.clone(),
|
||||
is_active: data.is_active.clone(),
|
||||
})
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_password_user(
|
||||
&self,
|
||||
data: AuthSetNewPasswordRequestDto,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb;
|
||||
let record: Option<AuthSetNewPasswordRequestDto> = db
|
||||
.update((ResourceEnum::Users.to_string(), &data.email))
|
||||
.merge(AuthSetNewPasswordRequestDto {
|
||||
email: data.email.clone(),
|
||||
password: data.password.clone(),
|
||||
})
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update password user".into()),
|
||||
None => bail!("Failed to update password user"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use axum::{http::StatusCode, response::Response};
|
||||
|
||||
use super::{
|
||||
AuthLoginRequestDto, AuthLoginResponsetDto, AuthRegisterRequestDto,
|
||||
AuthRepository, TokenDto,
|
||||
AuthActiveInactiveRequestDto, AuthLoginRequestDto, AuthLoginResponsetDto,
|
||||
AuthRegisterRequestDto, AuthRepository, AuthResendOtpRequestDto,
|
||||
AuthVerifyEmailRequestDto, TokenDto,
|
||||
};
|
||||
use crate::{
|
||||
common_response, encode_access_token, encode_refresh_token, hash_password,
|
||||
success_response, v1::UsersItemDto, verify_password, AppState, ResponseSuccessDto,
|
||||
common_response, encode_access_token, encode_refresh_token, generate_otp,
|
||||
hash_password, send_email, success_response, v1::UsersItemDto, verify_password,
|
||||
AppState, Env, ResponseSuccessDto,
|
||||
};
|
||||
|
||||
pub struct AuthService;
|
||||
@@ -17,40 +19,65 @@ impl AuthService {
|
||||
state: &AppState,
|
||||
) -> Response {
|
||||
let repository = AuthRepository::new(state);
|
||||
|
||||
match repository.query_user_by_email(payload.email.clone()).await {
|
||||
Ok(user) => {
|
||||
let is_password_correct =
|
||||
verify_password(&payload.password, &user.password).unwrap_or(false);
|
||||
|
||||
if is_password_correct {
|
||||
common_response(StatusCode::BAD_REQUEST, "Email or password not correct");
|
||||
if !is_password_correct {
|
||||
return common_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Email or password not correct",
|
||||
);
|
||||
}
|
||||
|
||||
let access_token = encode_access_token(payload.email.clone());
|
||||
let refresh_token = encode_refresh_token(payload.email.clone());
|
||||
if !user.is_active {
|
||||
return common_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Account not active, please verify your email",
|
||||
);
|
||||
}
|
||||
|
||||
let access_token = match encode_access_token(payload.email.clone()) {
|
||||
Ok(token) => token,
|
||||
Err(_) => {
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to generate access token",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let refresh_token = match encode_refresh_token(payload.email.clone()) {
|
||||
Ok(token) => token,
|
||||
Err(_) => {
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to generate refresh token",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let response = ResponseSuccessDto {
|
||||
data: AuthLoginResponsetDto {
|
||||
user: UsersItemDto {
|
||||
fullname: user.fullname.clone(),
|
||||
email: user.email.clone(),
|
||||
is_active: user.is_active.clone(),
|
||||
is_active: user.is_active,
|
||||
},
|
||||
token: TokenDto {
|
||||
access_token: access_token.unwrap(),
|
||||
refresh_token: refresh_token.unwrap(),
|
||||
access_token,
|
||||
refresh_token,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if !repository
|
||||
.query_store_user_data(AuthRegisterRequestDto {
|
||||
fullname: user.fullname,
|
||||
password: user.password,
|
||||
email: user.email,
|
||||
})
|
||||
.is_ok()
|
||||
{
|
||||
if let Err(_) = repository.query_store_user_data(AuthRegisterRequestDto {
|
||||
fullname: user.fullname,
|
||||
password: user.password,
|
||||
email: user.email,
|
||||
}) {
|
||||
return common_response(StatusCode::BAD_REQUEST, "Failed to store data");
|
||||
}
|
||||
|
||||
@@ -89,6 +116,16 @@ impl AuthService {
|
||||
fullname: payload.fullname,
|
||||
};
|
||||
|
||||
let otp = generate_otp::OtpManager::generate_otp();
|
||||
|
||||
repository
|
||||
.query_store_otp(new_user.email.clone(), otp.clone())
|
||||
.unwrap();
|
||||
|
||||
let message = format!("your otp code is {}", otp);
|
||||
|
||||
send_email(&new_user.email.clone(), "OTP Verification", &message).unwrap();
|
||||
|
||||
match repository.query_create_user(new_user).await {
|
||||
Ok(_) => common_response(StatusCode::CREATED, "Registration successful"),
|
||||
Err(err) => {
|
||||
@@ -96,4 +133,98 @@ impl AuthService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mutation_resend_otp(
|
||||
payload: AuthResendOtpRequestDto,
|
||||
state: &AppState,
|
||||
) -> Response {
|
||||
let repository = AuthRepository::new(state);
|
||||
let otp = generate_otp::OtpManager::generate_otp();
|
||||
let message = format!("Your OTP code is {}", otp);
|
||||
match repository.query_store_otp(payload.email.clone(), otp) {
|
||||
Ok(_) => match send_email(&payload.email, "OTP Verification", &message) {
|
||||
Ok(_) => common_response(StatusCode::OK, "OTP resent successfully"),
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
},
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mutation_forgot_password(
|
||||
payload: AuthResendOtpRequestDto,
|
||||
state: &AppState,
|
||||
) -> Response {
|
||||
let repository = AuthRepository::new(state);
|
||||
if repository
|
||||
.query_user_by_email(payload.email.clone())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return common_response(StatusCode::BAD_REQUEST, "User not found");
|
||||
}
|
||||
let token = match encode_access_token(payload.email.clone()) {
|
||||
Ok(token) => token,
|
||||
Err(_) => {
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to generate access token",
|
||||
)
|
||||
}
|
||||
};
|
||||
let env = Env::new();
|
||||
let fe_url = env.fe_url;
|
||||
let message = format!(
|
||||
"You have requested a password reset. Please click the link below to continue: {}/auth/reset-password?token={}",
|
||||
fe_url, token
|
||||
);
|
||||
|
||||
match send_email(&payload.email, "Reset Password Request", &message) {
|
||||
Ok(_) => common_response(StatusCode::OK, "Reset Password request send"),
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mutation_verify_email(
|
||||
payload: AuthVerifyEmailRequestDto,
|
||||
state: &AppState,
|
||||
) -> Response {
|
||||
let repository = AuthRepository::new(state);
|
||||
match repository.query_get_stored_otp(payload.email.clone()) {
|
||||
Ok(stored_otp) => {
|
||||
let user_otp = payload.otp;
|
||||
let is_otp_valid = stored_otp == user_otp;
|
||||
if is_otp_valid {
|
||||
match repository
|
||||
.query_active_inactive_user(AuthActiveInactiveRequestDto {
|
||||
email: payload.email.clone(),
|
||||
is_active: true,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
if let Err(e) =
|
||||
repository.query_delete_stored_otp(payload.email.clone())
|
||||
{
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("Failed to delete OTP: {}", e),
|
||||
);
|
||||
}
|
||||
common_response(StatusCode::OK, "Email verified successfully")
|
||||
}
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = repository.query_delete_stored_otp(payload.email.clone()) {
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("Failed to delete OTP: {}", e),
|
||||
);
|
||||
}
|
||||
common_response(StatusCode::BAD_REQUEST, "Failed to verify OTP")
|
||||
}
|
||||
}
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,7 @@ pub fn auth_router() -> Router {
|
||||
Router::new()
|
||||
.route("/login", post(auth_controller::post_login))
|
||||
.route("/register", post(auth_controller::post_register))
|
||||
.route("/verify", post(auth_controller::post_verify_email))
|
||||
.route("/resend", post(auth_controller::post_resend_otp))
|
||||
.route("/forgot", post(auth_controller::post_forgot_password))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
v1::{
|
||||
auth, gacha, AuthLoginRequestDto, AuthLoginResponsetDto,
|
||||
GachaCreateClaimRequestDto, GachaCreateItemRequestDto,
|
||||
GachaCreateRollRequestDto,
|
||||
AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, GachaCreateClaimRequestDto,
|
||||
GachaCreateItemRequestDto, GachaCreateRollRequestDto,
|
||||
},
|
||||
MessageResponseDto, MetaRequestDto, MetaResponseDto, ResponseSuccessDto,
|
||||
};
|
||||
@@ -17,6 +17,8 @@ use utoipa::{
|
||||
paths(
|
||||
auth::auth_controller::post_login,
|
||||
auth::auth_controller::post_register,
|
||||
auth::auth_controller::post_verify_email,
|
||||
auth::auth_controller::post_resend_otp,
|
||||
gacha::gacha_controller::post_create_gacha_claim,
|
||||
gacha::gacha_controller::post_create_gacha_item,
|
||||
gacha::gacha_controller::post_create_gacha_roll
|
||||
@@ -28,6 +30,8 @@ use utoipa::{
|
||||
MessageResponseDto,
|
||||
AuthLoginRequestDto,
|
||||
AuthLoginResponsetDto,
|
||||
AuthVerifyEmailRequestDto,
|
||||
AuthResendOtpRequestDto,
|
||||
ResponseSuccessDto<AuthLoginResponsetDto>,
|
||||
GachaCreateClaimRequestDto,
|
||||
GachaCreateItemRequestDto,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use rand::{rng, Rng};
|
||||
|
||||
pub struct OtpManager;
|
||||
|
||||
impl OtpManager {
|
||||
pub fn generate_otp() -> u32 {
|
||||
rng().random_range(100_000..1_000_000)
|
||||
}
|
||||
|
||||
pub fn validate_otp(stored_otp: u32, user_otp: u32) -> bool {
|
||||
stored_otp == user_otp
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod extract_email;
|
||||
pub mod generate_otp;
|
||||
pub mod response_format;
|
||||
|
||||
pub use extract_email::*;
|
||||
pub use generate_otp::*;
|
||||
pub use response_format::*;
|
||||
|
||||
Reference in New Issue
Block a user