Update login flow to have a fixed URL

This commit is contained in:
Quentin Boyer 2024-12-30 22:30:27 +01:00
parent 53ec443972
commit ec03532a88
5 changed files with 110 additions and 24 deletions

View file

@ -2,13 +2,14 @@ use std::sync::Arc;
use axum::{
async_trait,
extract::{FromRef, FromRequestParts, Path, Query, State},
extract::{FromRef, FromRequestParts, Query, State},
handler::HandlerWithoutStateExt,
http::{request::Parts, StatusCode},
response::{IntoResponse, Redirect},
routing::get,
Router,
};
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
use maud::{html, Markup};
use sea_orm::{prelude::*, ActiveValue, DbErr, TransactionError};
use serde::Deserialize;
@ -21,9 +22,9 @@ use crate::entity::{prelude::*, user};
use self::{household::CurrentHousehold, sidebar::SidebarLocation};
mod household;
mod ingredients;
mod recipe;
mod sidebar;
mod ingredients;
type AppState = Arc<crate::AppState>;
@ -156,13 +157,20 @@ impl IntoResponse for RouteError {
}
}
async fn oidc_login(State(state): State<AppState>) -> Redirect {
#[axum::debug_handler]
async fn oidc_login(State(state): State<AppState>, jar: CookieJar) -> (CookieJar, Redirect) {
tracing::info!("Starting OIDC login");
let oidc = state.oidc.as_ref().unwrap();
let redirect_url = oidc.start_auth();
let (flow_id, redirect_url) = oidc.start_auth();
let jar = jar.add(
Cookie::build(("login_flow_id", flow_id.to_string()))
.secure(true)
.same_site(SameSite::Lax)
.build(),
);
Redirect::to(redirect_url.as_str())
(jar, Redirect::to(redirect_url.as_str()))
}
enum RedirectOrError {
@ -236,10 +244,14 @@ struct OidcRedirectParams {
async fn oidc_login_finish(
State(state): State<AppState>,
Path(id): Path<Uuid>,
Query(redirect): Query<OidcRedirectParams>,
session: Session,
jar: CookieJar,
) -> Result<Redirect, RouteError> {
let Some(Ok(id)) = jar.get("login_flow_id").map(|c| c.value().parse()) else {
return Err(RouteError::Oauth2Failure);
};
match state
.oidc
.as_ref()
@ -370,7 +382,7 @@ pub(crate) fn router() -> Router<AppState> {
.route("/", get(index))
.route("/login", get(oidc_login))
.route("/logout", get(logout))
.route("/login/redirect/:id", get(oidc_login_finish))
.route("/login/redirect", get(oidc_login_finish))
.nest("/household", household::routes())
.nest("/recipe", recipe::routes())
.nest("/ingredients", ingredients::routes())

View file

@ -241,6 +241,10 @@ pub struct OpenidAccount {
}
impl OpenidConnector {
fn redirect_uri(&self) -> Url {
self.domain.join("login/redirect").unwrap()
}
async fn new(settings: OpenidConnectSettings) -> anyhow::Result<Self> {
let metadata = CoreProviderMetadata::discover_async(
IssuerUrl::new(settings.url)?,
@ -259,14 +263,15 @@ impl OpenidConnector {
})
}
pub fn start_auth(&self) -> Url {
pub fn start_auth(&self) -> (Uuid, Url) {
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let mut inflight = self.inflight.lock();
let slot = inflight.new_entry();
let id = slot.id;
tracing::info!("Login flow with id {}", slot.id);
tracing::info!("Login flow with id {id}");
let (url, csrf_token, nonce) = self
.provider
@ -278,9 +283,7 @@ impl OpenidConnector {
.add_scopes(self.scopes.iter().cloned())
.set_pkce_challenge(pkce_challenge)
.set_redirect_uri(std::borrow::Cow::Owned(RedirectUrl::from_url(
self.domain
.join(&format!("login/redirect/{}", slot.id))
.unwrap(),
self.redirect_uri(),
)))
.url();
@ -290,7 +293,7 @@ impl OpenidConnector {
nonce,
});
url
(id, url)
}
pub async fn redirected(
@ -317,7 +320,7 @@ impl OpenidConnector {
.exchange_code(AuthorizationCode::new(code))
.set_pkce_verifier(state.pkce_verifier)
.set_redirect_uri(std::borrow::Cow::Owned(RedirectUrl::from_url(
self.domain.join(&format!("login/redirect/{}", id)).unwrap(),
self.redirect_uri(),
)))
.request_async(openidconnect::reqwest::async_http_client)
.await?;

View file

@ -15,6 +15,7 @@ use axum::{
Json,
Router,
};
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
use jwt_simple::prelude::*;
use sea_orm::{prelude::*, ActiveValue, TransactionError};
use sha2::{Digest, Sha512};
@ -141,8 +142,9 @@ async fn login(
let Some(user) = User::find()
.filter(user::Column::Name.eq(&req.username))
.one(&state.db)
.await? else {
return Err(RouteError::UnknownAccount)
.await?
else {
return Err(RouteError::UnknownAccount);
};
let Some(password) = user.password.as_ref() else {
@ -171,13 +173,22 @@ struct OidcStartParam {
r#return: String,
}
async fn oidc_login(State(state): State<AppState>) -> Result<Redirect, RouteError> {
async fn oidc_login(
State(state): State<AppState>,
jar: CookieJar,
) -> Result<(CookieJar, Redirect), RouteError> {
tracing::info!("Starting OIDC login");
let oidc = state.oidc.as_ref().unwrap();
let redirect_url = oidc.start_auth();
let (flow_id, redirect_url) = oidc.start_auth();
let jar = jar.add(
Cookie::build(("login_flow_id", flow_id.to_string()))
.secure(true)
.same_site(SameSite::Lax)
.build(),
);
Ok(Redirect::to(redirect_url.as_str()))
Ok((jar, Redirect::to(redirect_url.as_str())))
}
#[derive(Deserialize)]
@ -188,9 +199,13 @@ struct OidcRedirectParams {
async fn oidc_login_finish(
State(state): State<AppState>,
Path(id): Path<Uuid>,
Query(redirect): Query<OidcRedirectParams>,
jar: CookieJar,
) -> Result<Redirect, RouteError> {
let Some(Ok(id)) = jar.get("login_flow_id").map(|c| c.value().parse()) else {
return Err(RouteError::Unauthorized);
};
match state
.oidc
.as_ref()
@ -250,8 +265,9 @@ async fn get_user_id(
let Some(user) = User::find()
.filter(user::Column::Name.eq(name))
.one(&state.db)
.await? else {
return Ok(Err(StatusCode::NOT_FOUND))
.await?
else {
return Ok(Err(StatusCode::NOT_FOUND));
};
Ok(Ok(Json(UserInfo {
@ -343,7 +359,7 @@ pub(crate) fn router(api_allowed: Option<HeaderValue>, has_oidc: bool) -> Router
"/login/has_oidc",
get(unit).layer(mk_service(vec![Method::GET])),
)
.route("/login/redirect/:id", get(oidc_login_finish))
.route("/login/redirect", get(oidc_login_finish))
} else {
router
}