Files
avam/src/lib/domain/api/models/oauth.rs
2024-10-17 00:56:02 +02:00

378 lines
8.8 KiB
Rust

use std::{fmt::Display, str::FromStr};
use derive_more::derive::{Display, From};
#[cfg(feature = "ssr")]
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::user::User;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Client {
id: uuid::Uuid,
user_id: uuid::Uuid,
name: ClientName,
secret: ClientSecret,
redirect_uri: RedirectUri,
}
impl Client {
pub fn new(
id: uuid::Uuid,
user_id: uuid::Uuid,
name: ClientName,
secret: ClientSecret,
redirect_uri: RedirectUri,
) -> Self {
Self {
id,
user_id,
name,
secret,
redirect_uri,
}
}
pub fn id(&self) -> uuid::Uuid {
self.id
}
pub fn name(&self) -> &ClientName {
&self.name
}
pub fn redirect_uri(&self) -> &RedirectUri {
&self.redirect_uri
}
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ClientName(String);
impl ClientName {
pub fn new(name: &str) -> Self {
Self(name.to_string())
}
}
#[derive(Clone, Display, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(from = "String")]
pub struct ClientSecret(String);
impl From<String> for ClientSecret {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for ClientSecret {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
#[cfg(feature = "ssr")]
impl ClientSecret {
pub fn new() -> Self {
let token: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect();
Self(token)
}
}
#[cfg(feature = "ssr")]
impl Default for ClientSecret {
fn default() -> Self {
Self::new()
}
}
#[derive(
Clone, Debug, Display, From, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct RedirectUri(String);
impl RedirectUri {
pub fn new(uri: &str) -> Self {
Self(uri.to_string())
}
}
#[cfg(feature = "ssr")]
#[derive(Debug, Error)]
pub enum CreateAuthorizationCodeError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
// to be extended as new error scenarios are introduced
}
#[derive(Clone, Display, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(from = "String")]
pub struct AuthorizationCode(String);
impl From<String> for AuthorizationCode {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for AuthorizationCode {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
#[cfg(feature = "ssr")]
impl AuthorizationCode {
pub fn new() -> Self {
let token: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(64)
.map(char::from)
.collect();
Self(token)
}
}
#[cfg(feature = "ssr")]
impl Default for AuthorizationCode {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AuthorizedClient {
client: Client,
user: User,
}
#[derive(Debug, Error)]
pub enum CodeChallengeMethodError {
#[error("Code challenge method is not valid.")]
Invalid,
#[error(transparent)]
Unknown(#[from] anyhow::Error),
// to be extended as new error scenarios are introduced
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum CodeChallengeMethod {
#[default]
#[serde(rename = "plain")]
Plain,
#[serde(rename = "S256")]
Sha256,
}
impl Display for CodeChallengeMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
CodeChallengeMethod::Plain => "plain",
CodeChallengeMethod::Sha256 => "S256",
}
)
}
}
impl FromStr for CodeChallengeMethod {
type Err = crate::domain::api::models::oauth::CodeChallengeMethodError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"plain" => Ok(Self::Plain),
"S256" => Ok(Self::Sha256),
_ => Err(crate::domain::api::models::oauth::CodeChallengeMethodError::Invalid),
}
}
}
impl TryFrom<String> for CodeChallengeMethod {
type Error = crate::domain::api::models::oauth::CodeChallengeMethodError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::from_str(&value)
}
}
#[derive(Debug, Error)]
pub enum ResponseTypeError {
#[error("The response type is not valid.")]
Invalid,
#[error(transparent)]
Unknown(#[from] anyhow::Error),
// to be extended as new error scenarios are introduced
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum ResponseType {
#[serde(rename = "code")]
Code,
}
impl Display for ResponseType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
ResponseType::Code => "code",
}
)
}
}
impl FromStr for ResponseType {
type Err = crate::domain::api::models::oauth::ResponseTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"code" => Ok(Self::Code),
_ => Err(crate::domain::api::models::oauth::ResponseTypeError::Invalid),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthorizeRequest {
client_id: uuid::Uuid,
response_type: ResponseType, // Make type (enum:code,)
state: Option<String>, // random string for CSRF protection
code_challenge: String, // pkce
code_challenge_method: Option<CodeChallengeMethod>, // Make type (enum:sha256,) hashing algo
redirect_uri: RedirectUri, // Make type
scope: Option<String>, // space seperated string with permissions
}
impl AuthorizeRequest {
pub fn new(
client_id: uuid::Uuid,
response_type: ResponseType,
state: Option<String>,
code_challenge: String,
code_challenge_method: Option<CodeChallengeMethod>,
redirect_uri: RedirectUri,
scope: Option<String>,
) -> Self {
Self {
client_id,
response_type,
state,
code_challenge,
code_challenge_method,
redirect_uri,
scope,
}
}
pub fn client_id(&self) -> uuid::Uuid {
self.client_id
}
pub fn response_type(&self) -> ResponseType {
self.response_type.clone()
}
pub fn state(&self) -> Option<String> {
self.state.clone()
}
pub fn code_challenge(&self) -> String {
self.code_challenge.clone()
}
pub fn code_challenge_method(&self) -> Option<CodeChallengeMethod> {
self.code_challenge_method.clone()
}
pub fn redirect_uri(&self) -> RedirectUri {
self.redirect_uri.clone()
}
pub fn scope(&self) -> Option<String> {
self.scope.clone()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthorizationResponse {
code: AuthorizationCode,
state: Option<String>,
}
impl AuthorizationResponse {
pub fn new(code: AuthorizationCode, state: Option<String>) -> Self {
Self { code, state }
}
pub fn code(&self) -> AuthorizationCode {
self.code.clone()
}
pub fn state(&self) -> Option<String> {
self.state.clone()
}
}
#[derive(Debug, Error)]
pub enum TokenError {
#[error("Invalid Token Request")]
InvalidRequest,
#[error(transparent)]
Unknown(#[from] anyhow::Error),
// to be extended as new error scenarios are introduced
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenSubject {
#[serde(rename = "a")]
user_id: uuid::Uuid,
#[serde(rename = "b")]
client_id: uuid::Uuid,
#[serde(skip)]
code_challenge: String,
#[serde(skip)]
code_challenge_method: CodeChallengeMethod,
}
impl TokenSubject {
pub fn new(
user_id: uuid::Uuid,
client_id: uuid::Uuid,
code_challenge: String,
code_challenge_method: CodeChallengeMethod,
) -> Self {
Self {
user_id,
client_id,
code_challenge,
code_challenge_method,
}
}
pub fn user_id(&self) -> uuid::Uuid {
self.user_id
}
pub fn client_id(&self) -> uuid::Uuid {
self.client_id
}
pub fn code_challenge(&self) -> String {
self.code_challenge.clone()
}
pub fn code_challenge_method(&self) -> CodeChallengeMethod {
self.code_challenge_method.clone()
}
}