Compare commits

..

2 commits

Author SHA1 Message Date
60f0219e85 feat: add logout endpoint
Some checks failed
ci/woodpecker/push/publish-docs Pipeline is pending
ci/woodpecker/push/build-and-publish Pipeline failed
2025-05-31 14:43:48 +02:00
38aab46534 style: rename refresh_token_cookie() to new_refresh_token_cookie() and fix error message when no refresh_token is found on refresh 2025-05-31 14:41:29 +02:00
6 changed files with 42 additions and 17 deletions

View file

@ -11,7 +11,7 @@ use crate::{
error::Error, error::Error,
schema::*, schema::*,
utils::{ utils::{
PASSWORD_REGEX, generate_access_token, generate_refresh_token, refresh_token_cookie, PASSWORD_REGEX, generate_access_token, generate_refresh_token, new_refresh_token_cookie,
user_uuid_from_identifier, user_uuid_from_identifier,
}, },
}; };
@ -89,6 +89,6 @@ pub async fn response(
.await?; .await?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.cookie(refresh_token_cookie(refresh_token)) .cookie(new_refresh_token_cookie(refresh_token))
.json(Response { access_token })) .json(Response { access_token }))
} }

31
src/api/v1/auth/logout.rs Normal file
View file

@ -0,0 +1,31 @@
use actix_web::{HttpRequest, HttpResponse, post, web};
use diesel::{ExpressionMethods, delete};
use diesel_async::RunQueryDsl;
use crate::{
Data,
error::Error,
schema::refresh_tokens::{self, dsl},
};
// TODO: Should maybe be a delete request?
#[post("/logout")]
pub async fn res(
req: HttpRequest,
data: web::Data<Data>,
) -> Result<HttpResponse, Error> {
let mut refresh_token_cookie = req.cookie("refresh_token").ok_or(Error::Unauthorized("request has no refresh token".to_string()))?;
let refresh_token = String::from(refresh_token_cookie.value());
let mut conn = data.pool.get().await?;
delete(refresh_tokens::table)
.filter(dsl::token.eq(refresh_token))
.execute(&mut conn)
.await?;
refresh_token_cookie.make_removal();
Ok(HttpResponse::Ok().cookie(refresh_token_cookie).finish())
}

View file

@ -9,6 +9,7 @@ use uuid::Uuid;
use crate::{Conn, error::Error, schema::access_tokens::dsl}; use crate::{Conn, error::Error, schema::access_tokens::dsl};
mod login; mod login;
mod logout;
mod refresh; mod refresh;
mod register; mod register;
mod reset_password; mod reset_password;
@ -24,6 +25,7 @@ pub fn web() -> Scope {
web::scope("/auth") web::scope("/auth")
.service(register::res) .service(register::res)
.service(login::response) .service(login::response)
.service(logout::res)
.service(refresh::res) .service(refresh::res)
.service(revoke::res) .service(revoke::res)
.service(verify_email::get) .service(verify_email::get)

View file

@ -11,20 +11,16 @@ use crate::{
access_tokens::{self, dsl}, access_tokens::{self, dsl},
refresh_tokens::{self, dsl as rdsl}, refresh_tokens::{self, dsl as rdsl},
}, },
utils::{generate_access_token, generate_refresh_token, refresh_token_cookie}, utils::{generate_access_token, generate_refresh_token, new_refresh_token_cookie},
}; };
use super::Response; use super::Response;
#[post("/refresh")] #[post("/refresh")]
pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> { pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
let recv_refresh_token_cookie = req.cookie("refresh_token"); let mut refresh_token_cookie = req.cookie("refresh_token").ok_or(Error::Unauthorized("request has no refresh token".to_string()))?;
if recv_refresh_token_cookie.is_none() { let mut refresh_token = String::from(refresh_token_cookie.value());
return Ok(HttpResponse::Unauthorized().finish());
}
let mut refresh_token = String::from(recv_refresh_token_cookie.unwrap().value());
let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
@ -47,8 +43,6 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
error!("{}", error); error!("{}", error);
} }
let mut refresh_token_cookie = refresh_token_cookie(refresh_token);
refresh_token_cookie.make_removal(); refresh_token_cookie.make_removal();
return Ok(HttpResponse::Unauthorized() return Ok(HttpResponse::Unauthorized()
@ -91,12 +85,10 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
.await?; .await?;
return Ok(HttpResponse::Ok() return Ok(HttpResponse::Ok()
.cookie(refresh_token_cookie(refresh_token)) .cookie(new_refresh_token_cookie(refresh_token))
.json(Response { access_token })); .json(Response { access_token }));
} }
let mut refresh_token_cookie = refresh_token_cookie(refresh_token);
refresh_token_cookie.make_removal(); refresh_token_cookie.make_removal();
Ok(HttpResponse::Unauthorized() Ok(HttpResponse::Unauthorized()

View file

@ -21,7 +21,7 @@ use crate::{
}, },
utils::{ utils::{
EMAIL_REGEX, PASSWORD_REGEX, USERNAME_REGEX, generate_access_token, generate_refresh_token, EMAIL_REGEX, PASSWORD_REGEX, USERNAME_REGEX, generate_access_token, generate_refresh_token,
refresh_token_cookie, new_refresh_token_cookie,
}, },
}; };
@ -146,7 +146,7 @@ pub async fn res(
.await?; .await?;
return Ok(HttpResponse::Ok() return Ok(HttpResponse::Ok()
.cookie(refresh_token_cookie(refresh_token)) .cookie(new_refresh_token_cookie(refresh_token))
.json(Response { access_token })); .json(Response { access_token }));
} }

View file

@ -100,7 +100,7 @@ pub fn get_ws_protocol_header(headers: &HeaderMap) -> Result<&str, Error> {
Ok(auth_value.unwrap()) Ok(auth_value.unwrap())
} }
pub fn refresh_token_cookie(refresh_token: String) -> Cookie<'static> { pub fn new_refresh_token_cookie(refresh_token: String) -> Cookie<'static> {
Cookie::build("refresh_token", refresh_token) Cookie::build("refresh_token", refresh_token)
.http_only(true) .http_only(true)
.secure(true) .secure(true)