use axum::{response::Response, routing::post, Router}; use serde::Deserialize; use time::{Duration, OffsetDateTime}; use tower_cookies::{Cookie, Cookies}; use crate::{ public::docs::{EndpointDocumentation, EndpointMethod}, types::{ extract::{AuthorizedUser, Check, CheckResult, Database, Json, Log}, http::ResponseCode, session::Session, user::User, }, }; pub const AUTH_REGISTER: EndpointDocumentation = EndpointDocumentation { uri: "/api/auth/register", method: EndpointMethod::Post, description: "Registeres a new account", body: Some( r#" { "firstname": "[Object]", "lastname": "object]", "email": "object@object.object", "password": "i love js", "gender": "object", "day": 1, "month": 1, "year": 1970 } "#, ), responses: &[ (201, "Successfully registered new user"), (400, "Body does not match parameters"), ], cookie: None, }; #[derive(Deserialize, Debug)] pub struct RegistrationRequet { pub firstname: String, pub lastname: String, pub email: String, pub password: String, pub gender: String, pub day: u8, pub month: u8, pub year: u32, } impl Check for RegistrationRequet { fn check(&self) -> CheckResult { Self::assert_length( &self.firstname, 1, 20, "First name can only by 1-20 characters long", )?; Self::assert_length( &self.lastname, 1, 20, "Last name can only by 1-20 characters long", )?; Self::assert_length(&self.email, 1, 50, "Email can only by 1-50 characters long")?; Self::assert_length( &self.password, 1, 50, "Password can only by 1-50 characters long", )?; Self::assert_length( &self.gender, 1, 100, "Gender can only by 1-100 characters long", )?; Self::assert_range( u64::from(self.day), 1, 255, "Birthday day can only be between 1-255", )?; Self::assert_range( u64::from(self.month), 1, 255, "Birthday month can only be between 1-255", )?; Self::assert_range( u64::from(self.year), 1, 4_294_967_295, "Birthday year can only be between 1-4294967295", )?; Ok(()) } } async fn register( cookies: Cookies, Database(db): Database, Json(body): Json, ) -> Response { let user = match User::new(&db, body) { Ok(user) => user, Err(err) => return err, }; let session = match Session::new(&db, user.user_id) { Ok(session) => session, Err(err) => return err, }; let mut now = OffsetDateTime::now_utc(); now += Duration::weeks(52); let mut cookie = Cookie::new("auth", session.token); cookie.set_secure(false); cookie.set_http_only(false); cookie.set_expires(now); cookie.set_path("/"); cookies.add(cookie); ResponseCode::Created.text("Successfully created new user, auth cookie is returned") } pub const AUTH_LOGIN: EndpointDocumentation = EndpointDocumentation { uri: "/api/auth/login", method: EndpointMethod::Post, description: "Logs into an existing account", body: Some( r#" { "email": "object@object.object", "password": "i love js" } "#, ), responses: &[ (200, "Successfully logged in, auth cookie is returned"), ( 400, "Body does not match parameters, or invalid email password combination", ), ], cookie: None, }; #[derive(Deserialize)] struct LoginRequest { email: String, password: String, } impl Check for LoginRequest { fn check(&self) -> CheckResult { Ok(()) } } async fn login( cookies: Cookies, Database(db): Database, Json(body): Json, ) -> Response { let Ok(user) = User::from_email(&db, &body.email) else { return ResponseCode::BadRequest.text("Email is not registered") }; if user.password != body.password { return ResponseCode::BadRequest.text("Password is not correct"); } let session = match Session::new(&db, user.user_id) { Ok(session) => session, Err(err) => return err, }; let mut now = OffsetDateTime::now_utc(); now += Duration::weeks(52); let mut cookie = Cookie::new("auth", session.token); cookie.set_secure(false); cookie.set_http_only(false); cookie.set_expires(now); cookie.set_path("/"); cookies.add(cookie); ResponseCode::Success.text("Successfully logged in") } pub const AUTH_LOGOUT: EndpointDocumentation = EndpointDocumentation { uri: "/api/auth/logout", method: EndpointMethod::Post, description: "Logs out of a logged in account", body: None, responses: &[ (200, "Successfully logged out"), (401, "Unauthorized"), (500, "Failed to log out user"), ], cookie: None, }; async fn logout( cookies: Cookies, AuthorizedUser(user): AuthorizedUser, Database(db): Database, _: Log, ) -> Response { cookies.remove(Cookie::new("auth", "")); if let Err(err) = Session::delete(&db, user.user_id) { return err; } ResponseCode::Success.text("Successfully logged out") } pub fn router() -> Router { Router::new() .route("/register", post(register)) .route("/login", post(login)) .route("/logout", post(logout)) }