From 608ce1d9910cd68ce825838ea313e02c598f908e Mon Sep 17 00:00:00 2001 From: Mica White Date: Mon, 8 Dec 2025 20:08:21 -0500 Subject: Stuff --- src/api/clients.rs | 966 ++++++++++++++++++++++++++--------------------------- 1 file changed, 483 insertions(+), 483 deletions(-) (limited to 'src/api/clients.rs') diff --git a/src/api/clients.rs b/src/api/clients.rs index 3f906bb..ded8b81 100644 --- a/src/api/clients.rs +++ b/src/api/clients.rs @@ -1,483 +1,483 @@ -use actix_web::http::{header, StatusCode}; -use actix_web::{get, post, put, web, HttpResponse, ResponseError, Scope}; -use raise::yeet; -use serde::{Deserialize, Serialize}; -use sqlx::MySqlPool; -use thiserror::Error; -use url::Url; -use uuid::Uuid; - -use crate::models::client::{Client, ClientType, CreateClientError}; -use crate::services::crypto::PasswordHash; -use crate::services::db::ClientRow; -use crate::services::{db, id}; - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -struct ClientResponse { - client_id: Uuid, - alias: Box, - client_type: ClientType, - allowed_scopes: Box<[Box]>, - default_scopes: Option]>>, - is_trusted: bool, -} - -impl From for ClientResponse { - fn from(value: ClientRow) -> Self { - Self { - client_id: value.id, - alias: value.alias.into_boxed_str(), - client_type: value.client_type, - allowed_scopes: value - .allowed_scopes - .split_whitespace() - .map(Box::from) - .collect(), - default_scopes: value - .default_scopes - .map(|s| s.split_whitespace().map(Box::from).collect()), - is_trusted: value.is_trusted, - } - } -} - -#[derive(Debug, Clone, Copy, Error)] -#[error("No client with the given client ID was found")] -struct ClientNotFound { - id: Uuid, -} - -impl ResponseError for ClientNotFound { - fn status_code(&self) -> StatusCode { - StatusCode::NOT_FOUND - } -} - -impl ClientNotFound { - fn new(id: Uuid) -> Self { - Self { id } - } -} - -#[get("/{client_id}")] -async fn get_client( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - let Some(client) = db::get_client_response(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id)) - }; - - let redirect_uris_link = format!("; rel=\"redirect-uris\""); - let response: ClientResponse = client.into(); - let response = HttpResponse::Ok() - .append_header((header::LINK, redirect_uris_link)) - .json(response); - Ok(response) -} - -#[get("/{client_id}/alias")] -async fn get_client_alias( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - let Some(alias) = db::get_client_alias(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id)) - }; - - Ok(HttpResponse::Ok().json(alias)) -} - -#[get("/{client_id}/type")] -async fn get_client_type( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - let Some(client_type) = db::get_client_type(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id)) - }; - - Ok(HttpResponse::Ok().json(client_type)) -} - -#[get("/{client_id}/redirect-uris")] -async fn get_client_redirect_uris( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - if !db::client_id_exists(db, id).await.unwrap() { - yeet!(ClientNotFound::new(id)) - }; - - let redirect_uris = db::get_client_redirect_uris(db, id).await.unwrap(); - - Ok(HttpResponse::Ok().json(redirect_uris)) -} - -#[get("/{client_id}/allowed-scopes")] -async fn get_client_allowed_scopes( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - let Some(allowed_scopes) = db::get_client_allowed_scopes(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id)) - }; - - let allowed_scopes = allowed_scopes.split_whitespace().collect::>(); - - Ok(HttpResponse::Ok().json(allowed_scopes)) -} - -#[get("/{client_id}/default-scopes")] -async fn get_client_default_scopes( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - let Some(default_scopes) = db::get_client_default_scopes(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id)) - }; - - let default_scopes = default_scopes.map(|scopes| { - scopes - .split_whitespace() - .map(Box::from) - .collect::]>>() - }); - - Ok(HttpResponse::Ok().json(default_scopes)) -} - -#[get("/{client_id}/is-trusted")] -async fn get_client_is_trusted( - client_id: web::Path, - db: web::Data, -) -> Result { - let db = db.as_ref(); - let id = *client_id; - - let Some(is_trusted) = db::is_client_trusted(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id)) - }; - - Ok(HttpResponse::Ok().json(is_trusted)) -} - -#[derive(Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ClientRequest { - alias: Box, - ty: ClientType, - redirect_uris: Box<[Url]>, - secret: Option>, - allowed_scopes: Box<[Box]>, - default_scopes: Option]>>, - trusted: bool, -} - -#[derive(Debug, Clone, Error)] -#[error("The given client alias is already taken")] -struct AliasTakenError { - alias: Box, -} - -impl ResponseError for AliasTakenError { - fn status_code(&self) -> StatusCode { - StatusCode::CONFLICT - } -} - -impl AliasTakenError { - fn new(alias: &str) -> Self { - Self { - alias: Box::from(alias), - } - } -} - -#[post("")] -async fn create_client( - body: web::Json, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let alias = &body.alias; - - if db::client_alias_exists(db, &alias).await.unwrap() { - yeet!(AliasTakenError::new(&alias).into()); - } - - let id = id::new_id(db, db::client_id_exists).await.unwrap(); - let client = Client::new( - id, - &alias, - body.ty, - body.secret.as_deref(), - body.allowed_scopes.clone(), - body.default_scopes.clone(), - &body.redirect_uris, - body.trusted, - ) - .map_err(|e| e.unwrap())?; - - let transaction = db.begin().await.unwrap(); - db::create_client(transaction, &client).await.unwrap(); - - let response = HttpResponse::Created() - .insert_header((header::LOCATION, format!("clients/{id}"))) - .finish(); - Ok(response) -} - -#[derive(Debug, Clone, Error)] -enum UpdateClientError { - #[error(transparent)] - NotFound(#[from] ClientNotFound), - #[error(transparent)] - ClientError(#[from] CreateClientError), - #[error(transparent)] - AliasTaken(#[from] AliasTakenError), -} - -impl ResponseError for UpdateClientError { - fn status_code(&self) -> StatusCode { - match self { - Self::NotFound(e) => e.status_code(), - Self::ClientError(e) => e.status_code(), - Self::AliasTaken(e) => e.status_code(), - } - } -} - -#[put("/{id}")] -async fn update_client( - id: web::Path, - body: web::Json, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - let alias = &body.alias; - - let Some(old_alias) = db::get_client_alias(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id).into()) - }; - if old_alias != alias.clone() && db::client_alias_exists(db, &alias).await.unwrap() { - yeet!(AliasTakenError::new(&alias).into()); - } - - let client = Client::new( - id, - &alias, - body.ty, - body.secret.as_deref(), - body.allowed_scopes.clone(), - body.default_scopes.clone(), - &body.redirect_uris, - body.trusted, - ) - .map_err(|e| e.unwrap())?; - - let transaction = db.begin().await.unwrap(); - db::update_client(transaction, &client).await.unwrap(); - - let response = HttpResponse::NoContent().finish(); - Ok(response) -} - -#[put("/{id}/alias")] -async fn update_client_alias( - id: web::Path, - body: web::Json>, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - let alias = body.0; - - let Some(old_alias) = db::get_client_alias(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id).into()) - }; - if old_alias == alias { - return Ok(HttpResponse::NoContent().finish()); - } - if db::client_alias_exists(db, &alias).await.unwrap() { - yeet!(AliasTakenError::new(&alias).into()); - } - - db::update_client_alias(db, id, &alias).await.unwrap(); - - let response = HttpResponse::NoContent().finish(); - Ok(response) -} - -#[put("/{id}/type")] -async fn update_client_type( - id: web::Path, - body: web::Json, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - let ty = body.0; - - if !db::client_id_exists(db, id).await.unwrap() { - yeet!(ClientNotFound::new(id).into()); - } - - db::update_client_type(db, id, ty).await.unwrap(); - - Ok(HttpResponse::NoContent().finish()) -} - -#[put("/{id}/allowed-scopes")] -async fn update_client_allowed_scopes( - id: web::Path, - body: web::Json]>>, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - let allowed_scopes = body.0.join(" "); - - if !db::client_id_exists(db, id).await.unwrap() { - yeet!(ClientNotFound::new(id).into()); - } - - db::update_client_allowed_scopes(db, id, &allowed_scopes) - .await - .unwrap(); - - Ok(HttpResponse::NoContent().finish()) -} - -#[put("/{id}/default-scopes")] -async fn update_client_default_scopes( - id: web::Path, - body: web::Json]>>>, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - let default_scopes = body.0.map(|s| s.join(" ")); - - if !db::client_id_exists(db, id).await.unwrap() { - yeet!(ClientNotFound::new(id).into()); - } - - db::update_client_default_scopes(db, id, default_scopes) - .await - .unwrap(); - - Ok(HttpResponse::NoContent().finish()) -} - -#[put("/{id}/is-trusted")] -async fn update_client_is_trusted( - id: web::Path, - body: web::Json, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - let is_trusted = *body; - - if !db::client_id_exists(db, id).await.unwrap() { - yeet!(ClientNotFound::new(id).into()); - } - - db::update_client_trusted(db, id, is_trusted).await.unwrap(); - - Ok(HttpResponse::NoContent().finish()) -} - -#[put("/{id}/redirect-uris")] -async fn update_client_redirect_uris( - id: web::Path, - body: web::Json>, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - - for uri in body.0.iter() { - if uri.scheme() != "https" { - yeet!(CreateClientError::NonHttpsUri.into()); - } - - if uri.fragment().is_some() { - yeet!(CreateClientError::UriFragment.into()) - } - } - - if !db::client_id_exists(db, id).await.unwrap() { - yeet!(ClientNotFound::new(id).into()); - } - - let transaction = db.begin().await.unwrap(); - db::update_client_redirect_uris(transaction, id, &body.0) - .await - .unwrap(); - - Ok(HttpResponse::NoContent().finish()) -} - -#[put("{id}/secret")] -async fn update_client_secret( - id: web::Path, - body: web::Json>>, - db: web::Data, -) -> Result { - let db = db.get_ref(); - let id = *id; - - let Some(client_type) = db::get_client_type(db, id).await.unwrap() else { - yeet!(ClientNotFound::new(id).into()) - }; - - if client_type == ClientType::Confidential && body.is_none() { - yeet!(CreateClientError::NoSecret.into()) - } - - let secret = body.0.map(|s| PasswordHash::new(&s).unwrap()); - db::update_client_secret(db, id, secret).await.unwrap(); - - Ok(HttpResponse::NoContent().finish()) -} - -pub fn service() -> Scope { - web::scope("/clients") - .service(get_client) - .service(get_client_alias) - .service(get_client_type) - .service(get_client_allowed_scopes) - .service(get_client_default_scopes) - .service(get_client_redirect_uris) - .service(get_client_is_trusted) - .service(create_client) - .service(update_client) - .service(update_client_alias) - .service(update_client_type) - .service(update_client_allowed_scopes) - .service(update_client_default_scopes) - .service(update_client_redirect_uris) - .service(update_client_secret) -} +use actix_web::http::{header, StatusCode}; +use actix_web::{get, post, put, web, HttpResponse, ResponseError, Scope}; +use raise::yeet; +use serde::{Deserialize, Serialize}; +use sqlx::MySqlPool; +use thiserror::Error; +use url::Url; +use uuid::Uuid; + +use crate::models::client::{Client, ClientType, CreateClientError}; +use crate::services::crypto::PasswordHash; +use crate::services::db::ClientRow; +use crate::services::{db, id}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ClientResponse { + client_id: Uuid, + alias: Box, + client_type: ClientType, + allowed_scopes: Box<[Box]>, + default_scopes: Option]>>, + is_trusted: bool, +} + +impl From for ClientResponse { + fn from(value: ClientRow) -> Self { + Self { + client_id: value.id, + alias: value.alias.into_boxed_str(), + client_type: value.client_type, + allowed_scopes: value + .allowed_scopes + .split_whitespace() + .map(Box::from) + .collect(), + default_scopes: value + .default_scopes + .map(|s| s.split_whitespace().map(Box::from).collect()), + is_trusted: value.is_trusted, + } + } +} + +#[derive(Debug, Clone, Copy, Error)] +#[error("No client with the given client ID was found")] +struct ClientNotFound { + id: Uuid, +} + +impl ResponseError for ClientNotFound { + fn status_code(&self) -> StatusCode { + StatusCode::NOT_FOUND + } +} + +impl ClientNotFound { + fn new(id: Uuid) -> Self { + Self { id } + } +} + +#[get("/{client_id}")] +async fn get_client( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + let Some(client) = db::get_client_response(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id)) + }; + + let redirect_uris_link = format!("; rel=\"redirect-uris\""); + let response: ClientResponse = client.into(); + let response = HttpResponse::Ok() + .append_header((header::LINK, redirect_uris_link)) + .json(response); + Ok(response) +} + +#[get("/{client_id}/alias")] +async fn get_client_alias( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + let Some(alias) = db::get_client_alias(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id)) + }; + + Ok(HttpResponse::Ok().json(alias)) +} + +#[get("/{client_id}/type")] +async fn get_client_type( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + let Some(client_type) = db::get_client_type(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id)) + }; + + Ok(HttpResponse::Ok().json(client_type)) +} + +#[get("/{client_id}/redirect-uris")] +async fn get_client_redirect_uris( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + if !db::client_id_exists(db, id).await.unwrap() { + yeet!(ClientNotFound::new(id)) + }; + + let redirect_uris = db::get_client_redirect_uris(db, id).await.unwrap(); + + Ok(HttpResponse::Ok().json(redirect_uris)) +} + +#[get("/{client_id}/allowed-scopes")] +async fn get_client_allowed_scopes( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + let Some(allowed_scopes) = db::get_client_allowed_scopes(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id)) + }; + + let allowed_scopes = allowed_scopes.split_whitespace().collect::>(); + + Ok(HttpResponse::Ok().json(allowed_scopes)) +} + +#[get("/{client_id}/default-scopes")] +async fn get_client_default_scopes( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + let Some(default_scopes) = db::get_client_default_scopes(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id)) + }; + + let default_scopes = default_scopes.map(|scopes| { + scopes + .split_whitespace() + .map(Box::from) + .collect::]>>() + }); + + Ok(HttpResponse::Ok().json(default_scopes)) +} + +#[get("/{client_id}/is-trusted")] +async fn get_client_is_trusted( + client_id: web::Path, + db: web::Data, +) -> Result { + let db = db.as_ref(); + let id = *client_id; + + let Some(is_trusted) = db::is_client_trusted(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id)) + }; + + Ok(HttpResponse::Ok().json(is_trusted)) +} + +#[derive(Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ClientRequest { + alias: Box, + ty: ClientType, + redirect_uris: Box<[Url]>, + secret: Option>, + allowed_scopes: Box<[Box]>, + default_scopes: Option]>>, + trusted: bool, +} + +#[derive(Debug, Clone, Error)] +#[error("The given client alias is already taken")] +struct AliasTakenError { + alias: Box, +} + +impl ResponseError for AliasTakenError { + fn status_code(&self) -> StatusCode { + StatusCode::CONFLICT + } +} + +impl AliasTakenError { + fn new(alias: &str) -> Self { + Self { + alias: Box::from(alias), + } + } +} + +#[post("")] +async fn create_client( + body: web::Json, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let alias = &body.alias; + + if db::client_alias_exists(db, &alias).await.unwrap() { + yeet!(AliasTakenError::new(&alias).into()); + } + + let id = id::new_id(db, db::client_id_exists).await.unwrap(); + let client = Client::new( + id, + &alias, + body.ty, + body.secret.as_deref(), + body.allowed_scopes.clone(), + body.default_scopes.clone(), + &body.redirect_uris, + body.trusted, + ) + .map_err(|e| e.unwrap())?; + + let transaction = db.begin().await.unwrap(); + db::create_client(transaction, &client).await.unwrap(); + + let response = HttpResponse::Created() + .insert_header((header::LOCATION, format!("clients/{id}"))) + .finish(); + Ok(response) +} + +#[derive(Debug, Clone, Error)] +enum UpdateClientError { + #[error(transparent)] + NotFound(#[from] ClientNotFound), + #[error(transparent)] + ClientError(#[from] CreateClientError), + #[error(transparent)] + AliasTaken(#[from] AliasTakenError), +} + +impl ResponseError for UpdateClientError { + fn status_code(&self) -> StatusCode { + match self { + Self::NotFound(e) => e.status_code(), + Self::ClientError(e) => e.status_code(), + Self::AliasTaken(e) => e.status_code(), + } + } +} + +#[put("/{id}")] +async fn update_client( + id: web::Path, + body: web::Json, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + let alias = &body.alias; + + let Some(old_alias) = db::get_client_alias(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id).into()) + }; + if old_alias != alias.clone() && db::client_alias_exists(db, &alias).await.unwrap() { + yeet!(AliasTakenError::new(&alias).into()); + } + + let client = Client::new( + id, + &alias, + body.ty, + body.secret.as_deref(), + body.allowed_scopes.clone(), + body.default_scopes.clone(), + &body.redirect_uris, + body.trusted, + ) + .map_err(|e| e.unwrap())?; + + let transaction = db.begin().await.unwrap(); + db::update_client(transaction, &client).await.unwrap(); + + let response = HttpResponse::NoContent().finish(); + Ok(response) +} + +#[put("/{id}/alias")] +async fn update_client_alias( + id: web::Path, + body: web::Json>, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + let alias = body.0; + + let Some(old_alias) = db::get_client_alias(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id).into()) + }; + if old_alias == alias { + return Ok(HttpResponse::NoContent().finish()); + } + if db::client_alias_exists(db, &alias).await.unwrap() { + yeet!(AliasTakenError::new(&alias).into()); + } + + db::update_client_alias(db, id, &alias).await.unwrap(); + + let response = HttpResponse::NoContent().finish(); + Ok(response) +} + +#[put("/{id}/type")] +async fn update_client_type( + id: web::Path, + body: web::Json, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + let ty = body.0; + + if !db::client_id_exists(db, id).await.unwrap() { + yeet!(ClientNotFound::new(id).into()); + } + + db::update_client_type(db, id, ty).await.unwrap(); + + Ok(HttpResponse::NoContent().finish()) +} + +#[put("/{id}/allowed-scopes")] +async fn update_client_allowed_scopes( + id: web::Path, + body: web::Json]>>, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + let allowed_scopes = body.0.join(" "); + + if !db::client_id_exists(db, id).await.unwrap() { + yeet!(ClientNotFound::new(id).into()); + } + + db::update_client_allowed_scopes(db, id, &allowed_scopes) + .await + .unwrap(); + + Ok(HttpResponse::NoContent().finish()) +} + +#[put("/{id}/default-scopes")] +async fn update_client_default_scopes( + id: web::Path, + body: web::Json]>>>, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + let default_scopes = body.0.map(|s| s.join(" ")); + + if !db::client_id_exists(db, id).await.unwrap() { + yeet!(ClientNotFound::new(id).into()); + } + + db::update_client_default_scopes(db, id, default_scopes) + .await + .unwrap(); + + Ok(HttpResponse::NoContent().finish()) +} + +#[put("/{id}/is-trusted")] +async fn update_client_is_trusted( + id: web::Path, + body: web::Json, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + let is_trusted = *body; + + if !db::client_id_exists(db, id).await.unwrap() { + yeet!(ClientNotFound::new(id).into()); + } + + db::update_client_trusted(db, id, is_trusted).await.unwrap(); + + Ok(HttpResponse::NoContent().finish()) +} + +#[put("/{id}/redirect-uris")] +async fn update_client_redirect_uris( + id: web::Path, + body: web::Json>, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + + for uri in body.0.iter() { + if uri.scheme() != "https" { + yeet!(CreateClientError::NonHttpsUri.into()); + } + + if uri.fragment().is_some() { + yeet!(CreateClientError::UriFragment.into()) + } + } + + if !db::client_id_exists(db, id).await.unwrap() { + yeet!(ClientNotFound::new(id).into()); + } + + let transaction = db.begin().await.unwrap(); + db::update_client_redirect_uris(transaction, id, &body.0) + .await + .unwrap(); + + Ok(HttpResponse::NoContent().finish()) +} + +#[put("{id}/secret")] +async fn update_client_secret( + id: web::Path, + body: web::Json>>, + db: web::Data, +) -> Result { + let db = db.get_ref(); + let id = *id; + + let Some(client_type) = db::get_client_type(db, id).await.unwrap() else { + yeet!(ClientNotFound::new(id).into()) + }; + + if client_type == ClientType::Confidential && body.is_none() { + yeet!(CreateClientError::NoSecret.into()) + } + + let secret = body.0.map(|s| PasswordHash::new(&s).unwrap()); + db::update_client_secret(db, id, secret).await.unwrap(); + + Ok(HttpResponse::NoContent().finish()) +} + +pub fn service() -> Scope { + web::scope("/clients") + .service(get_client) + .service(get_client_alias) + .service(get_client_type) + .service(get_client_allowed_scopes) + .service(get_client_default_scopes) + .service(get_client_redirect_uris) + .service(get_client_is_trusted) + .service(create_client) + .service(update_client) + .service(update_client_alias) + .service(update_client_type) + .service(update_client_allowed_scopes) + .service(update_client_default_scopes) + .service(update_client_redirect_uris) + .service(update_client_secret) +} -- cgit v1.2.3