login endpoint
This commit is contained in:
42
src/api/auth.rs
Normal file
42
src/api/auth.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use axum::{
|
||||||
|
Json,
|
||||||
|
http::{HeaderMap, header},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::users::{
|
||||||
|
User,
|
||||||
|
auth::{
|
||||||
|
AuthError, COOKIE_NAME, UserAuthRequired, UserAuthenticate,
|
||||||
|
implementation::authenticate_via_credentials,
|
||||||
|
},
|
||||||
|
sessions::Session,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginForm {
|
||||||
|
handle: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(Json(creds): Json<LoginForm>) -> Result<Response, AuthError> {
|
||||||
|
let u = authenticate_via_credentials(&creds.handle, &creds.password)?.required()?;
|
||||||
|
let (_, token) = Session::new_for_user(&u)?;
|
||||||
|
|
||||||
|
let secure = match cfg!(debug_assertions) {
|
||||||
|
false => "; Secure",
|
||||||
|
true => "",
|
||||||
|
};
|
||||||
|
let cookie = format!(
|
||||||
|
"{COOKIE_NAME}={token}; Path=/; HttpOnly; SameSite=Lax; Max-Age={}{}",
|
||||||
|
Session::DEFAULT_PROLONGATION.num_seconds(),
|
||||||
|
secure
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(([(header::SET_COOKIE, cookie)], token).into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn logout(headers: HeaderMap) -> Result<Response, AuthError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::get,
|
routing::{get, post},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
users::{UserError, auth::AuthError, sessions::SessionError},
|
users::{UserError, auth::AuthError, sessions::SessionError},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod auth;
|
||||||
mod sessions;
|
mod sessions;
|
||||||
mod tags;
|
mod tags;
|
||||||
mod users;
|
mod users;
|
||||||
@@ -17,6 +18,8 @@ mod users;
|
|||||||
pub fn api_router() -> Router {
|
pub fn api_router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/api/live", get(async || "Mnemosyne lives"))
|
.route("/api/live", get(async || "Mnemosyne lives"))
|
||||||
|
.route("/api/auth/login", post(auth::login))
|
||||||
|
.route("/api/auth/logout", post(auth::logout))
|
||||||
.route("/api/users/me", get(users::get_me))
|
.route("/api/users/me", get(users::get_me))
|
||||||
.route("/api/users/{id}", get(users::get_by_id))
|
.route("/api/users/{id}", get(users::get_by_id))
|
||||||
.route("/api/users/@{handle}", get(users::get_by_handle))
|
.route("/api/users/@{handle}", get(users::get_by_handle))
|
||||||
|
|||||||
@@ -170,13 +170,19 @@ fn authenticate_basic(credentials: &str) -> Result<Option<User>, AuthError> {
|
|||||||
let decoded = BASE64_STANDARD.decode(credentials)?;
|
let decoded = BASE64_STANDARD.decode(credentials)?;
|
||||||
let credentials_str = String::from_utf8(decoded)?;
|
let credentials_str = String::from_utf8(decoded)?;
|
||||||
|
|
||||||
let Some((username, password)) = credentials_str.split_once(':') else {
|
let Some((handle, password)) = credentials_str.split_once(':') else {
|
||||||
return Err(AuthError::InvalidFormat);
|
return Err(AuthError::InvalidFormat);
|
||||||
};
|
};
|
||||||
|
authenticate_via_credentials(handle, password)
|
||||||
|
}
|
||||||
|
pub fn authenticate_via_credentials(
|
||||||
|
handle: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<Option<User>, AuthError> {
|
||||||
let conn = database::conn()?;
|
let conn = database::conn()?;
|
||||||
let user: Option<(Uuid, Option<String>)> = conn
|
let user: Option<(Uuid, Option<String>)> = conn
|
||||||
.prepare("SELECT id, password FROM users WHERE handle = ?1")?
|
.prepare("SELECT id, password FROM users WHERE handle = ?1")?
|
||||||
.query_row([username], |r| Ok((r.get(0)?, r.get(1)?)))
|
.query_row([handle], |r| Ok((r.get(0)?, r.get(1)?)))
|
||||||
.optional()?;
|
.optional()?;
|
||||||
|
|
||||||
match user {
|
match user {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use rand08::{RngCore, rngs::OsRng};
|
|||||||
|
|
||||||
use crate::users::{User, UserError, sessions::SessionError};
|
use crate::users::{User, UserError, sessions::SessionError};
|
||||||
|
|
||||||
mod implementation;
|
pub mod implementation;
|
||||||
|
|
||||||
pub const COOKIE_NAME: &str = "mnemohash";
|
pub const COOKIE_NAME: &str = "mnemohash";
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ impl Session {
|
|||||||
None => Err(SessionError::NoSessionWithToken(token.to_string())),
|
None => Err(SessionError::NoSessionWithToken(token.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
|
||||||
pub fn new_for_user(user: &User) -> Result<(Session, String), SessionError> {
|
pub fn new_for_user(user: &User) -> Result<(Session, String), SessionError> {
|
||||||
let id = Uuid::now_v7();
|
let id = Uuid::now_v7();
|
||||||
let token = auth::generate_token(auth::TokenSize::Char64);
|
let token = auth::generate_token(auth::TokenSize::Char64);
|
||||||
@@ -112,7 +111,7 @@ impl Session {
|
|||||||
let expiry = Utc::now() + Session::DEFAULT_PROLONGATION;
|
let expiry = Utc::now() + Session::DEFAULT_PROLONGATION;
|
||||||
|
|
||||||
database::conn()?
|
database::conn()?
|
||||||
.prepare("INSERT INTO sessions VALUES (?1, ?2, ?3, ?4)")?
|
.prepare("INSERT INTO sessions(id, token, user_id, expiry) VALUES (?1, ?2, ?3, ?4)")?
|
||||||
.execute((&id, &hashed, user.id, expiry))?;
|
.execute((&id, &hashed, user.id, expiry))?;
|
||||||
let s = Session {
|
let s = Session {
|
||||||
id,
|
id,
|
||||||
@@ -123,7 +122,7 @@ impl Session {
|
|||||||
Ok((s, token))
|
Ok((s, token))
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_PROLONGATION: Duration = Duration::days(14);
|
pub const DEFAULT_PROLONGATION: Duration = Duration::days(14);
|
||||||
const PROLONGATION_THRESHOLD: Duration = Duration::hours(2);
|
const PROLONGATION_THRESHOLD: Duration = Duration::hours(2);
|
||||||
pub fn prolong(&mut self) -> Result<(), SessionError> {
|
pub fn prolong(&mut self) -> Result<(), SessionError> {
|
||||||
if self.expiry - Session::DEFAULT_PROLONGATION + Session::PROLONGATION_THRESHOLD
|
if self.expiry - Session::DEFAULT_PROLONGATION + Session::PROLONGATION_THRESHOLD
|
||||||
|
|||||||
Reference in New Issue
Block a user