use std::error::Error as StdError;
use std::future::Future;
use std::pin::Pin;
use jiff::Timestamp;
use crate::types::{Access, Conflict, Method, NewUser, Role, UserChange, UserId, UserInfo};
mod memory;
#[cfg(test)]
pub(crate) mod suite;
pub use self::memory::MemoryStore;
pub type Result<T, E = StoreError> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct StoreError(Box<dyn StdError + Send + Sync>);
impl StoreError {
pub fn new(error: impl Into<Box<dyn StdError + Send + Sync>>) -> Self {
Self(error.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct User {
pub id: UserId,
pub login: Option<String>,
pub email: Option<String>,
pub role: Role,
pub access: Access,
pub method: Method,
pub password_hash: Option<String>,
pub google_sub: Option<String>,
pub created_at: Timestamp,
}
impl User {
pub fn info(&self) -> UserInfo {
UserInfo {
id: self.id,
login: self.login.clone(),
email: self.email.clone(),
role: self.role,
access: self.access,
method: self.method,
has_password: self.password_hash.is_some(),
created_at: self.created_at,
impersonated_by: None,
}
}
pub fn is_admin(&self) -> bool {
self.role == Role::Admin
}
}
impl From<&User> for UserInfo {
#[inline]
fn from(user: &User) -> Self {
user.info()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Session {
pub id: String,
pub user: UserId,
pub impersonator: Option<UserId>,
pub created_at: Timestamp,
pub expires_at: Timestamp,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub id: String,
pub user: UserId,
pub kind: TokenKind,
pub expires_at: Timestamp,
pub used_at: Option<Timestamp>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TokenKind {
Registration,
Reset,
}
impl TokenKind {
pub fn as_str(self) -> &'static str {
match self {
TokenKind::Registration => "registration",
TokenKind::Reset => "reset",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Credential {
Password(String),
Google {
sub: String,
email: String,
},
}
pub trait Store: Send + Sync + 'static {
fn user(&self, id: UserId) -> impl Future<Output = Result<Option<User>>> + Send;
fn user_by_email(&self, email: &str) -> impl Future<Output = Result<Option<User>>> + Send;
fn user_by_login(&self, login: &str) -> impl Future<Output = Result<Option<User>>> + Send;
fn user_by_google_sub(&self, sub: &str) -> impl Future<Output = Result<Option<User>>> + Send;
fn users(
&self,
now: Timestamp,
) -> impl Future<Output = Result<Vec<(User, Option<Timestamp>)>>> + Send;
fn user_count(&self) -> impl Future<Output = Result<u64>> + Send;
fn create_user(
&self,
new: &NewUser,
now: Timestamp,
) -> impl Future<Output = Result<Result<User, Conflict>>> + Send;
fn update_user(
&self,
id: UserId,
change: &UserChange,
) -> impl Future<Output = Result<Result<Option<User>, Conflict>>> + Send;
fn delete_user(&self, id: UserId) -> impl Future<Output = Result<bool>> + Send;
fn set_credential(
&self,
id: UserId,
cred: &Credential,
keep: Option<&str>,
) -> impl Future<Output = Result<bool>> + Send;
fn bind_google_sub(&self, id: UserId, sub: &str) -> impl Future<Output = Result<bool>> + Send;
fn set_access(&self, id: UserId, access: Access) -> impl Future<Output = Result<bool>> + Send;
fn activate(&self, id: UserId) -> impl Future<Output = Result<bool>> + Send;
fn revoke(&self, id: UserId, now: Timestamp) -> impl Future<Output = Result<bool>> + Send;
fn create_session(&self, session: &Session) -> impl Future<Output = Result<()>> + Send;
fn session(&self, id: &str) -> impl Future<Output = Result<Option<Session>>> + Send;
fn delete_session(&self, id: &str) -> impl Future<Output = Result<()>> + Send;
fn delete_sessions(
&self,
user: UserId,
keep: Option<&str>,
) -> impl Future<Output = Result<()>> + Send;
fn delete_expired_sessions(&self, now: Timestamp) -> impl Future<Output = Result<()>> + Send;
fn create_token(
&self,
token: &Token,
now: Timestamp,
) -> impl Future<Output = Result<()>> + Send;
fn token(&self, id: &str) -> impl Future<Output = Result<Option<Token>>> + Send;
fn use_up_tokens(
&self,
user: UserId,
now: Timestamp,
) -> impl Future<Output = Result<()>> + Send;
fn redeem_token(
&self,
token: &str,
cred: &Credential,
session: &Session,
now: Timestamp,
) -> impl Future<Output = Result<bool>> + Send;
fn config_get(&self, key: &str) -> impl Future<Output = Result<Option<String>>> + Send;
fn config_set(&self, key: &str, value: &str) -> impl Future<Output = Result<()>> + Send;
}
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
pub(crate) trait DynStore: Send + Sync + 'static {
fn user(&self, id: UserId) -> BoxFuture<'_, Option<User>>;
fn user_by_email<'a>(&'a self, email: &'a str) -> BoxFuture<'a, Option<User>>;
fn user_by_login<'a>(&'a self, login: &'a str) -> BoxFuture<'a, Option<User>>;
#[cfg_attr(not(test), allow(dead_code))]
fn user_by_google_sub<'a>(&'a self, sub: &'a str) -> BoxFuture<'a, Option<User>>;
fn users(&self, now: Timestamp) -> BoxFuture<'_, Vec<(User, Option<Timestamp>)>>;
fn user_count(&self) -> BoxFuture<'_, u64>;
fn create_user<'a>(
&'a self,
new: &'a NewUser,
now: Timestamp,
) -> BoxFuture<'a, Result<User, Conflict>>;
fn update_user<'a>(
&'a self,
id: UserId,
change: &'a UserChange,
) -> BoxFuture<'a, Result<Option<User>, Conflict>>;
fn delete_user(&self, id: UserId) -> BoxFuture<'_, bool>;
fn set_credential<'a>(
&'a self,
id: UserId,
cred: &'a Credential,
keep: Option<&'a str>,
) -> BoxFuture<'a, bool>;
#[cfg_attr(not(test), allow(dead_code))]
fn bind_google_sub<'a>(&'a self, id: UserId, sub: &'a str) -> BoxFuture<'a, bool>;
#[cfg_attr(not(feature = "cloudflare"), allow(dead_code))]
fn activate(&self, id: UserId) -> BoxFuture<'_, bool>;
fn revoke(&self, id: UserId, now: Timestamp) -> BoxFuture<'_, bool>;
fn create_session<'a>(&'a self, session: &'a Session) -> BoxFuture<'a, ()>;
fn session<'a>(&'a self, id: &'a str) -> BoxFuture<'a, Option<Session>>;
fn delete_session<'a>(&'a self, id: &'a str) -> BoxFuture<'a, ()>;
fn delete_sessions<'a>(&'a self, user: UserId, keep: Option<&'a str>) -> BoxFuture<'a, ()>;
fn delete_expired_sessions(&self, now: Timestamp) -> BoxFuture<'_, ()>;
fn create_token<'a>(&'a self, token: &'a Token, now: Timestamp) -> BoxFuture<'a, ()>;
fn token<'a>(&'a self, id: &'a str) -> BoxFuture<'a, Option<Token>>;
fn use_up_tokens(&self, user: UserId, now: Timestamp) -> BoxFuture<'_, ()>;
fn redeem_token<'a>(
&'a self,
token: &'a str,
cred: &'a Credential,
session: &'a Session,
now: Timestamp,
) -> BoxFuture<'a, bool>;
}
impl<S> DynStore for S
where
S: Store,
{
fn user(&self, id: UserId) -> BoxFuture<'_, Option<User>> {
Box::pin(Store::user(self, id))
}
fn user_by_email<'a>(&'a self, email: &'a str) -> BoxFuture<'a, Option<User>> {
Box::pin(Store::user_by_email(self, email))
}
fn user_by_login<'a>(&'a self, login: &'a str) -> BoxFuture<'a, Option<User>> {
Box::pin(Store::user_by_login(self, login))
}
fn user_by_google_sub<'a>(&'a self, sub: &'a str) -> BoxFuture<'a, Option<User>> {
Box::pin(Store::user_by_google_sub(self, sub))
}
fn users(&self, now: Timestamp) -> BoxFuture<'_, Vec<(User, Option<Timestamp>)>> {
Box::pin(Store::users(self, now))
}
fn user_count(&self) -> BoxFuture<'_, u64> {
Box::pin(Store::user_count(self))
}
fn create_user<'a>(
&'a self,
new: &'a NewUser,
now: Timestamp,
) -> BoxFuture<'a, Result<User, Conflict>> {
Box::pin(Store::create_user(self, new, now))
}
fn update_user<'a>(
&'a self,
id: UserId,
change: &'a UserChange,
) -> BoxFuture<'a, Result<Option<User>, Conflict>> {
Box::pin(Store::update_user(self, id, change))
}
fn delete_user(&self, id: UserId) -> BoxFuture<'_, bool> {
Box::pin(Store::delete_user(self, id))
}
fn set_credential<'a>(
&'a self,
id: UserId,
cred: &'a Credential,
keep: Option<&'a str>,
) -> BoxFuture<'a, bool> {
Box::pin(Store::set_credential(self, id, cred, keep))
}
fn bind_google_sub<'a>(&'a self, id: UserId, sub: &'a str) -> BoxFuture<'a, bool> {
Box::pin(Store::bind_google_sub(self, id, sub))
}
fn activate(&self, id: UserId) -> BoxFuture<'_, bool> {
Box::pin(Store::activate(self, id))
}
fn revoke(&self, id: UserId, now: Timestamp) -> BoxFuture<'_, bool> {
Box::pin(Store::revoke(self, id, now))
}
fn create_session<'a>(&'a self, session: &'a Session) -> BoxFuture<'a, ()> {
Box::pin(Store::create_session(self, session))
}
fn session<'a>(&'a self, id: &'a str) -> BoxFuture<'a, Option<Session>> {
Box::pin(Store::session(self, id))
}
fn delete_session<'a>(&'a self, id: &'a str) -> BoxFuture<'a, ()> {
Box::pin(Store::delete_session(self, id))
}
fn delete_sessions<'a>(&'a self, user: UserId, keep: Option<&'a str>) -> BoxFuture<'a, ()> {
Box::pin(Store::delete_sessions(self, user, keep))
}
fn delete_expired_sessions(&self, now: Timestamp) -> BoxFuture<'_, ()> {
Box::pin(Store::delete_expired_sessions(self, now))
}
fn create_token<'a>(&'a self, token: &'a Token, now: Timestamp) -> BoxFuture<'a, ()> {
Box::pin(Store::create_token(self, token, now))
}
fn token<'a>(&'a self, id: &'a str) -> BoxFuture<'a, Option<Token>> {
Box::pin(Store::token(self, id))
}
fn use_up_tokens(&self, user: UserId, now: Timestamp) -> BoxFuture<'_, ()> {
Box::pin(Store::use_up_tokens(self, user, now))
}
fn redeem_token<'a>(
&'a self,
token: &'a str,
cred: &'a Credential,
session: &'a Session,
now: Timestamp,
) -> BoxFuture<'a, bool> {
Box::pin(Store::redeem_token(self, token, cred, session, now))
}
}