use crate::auth::EtwinOauthAccessTokenKey;
use crate::core::Instant;
use crate::password::{Password, PasswordHash};
use crate::twinoid::TwinoidUserId;
use crate::types::AnyError;
use crate::user::{ShortUser, UserId, UserIdRef};
use async_trait::async_trait;
use auto_impl::auto_impl;
#[cfg(feature = "serde")]
use etwin_serde_tools::{Deserialize, Serialize};
use std::str::FromStr;
use thiserror::Error;
use url::Url;
declare_new_uuid! {
pub struct OauthClientId(Uuid);
pub type ParseError = OauthClientIdParseError;
const SQL_NAME = "oauth_client_id";
}
declare_new_string! {
pub struct OauthClientKey(String);
pub type ParseError = OauthClientKeyParseError;
const PATTERN = r"^[a-z_][a-z0-9_]{1,31}@clients$";
const SQL_NAME = "oauth_client_key";
}
declare_new_string! {
pub struct RfcOauthAccessTokenKey(String);
pub type ParseError = RfcOauthAccessTokenKeyParseError;
const PATTERN = r"^.+$";
const SQL_NAME = "rfc_oauth_access_token_key";
}
declare_new_string! {
pub struct RfcOauthRefreshTokenKey(String);
pub type ParseError = RfcOauthRefreshTokenKeyParseError;
const PATTERN = r"^.+$";
const SQL_NAME = "rfc_oauth_refresh_token_key";
}
declare_new_enum!(
pub enum RfcOauthResponseType {
#[str("code")]
Code,
#[str("token")]
Token,
}
pub type ParseError = RfcOauthResponseTypeParseError;
);
declare_new_enum!(
pub enum RfcOauthGrantType {
#[str("authorization_code")]
AuthorizationCode,
}
pub type ParseError = RfcOauthGrantTypeParseError;
);
declare_new_enum!(
pub enum RfcOauthTokenType {
#[str("Bearer")]
Bearer,
}
pub type ParseError = RfcOauthTokenTypeParseError;
);
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TwinoidAccessToken {
pub key: RfcOauthAccessTokenKey,
#[cfg_attr(feature = "serde", serde(rename = "ctime"))]
pub created_at: Instant,
#[cfg_attr(feature = "serde", serde(rename = "atime"))]
pub accessed_at: Instant,
#[cfg_attr(feature = "serde", serde(rename = "expiration_time"))]
pub expires_at: Instant,
pub twinoid_user_id: TwinoidUserId,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TwinoidRefreshToken {
pub key: RfcOauthRefreshTokenKey,
#[cfg_attr(feature = "serde", serde(rename = "ctime"))]
pub created_at: Instant,
#[cfg_attr(feature = "serde", serde(rename = "atime"))]
pub accessed_at: Instant,
pub twinoid_user_id: TwinoidUserId,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StoredOauthAccessToken {
pub key: EtwinOauthAccessTokenKey,
#[cfg_attr(feature = "serde", serde(rename = "ctime"))]
pub created_at: Instant,
#[cfg_attr(feature = "serde", serde(rename = "atime"))]
pub accessed_at: Instant,
#[cfg_attr(feature = "serde", serde(rename = "expiration_time"))]
pub expires_at: Instant,
pub user: UserIdRef,
pub client: OauthClientIdRef,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OauthAccessToken {
pub token_type: RfcOauthTokenType,
pub access_token: EtwinOauthAccessTokenKey,
pub expires_in: i64,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub refresh_token: Option<RfcOauthRefreshTokenKey>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RfcOauthAccessToken {
pub token_type: RfcOauthTokenType,
pub access_token: RfcOauthAccessTokenKey,
pub expires_in: i64,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub refresh_token: Option<RfcOauthRefreshTokenKey>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "OauthClient"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShortOauthClient {
pub id: OauthClientId,
pub key: Option<OauthClientKey>,
pub display_name: OauthClientDisplayName,
}
impl From<SimpleOauthClient> for ShortOauthClient {
fn from(client: SimpleOauthClient) -> Self {
Self {
id: client.id,
key: client.key,
display_name: client.display_name,
}
}
}
declare_new_string! {
pub struct OauthClientDisplayName(String);
pub type ParseError = OauthClientDisplayNameParseError;
const PATTERN = r"^[A-Za-z_ ()-]{2,32}$";
const SQL_NAME = "oauth_client_display_name";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "OauthClient"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OauthClient {
pub id: OauthClientId,
pub key: Option<OauthClientKey>,
pub display_name: OauthClientDisplayName,
pub app_uri: Url,
pub callback_uri: Url,
pub owner: Option<ShortUser>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "OauthClient"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SimpleOauthClient {
pub id: OauthClientId,
pub key: Option<OauthClientKey>,
pub display_name: OauthClientDisplayName,
pub app_uri: Url,
pub callback_uri: Url,
pub owner: Option<UserIdRef>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "OauthClient"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SimpleOauthClientWithSecret {
pub id: OauthClientId,
pub key: Option<OauthClientKey>,
pub display_name: OauthClientDisplayName,
pub app_uri: Url,
pub callback_uri: Url,
pub owner: Option<UserIdRef>,
pub secret: PasswordHash,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UpsertSystemClientOptions {
pub key: OauthClientKey,
pub display_name: OauthClientDisplayName,
pub app_uri: Url,
pub callback_uri: Url,
pub secret: Password,
}
#[derive(Error, Debug)]
pub enum UpsertSystemClientError {
#[error(transparent)]
Other(AnyError),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OauthClientIdRef {
pub id: OauthClientId,
}
impl From<OauthClientId> for OauthClientIdRef {
fn from(id: OauthClientId) -> Self {
Self { id }
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OauthClientKeyRef {
pub key: OauthClientKey,
}
impl From<OauthClientKey> for OauthClientKeyRef {
fn from(key: OauthClientKey) -> Self {
Self { key }
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OauthClientRef {
Id(OauthClientIdRef),
Key(OauthClientKeyRef),
}
impl FromStr for OauthClientRef {
type Err = ();
fn from_str(input: &str) -> Result<Self, Self::Err> {
if let Ok(client_key) = OauthClientKey::from_str(input) {
Ok(OauthClientRef::Key(client_key.into()))
} else if let Ok(id) = OauthClientId::from_str(input) {
Ok(OauthClientRef::Id(id.into()))
} else {
Err(())
}
}
}
declare_new_string! {
pub struct EtwinOauthScopesString(String);
pub type ParseError = EtwinOauthScopesStringParseError;
const PATTERN = r"^.{0,100}$";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EtwinOauthScopes {
pub base: bool,
}
impl EtwinOauthScopes {
pub fn strings(&self) -> Vec<String> {
if self.base {
vec!["base".to_string()]
} else {
Vec::new()
}
}
}
impl Default for EtwinOauthScopes {
fn default() -> Self {
Self { base: true }
}
}
impl FromStr for EtwinOauthScopes {
type Err = ();
fn from_str(input: &str) -> Result<Self, Self::Err> {
let scopes = input.split(' ').map(str::trim).filter(|s| !s.is_empty());
let parsed = EtwinOauthScopes::default();
for scope in scopes {
match scope {
"base" => debug_assert!(parsed.base),
_ => return Err(()), }
}
Ok(parsed)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetOauthClientOptions {
pub r#ref: OauthClientRef,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VerifyClientSecretOptions {
pub r#ref: OauthClientRef,
pub secret: Password,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CreateStoredAccessTokenOptions {
pub key: EtwinOauthAccessTokenKey,
pub ctime: Instant,
pub expiration_time: Instant,
pub user: UserIdRef,
pub client: OauthClientIdRef,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetOauthAccessTokenOptions {
pub key: EtwinOauthAccessTokenKey,
pub touch_accessed_at: bool,
}
#[derive(Error, Debug)]
pub enum GetOauthClientError {
#[error("oauth client not found: {0:?}")]
NotFound(OauthClientRef),
#[error(transparent)]
Other(AnyError),
}
#[async_trait]
#[auto_impl(&, Arc)]
pub trait OauthProviderStore: Send + Sync {
async fn upsert_system_client(&self, options: &UpsertSystemClientOptions) -> Result<SimpleOauthClient, AnyError>;
async fn get_client(&self, options: &GetOauthClientOptions) -> Result<SimpleOauthClient, GetOauthClientError>;
async fn get_client_with_secret(
&self,
options: &GetOauthClientOptions,
) -> Result<SimpleOauthClientWithSecret, AnyError>;
async fn create_access_token(
&self,
options: &CreateStoredAccessTokenOptions,
) -> Result<StoredOauthAccessToken, AnyError>;
async fn get_access_token(&self, options: &GetOauthAccessTokenOptions) -> Result<StoredOauthAccessToken, AnyError>;
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EtwinOauthStateClaims {
#[cfg_attr(feature = "serde", serde(rename = "rfp"))]
pub request_forgery_protection: String,
#[cfg_attr(feature = "serde", serde(rename = "a"))]
pub action: EtwinOauthStateAction,
#[cfg_attr(feature = "serde", serde(rename = "iat", with = "serde_posix_timestamp"))]
pub issued_at: Instant,
#[cfg_attr(feature = "serde", serde(rename = "as"))]
pub authorization_server: String,
#[cfg_attr(feature = "serde", serde(rename = "exp", with = "serde_posix_timestamp"))]
pub expiration_time: Instant,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum EtwinOauthStateAction {
#[cfg_attr(feature = "serde", serde(rename = "Login"))]
Login,
#[cfg_attr(feature = "serde", serde(rename = "Link"))]
Link { user_id: UserId },
}
#[cfg(feature = "serde")]
pub mod serde_posix_timestamp {
use crate::core::Instant;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(value: &Instant, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
value.into_posix_timestamp().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
where
D: Deserializer<'de>,
{
i64::deserialize(deserializer).map(Instant::from_posix_timestamp)
}
}