diff options
| author | mrw1593 <botahamec@outlook.com> | 2023-05-29 10:51:10 -0400 |
|---|---|---|
| committer | mrw1593 <botahamec@outlook.com> | 2023-05-29 10:51:10 -0400 |
| commit | e048d7d050f87e9e5bf06f01e39fd417d6868c7e (patch) | |
| tree | 8312a39234b7f78699914cba06ce2328b047625d | |
| parent | e38854c7db0fe6f006304d7f638b6aa190fc2d87 (diff) | |
Create a Client struct
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/api/users.rs | 2 | ||||
| -rw-r--r-- | src/models/client.rs | 111 | ||||
| -rw-r--r-- | src/models/mod.rs | 5 | ||||
| -rw-r--r-- | src/services/db.rs | 2 |
6 files changed, 118 insertions, 5 deletions
@@ -1628,6 +1628,7 @@ dependencies = [ "tera", "thiserror", "unic-langid", + "url", "uuid", ] @@ -2309,6 +2310,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -12,6 +12,7 @@ serde = "1" thiserror = "1" rust-argon2 = "1" uuid = { version = "1", features = [ "v4", "fast-rng", "serde" ] } +url = { version = "2", features = ["serde"] } raise = "2" exun = "0.1" rust-ini = "0.18" diff --git a/src/api/users.rs b/src/api/users.rs index 353f8ff..2b67663 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -6,7 +6,7 @@ use sqlx::MySqlPool; use thiserror::Error; use uuid::Uuid; -use crate::models::User; +use crate::models::user::User; use crate::services::crypto::PasswordHash; use crate::services::{db, id}; diff --git a/src/models/client.rs b/src/models/client.rs new file mode 100644 index 0000000..a7df936 --- /dev/null +++ b/src/models/client.rs @@ -0,0 +1,111 @@ +use std::{hash::Hash, marker::PhantomData}; + +use exun::{Expect, RawUnexpected}; +use raise::yeet; +use thiserror::Error; +use url::Url; +use uuid::Uuid; + +use crate::services::crypto::PasswordHash; + +/// There are two types of clients, based on their ability to maintain the +/// security of their client credentials. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type)] +#[sqlx(rename_all = "lowercase")] +pub enum ClientType { + /// A client that is capable of maintaining the confidentiality of their + /// credentials, or capable of secure client authentication using other + /// means. An example would be a secure server with restricted access to + /// the client credentials. + Confidential, + /// A client that is incapable of maintaining the confidentiality of their + /// credentials and cannot authenticate securely by any other means, such + /// as an installed application, or a web-browser based application. + Public, +} + +#[derive(Debug, Clone)] +pub struct Client { + ty: ClientType, + id: Uuid, + secret: Option<PasswordHash>, + redirect_uris: Box<[Url]>, +} + +impl PartialEq for Client { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Client {} + +impl Hash for Client { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + state.write_u128(self.id.as_u128()) + } +} + +#[derive(Debug, Clone, Copy, Error)] +#[error("Confidential clients must have a secret, but it was not provided")] +pub struct NoSecretError { + _phantom: PhantomData<()>, +} + +impl NoSecretError { + fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl Client { + pub fn new_public( + id: Uuid, + ty: ClientType, + secret: Option<&str>, + redirect_uris: &[Url], + ) -> Result<Self, Expect<NoSecretError>> { + let secret = if let Some(secret) = secret { + Some(PasswordHash::new(secret)?) + } else { + None + }; + + if ty == ClientType::Confidential && secret.is_none() { + yeet!(NoSecretError::new().into()); + } + + Ok(Self { + id, + ty: ClientType::Public, + secret, + redirect_uris: redirect_uris.into_iter().cloned().collect(), + }) + } + + pub fn id(&self) -> Uuid { + self.id + } + + pub fn client_type(&self) -> ClientType { + self.ty + } + + pub fn secret_hash(&self) -> Option<&[u8]> { + self.secret.as_ref().map(|s| s.hash()) + } + + pub fn secret_salt(&self) -> Option<&[u8]> { + self.secret.as_ref().map(|s| s.salt()) + } + + pub fn secret_version(&self) -> Option<u8> { + self.secret.as_ref().map(|s| s.version()) + } + + 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/models/mod.rs b/src/models/mod.rs index 4a9be81..633f846 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,3 +1,2 @@ -mod user; - -pub use user::User; +pub mod client; +pub mod user; diff --git a/src/services/db.rs b/src/services/db.rs index f4da004..79df260 100644 --- a/src/services/db.rs +++ b/src/services/db.rs @@ -2,7 +2,7 @@ use exun::*; use sqlx::{mysql::MySqlQueryResult, query, query_as, query_scalar, Executor, MySql, MySqlPool}; use uuid::Uuid; -use crate::models::User; +use crate::models::user::User; use super::crypto::PasswordHash; |
