summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/clients.rs40
-rw-r--r--src/models/client.rs23
-rw-r--r--src/services/db/client.rs35
3 files changed, 79 insertions, 19 deletions
diff --git a/src/api/clients.rs b/src/api/clients.rs
index 59869ba..327a0a5 100644
--- a/src/api/clients.rs
+++ b/src/api/clients.rs
@@ -1,7 +1,7 @@
use actix_web::http::{header, StatusCode};
use actix_web::{get, post, put, web, HttpResponse, ResponseError, Scope};
use raise::yeet;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use sqlx::MySqlPool;
use thiserror::Error;
use url::Url;
@@ -9,8 +9,37 @@ use uuid::Uuid;
use crate::models::client::{Client, ClientType, NoSecretError};
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>]>>,
+}
+
+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()),
+ }
+ }
+}
+
#[derive(Debug, Clone, Copy, Error)]
#[error("No client with the given client ID was found")]
struct ClientNotFound {
@@ -42,9 +71,10 @@ async fn get_client(
};
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(client);
+ .json(response);
Ok(response)
}
@@ -102,6 +132,8 @@ struct ClientRequest {
ty: ClientType,
redirect_uris: Box<[Url]>,
secret: Option<Box<str>>,
+ allowed_scopes: Box<[Box<str>]>,
+ default_scopes: Option<Box<[Box<str>]>>,
}
#[derive(Debug, Clone, Error)]
@@ -142,6 +174,8 @@ async fn create_client(
&alias,
body.ty,
body.secret.as_deref(),
+ body.allowed_scopes.clone(),
+ body.default_scopes.clone(),
&body.redirect_uris,
)
.map_err(|e| e.unwrap())?;
@@ -197,6 +231,8 @@ async fn update_client(
&alias,
body.ty,
body.secret.as_deref(),
+ body.allowed_scopes.clone(),
+ body.default_scopes.clone(),
&body.redirect_uris,
)
.map_err(|e| e.unwrap())?;
diff --git a/src/models/client.rs b/src/models/client.rs
index 44079de..90c5902 100644
--- a/src/models/client.rs
+++ b/src/models/client.rs
@@ -4,7 +4,6 @@ use actix_web::{http::StatusCode, ResponseError};
use exun::{Expect, RawUnexpected};
use raise::yeet;
use serde::{Deserialize, Serialize};
-use sqlx::FromRow;
use thiserror::Error;
use url::Url;
use uuid::Uuid;
@@ -34,17 +33,11 @@ pub struct Client {
ty: ClientType,
alias: Box<str>,
secret: Option<PasswordHash>,
+ allowed_scopes: Box<[Box<str>]>,
+ default_scopes: Option<Box<[Box<str>]>>,
redirect_uris: Box<[Url]>,
}
-#[derive(Debug, Clone, Serialize, FromRow)]
-#[serde(rename_all = "camelCase")]
-pub struct ClientResponse {
- pub id: Uuid,
- pub alias: String,
- pub client_type: ClientType,
-}
-
impl PartialEq for Client {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
@@ -85,6 +78,8 @@ impl Client {
alias: &str,
ty: ClientType,
secret: Option<&str>,
+ allowed_scopes: Box<[Box<str>]>,
+ default_scopes: Option<Box<[Box<str>]>>,
redirect_uris: &[Url],
) -> Result<Self, Expect<NoSecretError>> {
let secret = if let Some(secret) = secret {
@@ -102,6 +97,8 @@ impl Client {
alias: Box::from(alias),
ty: ClientType::Public,
secret,
+ allowed_scopes,
+ default_scopes,
redirect_uris: redirect_uris.into_iter().cloned().collect(),
})
}
@@ -134,6 +131,14 @@ impl Client {
self.secret.as_ref().map(|s| s.version())
}
+ pub fn allowed_scopes(&self) -> String {
+ self.allowed_scopes.join(" ")
+ }
+
+ pub fn default_scopes(&self) -> Option<String> {
+ self.default_scopes.clone().map(|s| s.join(" "))
+ }
+
pub fn check_secret(&self, secret: &str) -> Option<Result<bool, RawUnexpected>> {
self.secret.as_ref().map(|s| s.check_password(secret))
}
diff --git a/src/services/db/client.rs b/src/services/db/client.rs
index 4643f49..ecf98a3 100644
--- a/src/services/db/client.rs
+++ b/src/services/db/client.rs
@@ -1,15 +1,26 @@
use std::str::FromStr;
use exun::{RawUnexpected, ResultErrorExt};
-use sqlx::{mysql::MySqlQueryResult, query, query_as, query_scalar, Executor, MySql, Transaction};
+use sqlx::{
+ mysql::MySqlQueryResult, query, query_as, query_scalar, Executor, FromRow, MySql, Transaction,
+};
use url::Url;
use uuid::Uuid;
use crate::{
- models::client::{Client, ClientResponse, ClientType},
+ models::client::{Client, ClientType},
services::crypto::PasswordHash,
};
+#[derive(Debug, Clone, FromRow)]
+pub struct ClientRow {
+ pub id: Uuid,
+ pub alias: String,
+ pub client_type: ClientType,
+ pub allowed_scopes: String,
+ pub default_scopes: Option<String>,
+}
+
pub async fn client_id_exists<'c>(
executor: impl Executor<'c, Database = MySql>,
id: Uuid,
@@ -39,12 +50,14 @@ pub async fn client_alias_exists<'c>(
pub async fn get_client_response<'c>(
executor: impl Executor<'c, Database = MySql>,
id: Uuid,
-) -> Result<Option<ClientResponse>, RawUnexpected> {
+) -> Result<Option<ClientRow>, RawUnexpected> {
let record = query_as!(
- ClientResponse,
+ ClientRow,
r"SELECT id as `id: Uuid`,
alias,
- type as `client_type: ClientType`
+ type as `client_type: ClientType`,
+ allowed_scopes,
+ default_scopes
FROM clients WHERE id = ?",
id
)
@@ -153,14 +166,16 @@ pub async fn create_client<'c>(
client: &Client,
) -> Result<(), sqlx::Error> {
query!(
- r"INSERT INTO clients (id, alias, type, secret_hash, secret_salt, secret_version)
- VALUES ( ?, ?, ?, ?, ?, ?)",
+ r"INSERT INTO clients (id, alias, type, secret_hash, secret_salt, secret_version, allowed_scopes, default_scopes)
+ VALUES ( ?, ?, ?, ?, ?, ?, ?, ?)",
client.id(),
client.alias(),
client.client_type(),
client.secret_hash(),
client.secret_salt(),
client.secret_version(),
+ client.allowed_scopes(),
+ client.default_scopes()
)
.execute(&mut transaction)
.await?;
@@ -180,13 +195,17 @@ pub async fn update_client<'c>(
type = ?,
secret_hash = ?,
secret_salt = ?,
- secret_version = ?
+ secret_version = ?,
+ allowed_scopes = ?,
+ default_scopes = ?
WHERE id = ?",
client.client_type(),
client.alias(),
client.secret_hash(),
client.secret_salt(),
client.secret_version(),
+ client.allowed_scopes(),
+ client.default_scopes(),
client.id()
)
.execute(&mut transaction)