Started adding doc to better build the system

This commit is contained in:
2025-04-09 22:48:11 +02:00
parent 9347db69fd
commit 2ffe9c0c2b

View File

@@ -1,3 +1,29 @@
//! # Authentication flow
//! Before being able to authenticate, the user needs to create an account
//! Here's how it can be done:
//! - The user submits information on the client
//! - The client sends a request to the "register" endpoint
//! - The endpoint ensures the information given is valid
//! - An account is created
//! - The user is redirected to the login page
//!
//! After, the user can authenticate
//! Here's how it works:
//! - The users submits information on the client
//! - The client sends a request to the "login" endpoint
//! - The endpoint makes sure the information given is valid (otherwise reject the request: no db call)
//! - Then it checks that the user exists
//! - Then it checks that the correct password was given
//! - Then it creates two tokens: a short-lived and a refresh-token
//! - The short-lived token must be saved in local-storage and the refresh-token must be saved as a http only cookie
//!
//! Once authenticated, the short-lived token can be renewed
//! Here's how it works:
//! - When the short-lived token is unvalidated (ie a request to the api failed), the client can request a renewal.
//! - The renewal request contains the now unvalid token with the refresh-token cookie.
//! - The server checks that the refresh-token is valid (good user, not expired, ...)
//! - If checks pass, the server generates a new short-lived token.
//! - If it fails, the client redirects the user to login.
use crate::AppState; use crate::AppState;
use crate::types::{User, UserLogin, UserRegister, UserTokenClaims}; use crate::types::{User, UserLogin, UserRegister, UserTokenClaims};
use actix_web::web::{Data, Json}; use actix_web::web::{Data, Json};
@@ -81,36 +107,37 @@ async fn logout(
req: HttpRequest, req: HttpRequest,
app_state: Data<AppState>, app_state: Data<AppState>,
) -> Result<impl Responder, Box<(dyn Error + 'static)>> { ) -> Result<impl Responder, Box<(dyn Error + 'static)>> {
todo!();
// Put the (KeyId, User) pair in the revoked table // Put the (KeyId, User) pair in the revoked table
// And remove data from client // And remove data from client
match req.headers().get("Authorization") { // match req.headers().get("Authorization") {
Some(token) => { // Some(token) => {
let token = token.to_str()?; // let token = token.to_str()?;
let token = match token.split_once(" ") { // let token = match token.split_once(" ") {
Some((_, token)) => token, // Some((_, token)) => token,
None => return Ok(HttpResponse::BadRequest().finish()), // None => return Ok(HttpResponse::BadRequest().finish()),
}; // };
let mut key = File::open("pub.pem")?; // let mut key = File::open("pub.pem")?;
let mut buf = vec![]; // let mut buf = vec![];
key.read_to_end(&mut buf)?; // key.read_to_end(&mut buf)?;
let token = decode::<UserTokenClaims>( // let token = decode::<UserTokenClaims>(
token, // token,
&DecodingKey::from_ec_pem(&buf).unwrap(), // &DecodingKey::from_ec_pem(&buf).unwrap(),
&Validation::new(Algorithm::ES256), // &Validation::new(Algorithm::ES256),
)?; // )?;
let exp = token.claims.exp as i64; // let exp = token.claims.exp as i64;
query!( // query!(
"INSERT INTO revoked ( token_id, user_id, expires ) VALUES ( $1, $2, $3 )", // "INSERT INTO revoked ( token_id, user_id, expires ) VALUES ( $1, $2, $3 )",
token.claims.kid, // token.claims.kid,
token.claims.uid, // token.claims.uid,
exp // exp
) // )
.execute(&app_state.database) // .execute(&app_state.database)
.await?; // .await?;
Ok(HttpResponse::Ok().finish()) // Ok(HttpResponse::Ok().finish())
} // }
None => Ok(HttpResponse::BadRequest().finish()), // None => Ok(HttpResponse::BadRequest().finish()),
} // }
} }
#[post("/register")] #[post("/register")]
@@ -147,31 +174,32 @@ async fn verify_token(
app_state: Data<AppState>, app_state: Data<AppState>,
token: &str, token: &str,
) -> Result<bool, Box<(dyn Error + 'static)>> { ) -> Result<bool, Box<(dyn Error + 'static)>> {
let mut key = File::open("pub.pem")?; todo!();
let mut buf = vec![]; // let mut key = File::open("pub.pem")?;
key.read_to_end(&mut buf)?; // let mut buf = vec![];
let token = decode::<UserTokenClaims>( // key.read_to_end(&mut buf)?;
token, // let token = decode::<UserTokenClaims>(
&DecodingKey::from_ec_pem(&buf).unwrap(), // token,
&Validation::new(Algorithm::ES256), // &DecodingKey::from_ec_pem(&buf).unwrap(),
)?; // &Validation::new(Algorithm::ES256),
let exp = token.claims.exp as u64; // )?;
let now = get_current_timestamp(); // let exp = token.claims.exp as u64;
if exp > now { // let now = get_current_timestamp();
return Ok(false); // if exp > now {
} // return Ok(false);
let kid = token.claims.kid; // }
let uid = token.claims.uid; // let kid = token.claims.kid;
if query!( // let uid = token.claims.uid;
"SELECT token_id FROM revoked WHERE token_id = $1 AND user_id = $2", // if query!(
kid, // "SELECT token_id FROM revoked WHERE token_id = $1 AND user_id = $2",
uid // kid,
) // uid
.fetch_optional(&app_state.database) // )
.await? // .fetch_optional(&app_state.database)
.is_some() // .await?
{ // .is_some()
return Ok(false); // {
} // return Ok(false);
Ok(true) // }
// Ok(true)
} }