|
|
@@ -1,161 +0,0 @@
|
|
|
-use actix_web::cookie::{Cookie, SameSite};
|
|
|
-use actix_web::{get, post, web, HttpResponse};
|
|
|
-use argon2::{
|
|
|
- password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
|
|
- Argon2,
|
|
|
-};
|
|
|
-use jsonwebtoken::{encode, EncodingKey, Header};
|
|
|
-use serde::Deserialize;
|
|
|
-use serde_json::json;
|
|
|
-use sqlx::PgPool;
|
|
|
-use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
-use validator::Validate;
|
|
|
-
|
|
|
-use crate::auth::{AuthUser, Claims};
|
|
|
-use crate::models::user::User;
|
|
|
-use crate::Config;
|
|
|
-
|
|
|
-#[derive(Deserialize, Validate)]
|
|
|
-pub struct CreateUserInput {
|
|
|
- pub name: String,
|
|
|
- #[validate(email(message = "Invalid email address"))]
|
|
|
- pub email: String,
|
|
|
- pub password: String,
|
|
|
- pub confirm_password: String,
|
|
|
-}
|
|
|
-
|
|
|
-#[post("/user")]
|
|
|
-pub async fn create_user(
|
|
|
- pool: web::Data<PgPool>,
|
|
|
- body: web::Json<CreateUserInput>,
|
|
|
-) -> HttpResponse {
|
|
|
- if body.validate().is_err() {
|
|
|
- return HttpResponse::BadRequest().json(json!({"error": "Invalid email address"}));
|
|
|
- }
|
|
|
-
|
|
|
- if body.password != body.confirm_password {
|
|
|
- return HttpResponse::BadRequest().json(json!({"error": "Passwords do not match"}));
|
|
|
- }
|
|
|
-
|
|
|
- if body.password.chars().count() < 12 {
|
|
|
- return HttpResponse::BadRequest()
|
|
|
- .json(json!({"error": "Password must be at least 12 characters"}));
|
|
|
- }
|
|
|
-
|
|
|
- let email = body.email.to_lowercase();
|
|
|
-
|
|
|
- let salt = SaltString::generate(&mut OsRng);
|
|
|
- let pass_hash = match Argon2::default().hash_password(body.password.as_bytes(), &salt) {
|
|
|
- Ok(h) => h.to_string(),
|
|
|
- Err(_) => {
|
|
|
- return HttpResponse::InternalServerError()
|
|
|
- .json(json!({"error": "Failed to hash password"}))
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- match User::create(pool.get_ref(), &body.name, &email, &pass_hash).await {
|
|
|
- Ok(_) => HttpResponse::Created().json(json!({"success": true})),
|
|
|
- Err(sqlx::Error::Database(e)) if e.code().as_deref() == Some("23505") => {
|
|
|
- HttpResponse::Conflict().json(json!({"error": "Email already in use"}))
|
|
|
- }
|
|
|
- Err(_) => {
|
|
|
- HttpResponse::InternalServerError().json(json!({"error": "Failed to create user"}))
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize)]
|
|
|
-pub struct LoginInput {
|
|
|
- pub email: String,
|
|
|
- pub password: String,
|
|
|
-}
|
|
|
-
|
|
|
-#[post("/user/login")]
|
|
|
-pub async fn login(
|
|
|
- pool: web::Data<PgPool>,
|
|
|
- config: web::Data<Config>,
|
|
|
- body: web::Json<LoginInput>,
|
|
|
-) -> HttpResponse {
|
|
|
- let email = body.email.to_lowercase();
|
|
|
-
|
|
|
- let user = match User::find_by_email(pool.get_ref(), &email).await {
|
|
|
- Ok(Some(u)) => u,
|
|
|
- Ok(None) => {
|
|
|
- return HttpResponse::Unauthorized()
|
|
|
- .json(json!({"error": "Invalid email or password"}))
|
|
|
- }
|
|
|
- Err(_) => {
|
|
|
- return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- let parsed_hash = match PasswordHash::new(&user.pass_hash) {
|
|
|
- Ok(h) => h,
|
|
|
- Err(_) => {
|
|
|
- return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- if Argon2::default()
|
|
|
- .verify_password(body.password.as_bytes(), &parsed_hash)
|
|
|
- .is_err()
|
|
|
- {
|
|
|
- return HttpResponse::Unauthorized().json(json!({"error": "Invalid email or password"}));
|
|
|
- }
|
|
|
-
|
|
|
- let exp = (SystemTime::now()
|
|
|
- .duration_since(UNIX_EPOCH)
|
|
|
- .unwrap()
|
|
|
- .as_secs()
|
|
|
- + 7 * 24 * 60 * 60) as usize;
|
|
|
-
|
|
|
- let claims = Claims {
|
|
|
- id: user.id.to_string(),
|
|
|
- uuid_key: user.uuid_key.to_string(),
|
|
|
- exp,
|
|
|
- };
|
|
|
-
|
|
|
- let token = match encode(
|
|
|
- &Header::default(),
|
|
|
- &claims,
|
|
|
- &EncodingKey::from_secret(config.jwt_secret.as_bytes()),
|
|
|
- ) {
|
|
|
- Ok(t) => t,
|
|
|
- Err(_) => {
|
|
|
- return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- let cookie = Cookie::build("token", token)
|
|
|
- .http_only(true)
|
|
|
- .secure(config.is_production)
|
|
|
- .same_site(SameSite::Strict)
|
|
|
- .path("/")
|
|
|
- .max_age(actix_web::cookie::time::Duration::days(7))
|
|
|
- .finish();
|
|
|
-
|
|
|
- HttpResponse::Ok().cookie(cookie).json(json!({"success": true}))
|
|
|
-}
|
|
|
-
|
|
|
-#[get("/user")]
|
|
|
-pub async fn get_user(auth: AuthUser) -> HttpResponse {
|
|
|
- let user = auth.0;
|
|
|
- HttpResponse::Ok().json(json!({
|
|
|
- "id": user.id,
|
|
|
- "name": user.name,
|
|
|
- "email": user.email,
|
|
|
- }))
|
|
|
-}
|
|
|
-
|
|
|
-#[post("/user/logout")]
|
|
|
-pub async fn logout(config: web::Data<Config>) -> HttpResponse {
|
|
|
- let cookie = Cookie::build("token", "")
|
|
|
- .http_only(true)
|
|
|
- .secure(config.is_production)
|
|
|
- .same_site(SameSite::Strict)
|
|
|
- .path("/")
|
|
|
- .max_age(actix_web::cookie::time::Duration::ZERO)
|
|
|
- .finish();
|
|
|
-
|
|
|
- HttpResponse::Ok().cookie(cookie).json(json!({"success": true}))
|
|
|
-}
|