diff --git a/src/main.rs b/src/main.rs index d4ce8ab..03b99ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ mod cases; -mod inventories; mod items; mod types; mod users; @@ -44,6 +43,7 @@ async fn main() -> std::io::Result<()> { .add(("Access-Control-Allow-Credentials", "true")), ) .service(login) + .service(logout) .service(get_case) .service(get_cases) .service(get_item) diff --git a/src/users.rs b/src/users.rs index 47dff7f..9f20003 100644 --- a/src/users.rs +++ b/src/users.rs @@ -30,12 +30,8 @@ use actix_web::cookie::time::Duration; use actix_web::web::{Data, Json}; use actix_web::{HttpRequest, HttpResponse, Responder, post}; use argon2::Argon2; -use argon2::password_hash::{ - PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng, -}; -use jsonwebtoken::{ - Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode, get_current_timestamp, -}; +use argon2::password_hash::{PasswordHash, PasswordVerifier}; +use jsonwebtoken::{Algorithm, EncodingKey, Header, encode, get_current_timestamp}; use rand::Rng; use rand::distr::Alphanumeric; use serde::{Deserialize, Deserializer}; @@ -45,14 +41,7 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use std::fs::File; use std::io::Read; -use uuid::Uuid; -/* -Will use cookies in final version of app -In the meanwhile, we'll just save the token in the userStore - -use actix_web::cookie::{Cookie, SameSite}; -use actix_web::cookie::time::{Duration, OffsetDateTime, UtcDateTime}; -*/ +use sqlx::sqlite::SqliteQueryResult; #[derive(Debug)] struct Password { @@ -77,14 +66,16 @@ impl User { async fn fetch_optional( database: &Pool, id: Option, + uuid: Option<&str>, username: Option<&Username>, ) -> Result, sqlx::Error> { match username { Some(username) => { query_as!( User, - "SELECT * FROM users WHERE 'id' = ?1 OR 'username' = ?2", + "SELECT * FROM users WHERE 'id' = ?1 OR 'username' = ?2 or 'uuid' = ?3", id, + uuid, username.value ) .fetch_optional(database) @@ -93,8 +84,9 @@ impl User { None => { query_as!( User, - "SELECT * FROM users WHERE 'id' = ?1 OR 'username' = ?2", + "SELECT * FROM users WHERE 'id' = ?1 OR 'username' = ?2 OR 'uuid' = ?3", id, + uuid, None:: ) .fetch_optional(database) @@ -187,7 +179,7 @@ async fn login( login_info: Json, ) -> Result> { if let Ok(Some(user)) = - User::fetch_optional(&app_state.database, None, Some(&login_info.username)).await + User::fetch_optional(&app_state.database, None, None, Some(&login_info.username)).await { if validate_authentication(&user, &login_info.password)? { let jwt_token = generate_jwt(&user, app_state.clone())?; @@ -213,6 +205,46 @@ async fn login( } } +#[derive(Deserialize, Debug)] +struct LogoutInfo { + uuid: String, + token: String, +} + +#[post("/logout")] +async fn logout( + app_state: Data, + logout_info: Json, + req: HttpRequest, +) -> Result> { + let refresh_cookie = match req.cookie("refresh_token") { + Some(cookie) => cookie, + None => return Ok(HttpResponse::Unauthorized().finish()), + }; + let refresh_token = refresh_cookie.value(); + match User::fetch_optional(&app_state.database, None, Some(&logout_info.uuid), None).await? { + Some(user) => { + let now = get_current_timestamp() as i64; + let result: SqliteQueryResult = query!( + "UPDATE refresh_tokens SET expiry = ?1 WHERE token = ?2 AND user = ?3 AND previous = ?4", + now, + refresh_token, + user.id, + logout_info.token + ) + .execute(&app_state.database) + .await?; + if result.rows_affected() == 0 { + return Ok(HttpResponse::Unauthorized().finish()); + } + let mut refresh_token_cookie = Cookie::new("refresh_token", ""); + refresh_token_cookie.make_removal(); + Ok(HttpResponse::Ok().cookie(refresh_token_cookie).finish()) + } + None => Ok(HttpResponse::Unauthorized().finish()), + } +} + fn validate_authentication(user: &User, password: &Password) -> Result> { let argon2 = Argon2::default(); let hash = PasswordHash::new(&user.hash)?;