diff options
| author | Mica White <botahamec@outlook.com> | 2025-12-08 20:08:21 -0500 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2025-12-08 20:08:21 -0500 |
| commit | 608ce1d9910cd68ce825838ea313e02c598f908e (patch) | |
| tree | 0bd4ad26f86e5c873f97308983112b0ffe593df3 /src/api/clients.rs | |
| parent | 93fd2e82e8fdc5ee62739053385f8ccffc660f02 (diff) | |
Diffstat (limited to 'src/api/clients.rs')
| -rw-r--r-- | src/api/clients.rs | 966 |
1 files changed, 483 insertions, 483 deletions
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<str>, - client_type: ClientType, - allowed_scopes: Box<[Box<str>]>, - default_scopes: Option<Box<[Box<str>]>>, - is_trusted: bool, -} - -impl From<ClientRow> 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<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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!("</clients/{client_id}/redirect-uris>; 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<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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::<Box<[&str]>>(); - - Ok(HttpResponse::Ok().json(allowed_scopes)) -} - -#[get("/{client_id}/default-scopes")] -async fn get_client_default_scopes( - client_id: web::Path<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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::<Box<[Box<str>]>>() - }); - - Ok(HttpResponse::Ok().json(default_scopes)) -} - -#[get("/{client_id}/is-trusted")] -async fn get_client_is_trusted( - client_id: web::Path<Uuid>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, ClientNotFound> { - 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<str>, - ty: ClientType, - redirect_uris: Box<[Url]>, - secret: Option<Box<str>>, - allowed_scopes: Box<[Box<str>]>, - default_scopes: Option<Box<[Box<str>]>>, - trusted: bool, -} - -#[derive(Debug, Clone, Error)] -#[error("The given client alias is already taken")] -struct AliasTakenError { - alias: Box<str>, -} - -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<ClientRequest>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<ClientRequest>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<Box<str>>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<ClientType>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<Box<[Box<str>]>>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<Option<Box<[Box<str>]>>>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<bool>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<Box<[Url]>>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<Uuid>, - body: web::Json<Option<Box<str>>>, - db: web::Data<MySqlPool>, -) -> Result<HttpResponse, UpdateClientError> { - 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<str>,
+ client_type: ClientType,
+ allowed_scopes: Box<[Box<str>]>,
+ default_scopes: Option<Box<[Box<str>]>>,
+ is_trusted: bool,
+}
+
+impl From<ClientRow> 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<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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!("</clients/{client_id}/redirect-uris>; 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<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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::<Box<[&str]>>();
+
+ Ok(HttpResponse::Ok().json(allowed_scopes))
+}
+
+#[get("/{client_id}/default-scopes")]
+async fn get_client_default_scopes(
+ client_id: web::Path<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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::<Box<[Box<str>]>>()
+ });
+
+ Ok(HttpResponse::Ok().json(default_scopes))
+}
+
+#[get("/{client_id}/is-trusted")]
+async fn get_client_is_trusted(
+ client_id: web::Path<Uuid>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, ClientNotFound> {
+ 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<str>,
+ ty: ClientType,
+ redirect_uris: Box<[Url]>,
+ secret: Option<Box<str>>,
+ allowed_scopes: Box<[Box<str>]>,
+ default_scopes: Option<Box<[Box<str>]>>,
+ trusted: bool,
+}
+
+#[derive(Debug, Clone, Error)]
+#[error("The given client alias is already taken")]
+struct AliasTakenError {
+ alias: Box<str>,
+}
+
+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<ClientRequest>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<ClientRequest>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<Box<str>>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<ClientType>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<Box<[Box<str>]>>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<Option<Box<[Box<str>]>>>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<bool>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<Box<[Url]>>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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<Uuid>,
+ body: web::Json<Option<Box<str>>>,
+ db: web::Data<MySqlPool>,
+) -> Result<HttpResponse, UpdateClientError> {
+ 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)
+}
|
