use crate::DbConn; use crate::user; use rocket::http::Status; use rocket::response::status::Custom; use rocket_contrib::json::Json; use serde::{Serialize, Deserialize}; use std::vec::Vec; pub fn routes() -> impl Into> { routes![ auth, auth_change_pw, auth_sign_in, auth_params, auth_ping ] } #[derive(Serialize)] #[serde(untagged)] enum Response { Error { errors: Vec }, Success(T) } // Some shorthands type JsonResp = Json>; fn success_resp(resp: T) -> Custom> { Custom(Status::Ok, Json(Response::Success(resp))) } fn error_resp(status: Status, errors: Vec) -> Custom> { Custom(status, Json(Response::Error { errors })) } #[derive(Serialize)] struct AuthResult { token: String } #[post("/auth", format = "json", data = "")] fn auth(db: DbConn, new_user: Json) -> Custom> { match user::User::create(&db.0, &new_user) { Ok(_) => _sign_in(db, &new_user.email, &new_user.password), Err(user::UserOpError(e)) => error_resp(Status::InternalServerError, vec![e]) } } #[derive(Deserialize)] struct SignInParams { email: String, password: String } #[post("/auth/sign_in", format = "json", data = "")] fn auth_sign_in(db: DbConn, params: Json) -> Custom> { _sign_in(db, ¶ms.email, ¶ms.password) } // Shared logic for all interfaces that needs to do an automatic sign-in fn _sign_in(db: DbConn, mail: &str, passwd: &str) -> Custom> { // Try to find the user first let res = user::User::find_user_by_email(&db, mail) .and_then(|u| u.create_token(passwd)); match res { Ok(token) => success_resp(AuthResult { token }), Err(user::UserOpError(e)) => error_resp(Status::InternalServerError, vec![e]) } } #[derive(Serialize)] struct AuthParams { pw_cost: String, pw_nonce: String, version: String } impl Into for user::User { fn into(self) -> AuthParams { AuthParams { pw_cost: self.pw_cost, pw_nonce: self.pw_nonce, version: self.version } } } #[get("/auth/params?")] fn auth_params(db: DbConn, email: String) -> Custom> { match user::User::find_user_by_email(&db, &email) { Ok(u) => success_resp(u.into()), Err(user::UserOpError(e)) => error_resp(Status::InternalServerError, vec![e]) } } #[derive(Deserialize)] struct ChangePwParams { email: String, password: String, current_password: String } #[post("/auth/change_pw", format = "json", data = "")] fn auth_change_pw(db: DbConn, params: Json) -> Custom> { let res = user::User::find_user_by_email(&db, ¶ms.email) .and_then(|u| u.change_pw(&db, ¶ms.current_password, ¶ms.password)); match res { Ok(_) => Custom(Status::NoContent, Json(Response::Success(()))), Err(user::UserOpError(e)) => error_resp(Status::InternalServerError, vec![e]) } } // For testing the User request guard #[get("/auth/ping")] fn auth_ping(_db: DbConn, u: user::User) -> Custom> { Custom(Status::Ok, Json(Response::Success(u.email))) }