1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Save the application_type and the contacts in the OAuth 2.0 clients

This also removes the dedicated "redirect_uris" table and makes it a field of the "oauth2_clients" table
This commit is contained in:
Quentin Gliech
2023-08-28 12:31:17 +02:00
parent f9dabf0bbc
commit 096386e9b9
22 changed files with 312 additions and 257 deletions

View File

@ -1,6 +1,7 @@
[workspace] [workspace]
default-members = ["crates/cli"] default-members = ["crates/cli"]
members = ["crates/*"] members = ["crates/*"]
resolver = "2"
[workspace.dependencies] [workspace.dependencies]

View File

@ -140,7 +140,6 @@ impl Options {
#[tracing::instrument(name = "cli.config.sync", skip(root), err(Debug))] #[tracing::instrument(name = "cli.config.sync", skip(root), err(Debug))]
async fn sync(root: &super::Options, prune: bool, dry_run: bool) -> anyhow::Result<()> { async fn sync(root: &super::Options, prune: bool, dry_run: bool) -> anyhow::Result<()> {
// XXX: we should disallow SeedableRng::from_entropy // XXX: we should disallow SeedableRng::from_entropy
let mut rng = rand_chacha::ChaChaRng::from_entropy();
let clock = SystemClock::default(); let clock = SystemClock::default();
let config: SyncConfig = root.load_config()?; let config: SyncConfig = root.load_config()?;
@ -282,8 +281,6 @@ async fn sync(root: &super::Options, prune: bool, dry_run: bool) -> anyhow::Resu
repo.oauth2_client() repo.oauth2_client()
.upsert_static( .upsert_static(
&mut rng,
&clock,
client.client_id, client.client_id,
client_auth_method, client_auth_method,
encrypted_client_secret, encrypted_client_secret,

View File

@ -18,7 +18,7 @@ use mas_iana::{
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod}, oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
}; };
use mas_jose::jwk::PublicJsonWebKeySet; use mas_jose::jwk::PublicJsonWebKeySet;
use oauth2_types::requests::GrantType; use oauth2_types::{oidc::ApplicationType, requests::GrantType};
use rand::RngCore; use rand::RngCore;
use serde::Serialize; use serde::Serialize;
use thiserror::Error; use thiserror::Error;
@ -44,6 +44,8 @@ pub struct Client {
pub encrypted_client_secret: Option<String>, pub encrypted_client_secret: Option<String>,
pub application_type: Option<ApplicationType>,
/// Array of Redirection URI values used by the Client /// Array of Redirection URI values used by the Client
pub redirect_uris: Vec<Url>, pub redirect_uris: Vec<Url>,
@ -130,6 +132,7 @@ impl Client {
id: Ulid::from_datetime_with_source(now.into(), rng), id: Ulid::from_datetime_with_source(now.into(), rng),
client_id: "client1".to_owned(), client_id: "client1".to_owned(),
encrypted_client_secret: None, encrypted_client_secret: None,
application_type: Some(ApplicationType::Web),
redirect_uris: vec![ redirect_uris: vec![
Url::parse("https://client1.example.com/redirect").unwrap(), Url::parse("https://client1.example.com/redirect").unwrap(),
Url::parse("https://client1.example.com/redirect2").unwrap(), Url::parse("https://client1.example.com/redirect2").unwrap(),
@ -156,6 +159,7 @@ impl Client {
id: Ulid::from_datetime_with_source(now.into(), rng), id: Ulid::from_datetime_with_source(now.into(), rng),
client_id: "client2".to_owned(), client_id: "client2".to_owned(),
encrypted_client_secret: None, encrypted_client_secret: None,
application_type: Some(ApplicationType::Native),
redirect_uris: vec![Url::parse("https://client2.example.com/redirect").unwrap()], redirect_uris: vec![Url::parse("https://client2.example.com/redirect").unwrap()],
response_types: vec![OAuthAuthorizationEndpointResponseType::Code], response_types: vec![OAuthAuthorizationEndpointResponseType::Code],
grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken], grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],

View File

@ -17,7 +17,7 @@ use async_graphql::{Context, Description, Enum, Object, ID};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use mas_data_model::SessionState; use mas_data_model::SessionState;
use mas_storage::{oauth2::OAuth2ClientRepository, user::BrowserSessionRepository}; use mas_storage::{oauth2::OAuth2ClientRepository, user::BrowserSessionRepository};
use oauth2_types::scope::Scope; use oauth2_types::{oidc::ApplicationType, scope::Scope};
use ulid::Ulid; use ulid::Ulid;
use url::Url; use url::Url;
@ -110,6 +110,16 @@ impl OAuth2Session {
} }
} }
/// The application type advertised by the client.
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum OAuth2ApplicationType {
/// Client is a web application.
Web,
/// Client is a native application.
Native,
}
/// An OAuth 2.0 client /// An OAuth 2.0 client
#[derive(Description)] #[derive(Description)]
pub struct OAuth2Client(pub mas_data_model::Client); pub struct OAuth2Client(pub mas_data_model::Client);
@ -150,6 +160,19 @@ impl OAuth2Client {
pub async fn redirect_uris(&self) -> &[Url] { pub async fn redirect_uris(&self) -> &[Url] {
&self.0.redirect_uris &self.0.redirect_uris
} }
/// List of contacts advertised by the client.
pub async fn contacts(&self) -> &[String] {
&self.0.contacts
}
/// The application type advertised by the client.
pub async fn application_type(&self) -> Option<OAuth2ApplicationType> {
match self.0.application_type? {
ApplicationType::Web => Some(OAuth2ApplicationType::Web),
ApplicationType::Native => Some(OAuth2ApplicationType::Native),
}
}
} }
/// An OAuth 2.0 consent represents the scope a user consented to grant to a /// An OAuth 2.0 consent represents the scope a user consented to grant to a

View File

@ -33,6 +33,7 @@ async fn create_test_client(state: &TestState) -> Client {
&state.clock, &state.clock,
vec![], vec![],
None, None,
None,
vec![], vec![],
vec![], vec![],
None, None,

View File

@ -175,6 +175,7 @@ pub(crate) async fn post(
&clock, &clock,
metadata.redirect_uris().to_vec(), metadata.redirect_uris().to_vec(),
encrypted_client_secret, encrypted_client_secret,
metadata.application_type,
//&metadata.response_types(), //&metadata.response_types(),
metadata.grant_types().to_vec(), metadata.grant_types().to_vec(),
metadata.contacts.clone().unwrap_or_default(), metadata.contacts.clone().unwrap_or_default(),

View File

@ -1,29 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth2_clients\n ( oauth2_client_id\n , encrypted_client_secret\n , grant_type_authorization_code\n , grant_type_refresh_token\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n , is_static\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, FALSE)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text",
"Bool",
"Bool",
"Text",
"Text",
"Text",
"Text",
"Text",
"Text",
"Jsonb",
"Text",
"Text",
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "31cbbd841029812c6d3500cae04a8e9e5723e4749d339465492b68e072c3a802"
}

View File

@ -0,0 +1,31 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth2_clients\n ( oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n , is_static\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, FALSE)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text",
"Text",
"TextArray",
"Bool",
"Bool",
"Text",
"Text",
"Text",
"Text",
"Text",
"Text",
"Jsonb",
"Text",
"Text",
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "34fbe0f0485a9c4060399509f087964c454252b9b111a57c9106cfc3fdc71b8a"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , ARRAY(\n SELECT redirect_uri\n FROM oauth2_client_redirect_uris r\n WHERE r.oauth2_client_id = c.oauth2_client_id\n ) AS \"redirect_uris!\"\n , grant_type_authorization_code\n , grant_type_refresh_token\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n\n WHERE oauth2_client_id = ANY($1::uuid[])\n ", "query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , contacts\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n\n WHERE oauth2_client_id = ANY($1::uuid[])\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -15,76 +15,86 @@
}, },
{ {
"ordinal": 2, "ordinal": 2,
"name": "redirect_uris!", "name": "application_type",
"type_info": "TextArray" "type_info": "Text"
}, },
{ {
"ordinal": 3, "ordinal": 3,
"name": "redirect_uris",
"type_info": "TextArray"
},
{
"ordinal": 4,
"name": "grant_type_authorization_code", "name": "grant_type_authorization_code",
"type_info": "Bool" "type_info": "Bool"
}, },
{ {
"ordinal": 4, "ordinal": 5,
"name": "grant_type_refresh_token", "name": "grant_type_refresh_token",
"type_info": "Bool" "type_info": "Bool"
}, },
{ {
"ordinal": 5, "ordinal": 6,
"name": "contacts",
"type_info": "TextArray"
},
{
"ordinal": 7,
"name": "client_name", "name": "client_name",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 6, "ordinal": 8,
"name": "logo_uri", "name": "logo_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 7, "ordinal": 9,
"name": "client_uri", "name": "client_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 8, "ordinal": 10,
"name": "policy_uri", "name": "policy_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 9, "ordinal": 11,
"name": "tos_uri", "name": "tos_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 10, "ordinal": 12,
"name": "jwks_uri", "name": "jwks_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 11, "ordinal": 13,
"name": "jwks", "name": "jwks",
"type_info": "Jsonb" "type_info": "Jsonb"
}, },
{ {
"ordinal": 12, "ordinal": 14,
"name": "id_token_signed_response_alg", "name": "id_token_signed_response_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 13, "ordinal": 15,
"name": "userinfo_signed_response_alg", "name": "userinfo_signed_response_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 14, "ordinal": 16,
"name": "token_endpoint_auth_method", "name": "token_endpoint_auth_method",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 15, "ordinal": 17,
"name": "token_endpoint_auth_signing_alg", "name": "token_endpoint_auth_signing_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 16, "ordinal": 18,
"name": "initiate_login_uri", "name": "initiate_login_uri",
"type_info": "Text" "type_info": "Text"
} }
@ -97,7 +107,9 @@
"nullable": [ "nullable": [
false, false,
true, true,
null, true,
false,
false,
false, false,
false, false,
true, true,
@ -114,5 +126,5 @@
true true
] ]
}, },
"hash": "85499663f1adc7b7439592063f06914089f6243126a177b365bde37db5f6b33d" "hash": "35734c4b54d2f1b2c311806af3e9a592f5f55f4898c6e39eb0d7c1b9cef7ca63"
} }

View File

@ -1,20 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth2_clients\n ( oauth2_client_id\n , encrypted_client_secret\n , grant_type_authorization_code\n , grant_type_refresh_token\n , token_endpoint_auth_method\n , jwks\n , jwks_uri\n , is_static\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, TRUE)\n ON CONFLICT (oauth2_client_id)\n DO\n UPDATE SET encrypted_client_secret = EXCLUDED.encrypted_client_secret\n , grant_type_authorization_code = EXCLUDED.grant_type_authorization_code\n , grant_type_refresh_token = EXCLUDED.grant_type_refresh_token\n , token_endpoint_auth_method = EXCLUDED.token_endpoint_auth_method\n , jwks = EXCLUDED.jwks\n , jwks_uri = EXCLUDED.jwks_uri\n , is_static = TRUE\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text",
"Bool",
"Bool",
"Text",
"Jsonb",
"Text"
]
},
"nullable": []
},
"hash": "68c4cd463e4035ba8384f11818b7be602e2fbc34a5582f31f95b0cc5fa2aeb92"
}

View File

@ -0,0 +1,21 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth2_clients\n ( oauth2_client_id\n , encrypted_client_secret\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , token_endpoint_auth_method\n , jwks\n , jwks_uri\n , is_static\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, TRUE)\n ON CONFLICT (oauth2_client_id)\n DO\n UPDATE SET encrypted_client_secret = EXCLUDED.encrypted_client_secret\n , grant_type_authorization_code = EXCLUDED.grant_type_authorization_code\n , grant_type_refresh_token = EXCLUDED.grant_type_refresh_token\n , token_endpoint_auth_method = EXCLUDED.token_endpoint_auth_method\n , jwks = EXCLUDED.jwks\n , jwks_uri = EXCLUDED.jwks_uri\n , is_static = TRUE\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text",
"TextArray",
"Bool",
"Bool",
"Text",
"Jsonb",
"Text"
]
},
"nullable": []
},
"hash": "73ea17a71d62bf96f7811b7f57802f5065f0ae831bc8f3c66b5be4a47b37467e"
}

View File

@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO oauth2_client_redirect_uris\n (oauth2_client_redirect_uri_id, oauth2_client_id, redirect_uri)\n SELECT id, $2, redirect_uri\n FROM UNNEST($1::uuid[], $3::text[]) r(id, redirect_uri)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray",
"Uuid",
"TextArray"
]
},
"nullable": []
},
"hash": "7be139553610ace03193a99fe27fcb4e3d50c90accdaf22ca1cfeefdc9734300"
}

View File

@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM oauth2_client_redirect_uris\n WHERE oauth2_client_id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "7cd0264707100f5b3cb2582f3f840bf66649742374e3643f1902ae69377fc9b6"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , ARRAY(\n SELECT redirect_uri\n FROM oauth2_client_redirect_uris r\n WHERE r.oauth2_client_id = c.oauth2_client_id\n ) AS \"redirect_uris!\"\n , grant_type_authorization_code\n , grant_type_refresh_token\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n WHERE is_static = TRUE\n ", "query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , contacts\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n WHERE is_static = TRUE\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -15,76 +15,86 @@
}, },
{ {
"ordinal": 2, "ordinal": 2,
"name": "redirect_uris!", "name": "application_type",
"type_info": "TextArray" "type_info": "Text"
}, },
{ {
"ordinal": 3, "ordinal": 3,
"name": "redirect_uris",
"type_info": "TextArray"
},
{
"ordinal": 4,
"name": "grant_type_authorization_code", "name": "grant_type_authorization_code",
"type_info": "Bool" "type_info": "Bool"
}, },
{ {
"ordinal": 4, "ordinal": 5,
"name": "grant_type_refresh_token", "name": "grant_type_refresh_token",
"type_info": "Bool" "type_info": "Bool"
}, },
{ {
"ordinal": 5, "ordinal": 6,
"name": "contacts",
"type_info": "TextArray"
},
{
"ordinal": 7,
"name": "client_name", "name": "client_name",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 6, "ordinal": 8,
"name": "logo_uri", "name": "logo_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 7, "ordinal": 9,
"name": "client_uri", "name": "client_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 8, "ordinal": 10,
"name": "policy_uri", "name": "policy_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 9, "ordinal": 11,
"name": "tos_uri", "name": "tos_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 10, "ordinal": 12,
"name": "jwks_uri", "name": "jwks_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 11, "ordinal": 13,
"name": "jwks", "name": "jwks",
"type_info": "Jsonb" "type_info": "Jsonb"
}, },
{ {
"ordinal": 12, "ordinal": 14,
"name": "id_token_signed_response_alg", "name": "id_token_signed_response_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 13, "ordinal": 15,
"name": "userinfo_signed_response_alg", "name": "userinfo_signed_response_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 14, "ordinal": 16,
"name": "token_endpoint_auth_method", "name": "token_endpoint_auth_method",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 15, "ordinal": 17,
"name": "token_endpoint_auth_signing_alg", "name": "token_endpoint_auth_signing_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 16, "ordinal": 18,
"name": "initiate_login_uri", "name": "initiate_login_uri",
"type_info": "Text" "type_info": "Text"
} }
@ -95,7 +105,9 @@
"nullable": [ "nullable": [
false, false,
true, true,
null, true,
false,
false,
false, false,
false, false,
true, true,
@ -112,5 +124,5 @@
true true
] ]
}, },
"hash": "7e676491b077d4bc8a9cdb4a27ebf119d98cd35ebb52b1064fdb2d9eed78d0e8" "hash": "b2b71d12c3a4a7436bbe961c6c57e17a5c5e4105d01184a38d12607d853df802"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , ARRAY(\n SELECT redirect_uri\n FROM oauth2_client_redirect_uris r\n WHERE r.oauth2_client_id = c.oauth2_client_id\n ) AS \"redirect_uris!\"\n , grant_type_authorization_code\n , grant_type_refresh_token\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n\n WHERE oauth2_client_id = $1\n ", "query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , contacts\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n\n WHERE oauth2_client_id = $1\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -15,76 +15,86 @@
}, },
{ {
"ordinal": 2, "ordinal": 2,
"name": "redirect_uris!", "name": "application_type",
"type_info": "TextArray" "type_info": "Text"
}, },
{ {
"ordinal": 3, "ordinal": 3,
"name": "redirect_uris",
"type_info": "TextArray"
},
{
"ordinal": 4,
"name": "grant_type_authorization_code", "name": "grant_type_authorization_code",
"type_info": "Bool" "type_info": "Bool"
}, },
{ {
"ordinal": 4, "ordinal": 5,
"name": "grant_type_refresh_token", "name": "grant_type_refresh_token",
"type_info": "Bool" "type_info": "Bool"
}, },
{ {
"ordinal": 5, "ordinal": 6,
"name": "contacts",
"type_info": "TextArray"
},
{
"ordinal": 7,
"name": "client_name", "name": "client_name",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 6, "ordinal": 8,
"name": "logo_uri", "name": "logo_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 7, "ordinal": 9,
"name": "client_uri", "name": "client_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 8, "ordinal": 10,
"name": "policy_uri", "name": "policy_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 9, "ordinal": 11,
"name": "tos_uri", "name": "tos_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 10, "ordinal": 12,
"name": "jwks_uri", "name": "jwks_uri",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 11, "ordinal": 13,
"name": "jwks", "name": "jwks",
"type_info": "Jsonb" "type_info": "Jsonb"
}, },
{ {
"ordinal": 12, "ordinal": 14,
"name": "id_token_signed_response_alg", "name": "id_token_signed_response_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 13, "ordinal": 15,
"name": "userinfo_signed_response_alg", "name": "userinfo_signed_response_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 14, "ordinal": 16,
"name": "token_endpoint_auth_method", "name": "token_endpoint_auth_method",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 15, "ordinal": 17,
"name": "token_endpoint_auth_signing_alg", "name": "token_endpoint_auth_signing_alg",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 16, "ordinal": 18,
"name": "initiate_login_uri", "name": "initiate_login_uri",
"type_info": "Text" "type_info": "Text"
} }
@ -97,7 +107,9 @@
"nullable": [ "nullable": [
false, false,
true, true,
null, true,
false,
false,
false, false,
false, false,
true, true,
@ -114,5 +126,5 @@
true true
] ]
}, },
"hash": "db90cbc406a399f5447bd2c1d8018464f83b927dec620353516c0285b76fcf24" "hash": "e6250ea5c861cd7568999fd8f490daf1407b1b3619e3a05c70b5fe9ccf9aa7b5"
} }

View File

@ -0,0 +1,32 @@
-- Copyright 2023 The Matrix.org Foundation C.I.C.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- Adds a few fields to OAuth 2.0 clients, and squash the redirect_uris in the same table
ALTER TABLE "oauth2_clients"
ADD COLUMN "redirect_uris" TEXT[] NOT NULL DEFAULT '{}',
ADD COLUMN "application_type" TEXT,
ADD COLUMN "contacts" TEXT[] NOT NULL DEFAULT '{}';
-- Insert in the new `redirect_uris` column the values from the old table
UPDATE "oauth2_clients"
SET "redirect_uris" = ARRAY(
SELECT "redirect_uri"
FROM "oauth2_client_redirect_uris"
WHERE "oauth2_client_redirect_uris"."oauth2_client_id" = "oauth2_clients"."oauth2_client_id"
GROUP BY "redirect_uri"
);
-- Drop the old table
DROP TABLE "oauth2_client_redirect_uris";

View File

@ -27,6 +27,7 @@ use mas_iana::{
use mas_jose::jwk::PublicJsonWebKeySet; use mas_jose::jwk::PublicJsonWebKeySet;
use mas_storage::{oauth2::OAuth2ClientRepository, Clock}; use mas_storage::{oauth2::OAuth2ClientRepository, Clock};
use oauth2_types::{ use oauth2_types::{
oidc::ApplicationType,
requests::GrantType, requests::GrantType,
scope::{Scope, ScopeToken}, scope::{Scope, ScopeToken},
}; };
@ -57,11 +58,12 @@ impl<'c> PgOAuth2ClientRepository<'c> {
struct OAuth2ClientLookup { struct OAuth2ClientLookup {
oauth2_client_id: Uuid, oauth2_client_id: Uuid,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,
application_type: Option<String>,
redirect_uris: Vec<String>, redirect_uris: Vec<String>,
// response_types: Vec<String>, // response_types: Vec<String>,
grant_type_authorization_code: bool, grant_type_authorization_code: bool,
grant_type_refresh_token: bool, grant_type_refresh_token: bool,
// contacts: Vec<String>, contacts: Vec<String>,
client_name: Option<String>, client_name: Option<String>,
logo_uri: Option<String>, logo_uri: Option<String>,
client_uri: Option<String>, client_uri: Option<String>,
@ -92,6 +94,17 @@ impl TryInto<Client> for OAuth2ClientLookup {
.source(e) .source(e)
})?; })?;
let application_type = self
.application_type
.map(|s| s.parse())
.transpose()
.map_err(|e| {
DatabaseInconsistencyError::on("oauth2_clients")
.column("application_type")
.row(id)
.source(e)
})?;
let response_types = vec![ let response_types = vec![
OAuthAuthorizationEndpointResponseType::Code, OAuthAuthorizationEndpointResponseType::Code,
OAuthAuthorizationEndpointResponseType::IdToken, OAuthAuthorizationEndpointResponseType::IdToken,
@ -237,11 +250,11 @@ impl TryInto<Client> for OAuth2ClientLookup {
id, id,
client_id: id.to_string(), client_id: id.to_string(),
encrypted_client_secret: self.encrypted_client_secret, encrypted_client_secret: self.encrypted_client_secret,
application_type,
redirect_uris, redirect_uris,
response_types, response_types,
grant_types, grant_types,
// contacts: self.contacts, contacts: self.contacts,
contacts: vec![],
client_name: self.client_name, client_name: self.client_name,
logo_uri, logo_uri,
client_uri, client_uri,
@ -276,13 +289,11 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
r#" r#"
SELECT oauth2_client_id SELECT oauth2_client_id
, encrypted_client_secret , encrypted_client_secret
, ARRAY( , application_type
SELECT redirect_uri , redirect_uris
FROM oauth2_client_redirect_uris r
WHERE r.oauth2_client_id = c.oauth2_client_id
) AS "redirect_uris!"
, grant_type_authorization_code , grant_type_authorization_code
, grant_type_refresh_token , grant_type_refresh_token
, contacts
, client_name , client_name
, logo_uri , logo_uri
, client_uri , client_uri
@ -328,13 +339,11 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
r#" r#"
SELECT oauth2_client_id SELECT oauth2_client_id
, encrypted_client_secret , encrypted_client_secret
, ARRAY( , application_type
SELECT redirect_uri , redirect_uris
FROM oauth2_client_redirect_uris r
WHERE r.oauth2_client_id = c.oauth2_client_id
) AS "redirect_uris!"
, grant_type_authorization_code , grant_type_authorization_code
, grant_type_refresh_token , grant_type_refresh_token
, contacts
, client_name , client_name
, logo_uri , logo_uri
, client_uri , client_uri
@ -379,10 +388,11 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
async fn add( async fn add(
&mut self, &mut self,
mut rng: &mut (dyn RngCore + Send), rng: &mut (dyn RngCore + Send),
clock: &dyn Clock, clock: &dyn Clock,
redirect_uris: Vec<Url>, redirect_uris: Vec<Url>,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,
application_type: Option<ApplicationType>,
grant_types: Vec<GrantType>, grant_types: Vec<GrantType>,
contacts: Vec<String>, contacts: Vec<String>,
client_name: Option<String>, client_name: Option<String>,
@ -408,11 +418,15 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
.transpose() .transpose()
.map_err(DatabaseError::to_invalid_operation)?; .map_err(DatabaseError::to_invalid_operation)?;
let redirect_uris_array = redirect_uris.iter().map(Url::to_string).collect::<Vec<_>>();
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO oauth2_clients INSERT INTO oauth2_clients
( oauth2_client_id ( oauth2_client_id
, encrypted_client_secret , encrypted_client_secret
, application_type
, redirect_uris
, grant_type_authorization_code , grant_type_authorization_code
, grant_type_refresh_token , grant_type_refresh_token
, client_name , client_name
@ -430,10 +444,12 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
, is_static , is_static
) )
VALUES VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, FALSE) ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, FALSE)
"#, "#,
Uuid::from(id), Uuid::from(id),
encrypted_client_secret, encrypted_client_secret,
application_type.map(|a| a.to_string()),
&redirect_uris_array,
grant_types.contains(&GrantType::AuthorizationCode), grant_types.contains(&GrantType::AuthorizationCode),
grant_types.contains(&GrantType::RefreshToken), grant_types.contains(&GrantType::RefreshToken),
client_name, client_name,
@ -459,40 +475,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
.execute(&mut *self.conn) .execute(&mut *self.conn)
.await?; .await?;
{
let span = info_span!(
"db.oauth2_client.add.redirect_uris",
db.statement = tracing::field::Empty,
client.id = %id,
);
let (uri_ids, redirect_uris): (Vec<Uuid>, Vec<String>) = redirect_uris
.iter()
.map(|uri| {
(
Uuid::from(Ulid::from_datetime_with_source(now.into(), &mut rng)),
uri.as_str().to_owned(),
)
})
.unzip();
sqlx::query!(
r#"
INSERT INTO oauth2_client_redirect_uris
(oauth2_client_redirect_uri_id, oauth2_client_id, redirect_uri)
SELECT id, $2, redirect_uri
FROM UNNEST($1::uuid[], $3::text[]) r(id, redirect_uri)
"#,
&uri_ids,
Uuid::from(id),
&redirect_uris,
)
.record(&span)
.execute(&mut *self.conn)
.instrument(span)
.await?;
}
let jwks = match (jwks, jwks_uri) { let jwks = match (jwks, jwks_uri) {
(None, None) => None, (None, None) => None,
(Some(jwks), None) => Some(JwksOrJwksUri::Jwks(jwks)), (Some(jwks), None) => Some(JwksOrJwksUri::Jwks(jwks)),
@ -504,6 +486,7 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
id, id,
client_id: id.to_string(), client_id: id.to_string(),
encrypted_client_secret, encrypted_client_secret,
application_type,
redirect_uris, redirect_uris,
response_types: vec![ response_types: vec![
OAuthAuthorizationEndpointResponseType::Code, OAuthAuthorizationEndpointResponseType::Code,
@ -537,8 +520,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
)] )]
async fn upsert_static( async fn upsert_static(
&mut self, &mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
client_id: Ulid, client_id: Ulid,
client_auth_method: OAuthClientAuthenticationMethod, client_auth_method: OAuthClientAuthenticationMethod,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,
@ -553,12 +534,14 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
.map_err(DatabaseError::to_invalid_operation)?; .map_err(DatabaseError::to_invalid_operation)?;
let client_auth_method = client_auth_method.to_string(); let client_auth_method = client_auth_method.to_string();
let redirect_uris_array = redirect_uris.iter().map(Url::to_string).collect::<Vec<_>>();
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO oauth2_clients INSERT INTO oauth2_clients
( oauth2_client_id ( oauth2_client_id
, encrypted_client_secret , encrypted_client_secret
, redirect_uris
, grant_type_authorization_code , grant_type_authorization_code
, grant_type_refresh_token , grant_type_refresh_token
, token_endpoint_auth_method , token_endpoint_auth_method
@ -567,7 +550,7 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
, is_static , is_static
) )
VALUES VALUES
($1, $2, $3, $4, $5, $6, $7, TRUE) ($1, $2, $3, $4, $5, $6, $7, $8, TRUE)
ON CONFLICT (oauth2_client_id) ON CONFLICT (oauth2_client_id)
DO DO
UPDATE SET encrypted_client_secret = EXCLUDED.encrypted_client_secret UPDATE SET encrypted_client_secret = EXCLUDED.encrypted_client_secret
@ -580,6 +563,7 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
"#, "#,
Uuid::from(client_id), Uuid::from(client_id),
encrypted_client_secret, encrypted_client_secret,
&redirect_uris_array,
true, true,
true, true,
client_auth_method, client_auth_method,
@ -590,41 +574,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
.execute(&mut *self.conn) .execute(&mut *self.conn)
.await?; .await?;
{
let span = info_span!(
"db.oauth2_client.upsert_static.redirect_uris",
client.id = %client_id,
db.statement = tracing::field::Empty,
);
let now = clock.now();
let (ids, redirect_uris): (Vec<Uuid>, Vec<String>) = redirect_uris
.iter()
.map(|uri| {
(
Uuid::from(Ulid::from_datetime_with_source(now.into(), &mut *rng)),
uri.as_str().to_owned(),
)
})
.unzip();
sqlx::query!(
r#"
INSERT INTO oauth2_client_redirect_uris
(oauth2_client_redirect_uri_id, oauth2_client_id, redirect_uri)
SELECT id, $2, redirect_uri
FROM UNNEST($1::uuid[], $3::text[]) r(id, redirect_uri)
"#,
&ids,
Uuid::from(client_id),
&redirect_uris,
)
.record(&span)
.execute(&mut *self.conn)
.instrument(span)
.await?;
}
let jwks = match (jwks, jwks_uri) { let jwks = match (jwks, jwks_uri) {
(None, None) => None, (None, None) => None,
(Some(jwks), None) => Some(JwksOrJwksUri::Jwks(jwks)), (Some(jwks), None) => Some(JwksOrJwksUri::Jwks(jwks)),
@ -636,6 +585,7 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
id: client_id, id: client_id,
client_id: client_id.to_string(), client_id: client_id.to_string(),
encrypted_client_secret, encrypted_client_secret,
application_type: None,
redirect_uris, redirect_uris,
response_types: vec![ response_types: vec![
OAuthAuthorizationEndpointResponseType::Code, OAuthAuthorizationEndpointResponseType::Code,
@ -672,13 +622,11 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
r#" r#"
SELECT oauth2_client_id SELECT oauth2_client_id
, encrypted_client_secret , encrypted_client_secret
, ARRAY( , application_type
SELECT redirect_uri , redirect_uris
FROM oauth2_client_redirect_uris r
WHERE r.oauth2_client_id = c.oauth2_client_id
) AS "redirect_uris!"
, grant_type_authorization_code , grant_type_authorization_code
, grant_type_refresh_token , grant_type_refresh_token
, contacts
, client_name , client_name
, logo_uri , logo_uri
, client_uri , client_uri
@ -911,26 +859,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
.await?; .await?;
} }
// Delete the redirect URIs
{
let span = info_span!(
"db.oauth2_client.delete_by_id.redirect_uris",
db.statement = tracing::field::Empty,
);
sqlx::query!(
r#"
DELETE FROM oauth2_client_redirect_uris
WHERE oauth2_client_id = $1
"#,
Uuid::from(id),
)
.record(&span)
.execute(&mut *self.conn)
.instrument(span)
.await?;
}
// Now delete the client itself // Now delete the client itself
let res = sqlx::query!( let res = sqlx::query!(
r#" r#"

View File

@ -73,6 +73,7 @@ mod tests {
&clock, &clock,
vec!["https://example.com/redirect".parse().unwrap()], vec!["https://example.com/redirect".parse().unwrap()],
None, None,
None,
vec![GrantType::AuthorizationCode], vec![GrantType::AuthorizationCode],
Vec::new(), // TODO: contacts are not yet saved Vec::new(), // TODO: contacts are not yet saved
// vec!["contact@example.com".to_owned()], // vec!["contact@example.com".to_owned()],
@ -409,6 +410,7 @@ mod tests {
&clock, &clock,
vec!["https://first.example.com/redirect".parse().unwrap()], vec!["https://first.example.com/redirect".parse().unwrap()],
None, None,
None,
vec![GrantType::AuthorizationCode], vec![GrantType::AuthorizationCode],
Vec::new(), // TODO: contacts are not yet saved Vec::new(), // TODO: contacts are not yet saved
// vec!["contact@first.example.com".to_owned()], // vec!["contact@first.example.com".to_owned()],
@ -434,6 +436,7 @@ mod tests {
&clock, &clock,
vec!["https://second.example.com/redirect".parse().unwrap()], vec!["https://second.example.com/redirect".parse().unwrap()],
None, None,
None,
vec![GrantType::AuthorizationCode], vec![GrantType::AuthorizationCode],
Vec::new(), // TODO: contacts are not yet saved Vec::new(), // TODO: contacts are not yet saved
// vec!["contact@second.example.com".to_owned()], // vec!["contact@second.example.com".to_owned()],

View File

@ -18,7 +18,7 @@ use async_trait::async_trait;
use mas_data_model::{Client, User}; use mas_data_model::{Client, User};
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod}; use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
use mas_jose::jwk::PublicJsonWebKeySet; use mas_jose::jwk::PublicJsonWebKeySet;
use oauth2_types::{requests::GrantType, scope::Scope}; use oauth2_types::{oidc::ApplicationType, requests::GrantType, scope::Scope};
use rand_core::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use url::Url; use url::Url;
@ -80,6 +80,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
/// * `clock`: The clock used to generate timestamps /// * `clock`: The clock used to generate timestamps
/// * `redirect_uris`: The list of redirect URIs used by this client /// * `redirect_uris`: The list of redirect URIs used by this client
/// * `encrypted_client_secret`: The encrypted client secret, if any /// * `encrypted_client_secret`: The encrypted client secret, if any
/// * `application_type`: The application type of this client
/// * `grant_types`: The list of grant types this client can use /// * `grant_types`: The list of grant types this client can use
/// * `contacts`: The list of contacts for this client /// * `contacts`: The list of contacts for this client
/// * `client_name`: The human-readable name of this client, if given /// * `client_name`: The human-readable name of this client, if given
@ -110,6 +111,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
clock: &dyn Clock, clock: &dyn Clock,
redirect_uris: Vec<Url>, redirect_uris: Vec<Url>,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,
application_type: Option<ApplicationType>,
grant_types: Vec<GrantType>, grant_types: Vec<GrantType>,
contacts: Vec<String>, contacts: Vec<String>,
client_name: Option<String>, client_name: Option<String>,
@ -132,8 +134,6 @@ pub trait OAuth2ClientRepository: Send + Sync {
/// ///
/// # Parameters /// # Parameters
/// ///
/// * `rng`: The random number generator to use
/// * `clock`: The clock used to generate timestamps
/// * `client_id`: The client ID /// * `client_id`: The client ID
/// * `client_auth_method`: The authentication method this client uses /// * `client_auth_method`: The authentication method this client uses
/// * `encrypted_client_secret`: The encrypted client secret, if any /// * `encrypted_client_secret`: The encrypted client secret, if any
@ -147,8 +147,6 @@ pub trait OAuth2ClientRepository: Send + Sync {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn upsert_static( async fn upsert_static(
&mut self, &mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
client_id: Ulid, client_id: Ulid,
client_auth_method: OAuthClientAuthenticationMethod, client_auth_method: OAuthClientAuthenticationMethod,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,
@ -244,6 +242,7 @@ repository_impl!(OAuth2ClientRepository:
clock: &dyn Clock, clock: &dyn Clock,
redirect_uris: Vec<Url>, redirect_uris: Vec<Url>,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,
application_type: Option<ApplicationType>,
grant_types: Vec<GrantType>, grant_types: Vec<GrantType>,
contacts: Vec<String>, contacts: Vec<String>,
client_name: Option<String>, client_name: Option<String>,
@ -262,8 +261,6 @@ repository_impl!(OAuth2ClientRepository:
async fn upsert_static( async fn upsert_static(
&mut self, &mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
client_id: Ulid, client_id: Ulid,
client_auth_method: OAuthClientAuthenticationMethod, client_auth_method: OAuthClientAuthenticationMethod,
encrypted_client_secret: Option<String>, encrypted_client_secret: Option<String>,

View File

@ -475,6 +475,20 @@ interface Node {
id: ID! id: ID!
} }
"""
The application type advertised by the client.
"""
enum Oauth2ApplicationType {
"""
Client is a web application.
"""
WEB
"""
Client is a native application.
"""
NATIVE
}
""" """
An OAuth 2.0 client An OAuth 2.0 client
""" """
@ -507,6 +521,14 @@ type Oauth2Client implements Node {
List of redirect URIs used for authorization grants by the client. List of redirect URIs used for authorization grants by the client.
""" """
redirectUris: [Url!]! redirectUris: [Url!]!
"""
List of contacts advertised by the client.
"""
contacts: [String!]!
"""
The application type advertised by the client.
"""
applicationType: Oauth2ApplicationType
} }
""" """

View File

@ -382,15 +382,27 @@ export type Node = {
id: Scalars["ID"]["output"]; id: Scalars["ID"]["output"];
}; };
/** The application type advertised by the client. */
export enum Oauth2ApplicationType {
/** Client is a native application. */
Native = "NATIVE",
/** Client is a web application. */
Web = "WEB",
}
/** An OAuth 2.0 client */ /** An OAuth 2.0 client */
export type Oauth2Client = Node & { export type Oauth2Client = Node & {
__typename?: "Oauth2Client"; __typename?: "Oauth2Client";
/** The application type advertised by the client. */
applicationType?: Maybe<Oauth2ApplicationType>;
/** OAuth 2.0 client ID */ /** OAuth 2.0 client ID */
clientId: Scalars["String"]["output"]; clientId: Scalars["String"]["output"];
/** Client name advertised by the client. */ /** Client name advertised by the client. */
clientName?: Maybe<Scalars["String"]["output"]>; clientName?: Maybe<Scalars["String"]["output"]>;
/** Client URI advertised by the client. */ /** Client URI advertised by the client. */
clientUri?: Maybe<Scalars["Url"]["output"]>; clientUri?: Maybe<Scalars["Url"]["output"]>;
/** List of contacts advertised by the client. */
contacts: Array<Scalars["String"]["output"]>;
/** ID of the object. */ /** ID of the object. */
id: Scalars["ID"]["output"]; id: Scalars["ID"]["output"];
/** Privacy policy URI advertised by the client. */ /** Privacy policy URI advertised by the client. */

View File

@ -1046,6 +1046,14 @@ export default {
kind: "OBJECT", kind: "OBJECT",
name: "Oauth2Client", name: "Oauth2Client",
fields: [ fields: [
{
name: "applicationType",
type: {
kind: "SCALAR",
name: "Any",
},
args: [],
},
{ {
name: "clientId", name: "clientId",
type: { type: {
@ -1073,6 +1081,23 @@ export default {
}, },
args: [], args: [],
}, },
{
name: "contacts",
type: {
kind: "NON_NULL",
ofType: {
kind: "LIST",
ofType: {
kind: "NON_NULL",
ofType: {
kind: "SCALAR",
name: "Any",
},
},
},
},
args: [],
},
{ {
name: "id", name: "id",
type: { type: {