Serve and check cookies on login route
This commit is contained in:
		
							parent
							
								
									1667c3295b
								
							
						
					
					
						commit
						c73f132421
					
				@ -24,4 +24,5 @@ yunohost-api = { path = "yunohost-api", features = [ "axum" ] }
 | 
			
		||||
axum_typed_multipart = "0.8"
 | 
			
		||||
async-trait = "0.1"
 | 
			
		||||
serde = { version = "1", features = [ "derive" ] }
 | 
			
		||||
file-owner = { version = "0.1" }
 | 
			
		||||
file-owner = { version = "0.1" }
 | 
			
		||||
tower-cookies = "0.9"
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
use axum::response::{IntoResponse, Response};
 | 
			
		||||
use snafu::Snafu;
 | 
			
		||||
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
@ -29,3 +30,9 @@ pub enum Error {
 | 
			
		||||
    #[snafu(display("Failed to set group on file {}", path.display()))]
 | 
			
		||||
    PermissionsChgrp { path: PathBuf, source: file_owner::FileOwnerError },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoResponse for Error {
 | 
			
		||||
    fn into_response(self) -> Response {
 | 
			
		||||
        self.to_string().into_response()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,19 @@
 | 
			
		||||
use axum::{
 | 
			
		||||
    extract::{FromRequest, Form, Json, Query, State},
 | 
			
		||||
    extract::{FromRequest, Form, Json, State},
 | 
			
		||||
    http::{self, Request, StatusCode},
 | 
			
		||||
    response::{IntoResponse, Response},
 | 
			
		||||
    RequestExt,
 | 
			
		||||
};
 | 
			
		||||
use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
 | 
			
		||||
use snafu::prelude::*;
 | 
			
		||||
use tower_cookies::{Cookies, Cookie};
 | 
			
		||||
use yunohost_api::{Username, Password};
 | 
			
		||||
 | 
			
		||||
use crate::state::RoutableAppState;
 | 
			
		||||
use crate::{
 | 
			
		||||
    error::*,
 | 
			
		||||
    routes::COOKIE_NAME,
 | 
			
		||||
    state::RoutableAppState,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, TryFromMultipart, Deserialize)]
 | 
			
		||||
pub struct LoginForm {
 | 
			
		||||
@ -55,10 +61,28 @@ where
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[debug_handler]
 | 
			
		||||
pub async fn route(state: State<RoutableAppState>, form: LoginForm) -> String {
 | 
			
		||||
	if state.check_login(&form.username, &form.password).await.unwrap() {
 | 
			
		||||
        format!("Welcome {}", &form.username)
 | 
			
		||||
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() {
 | 
			
		||||
        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 {
 | 
			
		||||
        format!("Invalid login for {}", &form.username)
 | 
			
		||||
        debug!("Login failed for user {}", &form.username);
 | 
			
		||||
        Ok(format!("Invalid login for {}", &form.username))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,22 @@
 | 
			
		||||
use axum::{
 | 
			
		||||
    extract::State,
 | 
			
		||||
    routing::{get, post},
 | 
			
		||||
    Router,
 | 
			
		||||
};
 | 
			
		||||
use tower_cookies::CookieManagerLayer;
 | 
			
		||||
 | 
			
		||||
use crate::state::RoutableAppState;
 | 
			
		||||
 | 
			
		||||
mod index;
 | 
			
		||||
mod login;
 | 
			
		||||
 | 
			
		||||
pub const COOKIE_NAME: &'static str = "yunohost.ssowat";
 | 
			
		||||
 | 
			
		||||
/// Build a router for the application, in a specific subpath eg `/yunohost/sso/`
 | 
			
		||||
pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router {
 | 
			
		||||
    let app = Router::new()
 | 
			
		||||
        .route("/", get(index::route))
 | 
			
		||||
        .route("/login/", post(login::route))
 | 
			
		||||
        .layer(CookieManagerLayer::new())
 | 
			
		||||
        .with_state(state);
 | 
			
		||||
    if let Some(p) = subpath {
 | 
			
		||||
        Router::new()
 | 
			
		||||
 | 
			
		||||
@ -11,8 +11,8 @@ use sessions::SessionManager;
 | 
			
		||||
pub type RoutableAppState = Arc<AppState>;
 | 
			
		||||
 | 
			
		||||
pub struct AppState {
 | 
			
		||||
    sessions: SessionManager,
 | 
			
		||||
    users: YunohostUsers,
 | 
			
		||||
    pub sessions: SessionManager,
 | 
			
		||||
    pub users: YunohostUsers,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppState {
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,7 @@ impl SessionManager {
 | 
			
		||||
    /// Generates a new valid cookie inside the `SessionManager`, and returns:
 | 
			
		||||
    /// - the cookie name to be set
 | 
			
		||||
    /// - 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 signable_payload = format!("{now}:{cookie_name}:{username}");
 | 
			
		||||
        let signed_payload = hmac::sign(&self.secret, signable_payload.as_bytes()).as_ref().to_vec();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user