378 lines
8.8 KiB
Rust
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()
|
|
}
|
|
}
|