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"
|
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"
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
if state.check_login(&form.username, &form.password).await.unwrap() {
|
trace!("ROUTE: /login/");
|
||||||
format!("Welcome {}", &form.username)
|
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 {
|
} 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::{
|
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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user