Merge remote-tracking branch 'origin/main' into feat/login_hint_with_email

This commit is contained in:
Quentin Gliech
2025-08-18 16:43:00 +02:00
55 changed files with 320 additions and 389 deletions

View File

@@ -46,7 +46,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
with:
# Need a full clone so that `git describe` reports the right version
fetch-depth: 0
@@ -67,7 +67,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- uses: ./.github/actions/build-frontend
- uses: ./.github/actions/build-policies
@@ -112,7 +112,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -162,19 +162,19 @@ jobs:
steps:
- name: Download assets
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: assets
path: assets-dist
- name: Download binary x86_64
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: binary-x86_64-unknown-linux-gnu
path: binary-x86_64
- name: Download binary aarch64
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: binary-aarch64-unknown-linux-gnu
path: binary-aarch64
@@ -320,7 +320,7 @@ jobs:
- build-image
steps:
- name: Download the artifacts from the previous job
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: mas-cli-*
path: artifacts
@@ -376,13 +376,13 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
with:
sparse-checkout: |
.github/scripts
- name: Download the artifacts from the previous job
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: mas-cli-*
path: artifacts
@@ -454,7 +454,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
with:
sparse-checkout: |
.github/scripts

View File

@@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- uses: ./.github/actions/build-policies
@@ -60,7 +60,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4.4.0
@@ -84,7 +84,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4.4.0
@@ -108,7 +108,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4.4.0
@@ -132,7 +132,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
@@ -155,10 +155,10 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Run `cargo-deny`
uses: EmbarkStudios/cargo-deny-action@v2.0.12
uses: EmbarkStudios/cargo-deny-action@v2.0.13
with:
rust-version: stable
@@ -171,7 +171,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
run: |
@@ -212,10 +212,10 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.87.0
uses: dtolnay/rust-toolchain@1.89.0
with:
components: clippy
@@ -237,7 +237,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -290,7 +290,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -304,7 +304,7 @@ jobs:
- uses: ./.github/actions/build-policies
- name: Download archive
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: nextest-archive

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- uses: ./.github/actions/build-policies
@@ -54,7 +54,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- uses: ./.github/actions/build-frontend
env:
@@ -99,7 +99,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -53,7 +53,7 @@ jobs:
done
- name: Upload GitHub Pages artifacts
uses: actions/upload-pages-artifact@v3.0.1
uses: actions/upload-pages-artifact@v4.0.0
with:
path: target/book/

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
sparse-checkout: |
.github/scripts

View File

@@ -34,7 +34,7 @@ jobs:
run: exit 1
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -61,7 +61,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4.4.0
@@ -106,7 +106,7 @@ jobs:
needs: [tag, compute-version, localazy]
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
with:
sparse-checkout: |
.github/scripts

View File

@@ -33,7 +33,7 @@ jobs:
run: exit 1
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -76,7 +76,7 @@ jobs:
needs: [tag, compute-version]
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
with:
sparse-checkout: |
.github/scripts

View File

@@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

View File

@@ -19,7 +19,7 @@ jobs:
run: exit 1
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4.4.0

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4.4.0

20
Cargo.lock generated
View File

@@ -831,9 +831,9 @@ dependencies = [
[[package]]
name = "camino"
version = "1.1.10"
version = "1.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0"
dependencies = [
"serde",
]
@@ -980,9 +980,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.42"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
dependencies = [
"clap_builder",
"clap_derive",
@@ -990,9 +990,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.42"
version = "4.5.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
dependencies = [
"anstream",
"anstyle",
@@ -1002,9 +1002,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.41"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2084,9 +2084,9 @@ dependencies = [
[[package]]
name = "governor"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cbe789d04bf14543f03c4b60cd494148aa79438c8440ae7d81a7778147745c3"
checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19"
dependencies = [
"cfg-if",
"dashmap",

View File

@@ -137,7 +137,7 @@ version = "1.10.1"
# UTF-8 paths
[workspace.dependencies.camino]
version = "1.1.10"
version = "1.1.11"
features = ["serde1"]
# ChaCha20Poly1305 AEAD
@@ -167,7 +167,7 @@ features = ["serde", "clock"]
# CLI argument parsing
[workspace.dependencies.clap]
version = "4.5.42"
version = "4.5.45"
features = ["derive"]
# Object Identifiers (OIDs) as constants
@@ -239,7 +239,7 @@ version = "0.14.7"
# Rate-limiting
[workspace.dependencies.governor]
version = "0.10.0"
version = "0.10.1"
default-features = false
features = ["std", "dashmap", "quanta"]

View File

@@ -12,7 +12,7 @@
# The Debian version and version name must be in sync
ARG DEBIAN_VERSION=12
ARG DEBIAN_VERSION_NAME=bookworm
ARG RUSTC_VERSION=1.87.0
ARG RUSTC_VERSION=1.89.0
ARG NODEJS_VERSION=20.15.0
ARG OPA_VERSION=1.1.0
ARG CARGO_AUDITABLE_VERSION=0.6.6

View File

@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
# Please see LICENSE files in the repository root for full details.
doc-valid-idents = ["OpenID", "OAuth", "..", "PostgreSQL", "SQLite"]
doc-valid-idents = ["OpenID", "OAuth", "UserInfo", "..", "PostgreSQL", "SQLite"]
disallowed-methods = [
{ path = "rand::thread_rng", reason = "do not create rngs on the fly, pass them as parameters" },

View File

@@ -12,13 +12,13 @@ fn main() -> anyhow::Result<()> {
// At build time, we override the version through the environment variable
// VERGEN_GIT_DESCRIBE. In some contexts, it means this variable is set but
// empty, so we unset it here.
if let Ok(ver) = std::env::var("VERGEN_GIT_DESCRIBE") {
if ver.is_empty() {
#[allow(unsafe_code)]
// SAFETY: This is safe because the build script is running a single thread
unsafe {
std::env::remove_var("VERGEN_GIT_DESCRIBE");
}
if let Ok(ver) = std::env::var("VERGEN_GIT_DESCRIBE")
&& ver.is_empty()
{
#[allow(unsafe_code)]
// SAFETY: This is safe because the build script is running a single thread
unsafe {
std::env::remove_var("VERGEN_GIT_DESCRIBE");
}
}

View File

@@ -273,10 +273,10 @@ fn infer_client_ip(
let peer = if let Some(info) = connection_info {
// We can always trust the proxy protocol to give us the correct IP address
if let Some(proxy) = info.get_proxy_ref() {
if let Some(source) = proxy.source() {
return Some(source.ip());
}
if let Some(proxy) = info.get_proxy_ref()
&& let Some(source) = proxy.source()
{
return Some(source.ip());
}
info.get_peer_addr().map(|addr| addr.ip())

View File

@@ -619,13 +619,12 @@ impl Options {
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
if let Some(password) = &password {
if !ignore_password_complexity
&& !password_manager.is_password_complex_enough(password)?
{
error!("That password is too weak.");
return Ok(ExitCode::from(1));
}
if let Some(password) = &password
&& !ignore_password_complexity
&& !password_manager.is_password_complex_enough(password)?
{
error!("That password is too weak.");
return Ok(ExitCode::from(1));
}
// If the username is provided, check if it's available and normalize it.

View File

@@ -209,11 +209,11 @@ pub async fn config_sync(
// private key to hold the content of the private key file.
// private key (raw) takes precedence so both can be defined
// without issues
if siwa.private_key.is_none() {
if let Some(private_key_file) = siwa.private_key_file.take() {
let key = tokio::fs::read_to_string(private_key_file).await?;
siwa.private_key = Some(key);
}
if siwa.private_key.is_none()
&& let Some(private_key_file) = siwa.private_key_file.take()
{
let key = tokio::fs::read_to_string(private_key_file).await?;
siwa.private_key = Some(key);
}
let encoded = serde_json::to_vec(&siwa)?;
Some(encrypter.encrypt_to_string(&encoded)?)

View File

@@ -149,7 +149,7 @@ impl KeyConfig {
/// Returns the password in case any is provided.
///
/// If `password_file` was given, the password is read from that file.
async fn password(&self) -> anyhow::Result<Option<Cow<[u8]>>> {
async fn password(&self) -> anyhow::Result<Option<Cow<'_, [u8]>>> {
Ok(match &self.password {
Some(Password::File(path)) => Some(Cow::Owned(tokio::fs::read(path).await?)),
Some(Password::Value(password)) => Some(Cow::Borrowed(password.as_bytes())),
@@ -160,7 +160,7 @@ impl KeyConfig {
/// Returns the key.
///
/// If `key_file` was given, the key is read from that file.
async fn key(&self) -> anyhow::Result<Cow<[u8]>> {
async fn key(&self) -> anyhow::Result<Cow<'_, [u8]>> {
Ok(match &self.key {
Key::File(path) => Cow::Owned(tokio::fs::read(path).await?),
Key::Value(key) => Cow::Borrowed(key.as_bytes()),

View File

@@ -198,34 +198,34 @@ impl ConfigurationSection for TelemetryConfig {
&self,
_figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
if let Some(sample_rate) = self.sentry.sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.sample_rate")
.into());
}
if let Some(sample_rate) = self.sentry.sample_rate
&& !(0.0..=1.0).contains(&sample_rate)
{
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.sample_rate")
.into());
}
if let Some(sample_rate) = self.sentry.traces_sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.traces_sample_rate")
.into());
}
if let Some(sample_rate) = self.sentry.traces_sample_rate
&& !(0.0..=1.0).contains(&sample_rate)
{
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.traces_sample_rate")
.into());
}
if let Some(sample_rate) = self.tracing.sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Tracing sample rate must be between 0.0 and 1.0",
)
.with_path("tracing.sample_rate")
.into());
}
if let Some(sample_rate) = self.tracing.sample_rate
&& !(0.0..=1.0).contains(&sample_rate)
{
return Err(figment::error::Error::custom(
"Tracing sample rate must be between 0.0 and 1.0",
)
.with_path("tracing.sample_rate")
.into());
}
Ok(())

View File

@@ -652,7 +652,7 @@ pub struct Provider {
/// What to do when receiving an OIDC Backchannel logout request.
///
/// Defaults to "do_nothing".
/// Defaults to `do_nothing`.
#[serde(default, skip_serializing_if = "OnBackchannelLogout::is_default")]
pub on_backchannel_logout: OnBackchannelLogout,
}

View File

@@ -129,31 +129,31 @@ where
field_fromatter.format_fields(writer.by_ref(), event)?;
// If we have a OTEL span, we can add the trace ID to the end of the log line
if let Some(span) = ctx.lookup_current() {
if let Some(otel) = span.extensions().get::<OtelData>() {
let parent_cx_span = otel.parent_cx.span();
let sc = parent_cx_span.span_context();
if let Some(span) = ctx.lookup_current()
&& let Some(otel) = span.extensions().get::<OtelData>()
{
let parent_cx_span = otel.parent_cx.span();
let sc = parent_cx_span.span_context();
// Check if the span is sampled, first from the span builder,
// then from the parent context if nothing is set there
if otel
.builder
.sampling_result
.as_ref()
.map_or(sc.is_sampled(), |r| {
r.decision == SamplingDecision::RecordAndSample
})
{
// If it is the root span, the trace ID will be in the span builder. Else, it
// will be in the parent OTEL context
let trace_id = otel.builder.trace_id.unwrap_or(sc.trace_id());
if trace_id != TraceId::INVALID {
let label = Style::new()
.italic()
.force_styling(ansi)
.apply_to("trace.id");
write!(&mut writer, " {label}={trace_id}")?;
}
// Check if the span is sampled, first from the span builder,
// then from the parent context if nothing is set there
if otel
.builder
.sampling_result
.as_ref()
.map_or(sc.is_sampled(), |r| {
r.decision == SamplingDecision::RecordAndSample
})
{
// If it is the root span, the trace ID will be in the span builder. Else, it
// will be in the parent OTEL context
let trace_id = otel.builder.trace_id.unwrap_or(sc.trace_id());
if trace_id != TraceId::INVALID {
let label = Style::new()
.italic()
.force_styling(ansi)
.apply_to("trace.id");
write!(&mut writer, " {label}={trace_id}")?;
}
}
}

View File

@@ -183,7 +183,7 @@ impl AuthorizationGrant {
///
/// Otherwise returns `LoginHint::None`
#[must_use]
pub fn parse_login_hint(&self, homeserver: &str) -> LoginHint {
pub fn parse_login_hint(&self, homeserver: &str) -> LoginHint<'_> {
let Some(login_hint) = &self.login_hint else {
return LoginHint::None;
};

View File

@@ -289,7 +289,7 @@ pub struct UpstreamOAuthProvider {
impl PartialOrd for UpstreamOAuthProvider {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.id.cmp(&other.id))
Some(self.cmp(other))
}
}

View File

@@ -88,32 +88,31 @@ impl UserAgent {
#[must_use]
pub fn parse(user_agent: String) -> Self {
if !user_agent.contains("Mozilla/") {
if let Some((name, version, model, os, os_version)) =
if !user_agent.contains("Mozilla/")
&& let Some((name, version, model, os, os_version)) =
UserAgent::parse_custom(&user_agent)
{
let mut device_type = DeviceType::Unknown;
{
let mut device_type = DeviceType::Unknown;
// Handle mobile simple mobile devices
if os == "Android" || os == "iOS" {
device_type = DeviceType::Mobile;
}
// Handle iPads
if model.contains("iPad") {
device_type = DeviceType::Tablet;
}
return Self {
name: Some(name.to_owned()),
version: Some(version.to_owned()),
os: Some(os.to_owned()),
os_version: os_version.map(std::borrow::ToOwned::to_owned),
model: Some(model.to_owned()),
device_type,
raw: user_agent,
};
// Handle mobile simple mobile devices
if os == "Android" || os == "iOS" {
device_type = DeviceType::Mobile;
}
// Handle iPads
if model.contains("iPad") {
device_type = DeviceType::Tablet;
}
return Self {
name: Some(name.to_owned()),
version: Some(version.to_owned()),
os: Some(os.to_owned()),
os_version: os_version.map(std::borrow::ToOwned::to_owned),
model: Some(model.to_owned()),
device_type,
raw: user_agent,
};
}
let mut model = None;
@@ -205,11 +204,11 @@ impl UserAgent {
}
// Special handling for Electron applications e.g. Element Desktop
if user_agent.contains("Electron/") {
if let Some(app) = UserAgent::parse_electron(&user_agent) {
result.name = app.0;
result.version = app.1;
}
if user_agent.contains("Electron/")
&& let Some(app) = UserAgent::parse_electron(&user_agent)
{
result.name = app.0;
result.version = app.1;
}
Self {

View File

@@ -223,17 +223,17 @@ impl UserRegistrationToken {
}
// Check if expired
if let Some(expires_at) = self.expires_at {
if now >= expires_at {
return false;
}
if let Some(expires_at) = self.expires_at
&& now >= expires_at
{
return false;
}
// Check if usage limit exceeded
if let Some(usage_limit) = self.usage_limit {
if self.times_used >= usage_limit {
return false;
}
if let Some(usage_limit) = self.usage_limit
&& self.times_used >= usage_limit
{
return false;
}
true

View File

@@ -187,10 +187,10 @@ where
};
// If there is a user for this session, check that it is not locked
if let Some(user) = &user {
if !user.is_valid() {
return Err(Rejection::UserLocked);
}
if let Some(user) = &user
&& !user.is_valid()
{
return Err(Rejection::UserLocked);
}
if !session.is_valid() {

View File

@@ -8,8 +8,7 @@ use anyhow::Context as _;
use async_graphql::{Context, Description, Enum, ID, Object};
use chrono::{DateTime, Utc};
use mas_storage::{oauth2::OAuth2ClientRepository, user::BrowserSessionRepository};
use oauth2_types::{oidc::ApplicationType, scope::Scope};
use ulid::Ulid;
use oauth2_types::oidc::ApplicationType;
use url::Url;
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
@@ -200,33 +199,3 @@ impl OAuth2Client {
}
}
}
/// An OAuth 2.0 consent represents the scope a user consented to grant to a
/// client.
#[derive(Description)]
pub struct OAuth2Consent {
scope: Scope,
client_id: Ulid,
}
#[Object(use_type_description)]
impl OAuth2Consent {
/// Scope consented by the user for this client.
pub async fn scope(&self) -> String {
self.scope.to_string()
}
/// OAuth 2.0 client for which the user granted access.
pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
let state = ctx.state();
let mut repo = state.repository().await?;
let client = repo
.oauth2_client()
.lookup(self.client_id)
.await?
.context("Could not load client")?;
repo.cancel().await?;
Ok(OAuth2Client(client))
}
}

View File

@@ -348,7 +348,7 @@ async fn test_oauth2_admin(pool: PgPool) {
}
/// Test that we can query the GraphQL endpoint with a token from a
/// client_credentials grant.
/// `client_credentials` grant.
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_oauth2_client_credentials(pool: PgPool) {
setup();

View File

@@ -476,13 +476,13 @@ fn recover_error(
) -> axum::response::Response {
// Error responses should have an ErrorContext attached to them
let ext = response.extensions().get::<ErrorContext>();
if let Some(ctx) = ext {
if let Ok(res) = templates.render_error(ctx) {
let (mut parts, _original_body) = response.into_parts();
parts.headers.remove(CONTENT_TYPE);
parts.headers.remove(CONTENT_LENGTH);
return (parts, Html(res)).into_response();
}
if let Some(ctx) = ext
&& let Ok(res) = templates.render_error(ctx)
{
let (mut parts, _original_body) = response.into_parts();
parts.headers.remove(CONTENT_TYPE);
parts.headers.remove(CONTENT_LENGTH);
return (parts, Html(res)).into_response();
}
response

View File

@@ -288,10 +288,10 @@ pub(crate) async fn post(
let token = &form.token;
let token_type = TokenType::check(token)?;
if let Some(hint) = form.token_type_hint {
if token_type != hint {
return Err(RouteError::UnexpectedTokenType);
}
if let Some(hint) = form.token_type_hint
&& token_type != hint
{
return Err(RouteError::UnexpectedTokenType);
}
// Not all device IDs can be encoded as scope. On OAuth 2.0 sessions, we

View File

@@ -242,34 +242,34 @@ pub(crate) async fn post(
// Some extra validation that is hard to do in OPA and not done by the
// `validate` method either
if let Some(client_uri) = &metadata.client_uri {
if localised_url_has_public_suffix(client_uri) {
return Err(RouteError::UrlIsPublicSuffix("client_uri"));
}
if let Some(client_uri) = &metadata.client_uri
&& localised_url_has_public_suffix(client_uri)
{
return Err(RouteError::UrlIsPublicSuffix("client_uri"));
}
if let Some(logo_uri) = &metadata.logo_uri {
if localised_url_has_public_suffix(logo_uri) {
return Err(RouteError::UrlIsPublicSuffix("logo_uri"));
}
if let Some(logo_uri) = &metadata.logo_uri
&& localised_url_has_public_suffix(logo_uri)
{
return Err(RouteError::UrlIsPublicSuffix("logo_uri"));
}
if let Some(policy_uri) = &metadata.policy_uri {
if localised_url_has_public_suffix(policy_uri) {
return Err(RouteError::UrlIsPublicSuffix("policy_uri"));
}
if let Some(policy_uri) = &metadata.policy_uri
&& localised_url_has_public_suffix(policy_uri)
{
return Err(RouteError::UrlIsPublicSuffix("policy_uri"));
}
if let Some(tos_uri) = &metadata.tos_uri {
if localised_url_has_public_suffix(tos_uri) {
return Err(RouteError::UrlIsPublicSuffix("tos_uri"));
}
if let Some(tos_uri) = &metadata.tos_uri
&& localised_url_has_public_suffix(tos_uri)
{
return Err(RouteError::UrlIsPublicSuffix("tos_uri"));
}
if let Some(initiate_login_uri) = &metadata.initiate_login_uri {
if host_is_public_suffix(initiate_login_uri) {
return Err(RouteError::UrlIsPublicSuffix("initiate_login_uri"));
}
if let Some(initiate_login_uri) = &metadata.initiate_login_uri
&& host_is_public_suffix(initiate_login_uri)
{
return Err(RouteError::UrlIsPublicSuffix("initiate_login_uri"));
}
for redirect_uri in metadata.redirect_uris() {

View File

@@ -93,17 +93,15 @@ pub(crate) async fn get(
// Forward the raw login hint upstream for the provider to handle however it
// sees fit
if provider.forward_login_hint {
if let Some(PostAuthAction::ContinueAuthorizationGrant { id }) = &query.post_auth_action {
if let Some(login_hint) = repo
.oauth2_authorization_grant()
.lookup(*id)
.await?
.and_then(|grant| grant.login_hint)
{
data = data.with_login_hint(login_hint);
}
}
if provider.forward_login_hint
&& let Some(PostAuthAction::ContinueAuthorizationGrant { id }) = &query.post_auth_action
&& let Some(login_hint) = repo
.oauth2_authorization_grant()
.lookup(*id)
.await?
.and_then(|grant| grant.login_hint)
{
data = data.with_login_hint(login_hint);
}
let data = if let Some(methods) = lazy_metadata.pkce_methods().await? {

View File

@@ -34,14 +34,14 @@ pub(crate) async fn post(
if let Some(session_id) = session_info.current_session_id() {
let maybe_session = repo.browser_session().lookup(session_id).await?;
if let Some(session) = maybe_session {
if session.finished_at.is_none() {
activity_tracker
.record_browser_session(&clock, &session)
.await;
if let Some(session) = maybe_session
&& session.finished_at.is_none()
{
activity_tracker
.record_browser_session(&clock, &session)
.await;
repo.browser_session().finish(&clock, session).await?;
}
repo.browser_session().finish(&clock, session).await?;
}
}

View File

@@ -110,36 +110,36 @@ fn find_in_call<'a>(
call: &'a Spanned<Call<'a>>,
) -> Result<(), minijinja::Error> {
let span = call.span();
if let Expr::Var(var_) = &call.expr {
if var_.id == context.func() {
let key = call
.args
.first()
.and_then(as_const)
.and_then(|const_| const_.value.as_str())
.ok_or(minijinja::Error::new(
ErrorKind::UndefinedError,
"t() first argument must be a string literal",
))?;
if let Expr::Var(var_) = &call.expr
&& var_.id == context.func()
{
let key = call
.args
.first()
.and_then(as_const)
.and_then(|const_| const_.value.as_str())
.ok_or(minijinja::Error::new(
ErrorKind::UndefinedError,
"t() first argument must be a string literal",
))?;
let has_count = call
.args
.iter()
.any(|arg| matches!(arg, CallArg::Kwarg("count", _)));
let has_count = call
.args
.iter()
.any(|arg| matches!(arg, CallArg::Kwarg("count", _)));
let key = Key::new(
if has_count {
crate::key::Kind::Plural
} else {
crate::key::Kind::Message
},
key.to_owned(),
);
let key = Key::new(
if has_count {
crate::key::Kind::Plural
} else {
crate::key::Kind::Message
},
key.to_owned(),
);
let key = context.set_key_location(key, span);
let key = context.set_key_location(key, span);
context.record(key);
}
context.record(key);
}
find_in_expr(context, &call.expr)?;

View File

@@ -72,6 +72,7 @@ pub(crate) use sprintf;
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
#[allow(dead_code)]
enum Error {
Format(#[from] self::formatter::FormatError),
Parse(Box<self::parser::Error>),

View File

@@ -279,11 +279,11 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
if let Poll::Ready(()) = this.cancellation_future.poll(cx) {
if !*this.did_start_shutdown {
*this.did_start_shutdown = true;
this.connection.as_mut().graceful_shutdown();
}
if let Poll::Ready(()) = this.cancellation_future.poll(cx)
&& !*this.did_start_shutdown
{
*this.did_start_shutdown = true;
this.connection.as_mut().graceful_shutdown();
}
this.connection.poll(cx)

View File

@@ -577,7 +577,7 @@ pub struct ProviderMetadata {
pub require_request_uri_registration: Option<bool>,
/// Indicates where authorization request needs to be protected as [Request
/// Object] and provided through either request or request_uri parameter.
/// Object] and provided through either request or `request_uri` parameter.
///
/// Defaults to `false`.
///
@@ -680,10 +680,10 @@ impl ProviderMetadata {
validate_url("registration_endpoint", url, ExtraUrlRestrictions::None)?;
}
if let Some(scopes) = &metadata.scopes_supported {
if !scopes.iter().any(|s| s == "openid") {
return Err(ProviderMetadataVerificationError::ScopesMissingOpenid);
}
if let Some(scopes) = &metadata.scopes_supported
&& !scopes.iter().any(|s| s == "openid")
{
return Err(ProviderMetadataVerificationError::ScopesMissingOpenid);
}
validate_signing_alg_values_supported(

View File

@@ -911,7 +911,8 @@ pub struct ClientRegistrationResponse {
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
pub client_id_issued_at: Option<DateTime<Utc>>,
/// Time at which the client_secret will expire or 0 if it will not expire.
/// Time at which the `client_secret` will expire or 0 if it will not
/// expire.
///
/// Required if `client_secret` is issued.
#[serde(default)]

View File

@@ -74,7 +74,7 @@ fn keystore(alg: &JsonWebSignatureAlg) -> Keystore {
}
/// Generate an ID token.
fn id_token(issuer: &str) -> (IdToken, PublicJsonWebKeySet) {
fn id_token(issuer: &str) -> (IdToken<'_>, PublicJsonWebKeySet) {
let signing_alg = ID_TOKEN_SIGNING_ALG;
let keystore = keystore(&signing_alg);

View File

@@ -34,7 +34,7 @@ fn id_token(
issuer: &str,
flag: Option<IdTokenFlag>,
auth_time: Option<DateTime<Utc>>,
) -> (IdToken, PublicJsonWebKeySet) {
) -> (IdToken<'_>, PublicJsonWebKeySet) {
let signing_alg = ID_TOKEN_SIGNING_ALG;
let keystore = keystore(&signing_alg);

View File

@@ -397,7 +397,7 @@ impl Policy {
Ok(res)
}
/// Evaluate the 'client_registration' entrypoint.
/// Evaluate the `client_registration` entrypoint.
///
/// # Errors
///
@@ -419,7 +419,7 @@ impl Policy {
Ok(res)
}
/// Evaluate the 'authorization_grant' entrypoint.
/// Evaluate the `authorization_grant` entrypoint.
///
/// # Errors
///

View File

@@ -696,7 +696,7 @@ mod tests {
// List all logins
let logins = repo.compat_sso_login().list(all, pagination).await.unwrap();
assert!(!logins.has_next_page);
assert_eq!(logins.edges, &[login.clone()]);
assert_eq!(logins.edges, vec![login.clone()]);
// List the logins for the user
let logins = repo
@@ -705,7 +705,7 @@ mod tests {
.await
.unwrap();
assert!(!logins.has_next_page);
assert_eq!(logins.edges, &[login.clone()]);
assert_eq!(logins.edges, vec![login.clone()]);
// List only the pending logins for the user
let logins = repo

View File

@@ -524,7 +524,7 @@ mod tests {
&mut rng,
&clock,
"alice".to_owned(),
Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
Some("Mozilla/5.0".to_owned()),
Some(serde_json::json!({"action": "continue_compat_sso_login", "id": "01FSHN9AG0MKGTBNZ16RDR3PVY"})),
)
@@ -534,7 +534,7 @@ mod tests {
assert_eq!(registration.user_agent, Some("Mozilla/5.0".to_owned()));
assert_eq!(
registration.ip_address,
Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
Some(IpAddr::V4(Ipv4Addr::LOCALHOST))
);
assert_eq!(
registration.post_auth_action,

View File

@@ -38,7 +38,7 @@ pub trait OAuth2AuthorizationGrantRepository: Send + Sync {
/// * `response_mode`: The response mode the client requested
/// * `response_type_id_token`: Whether the `id_token` `response_type` was
/// requested
/// * `login_hint`: The login_hint the client sent, if set
/// * `login_hint`: The `login_hint` the client sent, if set
/// * `locale`: The locale the detected when the user asked for the
/// authorization grant
///

View File

@@ -24,7 +24,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
/// The error type returned by the repository
type Error;
/// Lookup an OAuth2 client by its ID
/// Lookup an OAuth client by its ID
///
/// Returns `None` if the client does not exist
///
@@ -37,7 +37,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
/// Returns [`Self::Error`] if the underlying repository fails
async fn lookup(&mut self, id: Ulid) -> Result<Option<Client>, Self::Error>;
/// Find an OAuth2 client by its client ID
/// Find an OAuth client by its client ID
async fn find_by_client_id(&mut self, client_id: &str) -> Result<Option<Client>, Self::Error> {
let Ok(id) = client_id.parse() else {
return Ok(None);
@@ -45,7 +45,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
self.lookup(id).await
}
/// Find an OAuth2 client by its metadata digest
/// Find an OAuth client by its metadata digest
///
/// Returns `None` if the client does not exist
///
@@ -62,7 +62,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
digest: &str,
) -> Result<Option<Client>, Self::Error>;
/// Load a batch of OAuth2 clients by their IDs
/// Load a batch of OAuth clients by their IDs
///
/// Returns a map of client IDs to clients. If a client does not exist, it
/// is not present in the map.
@@ -79,7 +79,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
ids: BTreeSet<Ulid>,
) -> Result<BTreeMap<Ulid, Client>, Self::Error>;
/// Add a new OAuth2 client
/// Add a new OAuth client
///
/// Returns the client that was added
///

View File

@@ -250,7 +250,8 @@ pub async fn synapse_config_check_against_mas_config(
///
/// - If there is some database connection error, or the given database is not a
/// Synapse database.
/// - If the OAuth2 section of the MAS configuration could not be parsed.
/// - If the Upstream OAuth section of the MAS configuration could not be
/// parsed.
#[tracing::instrument(skip_all)]
pub async fn synapse_database_check(
synapse_connection: &mut PgConnection,

View File

@@ -120,14 +120,14 @@ impl Config {
pub fn all_oidc_providers(&self) -> BTreeMap<String, OidcProvider> {
let mut out = BTreeMap::new();
if let Some(provider) = &self.oidc_config {
if provider.has_required_fields() {
let mut provider = provider.clone();
// The legacy configuration has an implied IdP ID of `oidc`.
let idp_id = provider.idp_id.take().unwrap_or("oidc".to_owned());
provider.idp_id = Some(idp_id.clone());
out.insert(idp_id, provider);
}
if let Some(provider) = &self.oidc_config
&& provider.has_required_fields()
{
let mut provider = provider.clone();
// The legacy configuration has an implied IdP ID of `oidc`.
let idp_id = provider.idp_id.take().unwrap_or("oidc".to_owned());
provider.idp_id = Some(idp_id.clone());
out.insert(idp_id, provider);
}
for provider in &self.oidc_providers {

View File

@@ -29,7 +29,7 @@ use crate::{
/// Job to provision a user on the Matrix homeserver.
/// This works by doing a PUT request to the
/// /_synapse/admin/v2/users/{user_id} endpoint.
/// `/_synapse/admin/v2/users/{user_id}` endpoint.
#[async_trait]
impl RunnableJob for ProvisionUserJob {
#[tracing::instrument(

View File

@@ -1637,7 +1637,7 @@ impl TemplateContext for DeviceConsentContext {
device_code: Alphanumeric.sample_string(rng, 32),
created_at: now - Duration::try_minutes(5).unwrap(),
expires_at: now + Duration::try_minutes(25).unwrap(),
ip_address: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
ip_address: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
user_agent: Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36".to_owned()),
};
Self { grant, client }

View File

@@ -328,7 +328,7 @@ register_templates! {
/// Render the Swagger API reference
pub fn render_swagger(ApiDocContext) { "swagger/doc.html" }
/// Render the Swagger OAuth2 callback page
/// Render the Swagger OAuth callback page
pub fn render_swagger_callback(ApiDocContext) { "swagger/oauth2-redirect.html" }
/// Render the login page
@@ -382,7 +382,7 @@ register_templates! {
/// Render the account recovery disabled page
pub fn render_recovery_disabled(WithLanguage<EmptyContext>) { "pages/recovery/disabled.html" }
/// Render the form used by the form_post response mode
/// Render the form used by the `form_post` response mode
pub fn render_form_post<T: Serialize>(WithLanguage<FormPostContext<T>>) { "form_post.html" }
/// Render the HTML error page

View File

@@ -24,8 +24,8 @@ ignore = [
[licenses]
version = 2
allow = [
"LicenseRef-Element-Commercial",
"0BSD",
"AGPL-3.0",
"Apache-2.0 WITH LLVM-exception",
"Apache-2.0",
"BSD-2-Clause",
@@ -80,9 +80,6 @@ skip = [
{ name = "rustix", version = "0.38.44" },
{ name = "linux-raw-sys", version = "0.9.4" },
# This is a compatibility version of webpki-roots that depends on the 1.0 version
{ name = "webpki-roots", version = "0.26.11" },
# We are still mainly using rand 0.8
{ name = "rand", version = "0.8.5" },
{ name = "rand_chacha", version = "0.3.1" },
@@ -103,6 +100,6 @@ unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [
# https://github.com/open-telemetry/opentelemetry-rust/pull/3076
"https://github.com/sandhose/opentelemetry-rust",
# https://github.com/open-telemetry/opentelemetry-rust/pull/3076
"https://github.com/sandhose/opentelemetry-rust",
]

View File

@@ -2155,7 +2155,7 @@
"type": "boolean"
},
"on_backchannel_logout": {
"description": "What to do when receiving an OIDC Backchannel logout request.\n\nDefaults to \"do_nothing\".",
"description": "What to do when receiving an OIDC Backchannel logout request.\n\nDefaults to `do_nothing`.",
"allOf": [
{
"$ref": "#/definitions/OnBackchannelLogout"

View File

@@ -24,7 +24,7 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-i18next": "^15.6.1",
"swagger-ui-dist": "^5.27.0",
"swagger-ui-dist": "^5.27.1",
"valibot": "^1.1.0",
"vaul": "^1.1.2"
},
@@ -66,7 +66,7 @@
"storybook": "^9.0.1",
"storybook-react-i18next": "4.0.11",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"typescript": "^5.9.2",
"vite": "6.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-graphql-codegen": "^3.6.1",
@@ -3248,6 +3248,23 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/@inquirer/external-editor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.0.tgz",
"integrity": "sha512-5v3YXc5ZMfL6OJqXPrX9csb4l7NlQA2doO1yynUjpUChT9hg4JcuBVP0RbsEJ/3SL/sxWEyFjT2W69ZhtoBWqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"chardet": "^2.1.0",
"iconv-lite": "^0.6.3"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz",
@@ -6907,9 +6924,9 @@
}
},
"node_modules/chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz",
"integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==",
"dev": true,
"license": "MIT"
},
@@ -7854,34 +7871,6 @@
"node": ">=12.0.0"
}
},
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"license": "MIT",
"dependencies": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
},
"engines": {
"node": ">=4"
}
},
"node_modules/external-editor/node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -8980,17 +8969,17 @@
"license": "ISC"
},
"node_modules/inquirer": {
"version": "8.2.6",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz",
"integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==",
"version": "8.2.7",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz",
"integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@inquirer/external-editor": "^1.0.0",
"ansi-escapes": "^4.2.1",
"chalk": "^4.1.1",
"cli-cursor": "^3.1.0",
"cli-width": "^3.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.21",
"mute-stream": "0.0.8",
@@ -10281,16 +10270,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/outvariant": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
@@ -12355,9 +12334,9 @@
}
},
"node_modules/swagger-ui-dist": {
"version": "5.27.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz",
"integrity": "sha512-tS6LRyBhY6yAqxrfsA9IYpGWPUJOri6sclySa7TdC7XQfGLvTwDY531KLgfQwHEtQsn+sT4JlUspbeQDBVGWig==",
"version": "5.27.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.1.tgz",
"integrity": "sha512-oGtpYO3lnoaqyGtlJalvryl7TwzgRuxpOVWqEHx8af0YXI+Kt+4jMpLdgMtMcmWmuQ0QTCHLKExwrBFMSxvAUA==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -12714,19 +12693,6 @@
"tslib": "^2.0.3"
}
},
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"os-tmpdir": "~1.0.2"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -12875,9 +12841,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -34,7 +34,7 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-i18next": "^15.6.1",
"swagger-ui-dist": "^5.27.0",
"swagger-ui-dist": "^5.27.1",
"valibot": "^1.1.0",
"vaul": "^1.1.2"
},
@@ -76,7 +76,7 @@
"storybook": "^9.0.1",
"storybook-react-i18next": "4.0.11",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"typescript": "^5.9.2",
"vite": "6.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-graphql-codegen": "^3.6.1",