Serve and check cookies on login route

This commit is contained in:
selfhoster selfhoster 2023-08-22 19:29:40 +02:00
parent 1667c3295b
commit c73f132421
6 changed files with 46 additions and 11 deletions

View File

@ -25,3 +25,4 @@ axum_typed_multipart = "0.8"
async-trait = "0.1" async-trait = "0.1"
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
file-owner = { version = "0.1" } file-owner = { version = "0.1" }
tower-cookies = "0.9"

View File

@ -1,3 +1,4 @@
use axum::response::{IntoResponse, Response};
use snafu::Snafu; use snafu::Snafu;
use std::path::PathBuf; use std::path::PathBuf;
@ -29,3 +30,9 @@ pub enum Error {
#[snafu(display("Failed to set group on file {}", path.display()))] #[snafu(display("Failed to set group on file {}", path.display()))]
PermissionsChgrp { path: PathBuf, source: file_owner::FileOwnerError }, PermissionsChgrp { path: PathBuf, source: file_owner::FileOwnerError },
} }
impl IntoResponse for Error {
fn into_response(self) -> Response {
self.to_string().into_response()
}
}

View File

@ -1,13 +1,19 @@
use axum::{ use axum::{
extract::{FromRequest, Form, Json, Query, State}, extract::{FromRequest, Form, Json, State},
http::{self, Request, StatusCode}, http::{self, Request, StatusCode},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
RequestExt, RequestExt,
}; };
use axum_typed_multipart::{TryFromMultipart, TypedMultipart}; use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
use snafu::prelude::*;
use tower_cookies::{Cookies, Cookie};
use yunohost_api::{Username, Password}; use yunohost_api::{Username, Password};
use crate::state::RoutableAppState; use crate::{
error::*,
routes::COOKIE_NAME,
state::RoutableAppState,
};
#[derive(Debug, TryFromMultipart, Deserialize)] #[derive(Debug, TryFromMultipart, Deserialize)]
pub struct LoginForm { pub struct LoginForm {
@ -55,10 +61,28 @@ where
} }
#[debug_handler] #[debug_handler]
pub async fn route(state: State<RoutableAppState>, form: LoginForm) -> String { pub async fn route(cookies: Cookies, state: State<RoutableAppState>, form: LoginForm) -> Result<String, Error> {
trace!("ROUTE: /login/");
if let Some(session_cookie) = cookies.get(COOKIE_NAME) {
trace!("User claims to have valid {} session: {}", COOKIE_NAME, &session_cookie);
if let Some(username) = state.sessions.verify_cookie(session_cookie.value()).await.context(SessionSnafu)? {
debug!("User claims were verified. They are identified as {}", &username);
return Ok(format!("Welcome back, {}! You were already logged in.", username));
}
debug!("User claims for a {} session were unfounded. Performing login again.", COOKIE_NAME);
}
debug!("Performing login attempt for user {}", &form.username);
// No cookie, or cookie is invalid. Perform login.
if state.check_login(&form.username, &form.password).await.unwrap() { if state.check_login(&form.username, &form.password).await.unwrap() {
format!("Welcome {}", &form.username) debug!("Login was successful for user {}. Saving cookie now.", &form.username);
let (cookie_name, cookie_value) = state.sessions.make_session(COOKIE_NAME, &form.username).await;
cookies.add(Cookie::new(cookie_name, cookie_value));
Ok(format!("Welcome {}", &form.username))
} else { } else {
format!("Invalid login for {}", &form.username) debug!("Login failed for user {}", &form.username);
Ok(format!("Invalid login for {}", &form.username))
} }
} }

View File

@ -1,19 +1,22 @@
use axum::{ use axum::{
extract::State,
routing::{get, post}, routing::{get, post},
Router, Router,
}; };
use tower_cookies::CookieManagerLayer;
use crate::state::RoutableAppState; use crate::state::RoutableAppState;
mod index; mod index;
mod login; mod login;
pub const COOKIE_NAME: &'static str = "yunohost.ssowat";
/// Build a router for the application, in a specific subpath eg `/yunohost/sso/` /// Build a router for the application, in a specific subpath eg `/yunohost/sso/`
pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router { pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router {
let app = Router::new() let app = Router::new()
.route("/", get(index::route)) .route("/", get(index::route))
.route("/login/", post(login::route)) .route("/login/", post(login::route))
.layer(CookieManagerLayer::new())
.with_state(state); .with_state(state);
if let Some(p) = subpath { if let Some(p) = subpath {
Router::new() Router::new()

View File

@ -11,8 +11,8 @@ use sessions::SessionManager;
pub type RoutableAppState = Arc<AppState>; pub type RoutableAppState = Arc<AppState>;
pub struct AppState { pub struct AppState {
sessions: SessionManager, pub sessions: SessionManager,
users: YunohostUsers, pub users: YunohostUsers,
} }
impl AppState { impl AppState {

View File

@ -89,7 +89,7 @@ impl SessionManager {
/// Generates a new valid cookie inside the `SessionManager`, and returns: /// Generates a new valid cookie inside the `SessionManager`, and returns:
/// - the cookie name to be set /// - the cookie name to be set
/// - the cookie content that can be sent to a client /// - the cookie content that can be sent to a client
pub async fn make_session(&mut self, cookie_name: &str, username: &Username) -> (String, String) { pub async fn make_session(&self, cookie_name: &str, username: &Username) -> (String, String) {
let now = now(); let now = now();
let signable_payload = format!("{now}:{cookie_name}:{username}"); let signable_payload = format!("{now}:{cookie_name}:{username}");
let signed_payload = hmac::sign(&self.secret, signable_payload.as_bytes()).as_ref().to_vec(); let signed_payload = hmac::sign(&self.secret, signable_payload.as_bytes()).as_ref().to_vec();