Automatic merge back to main (#5056)
This commit is contained in:
56
Cargo.lock
generated
56
Cargo.lock
generated
@@ -3106,7 +3106,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-axum-utils"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -3140,7 +3140,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-cli"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -3213,7 +3213,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-config"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -3245,7 +3245,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-context"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"console",
|
||||
"opentelemetry",
|
||||
@@ -3261,7 +3261,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-data-model"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"chrono",
|
||||
@@ -3284,7 +3284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-email"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"lettre",
|
||||
@@ -3295,7 +3295,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-handlers"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"aide",
|
||||
"anyhow",
|
||||
@@ -3375,7 +3375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-http"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"headers",
|
||||
@@ -3396,7 +3396,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-i18n"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"icu_calendar",
|
||||
@@ -3418,7 +3418,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-i18n-scan"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"clap",
|
||||
@@ -3432,7 +3432,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-iana"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
@@ -3440,7 +3440,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-iana-codegen"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3456,7 +3456,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-jose"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"chrono",
|
||||
@@ -3486,7 +3486,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-keystore"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"base64ct",
|
||||
@@ -3514,7 +3514,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-listener"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -3539,7 +3539,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-matrix"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3549,7 +3549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-matrix-synapse"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3566,7 +3566,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-oidc-client"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
@@ -3602,7 +3602,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-policy"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -3619,7 +3619,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-router"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"serde",
|
||||
@@ -3630,7 +3630,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-spa"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"serde",
|
||||
@@ -3639,7 +3639,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-storage"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -3661,7 +3661,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-storage-pg"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -3688,7 +3688,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-tasks"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3720,7 +3720,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-templates"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -3750,7 +3750,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mas-tower"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"http",
|
||||
"opentelemetry",
|
||||
@@ -4020,7 +4020,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oauth2-types"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64ct",
|
||||
@@ -6103,7 +6103,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn2mas"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
|
||||
60
Cargo.toml
60
Cargo.toml
@@ -9,7 +9,7 @@ members = ["crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
# Updated in the CI with a `sed` command
|
||||
package.version = "1.3.0-rc.0"
|
||||
package.version = "1.3.0"
|
||||
package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial"
|
||||
package.authors = ["Element Backend Team"]
|
||||
package.edition = "2024"
|
||||
@@ -34,35 +34,35 @@ broken_intra_doc_links = "deny"
|
||||
[workspace.dependencies]
|
||||
|
||||
# Workspace crates
|
||||
mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.3.0-rc.0" }
|
||||
mas-cli = { path = "./crates/cli/", version = "=1.3.0-rc.0" }
|
||||
mas-config = { path = "./crates/config/", version = "=1.3.0-rc.0" }
|
||||
mas-context = { path = "./crates/context/", version = "=1.3.0-rc.0" }
|
||||
mas-data-model = { path = "./crates/data-model/", version = "=1.3.0-rc.0" }
|
||||
mas-email = { path = "./crates/email/", version = "=1.3.0-rc.0" }
|
||||
mas-graphql = { path = "./crates/graphql/", version = "=1.3.0-rc.0" }
|
||||
mas-handlers = { path = "./crates/handlers/", version = "=1.3.0-rc.0" }
|
||||
mas-http = { path = "./crates/http/", version = "=1.3.0-rc.0" }
|
||||
mas-i18n = { path = "./crates/i18n/", version = "=1.3.0-rc.0" }
|
||||
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.3.0-rc.0" }
|
||||
mas-iana = { path = "./crates/iana/", version = "=1.3.0-rc.0" }
|
||||
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.3.0-rc.0" }
|
||||
mas-jose = { path = "./crates/jose/", version = "=1.3.0-rc.0" }
|
||||
mas-keystore = { path = "./crates/keystore/", version = "=1.3.0-rc.0" }
|
||||
mas-listener = { path = "./crates/listener/", version = "=1.3.0-rc.0" }
|
||||
mas-matrix = { path = "./crates/matrix/", version = "=1.3.0-rc.0" }
|
||||
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.3.0-rc.0" }
|
||||
mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.3.0-rc.0" }
|
||||
mas-policy = { path = "./crates/policy/", version = "=1.3.0-rc.0" }
|
||||
mas-router = { path = "./crates/router/", version = "=1.3.0-rc.0" }
|
||||
mas-spa = { path = "./crates/spa/", version = "=1.3.0-rc.0" }
|
||||
mas-storage = { path = "./crates/storage/", version = "=1.3.0-rc.0" }
|
||||
mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.3.0-rc.0" }
|
||||
mas-tasks = { path = "./crates/tasks/", version = "=1.3.0-rc.0" }
|
||||
mas-templates = { path = "./crates/templates/", version = "=1.3.0-rc.0" }
|
||||
mas-tower = { path = "./crates/tower/", version = "=1.3.0-rc.0" }
|
||||
oauth2-types = { path = "./crates/oauth2-types/", version = "=1.3.0-rc.0" }
|
||||
syn2mas = { path = "./crates/syn2mas", version = "=1.3.0-rc.0" }
|
||||
mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.3.0" }
|
||||
mas-cli = { path = "./crates/cli/", version = "=1.3.0" }
|
||||
mas-config = { path = "./crates/config/", version = "=1.3.0" }
|
||||
mas-context = { path = "./crates/context/", version = "=1.3.0" }
|
||||
mas-data-model = { path = "./crates/data-model/", version = "=1.3.0" }
|
||||
mas-email = { path = "./crates/email/", version = "=1.3.0" }
|
||||
mas-graphql = { path = "./crates/graphql/", version = "=1.3.0" }
|
||||
mas-handlers = { path = "./crates/handlers/", version = "=1.3.0" }
|
||||
mas-http = { path = "./crates/http/", version = "=1.3.0" }
|
||||
mas-i18n = { path = "./crates/i18n/", version = "=1.3.0" }
|
||||
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.3.0" }
|
||||
mas-iana = { path = "./crates/iana/", version = "=1.3.0" }
|
||||
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.3.0" }
|
||||
mas-jose = { path = "./crates/jose/", version = "=1.3.0" }
|
||||
mas-keystore = { path = "./crates/keystore/", version = "=1.3.0" }
|
||||
mas-listener = { path = "./crates/listener/", version = "=1.3.0" }
|
||||
mas-matrix = { path = "./crates/matrix/", version = "=1.3.0" }
|
||||
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.3.0" }
|
||||
mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.3.0" }
|
||||
mas-policy = { path = "./crates/policy/", version = "=1.3.0" }
|
||||
mas-router = { path = "./crates/router/", version = "=1.3.0" }
|
||||
mas-spa = { path = "./crates/spa/", version = "=1.3.0" }
|
||||
mas-storage = { path = "./crates/storage/", version = "=1.3.0" }
|
||||
mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.3.0" }
|
||||
mas-tasks = { path = "./crates/tasks/", version = "=1.3.0" }
|
||||
mas-templates = { path = "./crates/templates/", version = "=1.3.0" }
|
||||
mas-tower = { path = "./crates/tower/", version = "=1.3.0" }
|
||||
oauth2-types = { path = "./crates/oauth2-types/", version = "=1.3.0" }
|
||||
syn2mas = { path = "./crates/syn2mas", version = "=1.3.0" }
|
||||
|
||||
# OpenAPI schema generation and validation
|
||||
[workspace.dependencies.aide]
|
||||
|
||||
@@ -91,6 +91,11 @@ fn finish(t: TransformOpenApi) -> TransformOpenApi {
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.tag(Tag {
|
||||
name: "upstream-oauth-provider".to_owned(),
|
||||
description: Some("Manage upstream OAuth 2.0 providers".to_owned()),
|
||||
..Tag::default()
|
||||
})
|
||||
.security_scheme("oauth2", oauth_security_scheme(None))
|
||||
.security_scheme(
|
||||
"token",
|
||||
|
||||
@@ -695,3 +695,79 @@ impl UserRegistrationToken {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// An upstream OAuth 2.0 provider
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct UpstreamOAuthProvider {
|
||||
#[serde(skip)]
|
||||
id: Ulid,
|
||||
|
||||
/// The OIDC issuer of the provider
|
||||
issuer: Option<String>,
|
||||
|
||||
/// A human-readable name for the provider
|
||||
human_name: Option<String>,
|
||||
|
||||
/// A brand identifier, e.g. "apple" or "google"
|
||||
brand_name: Option<String>,
|
||||
|
||||
/// When the provider was created
|
||||
created_at: DateTime<Utc>,
|
||||
|
||||
/// When the provider was disabled. If null, the provider is enabled.
|
||||
disabled_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl From<mas_data_model::UpstreamOAuthProvider> for UpstreamOAuthProvider {
|
||||
fn from(provider: mas_data_model::UpstreamOAuthProvider) -> Self {
|
||||
Self {
|
||||
id: provider.id,
|
||||
issuer: provider.issuer,
|
||||
human_name: provider.human_name,
|
||||
brand_name: provider.brand_name,
|
||||
created_at: provider.created_at,
|
||||
disabled_at: provider.disabled_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for UpstreamOAuthProvider {
|
||||
const KIND: &'static str = "upstream-oauth-provider";
|
||||
const PATH: &'static str = "/api/admin/v1/upstream-oauth-providers";
|
||||
|
||||
fn id(&self) -> Ulid {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl UpstreamOAuthProvider {
|
||||
/// Samples of upstream OAuth 2.0 providers
|
||||
pub fn samples() -> [Self; 3] {
|
||||
[
|
||||
Self {
|
||||
id: Ulid::from_bytes([0x01; 16]),
|
||||
issuer: Some("https://accounts.google.com".to_owned()),
|
||||
human_name: Some("Google".to_owned()),
|
||||
brand_name: Some("google".to_owned()),
|
||||
created_at: DateTime::default(),
|
||||
disabled_at: None,
|
||||
},
|
||||
Self {
|
||||
id: Ulid::from_bytes([0x02; 16]),
|
||||
issuer: Some("https://appleid.apple.com".to_owned()),
|
||||
human_name: Some("Apple ID".to_owned()),
|
||||
brand_name: Some("apple".to_owned()),
|
||||
created_at: DateTime::default(),
|
||||
disabled_at: Some(DateTime::default()),
|
||||
},
|
||||
Self {
|
||||
id: Ulid::from_bytes([0x03; 16]),
|
||||
issuer: None,
|
||||
human_name: Some("Custom OAuth Provider".to_owned()),
|
||||
brand_name: None,
|
||||
created_at: DateTime::default(),
|
||||
disabled_at: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ mod oauth2_sessions;
|
||||
mod policy_data;
|
||||
mod site_config;
|
||||
mod upstream_oauth_links;
|
||||
mod upstream_oauth_providers;
|
||||
mod user_emails;
|
||||
mod user_registration_tokens;
|
||||
mod user_sessions;
|
||||
@@ -187,4 +188,11 @@ where
|
||||
self::upstream_oauth_links::delete_doc,
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/upstream-oauth-providers",
|
||||
get_with(
|
||||
self::upstream_oauth_providers::list,
|
||||
self::upstream_oauth_providers::list_doc,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
554
crates/handlers/src/admin/v1/upstream_oauth_providers/list.rs
Normal file
554
crates/handlers/src/admin/v1/upstream_oauth_providers/list.rs
Normal file
@@ -0,0 +1,554 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
use aide::{OperationIo, transform::TransformOperation};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, rejection::QueryRejection},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum_macros::FromRequestParts;
|
||||
use hyper::StatusCode;
|
||||
use mas_axum_utils::record_error;
|
||||
use mas_storage::{Page, upstream_oauth2::UpstreamOAuthProviderFilter};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
admin::{
|
||||
call_context::CallContext,
|
||||
model::{Resource, UpstreamOAuthProvider},
|
||||
params::Pagination,
|
||||
response::{ErrorResponse, PaginatedResponse},
|
||||
},
|
||||
impl_from_error_for_route,
|
||||
};
|
||||
|
||||
#[derive(FromRequestParts, Deserialize, JsonSchema, OperationIo)]
|
||||
#[serde(rename = "UpstreamOAuthProviderFilter")]
|
||||
#[aide(input_with = "Query<FilterParams>")]
|
||||
#[from_request(via(Query), rejection(RouteError))]
|
||||
pub struct FilterParams {
|
||||
/// Retrieve providers that are (or are not) enabled
|
||||
#[serde(rename = "filter[enabled]")]
|
||||
enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FilterParams {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut sep = '?';
|
||||
|
||||
if let Some(enabled) = self.enabled {
|
||||
write!(f, "{sep}filter[enabled]={enabled}")?;
|
||||
sep = '&';
|
||||
}
|
||||
|
||||
let _ = sep;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, OperationIo)]
|
||||
#[aide(output_with = "Json<ErrorResponse>")]
|
||||
pub enum RouteError {
|
||||
#[error(transparent)]
|
||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
|
||||
#[error("Invalid filter parameters")]
|
||||
InvalidFilter(#[from] QueryRejection),
|
||||
}
|
||||
|
||||
impl_from_error_for_route!(mas_storage::RepositoryError);
|
||||
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let error = ErrorResponse::from_error(&self);
|
||||
let sentry_event_id = record_error!(self, Self::Internal(_));
|
||||
let status = match self {
|
||||
Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::InvalidFilter(_) => StatusCode::BAD_REQUEST,
|
||||
};
|
||||
|
||||
(status, sentry_event_id, Json(error)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn doc(operation: TransformOperation) -> TransformOperation {
|
||||
operation
|
||||
.id("listUpstreamOAuthProviders")
|
||||
.summary("List upstream OAuth 2.0 providers")
|
||||
.tag("upstream-oauth-provider")
|
||||
.response_with::<200, Json<PaginatedResponse<UpstreamOAuthProvider>>, _>(|t| {
|
||||
let providers = UpstreamOAuthProvider::samples();
|
||||
let pagination = mas_storage::Pagination::first(providers.len());
|
||||
let page = Page {
|
||||
edges: providers.into(),
|
||||
has_next_page: true,
|
||||
has_previous_page: false,
|
||||
};
|
||||
|
||||
t.description("Paginated response of upstream OAuth 2.0 providers")
|
||||
.example(PaginatedResponse::new(
|
||||
page,
|
||||
pagination,
|
||||
42,
|
||||
UpstreamOAuthProvider::PATH,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "handler.admin.v1.upstream_oauth_providers.list", skip_all)]
|
||||
pub async fn handler(
|
||||
CallContext { mut repo, .. }: CallContext,
|
||||
Pagination(pagination): Pagination,
|
||||
params: FilterParams,
|
||||
) -> Result<Json<PaginatedResponse<UpstreamOAuthProvider>>, RouteError> {
|
||||
let base = format!("{path}{params}", path = UpstreamOAuthProvider::PATH);
|
||||
let filter = UpstreamOAuthProviderFilter::new();
|
||||
|
||||
let filter = match params.enabled {
|
||||
Some(true) => filter.enabled_only(),
|
||||
Some(false) => filter.disabled_only(),
|
||||
None => filter,
|
||||
};
|
||||
|
||||
let page = repo
|
||||
.upstream_oauth_provider()
|
||||
.list(filter, pagination)
|
||||
.await?;
|
||||
let count = repo.upstream_oauth_provider().count(filter).await?;
|
||||
|
||||
Ok(Json(PaginatedResponse::new(
|
||||
page.map(UpstreamOAuthProvider::from),
|
||||
pagination,
|
||||
count,
|
||||
&base,
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hyper::{Request, StatusCode};
|
||||
use mas_data_model::{
|
||||
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode,
|
||||
UpstreamOAuthProviderOnBackchannelLogout, UpstreamOAuthProviderPkceMode,
|
||||
UpstreamOAuthProviderTokenAuthMethod,
|
||||
};
|
||||
use mas_iana::jose::JsonWebSignatureAlg;
|
||||
use mas_storage::{
|
||||
RepositoryAccess,
|
||||
upstream_oauth2::{UpstreamOAuthProviderParams, UpstreamOAuthProviderRepository},
|
||||
};
|
||||
use oauth2_types::scope::{OPENID, Scope};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
|
||||
|
||||
async fn create_test_providers(state: &mut TestState) {
|
||||
let mut repo = state.repository().await.unwrap();
|
||||
|
||||
// Create an enabled provider
|
||||
let enabled_params = UpstreamOAuthProviderParams {
|
||||
issuer: Some("https://accounts.google.com".to_owned()),
|
||||
human_name: Some("Google".to_owned()),
|
||||
brand_name: Some("google".to_owned()),
|
||||
discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc,
|
||||
pkce_mode: UpstreamOAuthProviderPkceMode::Auto,
|
||||
jwks_uri_override: None,
|
||||
authorization_endpoint_override: None,
|
||||
token_endpoint_override: None,
|
||||
userinfo_endpoint_override: None,
|
||||
fetch_userinfo: true,
|
||||
userinfo_signed_response_alg: None,
|
||||
client_id: "google-client-id".to_owned(),
|
||||
encrypted_client_secret: Some("encrypted-secret".to_owned()),
|
||||
token_endpoint_signing_alg: None,
|
||||
token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretPost,
|
||||
id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
|
||||
response_mode: None,
|
||||
scope: Scope::from_iter([OPENID]),
|
||||
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
|
||||
additional_authorization_parameters: vec![],
|
||||
forward_login_hint: false,
|
||||
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
|
||||
ui_order: 0,
|
||||
};
|
||||
|
||||
repo.upstream_oauth_provider()
|
||||
.add(&mut state.rng(), &state.clock, enabled_params)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create a disabled provider
|
||||
let disabled_params = UpstreamOAuthProviderParams {
|
||||
issuer: Some("https://appleid.apple.com".to_owned()),
|
||||
human_name: Some("Apple ID".to_owned()),
|
||||
brand_name: Some("apple".to_owned()),
|
||||
discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc,
|
||||
pkce_mode: UpstreamOAuthProviderPkceMode::S256,
|
||||
jwks_uri_override: None,
|
||||
authorization_endpoint_override: None,
|
||||
token_endpoint_override: None,
|
||||
userinfo_endpoint_override: None,
|
||||
fetch_userinfo: true,
|
||||
userinfo_signed_response_alg: None,
|
||||
client_id: "apple-client-id".to_owned(),
|
||||
encrypted_client_secret: Some("encrypted-secret".to_owned()),
|
||||
token_endpoint_signing_alg: None,
|
||||
token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretPost,
|
||||
id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
|
||||
response_mode: None,
|
||||
scope: Scope::from_iter([OPENID]),
|
||||
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
|
||||
additional_authorization_parameters: vec![],
|
||||
forward_login_hint: false,
|
||||
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
|
||||
ui_order: 1,
|
||||
};
|
||||
|
||||
let disabled_provider = repo
|
||||
.upstream_oauth_provider()
|
||||
.add(&mut state.rng(), &state.clock, disabled_params)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Disable the provider
|
||||
repo.upstream_oauth_provider()
|
||||
.disable(&state.clock, disabled_provider)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create another enabled provider
|
||||
let another_enabled_params = UpstreamOAuthProviderParams {
|
||||
issuer: Some("https://login.microsoftonline.com/common/v2.0".to_owned()),
|
||||
human_name: Some("Microsoft".to_owned()),
|
||||
brand_name: Some("microsoft".to_owned()),
|
||||
discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc,
|
||||
pkce_mode: UpstreamOAuthProviderPkceMode::Auto,
|
||||
jwks_uri_override: None,
|
||||
authorization_endpoint_override: None,
|
||||
token_endpoint_override: None,
|
||||
userinfo_endpoint_override: None,
|
||||
fetch_userinfo: true,
|
||||
userinfo_signed_response_alg: None,
|
||||
client_id: "microsoft-client-id".to_owned(),
|
||||
encrypted_client_secret: Some("encrypted-secret".to_owned()),
|
||||
token_endpoint_signing_alg: None,
|
||||
token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretPost,
|
||||
id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
|
||||
response_mode: None,
|
||||
scope: Scope::from_iter([OPENID]),
|
||||
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
|
||||
additional_authorization_parameters: vec![],
|
||||
forward_login_hint: false,
|
||||
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
|
||||
ui_order: 2,
|
||||
};
|
||||
|
||||
repo.upstream_oauth_provider()
|
||||
.add(&mut state.rng(), &state.clock, another_enabled_params)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Box::new(repo).save().await.unwrap();
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_list_all_providers(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
create_test_providers(&mut state).await;
|
||||
|
||||
let request = Request::get("/api/admin/v1/upstream-oauth-providers")
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::OK);
|
||||
let body: serde_json::Value = response.json::<serde_json::Value>();
|
||||
|
||||
// Should return all providers
|
||||
assert_eq!(body["data"].as_array().unwrap().len(), 3);
|
||||
|
||||
insta::assert_json_snapshot!(body, @r#"
|
||||
{
|
||||
"meta": {
|
||||
"count": 3
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG07HNEZXNQM2KNBNF6",
|
||||
"attributes": {
|
||||
"issuer": "https://appleid.apple.com",
|
||||
"human_name": "Apple ID",
|
||||
"brand_name": "apple",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": "2022-01-16T14:40:00Z"
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG07HNEZXNQM2KNBNF6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG09AVTNSQFMSR34AJC",
|
||||
"attributes": {
|
||||
"issuer": "https://login.microsoftonline.com/common/v2.0",
|
||||
"human_name": "Microsoft",
|
||||
"brand_name": "microsoft",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG09AVTNSQFMSR34AJC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG0MZAA6S4AF7CTV32E",
|
||||
"attributes": {
|
||||
"issuer": "https://accounts.google.com",
|
||||
"human_name": "Google",
|
||||
"brand_name": "google",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG0MZAA6S4AF7CTV32E"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers?page[first]=10",
|
||||
"first": "/api/admin/v1/upstream-oauth-providers?page[first]=10",
|
||||
"last": "/api/admin/v1/upstream-oauth-providers?page[last]=10"
|
||||
}
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_filter_by_enabled_true(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
create_test_providers(&mut state).await;
|
||||
|
||||
let request = Request::get("/api/admin/v1/upstream-oauth-providers?filter[enabled]=true")
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::OK);
|
||||
let body: serde_json::Value = response.json::<serde_json::Value>();
|
||||
|
||||
insta::assert_json_snapshot!(body, @r#"
|
||||
{
|
||||
"meta": {
|
||||
"count": 2
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG09AVTNSQFMSR34AJC",
|
||||
"attributes": {
|
||||
"issuer": "https://login.microsoftonline.com/common/v2.0",
|
||||
"human_name": "Microsoft",
|
||||
"brand_name": "microsoft",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG09AVTNSQFMSR34AJC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG0MZAA6S4AF7CTV32E",
|
||||
"attributes": {
|
||||
"issuer": "https://accounts.google.com",
|
||||
"human_name": "Google",
|
||||
"brand_name": "google",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG0MZAA6S4AF7CTV32E"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers?filter[enabled]=true&page[first]=10",
|
||||
"first": "/api/admin/v1/upstream-oauth-providers?filter[enabled]=true&page[first]=10",
|
||||
"last": "/api/admin/v1/upstream-oauth-providers?filter[enabled]=true&page[last]=10"
|
||||
}
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_filter_by_enabled_false(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
create_test_providers(&mut state).await;
|
||||
|
||||
let request = Request::get("/api/admin/v1/upstream-oauth-providers?filter[enabled]=false")
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::OK);
|
||||
let body: serde_json::Value = response.json::<serde_json::Value>();
|
||||
|
||||
insta::assert_json_snapshot!(body, @r#"
|
||||
{
|
||||
"meta": {
|
||||
"count": 1
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG07HNEZXNQM2KNBNF6",
|
||||
"attributes": {
|
||||
"issuer": "https://appleid.apple.com",
|
||||
"human_name": "Apple ID",
|
||||
"brand_name": "apple",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": "2022-01-16T14:40:00Z"
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG07HNEZXNQM2KNBNF6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers?filter[enabled]=false&page[first]=10",
|
||||
"first": "/api/admin/v1/upstream-oauth-providers?filter[enabled]=false&page[first]=10",
|
||||
"last": "/api/admin/v1/upstream-oauth-providers?filter[enabled]=false&page[last]=10"
|
||||
}
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_pagination(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
create_test_providers(&mut state).await;
|
||||
|
||||
// Test first page with limit of 2
|
||||
let request = Request::get("/api/admin/v1/upstream-oauth-providers?page[first]=2")
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::OK);
|
||||
let body: serde_json::Value = response.json::<serde_json::Value>();
|
||||
|
||||
insta::assert_json_snapshot!(body, @r#"
|
||||
{
|
||||
"meta": {
|
||||
"count": 3
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG07HNEZXNQM2KNBNF6",
|
||||
"attributes": {
|
||||
"issuer": "https://appleid.apple.com",
|
||||
"human_name": "Apple ID",
|
||||
"brand_name": "apple",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": "2022-01-16T14:40:00Z"
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG07HNEZXNQM2KNBNF6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG09AVTNSQFMSR34AJC",
|
||||
"attributes": {
|
||||
"issuer": "https://login.microsoftonline.com/common/v2.0",
|
||||
"human_name": "Microsoft",
|
||||
"brand_name": "microsoft",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG09AVTNSQFMSR34AJC"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers?page[first]=2",
|
||||
"first": "/api/admin/v1/upstream-oauth-providers?page[first]=2",
|
||||
"last": "/api/admin/v1/upstream-oauth-providers?page[last]=2",
|
||||
"next": "/api/admin/v1/upstream-oauth-providers?page[after]=01FSHN9AG09AVTNSQFMSR34AJC&page[first]=2"
|
||||
}
|
||||
}
|
||||
"#);
|
||||
|
||||
// Extract the ID of the last item for pagination
|
||||
let last_item_id = body["data"][1]["id"].as_str().unwrap();
|
||||
let request = Request::get(format!(
|
||||
"/api/admin/v1/upstream-oauth-providers?page[first]=2&page[after]={last_item_id}",
|
||||
))
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::OK);
|
||||
let body: serde_json::Value = response.json::<serde_json::Value>();
|
||||
|
||||
insta::assert_json_snapshot!(body, @r#"
|
||||
{
|
||||
"meta": {
|
||||
"count": 3
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01FSHN9AG0MZAA6S4AF7CTV32E",
|
||||
"attributes": {
|
||||
"issuer": "https://accounts.google.com",
|
||||
"human_name": "Google",
|
||||
"brand_name": "google",
|
||||
"created_at": "2022-01-16T14:40:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01FSHN9AG0MZAA6S4AF7CTV32E"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers?page[after]=01FSHN9AG09AVTNSQFMSR34AJC&page[first]=2",
|
||||
"first": "/api/admin/v1/upstream-oauth-providers?page[first]=2",
|
||||
"last": "/api/admin/v1/upstream-oauth-providers?page[last]=2"
|
||||
}
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_invalid_filter(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let admin_token = state.token_with_scope("urn:mas:admin").await;
|
||||
|
||||
let request =
|
||||
Request::get("/api/admin/v1/upstream-oauth-providers?filter[enabled]=invalid")
|
||||
.bearer(&admin_token)
|
||||
.empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
response.assert_status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
mod list;
|
||||
|
||||
pub use self::list::{doc as list_doc, handler as list};
|
||||
@@ -3222,6 +3222,143 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/admin/v1/upstream-oauth-providers": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"upstream-oauth-provider"
|
||||
],
|
||||
"summary": "List upstream OAuth 2.0 providers",
|
||||
"operationId": "listUpstreamOAuthProviders",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "page[before]",
|
||||
"description": "Retrieve the items before the given ID",
|
||||
"schema": {
|
||||
"description": "Retrieve the items before the given ID",
|
||||
"$ref": "#/components/schemas/ULID",
|
||||
"nullable": true
|
||||
},
|
||||
"style": "form"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "page[after]",
|
||||
"description": "Retrieve the items after the given ID",
|
||||
"schema": {
|
||||
"description": "Retrieve the items after the given ID",
|
||||
"$ref": "#/components/schemas/ULID",
|
||||
"nullable": true
|
||||
},
|
||||
"style": "form"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "page[first]",
|
||||
"description": "Retrieve the first N items",
|
||||
"schema": {
|
||||
"description": "Retrieve the first N items",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"nullable": true
|
||||
},
|
||||
"style": "form"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "page[last]",
|
||||
"description": "Retrieve the last N items",
|
||||
"schema": {
|
||||
"description": "Retrieve the last N items",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"nullable": true
|
||||
},
|
||||
"style": "form"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "filter[enabled]",
|
||||
"description": "Retrieve providers that are (or are not) enabled",
|
||||
"schema": {
|
||||
"description": "Retrieve providers that are (or are not) enabled",
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
},
|
||||
"style": "form"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Paginated response of upstream OAuth 2.0 providers",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PaginatedResponse_for_UpstreamOAuthProvider"
|
||||
},
|
||||
"example": {
|
||||
"meta": {
|
||||
"count": 42
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "01040G2081040G2081040G2081",
|
||||
"attributes": {
|
||||
"issuer": "https://accounts.google.com",
|
||||
"human_name": "Google",
|
||||
"brand_name": "google",
|
||||
"created_at": "1970-01-01T00:00:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/01040G2081040G2081040G2081"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "02081040G2081040G2081040G2",
|
||||
"attributes": {
|
||||
"issuer": "https://appleid.apple.com",
|
||||
"human_name": "Apple ID",
|
||||
"brand_name": "apple",
|
||||
"created_at": "1970-01-01T00:00:00Z",
|
||||
"disabled_at": "1970-01-01T00:00:00Z"
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/02081040G2081040G2081040G2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "upstream-oauth-provider",
|
||||
"id": "030C1G60R30C1G60R30C1G60R3",
|
||||
"attributes": {
|
||||
"issuer": null,
|
||||
"human_name": "Custom OAuth Provider",
|
||||
"brand_name": null,
|
||||
"created_at": "1970-01-01T00:00:00Z",
|
||||
"disabled_at": null
|
||||
},
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers/030C1G60R30C1G60R30C1G60R3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/api/admin/v1/upstream-oauth-providers?page[first]=3",
|
||||
"first": "/api/admin/v1/upstream-oauth-providers?page[first]=3",
|
||||
"last": "/api/admin/v1/upstream-oauth-providers?page[last]=3",
|
||||
"next": "/api/admin/v1/upstream-oauth-providers?page[after]=030C1G60R30C1G60R30C1G60R3&page[first]=3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -4717,6 +4854,105 @@
|
||||
"$ref": "#/components/schemas/SelfLinks"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpstreamOAuthProviderFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filter[enabled]": {
|
||||
"description": "Retrieve providers that are (or are not) enabled",
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PaginatedResponse_for_UpstreamOAuthProvider": {
|
||||
"description": "A top-level response with a page of resources",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data",
|
||||
"links",
|
||||
"meta"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"description": "Response metadata",
|
||||
"$ref": "#/components/schemas/PaginationMeta"
|
||||
},
|
||||
"data": {
|
||||
"description": "The list of resources",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SingleResource_for_UpstreamOAuthProvider"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"description": "Related links",
|
||||
"$ref": "#/components/schemas/PaginationLinks"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SingleResource_for_UpstreamOAuthProvider": {
|
||||
"description": "A single resource, with its type, ID, attributes and related links",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"attributes",
|
||||
"id",
|
||||
"links",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "The type of the resource",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "The ID of the resource",
|
||||
"$ref": "#/components/schemas/ULID"
|
||||
},
|
||||
"attributes": {
|
||||
"description": "The attributes of the resource",
|
||||
"$ref": "#/components/schemas/UpstreamOAuthProvider"
|
||||
},
|
||||
"links": {
|
||||
"description": "Related links",
|
||||
"$ref": "#/components/schemas/SelfLinks"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpstreamOAuthProvider": {
|
||||
"description": "An upstream OAuth 2.0 provider",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"created_at"
|
||||
],
|
||||
"properties": {
|
||||
"issuer": {
|
||||
"description": "The OIDC issuer of the provider",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"human_name": {
|
||||
"description": "A human-readable name for the provider",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"brand_name": {
|
||||
"description": "A brand identifier, e.g. \"apple\" or \"google\"",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"created_at": {
|
||||
"description": "When the provider was created",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"disabled_at": {
|
||||
"description": "When the provider was disabled. If null, the provider is enabled.",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4768,6 +5004,10 @@
|
||||
{
|
||||
"name": "upstream-oauth-link",
|
||||
"description": "Manage links between local users and identities from upstream OAuth 2.0 providers"
|
||||
},
|
||||
{
|
||||
"name": "upstream-oauth-provider",
|
||||
"description": "Manage upstream OAuth 2.0 providers"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export type LocalazyMetadata = {
|
||||
};
|
||||
|
||||
const localazyMetadata: LocalazyMetadata = {
|
||||
projectUrl: "https://localazy.com/p/matrix-authentication-service",
|
||||
projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.3",
|
||||
baseLocale: "en",
|
||||
languages: [
|
||||
{
|
||||
@@ -172,21 +172,21 @@ const localazyMetadata: LocalazyMetadata = {
|
||||
file: "frontend.json",
|
||||
path: "",
|
||||
cdnFiles: {
|
||||
"cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json",
|
||||
"da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json",
|
||||
"de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json",
|
||||
"en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json",
|
||||
"et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json",
|
||||
"fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json",
|
||||
"fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json",
|
||||
"hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json",
|
||||
"nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json",
|
||||
"nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json",
|
||||
"pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json",
|
||||
"ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json",
|
||||
"sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json",
|
||||
"uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json",
|
||||
"zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json"
|
||||
"cs": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json",
|
||||
"da": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json",
|
||||
"de": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json",
|
||||
"en": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json",
|
||||
"et": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json",
|
||||
"fi": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json",
|
||||
"fr": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json",
|
||||
"hu": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json",
|
||||
"nb_NO": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json",
|
||||
"nl": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json",
|
||||
"pt": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json",
|
||||
"ru": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json",
|
||||
"sv": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json",
|
||||
"uk": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json",
|
||||
"zh#Hans": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -194,21 +194,21 @@ const localazyMetadata: LocalazyMetadata = {
|
||||
file: "file.json",
|
||||
path: "",
|
||||
cdnFiles: {
|
||||
"cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json",
|
||||
"da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json",
|
||||
"de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json",
|
||||
"en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json",
|
||||
"et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json",
|
||||
"fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json",
|
||||
"fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json",
|
||||
"hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json",
|
||||
"nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json",
|
||||
"nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json",
|
||||
"pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json",
|
||||
"ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json",
|
||||
"sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json",
|
||||
"uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json",
|
||||
"zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json"
|
||||
"cs": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json",
|
||||
"da": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json",
|
||||
"de": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json",
|
||||
"en": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json",
|
||||
"et": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json",
|
||||
"fi": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json",
|
||||
"fr": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json",
|
||||
"hu": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json",
|
||||
"nb_NO": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json",
|
||||
"nl": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json",
|
||||
"pt": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json",
|
||||
"ru": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json",
|
||||
"sv": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json",
|
||||
"uk": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json",
|
||||
"zh#Hans": "https://delivery.localazy.com/_a6804865183625420345536edd7e/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user