Compare commits

..
8 changed files with 95 additions and 129 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20' if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
uses: https://github.com/actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with: with:
node-version: 22 node-version: 22
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: 📦 Setup Node.js - name: 📦 Setup Node.js
uses: https://github.com/actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with: with:
node-version: "22" node-version: "22"
Generated
+9 -9
View File
@@ -637,7 +637,7 @@ checksum = "aa61aec073ec94791433ddf3df2323ff9d1711557c2a0eefb0f99cb4f8dca520"
dependencies = [ dependencies = [
"semver", "semver",
"serde", "serde",
"toml 1.1.3+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
] ]
[[package]] [[package]]
@@ -996,7 +996,7 @@ dependencies = [
"tikv-jemallocator", "tikv-jemallocator",
"tokio", "tokio",
"tokio-metrics", "tokio-metrics",
"toml 1.1.3+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-subscriber", "tracing-subscriber",
@@ -4728,7 +4728,7 @@ dependencies = [
"ruma-identifiers-validation", "ruma-identifiers-validation",
"serde", "serde",
"syn", "syn",
"toml 1.1.3+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
] ]
[[package]] [[package]]
@@ -5631,9 +5631,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.119" version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -5944,9 +5944,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "1.1.3+spec-1.1.0" version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [ dependencies = [
"indexmap 2.14.0", "indexmap 2.14.0",
"serde_core", "serde_core",
@@ -6018,9 +6018,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]] [[package]]
name = "toml_writer" name = "toml_writer"
version = "1.1.2+spec-1.1.0" version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]] [[package]]
name = "tonic" name = "tonic"
-1
View File
@@ -1 +0,0 @@
Fixed `create` being returned as a supported prompt value regardless of if registration is enabled or not. Contributed by @ginger
+1 -12
View File
@@ -37,17 +37,6 @@ pub(crate) async fn authorization_server_metadata(services: &Services) -> Value
.join(super::BASE_PATH) .join(super::BASE_PATH)
.unwrap(); .unwrap();
let prompt_values_supported = if services
.uiaa
.registration_flow_status()
.await
.any_available()
{
json!(["create"])
} else {
json!([])
};
json!({ json!({
"account_management_uri": endpoint_base.join(ACCOUNT_MANAGEMENT_PATH).unwrap(), "account_management_uri": endpoint_base.join(ACCOUNT_MANAGEMENT_PATH).unwrap(),
"account_management_actions_supported": [ "account_management_actions_supported": [
@@ -63,7 +52,7 @@ pub(crate) async fn authorization_server_metadata(services: &Services) -> Value
"grant_types_supported": ["authorization_code", "refresh_token"], "grant_types_supported": ["authorization_code", "refresh_token"],
"issuer": services.config.get_client_domain(), "issuer": services.config.get_client_domain(),
"jwks_uri": endpoint_base.join(JWKS_URI_PATH).unwrap(), "jwks_uri": endpoint_base.join(JWKS_URI_PATH).unwrap(),
"prompt_values_supported": prompt_values_supported, "prompt_values_supported": ["create"],
"registration_endpoint": endpoint_base.join(CLIENT_REGISTER_PATH).unwrap(), "registration_endpoint": endpoint_base.join(CLIENT_REGISTER_PATH).unwrap(),
"response_modes_supported": ["query", "fragment"], "response_modes_supported": ["query", "fragment"],
"response_types_supported": ["code"], "response_types_supported": ["code"],
+7 -89
View File
@@ -5,7 +5,6 @@
}; };
use conduwuit::{Err, Error, Result, error, utils}; use conduwuit::{Err, Error, Result, error, utils};
use futures::StreamExt;
use lettre::Address; use lettre::Address;
use ruma::{ use ruma::{
DeviceId, UserId, DeviceId, UserId,
@@ -26,7 +25,7 @@
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
Dep, config, firstrun, globals, Dep, config, globals,
oauth::{self, OAuthTicket}, oauth::{self, OAuthTicket},
registration_tokens, threepid, users, registration_tokens, threepid, users,
}; };
@@ -37,54 +36,25 @@ pub struct Service {
} }
struct Services { struct Services {
config: Dep<config::Service>,
firstrun: Dep<firstrun::Service>,
globals: Dep<globals::Service>, globals: Dep<globals::Service>,
oauth: Dep<oauth::Service>, users: Dep<users::Service>,
config: Dep<config::Service>,
registration_tokens: Dep<registration_tokens::Service>, registration_tokens: Dep<registration_tokens::Service>,
threepid: Dep<threepid::Service>, threepid: Dep<threepid::Service>,
users: Dep<users::Service>, oauth: Dep<oauth::Service>,
}
#[derive(Debug)]
pub enum TrustedFlowStatus {
Unavailable,
Available,
}
#[derive(Debug)]
pub enum UntrustedFlowStatus {
Unavailable,
Available {
require_email: bool,
},
}
pub struct FlowStatus {
pub trusted: TrustedFlowStatus,
pub untrusted: UntrustedFlowStatus,
}
impl FlowStatus {
#[must_use]
pub fn any_available(&self) -> bool {
matches!(self.trusted, TrustedFlowStatus::Available)
|| matches!(self.untrusted, UntrustedFlowStatus::Available { .. })
}
} }
impl crate::Service for Service { impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> { fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self { Ok(Arc::new(Self {
services: Services { services: Services {
config: args.depend::<config::Service>("config"),
firstrun: args.depend::<firstrun::Service>("firstrun"),
globals: args.depend::<globals::Service>("globals"), globals: args.depend::<globals::Service>("globals"),
oauth: args.depend::<oauth::Service>("oauth"), users: args.depend::<users::Service>("users"),
config: args.depend::<config::Service>("config"),
registration_tokens: args registration_tokens: args
.depend::<registration_tokens::Service>("registration_tokens"), .depend::<registration_tokens::Service>("registration_tokens"),
threepid: args.depend::<threepid::Service>("threepid"), threepid: args.depend::<threepid::Service>("threepid"),
users: args.depend::<users::Service>("users"), oauth: args.depend::<oauth::Service>("oauth"),
}, },
uiaa_sessions: Mutex::new(HashMap::new()), uiaa_sessions: Mutex::new(HashMap::new()),
})) }))
@@ -625,56 +595,4 @@ async fn check_stage(
Ok((completed_auth_type, session_metadata)) Ok((completed_auth_type, session_metadata))
} }
pub async fn registration_flow_status(&self) -> FlowStatus {
// Allow registration if it's enabled in the config file or if this is the first
// run (so the first user account can be created)
let allow_registration =
self.services.config.allow_registration || self.services.firstrun.is_first_run();
// Trusted flow is only available if any registration tokens exist
let trusted = {
if !allow_registration {
TrustedFlowStatus::Unavailable
} else if self
.services
.registration_tokens
.iterate_tokens()
.next()
.await
.is_some()
{
TrustedFlowStatus::Available
} else {
TrustedFlowStatus::Unavailable
}
};
// Untrusted flow is available if email is required for registration,
// or reCAPTCHA is configured, or open registration is enabled
let untrusted = {
let require_email = self
.services
.config
.smtp
.as_ref()
.is_some_and(|smtp| smtp.require_email_for_registration);
if !allow_registration || self.services.firstrun.is_first_run() {
UntrustedFlowStatus::Unavailable
} else if self.services.config.recaptcha_private_site_key.is_some() || require_email {
UntrustedFlowStatus::Available { require_email }
} else if self
.services
.config
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
{
UntrustedFlowStatus::Available { require_email: false }
} else {
UntrustedFlowStatus::Unavailable
}
};
FlowStatus { trusted, untrusted }
}
} }
+8 -7
View File
@@ -21,6 +21,7 @@
extract::{Expect, PostForm}, extract::{Expect, PostForm},
pages::{ pages::{
GET_POST, Result, TemplateContext, GET_POST, Result, TemplateContext,
account::register::{TrustedFlowStatus, UntrustedFlowStatus, registration_flow_status},
components::UserCard, components::UserCard,
oidc::{OIDC_SESSION_ID_KEY, OidcSession, OidcSessionState}, oidc::{OIDC_SESSION_ID_KEY, OidcSession, OidcSessionState},
}, },
@@ -100,13 +101,13 @@ async fn route_login(
LoginType::Oidc { redirect_url } LoginType::Oidc { redirect_url }
} else { } else {
LoginType::Interactive { let (trusted_flow_status, untrusted_flow_status) =
registration_available: services registration_flow_status(&services).await;
.uiaa
.registration_flow_status() let registration_available = matches!(trusted_flow_status, TrustedFlowStatus::Available)
.await || matches!(untrusted_flow_status, UntrustedFlowStatus::Available { .. });
.any_available(),
} LoginType::Interactive { registration_available }
}; };
let body = match &user_id { let body = match &user_id {
+68 -9
View File
@@ -8,12 +8,9 @@
}; };
use conduwuit_core::{config::TermsDocument, warn}; use conduwuit_core::{config::TermsDocument, warn};
use conduwuit_service::{ use conduwuit_service::{
mailer::messages, mailer::messages, registration_tokens::ValidToken, users::HashedPassword,
registration_tokens::ValidToken,
uiaa::{FlowStatus, TrustedFlowStatus, UntrustedFlowStatus},
users::HashedPassword,
}; };
use futures::FutureExt; use futures::{FutureExt, StreamExt};
use lettre::{Address, message::Mailbox}; use lettre::{Address, message::Mailbox};
use ruma::{ClientSecret, OwnedClientSecret, OwnedServerName, OwnedSessionId, OwnedUserId}; use ruma::{ClientSecret, OwnedClientSecret, OwnedServerName, OwnedSessionId, OwnedUserId};
use serde::{Deserialize, Serialize, de::IgnoredAny}; use serde::{Deserialize, Serialize, de::IgnoredAny};
@@ -64,6 +61,20 @@ enum RegisterBody {
}, },
} }
#[derive(Debug)]
pub(super) enum TrustedFlowStatus {
Unavailable,
Available,
}
#[derive(Debug)]
pub(super) enum UntrustedFlowStatus {
Unavailable,
Available {
require_email: bool,
},
}
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]
pub(crate) struct RegisterQuery { pub(crate) struct RegisterQuery {
pub username: Option<String>, pub username: Option<String>,
@@ -158,10 +169,7 @@ async fn route_register(
ValidationErrors::new() ValidationErrors::new()
}; };
let FlowStatus { let (trusted_flow_status, untrusted_flow_status) = registration_flow_status(&services).await;
trusted: trusted_flow_status,
untrusted: untrusted_flow_status,
} = services.uiaa.registration_flow_status().await;
if matches!(trusted_flow_status, TrustedFlowStatus::Unavailable) if matches!(trusted_flow_status, TrustedFlowStatus::Unavailable)
&& matches!(untrusted_flow_status, UntrustedFlowStatus::Unavailable) && matches!(untrusted_flow_status, UntrustedFlowStatus::Unavailable)
@@ -532,3 +540,54 @@ async fn complete_registration(
Ok(Redirect::to(&next.unwrap_or_default().target_path())) Ok(Redirect::to(&next.unwrap_or_default().target_path()))
} }
pub(super) async fn registration_flow_status(
services: &crate::State,
) -> (TrustedFlowStatus, UntrustedFlowStatus) {
// Allow registration if it's enabled in the config file or if this is the first
// run (so the first user account can be created)
let allow_registration =
services.config.allow_registration || services.firstrun.is_first_run();
// Trusted flow is only available if any registration tokens exist
let trusted_flow_status = {
if !allow_registration {
TrustedFlowStatus::Unavailable
} else if services
.registration_tokens
.iterate_tokens()
.next()
.await
.is_some()
{
TrustedFlowStatus::Available
} else {
TrustedFlowStatus::Unavailable
}
};
// Untrusted flow is available if email is required for registration,
// or reCAPTCHA is configured, or open registration is enabled
let untrusted_flow_status = {
let require_email = services
.config
.smtp
.as_ref()
.is_some_and(|smtp| smtp.require_email_for_registration);
if !allow_registration || services.firstrun.is_first_run() {
UntrustedFlowStatus::Unavailable
} else if services.config.recaptcha_private_site_key.is_some() || require_email {
UntrustedFlowStatus::Available { require_email }
} else if services
.config
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
{
UntrustedFlowStatus::Available { require_email: false }
} else {
UntrustedFlowStatus::Unavailable
}
};
(trusted_flow_status, untrusted_flow_status)
}