You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
Allow running the authentication service on a different base path
This commit is contained in:
@ -57,11 +57,6 @@ impl Options {
|
|||||||
let span = info_span!("cli.run.init").entered();
|
let span = info_span!("cli.run.init").entered();
|
||||||
let config: AppConfig = root.load_config()?;
|
let config: AppConfig = root.load_config()?;
|
||||||
|
|
||||||
// XXX: there should be a generic config verification step
|
|
||||||
if config.http.public_base.path() != "/" {
|
|
||||||
anyhow::bail!("The http.public_base path is not set to /, this is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the database
|
// Connect to the database
|
||||||
info!("Connecting to the database");
|
info!("Connecting to the database");
|
||||||
let pool = database_pool_from_config(&config.database).await?;
|
let pool = database_pool_from_config(&config.database).await?;
|
||||||
@ -201,19 +196,22 @@ impl Options {
|
|||||||
let router = crate::server::build_router(
|
let router = crate::server::build_router(
|
||||||
state.clone(),
|
state.clone(),
|
||||||
&config.resources,
|
&config.resources,
|
||||||
|
config.prefix.as_deref(),
|
||||||
config.name.as_deref(),
|
config.name.as_deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Display some informations about where we'll be serving connections
|
// Display some informations about where we'll be serving connections
|
||||||
let proto = if config.tls.is_some() { "https" } else { "http" };
|
let proto = if config.tls.is_some() { "https" } else { "http" };
|
||||||
|
let prefix = config.prefix.unwrap_or_default();
|
||||||
let addresses= listeners
|
let addresses= listeners
|
||||||
.iter()
|
.iter()
|
||||||
.map(|listener| {
|
.map(|listener| {
|
||||||
if let Ok(addr) = listener.local_addr() {
|
if let Ok(addr) = listener.local_addr() {
|
||||||
format!("{proto}://{addr:?}")
|
format!("{proto}://{addr:?}{prefix}")
|
||||||
} else {
|
} else {
|
||||||
warn!("Could not get local address for listener, something might be wrong!");
|
warn!("Could not get local address for listener, something might be wrong!");
|
||||||
format!("{proto}://???")
|
format!("{proto}://???{prefix}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
@ -175,6 +175,7 @@ fn on_http_response_labels<B>(res: &Response<B>) -> Vec<KeyValue> {
|
|||||||
pub fn build_router<B>(
|
pub fn build_router<B>(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
resources: &[HttpResource],
|
resources: &[HttpResource],
|
||||||
|
prefix: Option<&str>,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
) -> Router<(), B>
|
) -> Router<(), B>
|
||||||
where
|
where
|
||||||
@ -244,6 +245,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(prefix) = prefix {
|
||||||
|
let path = format!("{}/", prefix.trim_end_matches('/'));
|
||||||
|
router = Router::new().nest(&path, router);
|
||||||
|
}
|
||||||
|
|
||||||
router = router.fallback(mas_handlers::fallback);
|
router = router.fallback(mas_handlers::fallback);
|
||||||
|
|
||||||
router
|
router
|
||||||
|
@ -312,6 +312,10 @@ pub struct ListenerConfig {
|
|||||||
/// List of resources to mount
|
/// List of resources to mount
|
||||||
pub resources: Vec<Resource>,
|
pub resources: Vec<Resource>,
|
||||||
|
|
||||||
|
/// HTTP prefix to mount the resources on
|
||||||
|
#[serde(default)]
|
||||||
|
pub prefix: Option<String>,
|
||||||
|
|
||||||
/// List of sockets to bind
|
/// List of sockets to bind
|
||||||
pub binds: Vec<BindConfig>,
|
pub binds: Vec<BindConfig>,
|
||||||
|
|
||||||
@ -359,6 +363,7 @@ impl Default for HttpConfig {
|
|||||||
path: http_listener_assets_path_default(),
|
path: http_listener_assets_path_default(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
prefix: None,
|
||||||
tls: None,
|
tls: None,
|
||||||
proxy_protocol: false,
|
proxy_protocol: false,
|
||||||
binds: vec![BindConfig::Address {
|
binds: vec![BindConfig::Address {
|
||||||
@ -368,6 +373,7 @@ impl Default for HttpConfig {
|
|||||||
ListenerConfig {
|
ListenerConfig {
|
||||||
name: Some("internal".to_owned()),
|
name: Some("internal".to_owned()),
|
||||||
resources: vec![Resource::Health],
|
resources: vec![Resource::Health],
|
||||||
|
prefix: None,
|
||||||
tls: None,
|
tls: None,
|
||||||
proxy_protocol: false,
|
proxy_protocol: false,
|
||||||
binds: vec![BindConfig::Listen {
|
binds: vec![BindConfig::Listen {
|
||||||
|
@ -27,7 +27,7 @@ use mas_axum_utils::{
|
|||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_data_model::Device;
|
use mas_data_model::Device;
|
||||||
use mas_router::{CompatLoginSsoAction, PostAuthAction, Route};
|
use mas_router::{CompatLoginSsoAction, PostAuthAction, UrlBuilder};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
compat::{CompatSessionRepository, CompatSsoLoginRepository},
|
compat::{CompatSessionRepository, CompatSsoLoginRepository},
|
||||||
job::{JobRepositoryExt, ProvisionDeviceJob},
|
job::{JobRepositoryExt, ProvisionDeviceJob},
|
||||||
@ -65,6 +65,7 @@ pub async fn get(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Path(id): Path<Ulid>,
|
Path(id): Path<Ulid>,
|
||||||
Query(params): Query<Params>,
|
Query(params): Query<Params>,
|
||||||
@ -78,10 +79,10 @@ pub async fn get(
|
|||||||
// If there is no session, redirect to the login or register screen
|
// If there is no session, redirect to the login or register screen
|
||||||
let url = match params.action {
|
let url = match params.action {
|
||||||
Some(CompatLoginSsoAction::Register) => {
|
Some(CompatLoginSsoAction::Register) => {
|
||||||
mas_router::Register::and_continue_compat_sso_login(id).go()
|
url_builder.redirect(&mas_router::Register::and_continue_compat_sso_login(id))
|
||||||
}
|
}
|
||||||
Some(CompatLoginSsoAction::Login) | None => {
|
Some(CompatLoginSsoAction::Login) | None => {
|
||||||
mas_router::Login::and_continue_compat_sso_login(id).go()
|
url_builder.redirect(&mas_router::Login::and_continue_compat_sso_login(id))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ pub async fn get(
|
|||||||
if session.user.primary_user_email_id.is_none() {
|
if session.user.primary_user_email_id.is_none() {
|
||||||
let destination = mas_router::AccountAddEmail::default()
|
let destination = mas_router::AccountAddEmail::default()
|
||||||
.and_then(PostAuthAction::continue_compat_sso_login(id));
|
.and_then(PostAuthAction::continue_compat_sso_login(id));
|
||||||
return Ok((cookie_jar, destination.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&destination)).into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
let login = repo
|
let login = repo
|
||||||
@ -134,6 +135,7 @@ pub async fn post(
|
|||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Path(id): Path<Ulid>,
|
Path(id): Path<Ulid>,
|
||||||
Query(params): Query<Params>,
|
Query(params): Query<Params>,
|
||||||
@ -148,10 +150,10 @@ pub async fn post(
|
|||||||
// If there is no session, redirect to the login or register screen
|
// If there is no session, redirect to the login or register screen
|
||||||
let url = match params.action {
|
let url = match params.action {
|
||||||
Some(CompatLoginSsoAction::Register) => {
|
Some(CompatLoginSsoAction::Register) => {
|
||||||
mas_router::Register::and_continue_compat_sso_login(id).go()
|
url_builder.redirect(&mas_router::Register::and_continue_compat_sso_login(id))
|
||||||
}
|
}
|
||||||
Some(CompatLoginSsoAction::Login) | None => {
|
Some(CompatLoginSsoAction::Login) | None => {
|
||||||
mas_router::Login::and_continue_compat_sso_login(id).go()
|
url_builder.redirect(&mas_router::Login::and_continue_compat_sso_login(id))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,7 +164,7 @@ pub async fn post(
|
|||||||
if session.user.primary_user_email_id.is_none() {
|
if session.user.primary_user_email_id.is_none() {
|
||||||
let destination = mas_router::AccountAddEmail::default()
|
let destination = mas_router::AccountAddEmail::default()
|
||||||
.and_then(PostAuthAction::continue_compat_sso_login(id));
|
.and_then(PostAuthAction::continue_compat_sso_login(id));
|
||||||
return Ok((cookie_jar, destination.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&destination)).into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
let login = repo
|
let login = repo
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
clippy::let_with_type_underscore,
|
clippy::let_with_type_underscore,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::{borrow::Cow, convert::Infallible, time::Duration};
|
use std::{convert::Infallible, time::Duration};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{Bytes, HttpBody},
|
body::{Bytes, HttpBody},
|
||||||
@ -276,6 +276,18 @@ where
|
|||||||
mas_router::CompatRefresh::route(),
|
mas_router::CompatRefresh::route(),
|
||||||
post(self::compat::refresh::post),
|
post(self::compat::refresh::post),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
mas_router::CompatLoginSsoRedirect::route(),
|
||||||
|
get(self::compat::login_sso_redirect::get),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
mas_router::CompatLoginSsoRedirectIdp::route(),
|
||||||
|
get(self::compat::login_sso_redirect::get),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
mas_router::CompatLoginSsoRedirectSlash::route(),
|
||||||
|
get(self::compat::login_sso_redirect::get),
|
||||||
|
)
|
||||||
.layer(
|
.layer(
|
||||||
CorsLayer::new()
|
CorsLayer::new()
|
||||||
.allow_origin(Any)
|
.allow_origin(Any)
|
||||||
@ -318,16 +330,19 @@ where
|
|||||||
// XXX: hard-coded redirect from /account to /account/
|
// XXX: hard-coded redirect from /account to /account/
|
||||||
.route(
|
.route(
|
||||||
"/account",
|
"/account",
|
||||||
get(|RawQuery(query): RawQuery| async {
|
get(
|
||||||
let route = mas_router::Account::route();
|
|State(url_builder): State<UrlBuilder>, RawQuery(query): RawQuery| async move {
|
||||||
let destination = if let Some(query) = query {
|
let prefix = url_builder.prefix().unwrap_or_default();
|
||||||
Cow::Owned(format!("{route}?{query}"))
|
let route = mas_router::Account::route();
|
||||||
} else {
|
let destination = if let Some(query) = query {
|
||||||
Cow::Borrowed(route)
|
format!("{prefix}{route}?{query}")
|
||||||
};
|
} else {
|
||||||
|
format!("{prefix}{route}")
|
||||||
|
};
|
||||||
|
|
||||||
axum::response::Redirect::to(&destination)
|
axum::response::Redirect::to(&destination)
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.route(mas_router::Account::route(), get(self::views::app::get))
|
.route(mas_router::Account::route(), get(self::views::app::get))
|
||||||
.route(
|
.route(
|
||||||
@ -336,7 +351,9 @@ where
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
mas_router::ChangePasswordDiscovery::route(),
|
mas_router::ChangePasswordDiscovery::route(),
|
||||||
get(|| async { mas_router::AccountPassword.go() }),
|
get(|State(url_builder): State<UrlBuilder>| async move {
|
||||||
|
url_builder.redirect(&mas_router::AccountPassword)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.route(mas_router::Index::route(), get(self::views::index::get))
|
.route(mas_router::Index::route(), get(self::views::index::get))
|
||||||
.route(
|
.route(
|
||||||
@ -378,18 +395,6 @@ where
|
|||||||
mas_router::Consent::route(),
|
mas_router::Consent::route(),
|
||||||
get(self::oauth2::consent::get).post(self::oauth2::consent::post),
|
get(self::oauth2::consent::get).post(self::oauth2::consent::post),
|
||||||
)
|
)
|
||||||
.route(
|
|
||||||
mas_router::CompatLoginSsoRedirect::route(),
|
|
||||||
get(self::compat::login_sso_redirect::get),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
mas_router::CompatLoginSsoRedirectIdp::route(),
|
|
||||||
get(self::compat::login_sso_redirect::get),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
mas_router::CompatLoginSsoRedirectSlash::route(),
|
|
||||||
get(self::compat::login_sso_redirect::get),
|
|
||||||
)
|
|
||||||
.route(
|
.route(
|
||||||
mas_router::CompatLoginSsoComplete::route(),
|
mas_router::CompatLoginSsoComplete::route(),
|
||||||
get(self::compat::login_sso_complete::get).post(self::compat::login_sso_complete::post),
|
get(self::compat::login_sso_complete::get).post(self::compat::login_sso_complete::post),
|
||||||
|
@ -21,7 +21,7 @@ use mas_axum_utils::{cookies::CookieJar, csrf::CsrfExt, sentry::SentryEventID, S
|
|||||||
use mas_data_model::{AuthorizationGrant, BrowserSession, Client, Device};
|
use mas_data_model::{AuthorizationGrant, BrowserSession, Client, Device};
|
||||||
use mas_keystore::Keystore;
|
use mas_keystore::Keystore;
|
||||||
use mas_policy::{EvaluationResult, Policy};
|
use mas_policy::{EvaluationResult, Policy};
|
||||||
use mas_router::{PostAuthAction, Route, UrlBuilder};
|
use mas_router::{PostAuthAction, UrlBuilder};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository, OAuth2SessionRepository},
|
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository, OAuth2SessionRepository},
|
||||||
user::BrowserSessionRepository,
|
user::BrowserSessionRepository,
|
||||||
@ -117,7 +117,11 @@ pub(crate) async fn get(
|
|||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
// If there is no session, redirect to the login screen, redirecting here after
|
// If there is no session, redirect to the login screen, redirecting here after
|
||||||
// logout
|
// logout
|
||||||
return Ok((cookie_jar, mas_router::Login::and_then(continue_grant).go()).into_response());
|
return Ok((
|
||||||
|
cookie_jar,
|
||||||
|
url_builder.redirect(&mas_router::Login::and_then(continue_grant)),
|
||||||
|
)
|
||||||
|
.into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
activity_tracker
|
activity_tracker
|
||||||
@ -137,7 +141,7 @@ pub(crate) async fn get(
|
|||||||
repo,
|
repo,
|
||||||
key_store,
|
key_store,
|
||||||
policy,
|
policy,
|
||||||
url_builder,
|
&url_builder,
|
||||||
grant,
|
grant,
|
||||||
&client,
|
&client,
|
||||||
&session,
|
&session,
|
||||||
@ -150,12 +154,12 @@ pub(crate) async fn get(
|
|||||||
}
|
}
|
||||||
Err(GrantCompletionError::RequiresReauth) => Ok((
|
Err(GrantCompletionError::RequiresReauth) => Ok((
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
mas_router::Reauth::and_then(continue_grant).go(),
|
url_builder.redirect(&mas_router::Reauth::and_then(continue_grant)),
|
||||||
)
|
)
|
||||||
.into_response()),
|
.into_response()),
|
||||||
Err(GrantCompletionError::RequiresConsent) => {
|
Err(GrantCompletionError::RequiresConsent) => {
|
||||||
let next = mas_router::Consent(grant_id);
|
let next = mas_router::Consent(grant_id);
|
||||||
Ok((cookie_jar, next.go()).into_response())
|
Ok((cookie_jar, url_builder.redirect(&next)).into_response())
|
||||||
}
|
}
|
||||||
Err(GrantCompletionError::PolicyViolation(grant, res)) => {
|
Err(GrantCompletionError::PolicyViolation(grant, res)) => {
|
||||||
warn!(violation = ?res, "Authorization grant for client {} denied by policy", client.id);
|
warn!(violation = ?res, "Authorization grant for client {} denied by policy", client.id);
|
||||||
@ -206,7 +210,7 @@ pub(crate) async fn complete(
|
|||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
key_store: Keystore,
|
key_store: Keystore,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
url_builder: UrlBuilder,
|
url_builder: &UrlBuilder,
|
||||||
grant: AuthorizationGrant,
|
grant: AuthorizationGrant,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
browser_session: &BrowserSession,
|
browser_session: &BrowserSession,
|
||||||
@ -273,7 +277,7 @@ pub(crate) async fn complete(
|
|||||||
params.id_token = Some(generate_id_token(
|
params.id_token = Some(generate_id_token(
|
||||||
rng,
|
rng,
|
||||||
clock,
|
clock,
|
||||||
&url_builder,
|
url_builder,
|
||||||
&key_store,
|
&key_store,
|
||||||
client,
|
client,
|
||||||
&grant,
|
&grant,
|
||||||
|
@ -21,7 +21,7 @@ use mas_axum_utils::{cookies::CookieJar, csrf::CsrfExt, sentry::SentryEventID, S
|
|||||||
use mas_data_model::{AuthorizationCode, Pkce};
|
use mas_data_model::{AuthorizationCode, Pkce};
|
||||||
use mas_keystore::Keystore;
|
use mas_keystore::Keystore;
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_router::{PostAuthAction, Route, UrlBuilder};
|
use mas_router::{PostAuthAction, UrlBuilder};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
|
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
|
||||||
BoxClock, BoxRepository, BoxRng,
|
BoxClock, BoxRepository, BoxRng,
|
||||||
@ -313,16 +313,14 @@ pub(crate) async fn get(
|
|||||||
// Client asked for a registration, show the registration prompt
|
// Client asked for a registration, show the registration prompt
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
mas_router::Register::and_then(continue_grant)
|
url_builder.redirect(&mas_router::Register::and_then(continue_grant))
|
||||||
.go()
|
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Other cases where we don't have a session, ask for a login
|
// Other cases where we don't have a session, ask for a login
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
mas_router::Login::and_then(continue_grant)
|
url_builder.redirect(&mas_router::Login::and_then(continue_grant))
|
||||||
.go()
|
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,8 +334,7 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
activity_tracker.record_browser_session(&clock, &session).await;
|
activity_tracker.record_browser_session(&clock, &session).await;
|
||||||
|
|
||||||
mas_router::Reauth::and_then(continue_grant)
|
url_builder.redirect(&mas_router::Reauth::and_then(continue_grant))
|
||||||
.go()
|
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,7 +350,7 @@ pub(crate) async fn get(
|
|||||||
repo,
|
repo,
|
||||||
key_store,
|
key_store,
|
||||||
policy,
|
policy,
|
||||||
url_builder,
|
&url_builder,
|
||||||
grant,
|
grant,
|
||||||
&client,
|
&client,
|
||||||
&user_session,
|
&user_session,
|
||||||
@ -403,7 +400,7 @@ pub(crate) async fn get(
|
|||||||
repo,
|
repo,
|
||||||
key_store,
|
key_store,
|
||||||
policy,
|
policy,
|
||||||
url_builder,
|
&url_builder,
|
||||||
grant,
|
grant,
|
||||||
&client,
|
&client,
|
||||||
&user_session,
|
&user_session,
|
||||||
@ -412,7 +409,7 @@ pub(crate) async fn get(
|
|||||||
{
|
{
|
||||||
Ok(params) => callback_destination.go(&templates, params).await?,
|
Ok(params) => callback_destination.go(&templates, params).await?,
|
||||||
Err(GrantCompletionError::RequiresConsent) => {
|
Err(GrantCompletionError::RequiresConsent) => {
|
||||||
mas_router::Consent(grant_id).go().into_response()
|
url_builder.redirect(&mas_router::Consent(grant_id)).into_response()
|
||||||
}
|
}
|
||||||
Err(GrantCompletionError::PolicyViolation(grant, res)) => {
|
Err(GrantCompletionError::PolicyViolation(grant, res)) => {
|
||||||
warn!(violation = ?res, "Authorization grant for client {} denied by policy", client.id);
|
warn!(violation = ?res, "Authorization grant for client {} denied by policy", client.id);
|
||||||
@ -426,8 +423,7 @@ pub(crate) async fn get(
|
|||||||
Html(content).into_response()
|
Html(content).into_response()
|
||||||
}
|
}
|
||||||
Err(GrantCompletionError::RequiresReauth) => {
|
Err(GrantCompletionError::RequiresReauth) => {
|
||||||
mas_router::Reauth::and_then(continue_grant)
|
url_builder.redirect(&mas_router::Reauth::and_then(continue_grant))
|
||||||
.go()
|
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
Err(GrantCompletionError::Internal(e)) => {
|
Err(GrantCompletionError::Internal(e)) => {
|
||||||
|
@ -25,7 +25,7 @@ use mas_axum_utils::{
|
|||||||
};
|
};
|
||||||
use mas_data_model::{AuthorizationGrantStage, Device};
|
use mas_data_model::{AuthorizationGrantStage, Device};
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_router::{PostAuthAction, Route};
|
use mas_router::{PostAuthAction, UrlBuilder};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
|
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
|
||||||
BoxClock, BoxRepository, BoxRng,
|
BoxClock, BoxRepository, BoxRng,
|
||||||
@ -84,6 +84,7 @@ pub(crate) async fn get(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
@ -142,7 +143,7 @@ pub(crate) async fn get(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let login = mas_router::Login::and_continue_grant(grant_id);
|
let login = mas_router::Login::and_continue_grant(grant_id);
|
||||||
Ok((cookie_jar, login.go()).into_response())
|
Ok((cookie_jar, url_builder.redirect(&login)).into_response())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +160,7 @@ pub(crate) async fn post(
|
|||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
Path(grant_id): Path<Ulid>,
|
Path(grant_id): Path<Ulid>,
|
||||||
Form(form): Form<ProtectedForm<()>>,
|
Form(form): Form<ProtectedForm<()>>,
|
||||||
) -> Result<Response, RouteError> {
|
) -> Result<Response, RouteError> {
|
||||||
@ -177,7 +179,7 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
let login = mas_router::Login::and_then(next);
|
let login = mas_router::Login::and_then(next);
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
activity_tracker
|
activity_tracker
|
||||||
@ -222,5 +224,5 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
Ok((cookie_jar, next.go_next()).into_response())
|
Ok((cookie_jar, next.go_next(&url_builder)).into_response())
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ use mas_keystore::{Encrypter, Keystore};
|
|||||||
use mas_oidc_client::requests::{
|
use mas_oidc_client::requests::{
|
||||||
authorization_code::AuthorizationValidationData, jose::JwtVerificationData,
|
authorization_code::AuthorizationValidationData, jose::JwtVerificationData,
|
||||||
};
|
};
|
||||||
use mas_router::{Route, UrlBuilder};
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
upstream_oauth2::{
|
upstream_oauth2::{
|
||||||
UpstreamOAuthLinkRepository, UpstreamOAuthProviderRepository,
|
UpstreamOAuthLinkRepository, UpstreamOAuthProviderRepository,
|
||||||
@ -268,6 +268,6 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
mas_router::UpstreamOAuth2Link::new(link.id).go(),
|
url_builder.redirect(&mas_router::UpstreamOAuth2Link::new(link.id)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ use mas_axum_utils::{
|
|||||||
use mas_data_model::{UpstreamOAuthProviderImportPreference, User};
|
use mas_data_model::{UpstreamOAuthProviderImportPreference, User};
|
||||||
use mas_jose::jwt::Jwt;
|
use mas_jose::jwt::Jwt;
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
job::{JobRepositoryExt, ProvisionUserJob},
|
job::{JobRepositoryExt, ProvisionUserJob},
|
||||||
upstream_oauth2::{UpstreamOAuthLinkRepository, UpstreamOAuthSessionRepository},
|
upstream_oauth2::{UpstreamOAuthLinkRepository, UpstreamOAuthSessionRepository},
|
||||||
@ -191,6 +192,7 @@ pub(crate) async fn get(
|
|||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
||||||
Path(link_id): Path<Ulid>,
|
Path(link_id): Path<Ulid>,
|
||||||
@ -248,7 +250,7 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
post_auth_action.go_next().into_response()
|
post_auth_action.go_next(&url_builder).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(user_session), Some(user_id)) => {
|
(Some(user_session), Some(user_id)) => {
|
||||||
@ -311,7 +313,7 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
post_auth_action.go_next().into_response()
|
post_auth_action.go_next(&url_builder).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
@ -383,6 +385,7 @@ pub(crate) async fn post(
|
|||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
Path(link_id): Path<Ulid>,
|
Path(link_id): Path<Ulid>,
|
||||||
Form(form): Form<ProtectedForm<FormData>>,
|
Form(form): Form<ProtectedForm<FormData>>,
|
||||||
) -> Result<impl IntoResponse, RouteError> {
|
) -> Result<impl IntoResponse, RouteError> {
|
||||||
@ -577,5 +580,5 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
Ok((cookie_jar, post_auth_action.go_next()))
|
Ok((cookie_jar, post_auth_action.go_next(&url_builder)))
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ use mas_axum_utils::{
|
|||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_router::Route;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
job::{JobRepositoryExt, VerifyEmailJob},
|
job::{JobRepositoryExt, VerifyEmailJob},
|
||||||
user::UserEmailRepository,
|
user::UserEmailRepository,
|
||||||
@ -44,6 +44,7 @@ pub(crate) async fn get(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
@ -55,7 +56,7 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
let login = mas_router::Login::default();
|
let login = mas_router::Login::default();
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
activity_tracker
|
activity_tracker
|
||||||
@ -80,6 +81,7 @@ pub(crate) async fn post(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
Form(form): Form<ProtectedForm<EmailForm>>,
|
Form(form): Form<ProtectedForm<EmailForm>>,
|
||||||
@ -91,7 +93,7 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
let login = mas_router::Login::default();
|
let login = mas_router::Login::default();
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
// XXX: we really should show human readable errors on the form here
|
// XXX: we really should show human readable errors on the form here
|
||||||
@ -135,9 +137,9 @@ pub(crate) async fn post(
|
|||||||
next
|
next
|
||||||
};
|
};
|
||||||
|
|
||||||
next.go()
|
url_builder.redirect(&next)
|
||||||
} else {
|
} else {
|
||||||
query.go_next_or_default(&mas_router::Account::default())
|
query.go_next_or_default(&url_builder, &mas_router::Account::default())
|
||||||
};
|
};
|
||||||
|
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
@ -22,7 +22,7 @@ use mas_axum_utils::{
|
|||||||
csrf::{CsrfExt, ProtectedForm},
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_router::Route;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
job::{JobRepositoryExt, ProvisionUserJob},
|
job::{JobRepositoryExt, ProvisionUserJob},
|
||||||
user::UserEmailRepository,
|
user::UserEmailRepository,
|
||||||
@ -50,6 +50,7 @@ pub(crate) async fn get(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
@ -63,7 +64,7 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
let login = mas_router::Login::default();
|
let login = mas_router::Login::default();
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
activity_tracker
|
activity_tracker
|
||||||
@ -79,7 +80,7 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
if user_email.confirmed_at.is_some() {
|
if user_email.confirmed_at.is_some() {
|
||||||
// This email was already verified, skip
|
// This email was already verified, skip
|
||||||
let destination = query.go_next_or_default(&mas_router::Account::default());
|
let destination = query.go_next_or_default(&url_builder, &mas_router::Account::default());
|
||||||
return Ok((cookie_jar, destination).into_response());
|
return Ok((cookie_jar, destination).into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +104,7 @@ pub(crate) async fn post(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
Path(id): Path<Ulid>,
|
Path(id): Path<Ulid>,
|
||||||
@ -115,7 +117,7 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
let login = mas_router::Login::default();
|
let login = mas_router::Login::default();
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_email = repo
|
let user_email = repo
|
||||||
@ -157,6 +159,6 @@ pub(crate) async fn post(
|
|||||||
.record_browser_session(&clock, &session)
|
.record_browser_session(&clock, &session)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let destination = query.go_next_or_default(&mas_router::Account::default());
|
let destination = query.go_next_or_default(&url_builder, &mas_router::Account::default());
|
||||||
Ok((cookie_jar, destination).into_response())
|
Ok((cookie_jar, destination).into_response())
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ use mas_axum_utils::{
|
|||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
use mas_i18n::DataLocale;
|
use mas_i18n::DataLocale;
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_router::Route;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
user::{BrowserSessionRepository, UserPasswordRepository},
|
user::{BrowserSessionRepository, UserPasswordRepository},
|
||||||
BoxClock, BoxRepository, BoxRng, Clock,
|
BoxClock, BoxRepository, BoxRng, Clock,
|
||||||
@ -53,12 +53,15 @@ pub(crate) async fn get(
|
|||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
// If the password manager is disabled, we can go back to the account page.
|
// If the password manager is disabled, we can go back to the account page.
|
||||||
if !password_manager.is_enabled() {
|
if !password_manager.is_enabled() {
|
||||||
return Ok(mas_router::Account::default().go().into_response());
|
return Ok(url_builder
|
||||||
|
.redirect(&mas_router::Account::default())
|
||||||
|
.into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
@ -73,7 +76,7 @@ pub(crate) async fn get(
|
|||||||
render(&mut rng, &clock, locale, templates, session, cookie_jar).await
|
render(&mut rng, &clock, locale, templates, session, cookie_jar).await
|
||||||
} else {
|
} else {
|
||||||
let login = mas_router::Login::and_then(mas_router::PostAuthAction::ChangePassword);
|
let login = mas_router::Login::and_then(mas_router::PostAuthAction::ChangePassword);
|
||||||
Ok((cookie_jar, login.go()).into_response())
|
Ok((cookie_jar, url_builder.redirect(&login)).into_response())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +108,7 @@ pub(crate) async fn post(
|
|||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
@ -123,7 +127,7 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
let Some(session) = maybe_session else {
|
let Some(session) = maybe_session else {
|
||||||
let login = mas_router::Login::and_then(mas_router::PostAuthAction::ChangePassword);
|
let login = mas_router::Login::and_then(mas_router::PostAuthAction::ChangePassword);
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_password = repo
|
let user_password = repo
|
||||||
|
@ -17,7 +17,7 @@ use axum::{
|
|||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
};
|
};
|
||||||
use mas_axum_utils::{cookies::CookieJar, FancyError, SessionInfoExt};
|
use mas_axum_utils::{cookies::CookieJar, FancyError, SessionInfoExt};
|
||||||
use mas_router::{PostAuthAction, Route};
|
use mas_router::{PostAuthAction, UrlBuilder};
|
||||||
use mas_storage::{BoxClock, BoxRepository};
|
use mas_storage::{BoxClock, BoxRepository};
|
||||||
use mas_templates::{AppContext, TemplateContext, Templates};
|
use mas_templates::{AppContext, TemplateContext, Templates};
|
||||||
|
|
||||||
@ -28,6 +28,7 @@ pub async fn get(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
action: Option<Query<mas_router::AccountAction>>,
|
action: Option<Query<mas_router::AccountAction>>,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
@ -41,7 +42,9 @@ pub async fn get(
|
|||||||
let Some(session) = session else {
|
let Some(session) = session else {
|
||||||
return Ok((
|
return Ok((
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
mas_router::Login::and_then(PostAuthAction::manage_account(action)).go(),
|
url_builder.redirect(&mas_router::Login::and_then(
|
||||||
|
PostAuthAction::manage_account(action),
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.into_response());
|
.into_response());
|
||||||
};
|
};
|
||||||
@ -50,7 +53,7 @@ pub async fn get(
|
|||||||
.record_browser_session(&clock, &session)
|
.record_browser_session(&clock, &session)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ctx = AppContext::default().with_language(locale);
|
let ctx = AppContext::from_url_builder(&url_builder).with_language(locale);
|
||||||
let content = templates.render_app(&ctx)?;
|
let content = templates.render_app(&ctx)?;
|
||||||
|
|
||||||
Ok((cookie_jar, Html(content)).into_response())
|
Ok((cookie_jar, Html(content)).into_response())
|
||||||
|
@ -26,7 +26,7 @@ use mas_axum_utils::{
|
|||||||
};
|
};
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
use mas_i18n::DataLocale;
|
use mas_i18n::DataLocale;
|
||||||
use mas_router::{Route, UpstreamOAuth2Authorize};
|
use mas_router::{UpstreamOAuth2Authorize, UrlBuilder};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
upstream_oauth2::UpstreamOAuthProviderRepository,
|
upstream_oauth2::UpstreamOAuthProviderRepository,
|
||||||
user::{BrowserSessionRepository, UserPasswordRepository, UserRepository},
|
user::{BrowserSessionRepository, UserPasswordRepository, UserRepository},
|
||||||
@ -59,6 +59,7 @@ pub(crate) async fn get(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
@ -74,7 +75,7 @@ pub(crate) async fn get(
|
|||||||
.record_browser_session(&clock, &session)
|
.record_browser_session(&clock, &session)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let reply = query.go_next();
|
let reply = query.go_next(&url_builder);
|
||||||
return Ok((cookie_jar, reply).into_response());
|
return Ok((cookie_jar, reply).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ pub(crate) async fn get(
|
|||||||
destination = destination.and_then(action);
|
destination = destination.and_then(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok((cookie_jar, destination.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&destination)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = render(
|
let content = render(
|
||||||
@ -117,6 +118,7 @@ pub(crate) async fn post(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
@ -185,7 +187,7 @@ pub(crate) async fn post(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let cookie_jar = cookie_jar.set_session(&session_info);
|
let cookie_jar = cookie_jar.set_session(&session_info);
|
||||||
let reply = query.go_next();
|
let reply = query.go_next(&url_builder);
|
||||||
Ok((cookie_jar, reply).into_response())
|
Ok((cookie_jar, reply).into_response())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -360,7 +362,7 @@ mod test {
|
|||||||
|
|
||||||
let response = state.request(Request::get("/login").empty()).await;
|
let response = state.request(Request::get("/login").empty()).await;
|
||||||
response.assert_status(StatusCode::SEE_OTHER);
|
response.assert_status(StatusCode::SEE_OTHER);
|
||||||
response.assert_header_value(LOCATION, &first_provider_login.relative_url());
|
response.assert_header_value(LOCATION, &first_provider_login.path_and_query());
|
||||||
|
|
||||||
// Adding a second provider should show a login page with both providers
|
// Adding a second provider should show a login page with both providers
|
||||||
let mut repo = state.repository().await.unwrap();
|
let mut repo = state.repository().await.unwrap();
|
||||||
@ -391,13 +393,13 @@ mod test {
|
|||||||
.contains(&escape_html(&first_provider.issuer)));
|
.contains(&escape_html(&first_provider.issuer)));
|
||||||
assert!(response
|
assert!(response
|
||||||
.body()
|
.body()
|
||||||
.contains(&escape_html(&first_provider_login.relative_url())));
|
.contains(&escape_html(&first_provider_login.path_and_query())));
|
||||||
assert!(response
|
assert!(response
|
||||||
.body()
|
.body()
|
||||||
.contains(&escape_html(&second_provider.issuer)));
|
.contains(&escape_html(&second_provider.issuer)));
|
||||||
assert!(response
|
assert!(response
|
||||||
.body()
|
.body()
|
||||||
.contains(&escape_html(&second_provider_login.relative_url())));
|
.contains(&escape_html(&second_provider_login.path_and_query())));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||||
|
@ -12,13 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use axum::{extract::Form, response::IntoResponse};
|
use axum::{
|
||||||
|
extract::{Form, State},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
use mas_axum_utils::{
|
use mas_axum_utils::{
|
||||||
cookies::CookieJar,
|
cookies::CookieJar,
|
||||||
csrf::{CsrfExt, ProtectedForm},
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_router::{PostAuthAction, Route};
|
use mas_router::{PostAuthAction, UrlBuilder};
|
||||||
use mas_storage::{user::BrowserSessionRepository, BoxClock, BoxRepository};
|
use mas_storage::{user::BrowserSessionRepository, BoxClock, BoxRepository};
|
||||||
|
|
||||||
use crate::BoundActivityTracker;
|
use crate::BoundActivityTracker;
|
||||||
@ -28,6 +31,7 @@ pub(crate) async fn post(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
Form(form): Form<ProtectedForm<Option<PostAuthAction>>>,
|
Form(form): Form<ProtectedForm<Option<PostAuthAction>>>,
|
||||||
) -> Result<impl IntoResponse, FancyError> {
|
) -> Result<impl IntoResponse, FancyError> {
|
||||||
@ -49,9 +53,9 @@ pub(crate) async fn post(
|
|||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
let destination = if let Some(action) = form {
|
let destination = if let Some(action) = form {
|
||||||
action.go_next()
|
action.go_next(&url_builder)
|
||||||
} else {
|
} else {
|
||||||
mas_router::Login::default().go()
|
url_builder.redirect(&mas_router::Login::default())
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((cookie_jar, destination))
|
Ok((cookie_jar, destination))
|
||||||
|
@ -23,7 +23,7 @@ use mas_axum_utils::{
|
|||||||
csrf::{CsrfExt, ProtectedForm},
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_router::Route;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
user::{BrowserSessionRepository, UserPasswordRepository},
|
user::{BrowserSessionRepository, UserPasswordRepository},
|
||||||
BoxClock, BoxRepository, BoxRng,
|
BoxClock, BoxRepository, BoxRng,
|
||||||
@ -47,6 +47,7 @@ pub(crate) async fn get(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
@ -54,7 +55,9 @@ pub(crate) async fn get(
|
|||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
if !password_manager.is_enabled() {
|
if !password_manager.is_enabled() {
|
||||||
// XXX: do something better here
|
// XXX: do something better here
|
||||||
return Ok(mas_router::Account::default().go().into_response());
|
return Ok(url_builder
|
||||||
|
.redirect(&mas_router::Account::default())
|
||||||
|
.into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
@ -66,7 +69,7 @@ pub(crate) async fn get(
|
|||||||
// If there is no session, redirect to the login screen, keeping the
|
// If there is no session, redirect to the login screen, keeping the
|
||||||
// PostAuthAction
|
// PostAuthAction
|
||||||
let login = mas_router::Login::from(query.post_auth_action);
|
let login = mas_router::Login::from(query.post_auth_action);
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
activity_tracker
|
activity_tracker
|
||||||
@ -95,6 +98,7 @@ pub(crate) async fn post(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
@ -115,7 +119,7 @@ pub(crate) async fn post(
|
|||||||
// If there is no session, redirect to the login screen, keeping the
|
// If there is no session, redirect to the login screen, keeping the
|
||||||
// PostAuthAction
|
// PostAuthAction
|
||||||
let login = mas_router::Login::from(query.post_auth_action);
|
let login = mas_router::Login::from(query.post_auth_action);
|
||||||
return Ok((cookie_jar, login.go()).into_response());
|
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load the user password
|
// Load the user password
|
||||||
@ -162,6 +166,6 @@ pub(crate) async fn post(
|
|||||||
let cookie_jar = cookie_jar.set_session(&session);
|
let cookie_jar = cookie_jar.set_session(&session);
|
||||||
repo.save().await?;
|
repo.save().await?;
|
||||||
|
|
||||||
let reply = query.go_next();
|
let reply = query.go_next(&url_builder);
|
||||||
Ok((cookie_jar, reply).into_response())
|
Ok((cookie_jar, reply).into_response())
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ use mas_axum_utils::{
|
|||||||
};
|
};
|
||||||
use mas_i18n::DataLocale;
|
use mas_i18n::DataLocale;
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_router::Route;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
job::{JobRepositoryExt, ProvisionUserJob, VerifyEmailJob},
|
job::{JobRepositoryExt, ProvisionUserJob, VerifyEmailJob},
|
||||||
user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository},
|
user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository},
|
||||||
@ -64,6 +64,7 @@ pub(crate) async fn get(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
Query(query): Query<OptionalPostAuthAction>,
|
Query(query): Query<OptionalPostAuthAction>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
@ -74,14 +75,14 @@ pub(crate) async fn get(
|
|||||||
let maybe_session = session_info.load_session(&mut repo).await?;
|
let maybe_session = session_info.load_session(&mut repo).await?;
|
||||||
|
|
||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
let reply = query.go_next();
|
let reply = query.go_next(&url_builder);
|
||||||
return Ok((cookie_jar, reply).into_response());
|
return Ok((cookie_jar, reply).into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !password_manager.is_enabled() {
|
if !password_manager.is_enabled() {
|
||||||
// If password-based login is disabled, redirect to the login page here
|
// If password-based login is disabled, redirect to the login page here
|
||||||
return Ok(mas_router::Login::from(query.post_auth_action)
|
return Ok(url_builder
|
||||||
.go()
|
.redirect(&mas_router::Login::from(query.post_auth_action))
|
||||||
.into_response());
|
.into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ pub(crate) async fn post(
|
|||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
|
State(url_builder): State<UrlBuilder>,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
@ -239,7 +241,7 @@ pub(crate) async fn post(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let cookie_jar = cookie_jar.set_session(&session);
|
let cookie_jar = cookie_jar.set_session(&session);
|
||||||
Ok((cookie_jar, next.go()).into_response())
|
Ok((cookie_jar, url_builder.redirect(&next)).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(
|
async fn render(
|
||||||
@ -282,12 +284,12 @@ mod tests {
|
|||||||
state
|
state
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = Request::get(&*mas_router::Register::default().relative_url()).empty();
|
let request = Request::get(&*mas_router::Register::default().path_and_query()).empty();
|
||||||
let response = state.request(request).await;
|
let response = state.request(request).await;
|
||||||
response.assert_status(StatusCode::SEE_OTHER);
|
response.assert_status(StatusCode::SEE_OTHER);
|
||||||
response.assert_header_value(LOCATION, "/login");
|
response.assert_header_value(LOCATION, "/login");
|
||||||
|
|
||||||
let request = Request::post(&*mas_router::Register::default().relative_url()).form(
|
let request = Request::post(&*mas_router::Register::default().path_and_query()).form(
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"csrf": "abc",
|
"csrf": "abc",
|
||||||
"username": "john",
|
"username": "john",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use mas_router::{PostAuthAction, Route};
|
use mas_router::{PostAuthAction, Route, UrlBuilder};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
compat::CompatSsoLoginRepository,
|
compat::CompatSsoLoginRepository,
|
||||||
oauth2::OAuth2AuthorizationGrantRepository,
|
oauth2::OAuth2AuthorizationGrantRepository,
|
||||||
@ -30,14 +30,19 @@ pub(crate) struct OptionalPostAuthAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OptionalPostAuthAction {
|
impl OptionalPostAuthAction {
|
||||||
pub fn go_next_or_default<T: Route>(&self, default: &T) -> axum::response::Redirect {
|
pub fn go_next_or_default<T: Route>(
|
||||||
self.post_auth_action
|
&self,
|
||||||
.as_ref()
|
url_builder: &UrlBuilder,
|
||||||
.map_or_else(|| default.go(), mas_router::PostAuthAction::go_next)
|
default: &T,
|
||||||
|
) -> axum::response::Redirect {
|
||||||
|
self.post_auth_action.as_ref().map_or_else(
|
||||||
|
|| url_builder.redirect(default),
|
||||||
|
|action| action.go_next(url_builder),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_next(&self) -> axum::response::Redirect {
|
pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
|
||||||
self.go_next_or_default(&mas_router::Index)
|
self.go_next_or_default(url_builder, &mas_router::Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_context<'a>(
|
pub async fn load_context<'a>(
|
||||||
|
@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
pub use crate::traits::*;
|
pub use crate::traits::*;
|
||||||
|
use crate::UrlBuilder;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "snake_case", tag = "kind")]
|
#[serde(rename_all = "snake_case", tag = "kind")]
|
||||||
@ -57,16 +58,19 @@ impl PostAuthAction {
|
|||||||
PostAuthAction::ManageAccount { action }
|
PostAuthAction::ManageAccount { action }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_next(&self) -> axum::response::Redirect {
|
pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
|
||||||
match self {
|
match self {
|
||||||
Self::ContinueAuthorizationGrant { id } => ContinueAuthorizationGrant(*id).go(),
|
Self::ContinueAuthorizationGrant { id } => {
|
||||||
Self::ContinueCompatSsoLogin { id } => CompatLoginSsoComplete::new(*id, None).go(),
|
url_builder.redirect(&ContinueAuthorizationGrant(*id))
|
||||||
Self::ChangePassword => AccountPassword.go(),
|
|
||||||
Self::LinkUpstream { id } => UpstreamOAuth2Link::new(*id).go(),
|
|
||||||
Self::ManageAccount { action } => Account {
|
|
||||||
action: action.clone(),
|
|
||||||
}
|
}
|
||||||
.go(),
|
Self::ContinueCompatSsoLogin { id } => {
|
||||||
|
url_builder.redirect(&CompatLoginSsoComplete::new(*id, None))
|
||||||
|
}
|
||||||
|
Self::ChangePassword => url_builder.redirect(&AccountPassword),
|
||||||
|
Self::LinkUpstream { id } => url_builder.redirect(&UpstreamOAuth2Link::new(*id)),
|
||||||
|
Self::ManageAccount { action } => url_builder.redirect(&Account {
|
||||||
|
action: action.clone(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,10 +223,10 @@ impl Login {
|
|||||||
self.post_auth_action.as_ref()
|
self.post_auth_action.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_next(&self) -> axum::response::Redirect {
|
pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
|
||||||
match &self.post_auth_action {
|
match &self.post_auth_action {
|
||||||
Some(action) => action.go_next(),
|
Some(action) => action.go_next(url_builder),
|
||||||
None => Index.go(),
|
None => url_builder.redirect(&Index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,10 +272,10 @@ impl Reauth {
|
|||||||
self.post_auth_action.as_ref()
|
self.post_auth_action.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_next(&self) -> axum::response::Redirect {
|
pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
|
||||||
match &self.post_auth_action {
|
match &self.post_auth_action {
|
||||||
Some(action) => action.go_next(),
|
Some(action) => action.go_next(url_builder),
|
||||||
None => Index.go(),
|
None => url_builder.redirect(&Index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,10 +332,10 @@ impl Register {
|
|||||||
self.post_auth_action.as_ref()
|
self.post_auth_action.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_next(&self) -> axum::response::Redirect {
|
pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
|
||||||
match &self.post_auth_action {
|
match &self.post_auth_action {
|
||||||
Some(action) => action.go_next(),
|
Some(action) => action.go_next(url_builder),
|
||||||
None => Index.go(),
|
None => url_builder.redirect(&Index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_relative_urls() {
|
fn test_relative_urls() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
OidcConfiguration.relative_url(),
|
OidcConfiguration.path_and_query(),
|
||||||
Cow::Borrowed("/.well-known/openid-configuration")
|
Cow::Borrowed("/.well-known/openid-configuration")
|
||||||
);
|
);
|
||||||
assert_eq!(Index.relative_url(), Cow::Borrowed("/"));
|
assert_eq!(Index.path_and_query(), Cow::Borrowed("/"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Login::and_continue_grant(Ulid::nil()).relative_url(),
|
Login::and_continue_grant(Ulid::nil()).path_and_query(),
|
||||||
Cow::Borrowed("/login?kind=continue_authorization_grant&id=00000000000000000000000000")
|
Cow::Borrowed("/login?kind=continue_authorization_grant&id=00000000000000000000000000")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ pub trait Route {
|
|||||||
Cow::Borrowed(Self::route())
|
Cow::Borrowed(Self::route())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn relative_url(&self) -> Cow<'static, str> {
|
fn path_and_query(&self) -> Cow<'static, str> {
|
||||||
let path = self.path();
|
let path = self.path();
|
||||||
if let Some(query) = self.query() {
|
if let Some(query) = self.query() {
|
||||||
let query = serde_urlencoded::to_string(query).unwrap();
|
let query = serde_urlencoded::to_string(query).unwrap();
|
||||||
@ -39,17 +39,10 @@ pub trait Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn absolute_url(&self, base: &Url) -> Url {
|
fn absolute_url(&self, base: &Url) -> Url {
|
||||||
let relative = self.relative_url();
|
let relative = self.path_and_query();
|
||||||
|
let relative = relative.trim_start_matches('/');
|
||||||
base.join(relative.borrow()).unwrap()
|
base.join(relative.borrow()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn go(&self) -> axum::response::Redirect {
|
|
||||||
axum::response::Redirect::to(&self.relative_url())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn go_absolute(&self, base: &Url) -> axum::response::Redirect {
|
|
||||||
axum::response::Redirect::to(self.absolute_url(base).as_str())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SimpleRoute {
|
pub trait SimpleRoute {
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
//! Utility to build URLs
|
//! Utility to build URLs
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -24,32 +22,91 @@ use crate::traits::Route;
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct UrlBuilder {
|
pub struct UrlBuilder {
|
||||||
http_base: Url,
|
http_base: Url,
|
||||||
assets_base: Cow<'static, str>,
|
prefix: String,
|
||||||
|
assets_base: String,
|
||||||
issuer: Url,
|
issuer: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UrlBuilder {
|
impl UrlBuilder {
|
||||||
fn url_for<U>(&self, destination: &U) -> Url
|
fn absolute_url_for<U>(&self, destination: &U) -> Url
|
||||||
where
|
where
|
||||||
U: Route,
|
U: Route,
|
||||||
{
|
{
|
||||||
destination.absolute_url(&self.http_base)
|
destination.absolute_url(&self.http_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a relative URL for a route, prefixed with the base URL
|
||||||
|
#[must_use]
|
||||||
|
pub fn relative_url_for<U>(&self, destination: &U) -> String
|
||||||
|
where
|
||||||
|
U: Route,
|
||||||
|
{
|
||||||
|
format!(
|
||||||
|
"{prefix}{destination}",
|
||||||
|
prefix = self.prefix,
|
||||||
|
destination = destination.path_and_query()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The prefix added to all relative URLs
|
||||||
|
#[must_use]
|
||||||
|
pub fn prefix(&self) -> Option<&str> {
|
||||||
|
if self.prefix.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a (relative) redirect response to a route
|
||||||
|
pub fn redirect<U>(&self, destination: &U) -> axum::response::Redirect
|
||||||
|
where
|
||||||
|
U: Route,
|
||||||
|
{
|
||||||
|
let uri = self.relative_url_for(destination);
|
||||||
|
axum::response::Redirect::to(&uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an absolute redirect response to a route
|
||||||
pub fn absolute_redirect<U>(&self, destination: &U) -> axum::response::Redirect
|
pub fn absolute_redirect<U>(&self, destination: &U) -> axum::response::Redirect
|
||||||
where
|
where
|
||||||
U: Route,
|
U: Route,
|
||||||
{
|
{
|
||||||
destination.go_absolute(&self.http_base)
|
let uri = self.absolute_url_for(destination);
|
||||||
|
axum::response::Redirect::to(uri.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`UrlBuilder`] from a base URL
|
/// Create a new [`UrlBuilder`] from a base URL
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the base URL contains a fragment, a query, credentials or
|
||||||
|
/// isn't HTTP/HTTPS;
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(base: Url, issuer: Option<Url>, assets_base: Option<String>) -> Self {
|
pub fn new(base: Url, issuer: Option<Url>, assets_base: Option<String>) -> Self {
|
||||||
|
assert!(
|
||||||
|
base.scheme() == "http" || base.scheme() == "https",
|
||||||
|
"base URL must be HTTP/HTTPS"
|
||||||
|
);
|
||||||
|
assert_eq!(base.query(), None, "base URL must not contain a query");
|
||||||
|
assert_eq!(
|
||||||
|
base.fragment(),
|
||||||
|
None,
|
||||||
|
"base URL must not contain a fragment"
|
||||||
|
);
|
||||||
|
assert_eq!(base.username(), "", "base URL must not contain credentials");
|
||||||
|
assert_eq!(
|
||||||
|
base.password(),
|
||||||
|
None,
|
||||||
|
"base URL must not contain credentials"
|
||||||
|
);
|
||||||
|
|
||||||
let issuer = issuer.unwrap_or_else(|| base.clone());
|
let issuer = issuer.unwrap_or_else(|| base.clone());
|
||||||
let assets_base = assets_base.map_or(Cow::Borrowed("/assets/"), Cow::Owned);
|
let prefix = base.path().trim_end_matches('/').to_owned();
|
||||||
|
let assets_base = assets_base.unwrap_or_else(|| format!("{prefix}/assets/"));
|
||||||
Self {
|
Self {
|
||||||
http_base: base,
|
http_base: base,
|
||||||
|
prefix,
|
||||||
assets_base,
|
assets_base,
|
||||||
issuer,
|
issuer,
|
||||||
}
|
}
|
||||||
@ -70,49 +127,49 @@ impl UrlBuilder {
|
|||||||
/// OAuth 2.0 authorization endpoint
|
/// OAuth 2.0 authorization endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn oauth_authorization_endpoint(&self) -> Url {
|
pub fn oauth_authorization_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OAuth2AuthorizationEndpoint)
|
self.absolute_url_for(&crate::endpoints::OAuth2AuthorizationEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OAuth 2.0 token endpoint
|
/// OAuth 2.0 token endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn oauth_token_endpoint(&self) -> Url {
|
pub fn oauth_token_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OAuth2TokenEndpoint)
|
self.absolute_url_for(&crate::endpoints::OAuth2TokenEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OAuth 2.0 introspection endpoint
|
/// OAuth 2.0 introspection endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn oauth_introspection_endpoint(&self) -> Url {
|
pub fn oauth_introspection_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OAuth2Introspection)
|
self.absolute_url_for(&crate::endpoints::OAuth2Introspection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OAuth 2.0 revocation endpoint
|
/// OAuth 2.0 revocation endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn oauth_revocation_endpoint(&self) -> Url {
|
pub fn oauth_revocation_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OAuth2Revocation)
|
self.absolute_url_for(&crate::endpoints::OAuth2Revocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OAuth 2.0 client registration endpoint
|
/// OAuth 2.0 client registration endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn oauth_registration_endpoint(&self) -> Url {
|
pub fn oauth_registration_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OAuth2RegistrationEndpoint)
|
self.absolute_url_for(&crate::endpoints::OAuth2RegistrationEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDC userinfo endpoint
|
// OIDC userinfo endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn oidc_userinfo_endpoint(&self) -> Url {
|
pub fn oidc_userinfo_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OidcUserinfo)
|
self.absolute_url_for(&crate::endpoints::OidcUserinfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JWKS URI
|
/// JWKS URI
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn jwks_uri(&self) -> Url {
|
pub fn jwks_uri(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::OAuth2Keys)
|
self.absolute_url_for(&crate::endpoints::OAuth2Keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static asset
|
/// Static asset
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn static_asset(&self, path: String) -> Url {
|
pub fn static_asset(&self, path: String) -> Url {
|
||||||
self.url_for(&crate::endpoints::StaticAsset::new(path))
|
self.absolute_url_for(&crate::endpoints::StaticAsset::new(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static asset base
|
/// Static asset base
|
||||||
@ -124,18 +181,83 @@ impl UrlBuilder {
|
|||||||
/// GraphQL endpoint
|
/// GraphQL endpoint
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn graphql_endpoint(&self) -> Url {
|
pub fn graphql_endpoint(&self) -> Url {
|
||||||
self.url_for(&crate::endpoints::GraphQL)
|
self.absolute_url_for(&crate::endpoints::GraphQL)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upstream redirect URI
|
/// Upstream redirect URI
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn upstream_oauth_callback(&self, id: Ulid) -> Url {
|
pub fn upstream_oauth_callback(&self, id: Ulid) -> Url {
|
||||||
self.url_for(&crate::endpoints::UpstreamOAuth2Callback::new(id))
|
self.absolute_url_for(&crate::endpoints::UpstreamOAuth2Callback::new(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upstream authorize URI
|
/// Upstream authorize URI
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn upstream_oauth_authorize(&self, id: Ulid) -> Url {
|
pub fn upstream_oauth_authorize(&self, id: Ulid) -> Url {
|
||||||
self.url_for(&crate::endpoints::UpstreamOAuth2Authorize::new(id))
|
self.absolute_url_for(&crate::endpoints::UpstreamOAuth2Authorize::new(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_invalid_base_url_scheme() {
|
||||||
|
let _ = super::UrlBuilder::new(url::Url::parse("file:///tmp/").unwrap(), None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_invalid_base_url_query() {
|
||||||
|
let _ = super::UrlBuilder::new(
|
||||||
|
url::Url::parse("https://example.com/?foo=bar").unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_invalid_base_url_fragment() {
|
||||||
|
let _ = super::UrlBuilder::new(
|
||||||
|
url::Url::parse("https://example.com/#foo").unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_invalid_base_url_credentials() {
|
||||||
|
let _ = super::UrlBuilder::new(
|
||||||
|
url::Url::parse("https://foo@example.com/").unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_url_prefix() {
|
||||||
|
let builder = super::UrlBuilder::new(
|
||||||
|
url::Url::parse("https://example.com/foo/").unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert_eq!(builder.prefix, "/foo");
|
||||||
|
|
||||||
|
let builder =
|
||||||
|
super::UrlBuilder::new(url::Url::parse("https://example.com/").unwrap(), None, None);
|
||||||
|
assert_eq!(builder.prefix, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_absolute_uri_prefix() {
|
||||||
|
let builder = super::UrlBuilder::new(
|
||||||
|
url::Url::parse("https://example.com/foo/").unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let uri = builder.absolute_url_for(&crate::endpoints::OAuth2AuthorizationEndpoint);
|
||||||
|
assert_eq!(uri.as_str(), "https://example.com/foo/authorize");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ use mas_data_model::{
|
|||||||
UpstreamOAuthLink, UpstreamOAuthProvider, User, UserEmail, UserEmailVerification,
|
UpstreamOAuthLink, UpstreamOAuthProvider, User, UserEmail, UserEmailVerification,
|
||||||
};
|
};
|
||||||
use mas_i18n::DataLocale;
|
use mas_i18n::DataLocale;
|
||||||
use mas_router::{PostAuthAction, Route};
|
use mas_router::{Account, GraphQL, PostAuthAction, Route, UrlBuilder};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
@ -276,30 +276,29 @@ impl TemplateContext for IndexContext {
|
|||||||
|
|
||||||
/// Config used by the frontend app
|
/// Config used by the frontend app
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
root: String,
|
root: String,
|
||||||
}
|
graphql_endpoint: String,
|
||||||
|
|
||||||
impl Default for AppConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
root: "/account/".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context used by the `app.html` template
|
/// Context used by the `app.html` template
|
||||||
#[derive(Serialize, Default)]
|
#[derive(Serialize)]
|
||||||
pub struct AppContext {
|
pub struct AppContext {
|
||||||
app_config: AppConfig,
|
app_config: AppConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppContext {
|
impl AppContext {
|
||||||
/// Constructs the context for the app page with the given app root
|
/// Constructs the context given the [`UrlBuilder`]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_app_root(root: String) -> Self {
|
pub fn from_url_builder(url_builder: &UrlBuilder) -> Self {
|
||||||
|
let root = url_builder.relative_url_for(&Account::default());
|
||||||
|
let graphql_endpoint = url_builder.relative_url_for(&GraphQL);
|
||||||
Self {
|
Self {
|
||||||
app_config: AppConfig { root },
|
app_config: AppConfig {
|
||||||
|
root,
|
||||||
|
graphql_endpoint,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,7 +308,8 @@ impl TemplateContext for AppContext {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
vec![Self::default()]
|
let url_builder = UrlBuilder::new("https://example.com/".parse().unwrap(), None, None);
|
||||||
|
vec![Self::from_url_builder(&url_builder)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,7 +935,7 @@ impl UpstreamRegister {
|
|||||||
|
|
||||||
fn for_link_id(id: Ulid) -> Self {
|
fn for_link_id(id: Ulid) -> Self {
|
||||||
let login_link = mas_router::Login::and_link_upstream(id)
|
let login_link = mas_router::Login::and_link_upstream(id)
|
||||||
.relative_url()
|
.path_and_query()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -52,7 +52,7 @@ pub fn register(
|
|||||||
env.add_global(
|
env.add_global(
|
||||||
"include_asset",
|
"include_asset",
|
||||||
Value::from_object(IncludeAsset {
|
Value::from_object(IncludeAsset {
|
||||||
url_builder,
|
url_builder: url_builder.clone(),
|
||||||
vite_manifest,
|
vite_manifest,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -60,6 +60,19 @@ pub fn register(
|
|||||||
"translator",
|
"translator",
|
||||||
Value::from_object(TranslatorFunc { translator }),
|
Value::from_object(TranslatorFunc { translator }),
|
||||||
);
|
);
|
||||||
|
env.add_filter("prefix_url", move |url: &str| -> String {
|
||||||
|
if !url.starts_with('/') {
|
||||||
|
// Let's assume it's not an internal URL and return it as-is
|
||||||
|
return url.to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(prefix) = url_builder.prefix() else {
|
||||||
|
// If there is no prefix to add, return the URL as-is
|
||||||
|
return url.to_owned();
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{prefix}{url}")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tester_empty(seq: &dyn SeqObject) -> bool {
|
fn tester_empty(seq: &dyn SeqObject) -> bool {
|
||||||
|
@ -1278,6 +1278,10 @@
|
|||||||
"description": "A unique name for this listener which will be shown in traces and in metrics labels",
|
"description": "A unique name for this listener which will be shown in traces and in metrics labels",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"prefix": {
|
||||||
|
"description": "HTTP prefix to mount the resources on",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"proxy_protocol": {
|
"proxy_protocol": {
|
||||||
"description": "Accept HAProxy's Proxy Protocol V1",
|
"description": "Accept HAProxy's Proxy Protocol V1",
|
||||||
"default": false,
|
"default": false,
|
||||||
|
@ -23,7 +23,7 @@ limitations under the License.
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>matrix-authentication-service</title>
|
<title>matrix-authentication-service</title>
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
window.APP_CONFIG = JSON.parse('{"root": "/account/"}');
|
window.APP_CONFIG = JSON.parse('{"root": "/account/", "graphqlEndpoint": "/graphql"}');
|
||||||
(function () {
|
(function () {
|
||||||
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
function handleChange(list) {
|
function handleChange(list) {
|
||||||
|
@ -38,7 +38,7 @@ const HydrateLocation: React.FC<React.PropsWithChildren<{ path: string }>> = ({
|
|||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
useHydrateAtoms([
|
useHydrateAtoms([
|
||||||
[appConfigAtom, { root: "/" }],
|
[appConfigAtom, { root: "/", graphqlEndpoint: "/graphql" }],
|
||||||
[locationAtom, { pathname: path }],
|
[locationAtom, { pathname: path }],
|
||||||
]);
|
]);
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
@ -40,7 +40,7 @@ const meta = {
|
|||||||
|
|
||||||
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
||||||
useHydrateAtoms([
|
useHydrateAtoms([
|
||||||
[appConfigAtom, { root: "/" }],
|
[appConfigAtom, { root: "/", graphqlEndpoint: "/graphql" }],
|
||||||
[locationAtom, { pathname: "/" }],
|
[locationAtom, { pathname: "/" }],
|
||||||
]);
|
]);
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
@ -35,7 +35,7 @@ const meta = {
|
|||||||
|
|
||||||
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
||||||
useHydrateAtoms([
|
useHydrateAtoms([
|
||||||
[appConfigAtom, { root: "/" }],
|
[appConfigAtom, { root: "/", graphqlEndpoint: "/graphql" }],
|
||||||
[locationAtom, { pathname: "/" }],
|
[locationAtom, { pathname: "/" }],
|
||||||
]);
|
]);
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
@ -38,7 +38,7 @@ const HydrateLocation: React.FC<React.PropsWithChildren<{ path: string }>> = ({
|
|||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
useHydrateAtoms([
|
useHydrateAtoms([
|
||||||
[appConfigAtom, { root: "/" }],
|
[appConfigAtom, { root: "/", graphqlEndpoint: "/graphql" }],
|
||||||
[locationAtom, { pathname: path }],
|
[locationAtom, { pathname: path }],
|
||||||
]);
|
]);
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
@ -27,7 +27,7 @@ type Props = {
|
|||||||
|
|
||||||
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
||||||
useHydrateAtoms([
|
useHydrateAtoms([
|
||||||
[appConfigAtom, { root: "/" }],
|
[appConfigAtom, { root: "/", graphqlEndpoint: "/graphql" }],
|
||||||
[locationAtom, { pathname: "/" }],
|
[locationAtom, { pathname: "/" }],
|
||||||
]);
|
]);
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
@ -12,10 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
type AppConfig = {
|
export type AppConfig = {
|
||||||
root: string;
|
root: string;
|
||||||
|
graphqlEndpoint: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Window {
|
interface IWindow {
|
||||||
APP_CONFIG: AppConfig;
|
APP_CONFIG?: AppConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const config: AppConfig = (typeof window !== "undefined" &&
|
||||||
|
(window as IWindow).APP_CONFIG) || { root: "/", graphqlEndpoint: "/graphql" };
|
||||||
|
|
||||||
|
export default config;
|
@ -18,6 +18,7 @@ import { cacheExchange } from "@urql/exchange-graphcache";
|
|||||||
import { refocusExchange } from "@urql/exchange-refocus";
|
import { refocusExchange } from "@urql/exchange-refocus";
|
||||||
import { requestPolicyExchange } from "@urql/exchange-request-policy";
|
import { requestPolicyExchange } from "@urql/exchange-request-policy";
|
||||||
|
|
||||||
|
import appConfig from "./config";
|
||||||
import type {
|
import type {
|
||||||
MutationAddEmailArgs,
|
MutationAddEmailArgs,
|
||||||
MutationRemoveEmailArgs,
|
MutationRemoveEmailArgs,
|
||||||
@ -130,7 +131,7 @@ const exchanges = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const client = createClient({
|
export const client = createClient({
|
||||||
url: "/graphql",
|
url: appConfig.graphqlEndpoint,
|
||||||
// Add the devtools exchange in development
|
// Add the devtools exchange in development
|
||||||
exchanges: import.meta.env.DEV ? [devtoolsExchange, ...exchanges] : exchanges,
|
exchanges: import.meta.env.DEV ? [devtoolsExchange, ...exchanges] : exchanges,
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { atomWithLocation } from "jotai-location";
|
import { atomWithLocation } from "jotai-location";
|
||||||
|
|
||||||
|
import appConfig, { AppConfig } from "../config";
|
||||||
|
|
||||||
import { Location, pathToRoute, Route, routeToPath } from "./routes";
|
import { Location, pathToRoute, Route, routeToPath } from "./routes";
|
||||||
|
|
||||||
export const appConfigAtom = atom<AppConfig>(
|
export const appConfigAtom = atom<AppConfig>(appConfig);
|
||||||
typeof window !== "undefined" ? window.APP_CONFIG : { root: "/" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const locationToRoute = (root: string, location: Location): Route => {
|
const locationToRoute = (root: string, location: Location): Route => {
|
||||||
if (!location.pathname || !location.pathname.startsWith(root)) {
|
if (!location.pathname || !location.pathname.startsWith(root)) {
|
||||||
|
@ -24,7 +24,7 @@ const HydrateLocation: React.FC<React.PropsWithChildren<{ path: string }>> = ({
|
|||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
useHydrateAtoms([
|
useHydrateAtoms([
|
||||||
[appConfigAtom, { root: "/" }],
|
[appConfigAtom, { root: "/", graphqlEndpoint: "/graphql" }],
|
||||||
[locationAtom, { pathname: path }],
|
[locationAtom, { pathname: path }],
|
||||||
]);
|
]);
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
@ -15,15 +15,15 @@ limitations under the License.
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% macro link(text, href="#", class="") %}
|
{% macro link(text, href="#", class="") %}
|
||||||
<a class="cpd-button {{ class }}" data-kind="primary" data-size="lg" href="{{ href }}">{{ text }}</a>
|
<a class="cpd-button {{ class }}" data-kind="primary" data-size="lg" href="{{ href | prefix_url }}">{{ text }}</a>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro link_text(text, href="#", class="") %}
|
{% macro link_text(text, href="#", class="") %}
|
||||||
<a class="cpd-link {{ class }}" data-kind="primary" href="{{ href }}">{{ text }}</a>
|
<a class="cpd-link {{ class }}" data-kind="primary" href="{{ href | prefix_url }}">{{ text }}</a>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro link_outline(text, href="#", class="") %}
|
{% macro link_outline(text, href="#", class="") %}
|
||||||
<a class="cpd-button {{ class }}" data-kind="secondary" data-size="lg" href="{{ href }}">{{ text }}</a>
|
<a class="cpd-button {{ class }}" data-kind="secondary" data-size="lg" href="{{ href | prefix_url }}">{{ text }}</a>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro button(
|
{% macro button(
|
||||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% macro button(text, csrf_token, as_link=false, post_logout_action={}) %}
|
{% macro button(text, csrf_token, as_link=false, post_logout_action={}) %}
|
||||||
<form method="POST" action="/logout" class="inline">
|
<form method="POST" action="{{ "/logout" | prefix_url }}" class="inline">
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{% for key, value in post_logout_action|items %}
|
{% for key, value in post_logout_action|items %}
|
||||||
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
||||||
|
@ -22,7 +22,7 @@ limitations under the License.
|
|||||||
<h1 class="text-xl font-semibold">{{ _("mas.not_found.heading") }}</h1>
|
<h1 class="text-xl font-semibold">{{ _("mas.not_found.heading") }}</h1>
|
||||||
<p>{{ _("mas.not_found.description") }}</p>
|
<p>{{ _("mas.not_found.description") }}</p>
|
||||||
<div>
|
<div>
|
||||||
<a class="cpd-link" data-kind="primary" href="/">{{ _("mas.back_to_homepage") }}</a>
|
{{ button.link_text(text=_("mas.back_to_homepage"), href="/") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"back_to_homepage": "Go back to the homepage",
|
"back_to_homepage": "Go back to the homepage",
|
||||||
"@back_to_homepage": {
|
"@back_to_homepage": {
|
||||||
"context": "pages/404.html:25:64-89"
|
"context": "pages/404.html:25:37-62"
|
||||||
},
|
},
|
||||||
"change_password": {
|
"change_password": {
|
||||||
"change": "Change password",
|
"change": "Change password",
|
||||||
|
Reference in New Issue
Block a user