aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/client/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/secd/src/client/mod.rs422
1 files changed, 1 insertions, 421 deletions
diff --git a/crates/secd/src/client/mod.rs b/crates/secd/src/client/mod.rs
index 38426ef..e5272fd 100644
--- a/crates/secd/src/client/mod.rs
+++ b/crates/secd/src/client/mod.rs
@@ -1,422 +1,2 @@
pub(crate) mod email;
-pub(crate) mod sqldb;
-pub(crate) mod types;
-
-use std::{collections::HashMap, str::FromStr};
-
-use super::Identity;
-use crate::{
- EmailValidation, OauthProvider, OauthProviderName, OauthResponseType, OauthValidation, Session,
- SessionSecret, ValidationRequestId, ValidationType,
-};
-
-use email_address::EmailAddress;
-use lazy_static::lazy_static;
-use sqlx::{
- database::HasValueRef, sqlite::SqliteRow, ColumnIndex, Database, Decode, FromRow, Row, Sqlite,
- Type,
-};
-use thiserror::Error;
-use time::OffsetDateTime;
-use url::Url;
-use uuid::Uuid;
-
-pub enum EmailType {
- Login,
- Signup,
-}
-
-#[derive(Error, Debug, derive_more::Display)]
-pub enum EmailMessengerError {
- InvalidEmailAddress,
- FailedToSendEmail,
- Unknown,
-}
-
-#[async_trait::async_trait]
-pub trait EmailMessenger {
- async fn send_email(
- &self,
- email_address: &str,
- validation_id: &str,
- secret_code: &str,
- t: EmailType,
- ) -> Result<(), EmailMessengerError>;
-}
-
-#[derive(Error, Debug, derive_more::Display)]
-pub enum StoreError {
- SqlxError(#[from] sqlx::Error),
- CodeAppearsMoreThanOnce,
- CodeDoesNotExist(String),
- IdentityIdMustExistInvariant,
- TooManyValidations,
- TooManyIdentitiesFound,
- NoEmailValidationFound,
- OauthProviderDoesNotExist(OauthProviderName),
- OauthValidationDoesNotExist(ValidationRequestId),
- Other(String),
-}
-
-const EMAIL_TEMPLATE_DEFAULT_LOGIN: &str = "You requested a login link. Please click the following link %secd_code% to login as %secd_email_address%";
-const EMAIL_TEMPLATE_DEFAULT_SIGNUP: &str = "You requested a sign up. Please click the following link %secd_code% to complete your sign up and validate %secd_email_address%";
-
-const ERR_MSG_MIGRATION_FAILED: &str = "Failed to execute migrations. This appears to be a secd issue. File a bug at https://www.github.com/secd-lib";
-
-const SQLITE: &str = "sqlite";
-const PGSQL: &str = "pgsql";
-
-const WRITE_IDENTITY: &str = "write_identity";
-const WRITE_EMAIL_VALIDATION: &str = "write_email_validation";
-const FIND_EMAIL_VALIDATION: &str = "find_email_validation";
-const READ_VALIDATION_TYPE: &str = "read_validation_type";
-
-const WRITE_EMAIL: &str = "write_email";
-
-const READ_IDENTITY: &str = "read_identity";
-const FIND_IDENTITY: &str = "find_identity";
-const FIND_IDENTITY_BY_CODE: &str = "find_identity_by_code";
-
-const READ_IDENTITY_RAW_ID: &str = "read_identity_raw_id";
-const READ_EMAIL_RAW_ID: &str = "read_email_raw_id";
-
-const WRITE_SESSION: &str = "write_session";
-const READ_SESSION: &str = "read_session";
-
-const WRITE_OAUTH_PROVIDER: &str = "write_oauth_provider";
-const READ_OAUTH_PROVIDER: &str = "read_oauth_provider";
-const WRITE_OAUTH_VALIDATION: &str = "write_oauth_validation";
-const READ_OAUTH_VALIDATION: &str = "read_oauth_validation";
-
-lazy_static! {
- static ref SQLS: HashMap<&'static str, HashMap<&'static str, &'static str>> = {
- let sqlite_sqls: HashMap<&'static str, &'static str> = [
- (
- WRITE_IDENTITY,
- include_str!("../../store/sqlite/sql/write_identity.sql"),
- ),
- (
- WRITE_EMAIL_VALIDATION,
- include_str!("../../store/sqlite/sql/write_email_validation.sql"),
- ),
- (
- WRITE_EMAIL,
- include_str!("../../store/sqlite/sql/write_email.sql"),
- ),
- (
- READ_IDENTITY,
- include_str!("../../store/sqlite/sql/read_identity.sql"),
- ),
- (
- FIND_IDENTITY,
- include_str!("../../store/sqlite/sql/find_identity.sql"),
- ),
- (
- FIND_IDENTITY_BY_CODE,
- include_str!("../../store/sqlite/sql/find_identity_by_code.sql"),
- ),
- (
- READ_IDENTITY_RAW_ID,
- include_str!("../../store/sqlite/sql/read_identity_raw_id.sql"),
- ),
- (
- READ_EMAIL_RAW_ID,
- include_str!("../../store/sqlite/sql/read_email_raw_id.sql"),
- ),
- (
- WRITE_SESSION,
- include_str!("../../store/sqlite/sql/write_session.sql"),
- ),
- (
- READ_SESSION,
- include_str!("../../store/sqlite/sql/read_session.sql"),
- ),
- (
- FIND_EMAIL_VALIDATION,
- include_str!("../../store/sqlite/sql/find_email_validation.sql"),
- ),
- (
- WRITE_OAUTH_PROVIDER,
- include_str!("../../store/sqlite/sql/write_oauth_provider.sql"),
- ),
- (
- READ_OAUTH_PROVIDER,
- include_str!("../../store/sqlite/sql/read_oauth_provider.sql"),
- ),
- (
- READ_OAUTH_VALIDATION,
- include_str!("../../store/sqlite/sql/read_oauth_validation.sql"),
- ),
- (
- WRITE_OAUTH_VALIDATION,
- include_str!("../../store/sqlite/sql/write_oauth_validation.sql"),
- ),
- (
- READ_VALIDATION_TYPE,
- include_str!("../../store/sqlite/sql/read_validation_type.sql"),
- ),
- ]
- .iter()
- .cloned()
- .collect();
-
- let pg_sqls: HashMap<&'static str, &'static str> = [
- (
- WRITE_IDENTITY,
- include_str!("../../store/pg/sql/write_identity.sql"),
- ),
- (
- WRITE_EMAIL_VALIDATION,
- include_str!("../../store/pg/sql/write_email_validation.sql"),
- ),
- (
- WRITE_EMAIL,
- include_str!("../../store/pg/sql/write_email.sql"),
- ),
- (
- READ_IDENTITY,
- include_str!("../../store/pg/sql/read_identity.sql"),
- ),
- (
- FIND_IDENTITY,
- include_str!("../../store/pg/sql/find_identity.sql"),
- ),
- (
- FIND_IDENTITY_BY_CODE,
- include_str!("../../store/pg/sql/find_identity_by_code.sql"),
- ),
- (
- READ_IDENTITY_RAW_ID,
- include_str!("../../store/pg/sql/read_identity_raw_id.sql"),
- ),
- (
- READ_EMAIL_RAW_ID,
- include_str!("../../store/pg/sql/read_email_raw_id.sql"),
- ),
- (
- WRITE_SESSION,
- include_str!("../../store/pg/sql/write_session.sql"),
- ),
- (
- READ_SESSION,
- include_str!("../../store/pg/sql/read_session.sql"),
- ),
- (
- FIND_EMAIL_VALIDATION,
- include_str!("../../store/pg/sql/find_email_validation.sql"),
- ),
- (
- WRITE_OAUTH_PROVIDER,
- include_str!("../../store/pg/sql/write_oauth_provider.sql"),
- ),
- (
- READ_OAUTH_PROVIDER,
- include_str!("../../store/pg/sql/read_oauth_provider.sql"),
- ),
- (
- READ_OAUTH_VALIDATION,
- include_str!("../../store/pg/sql/read_oauth_validation.sql"),
- ),
- (
- WRITE_OAUTH_VALIDATION,
- include_str!("../../store/pg/sql/write_oauth_validation.sql"),
- ),
- (
- READ_VALIDATION_TYPE,
- include_str!("../../store/pg/sql/read_validation_type.sql"),
- ),
- ]
- .iter()
- .cloned()
- .collect();
-
- let sqls: HashMap<&'static str, HashMap<&'static str, &'static str>> =
- [(SQLITE, sqlite_sqls), (PGSQL, pg_sqls)]
- .iter()
- .cloned()
- .collect();
- sqls
- };
-}
-
-impl<'a, R: Row> FromRow<'a, R> for OauthValidation
-where
- &'a str: ColumnIndex<R>,
- OauthProviderName: Decode<'a, R::Database> + Type<R::Database>,
- OauthResponseType: Decode<'a, R::Database> + Type<R::Database>,
- OffsetDateTime: Decode<'a, R::Database> + Type<R::Database>,
- String: Decode<'a, R::Database> + Type<R::Database>,
- Uuid: Decode<'a, R::Database> + Type<R::Database>,
-{
- fn from_row(row: &'a R) -> Result<Self, sqlx::Error> {
- let id: Option<Uuid> = row.try_get("oauth_validation_public_id")?;
- let identity_id: Option<Uuid> = row.try_get("identity_public_id")?;
- let access_token: Option<String> = row.try_get("access_token")?;
- let raw_response: Option<String> = row.try_get("raw_response")?;
- let created_at: Option<OffsetDateTime> = row.try_get("created_at")?;
- let validated_at: Option<OffsetDateTime> = row.try_get("validated_at")?;
- let revoked_at: Option<OffsetDateTime> = row.try_get("revoked_at")?;
- let deleted_at: Option<OffsetDateTime> = row.try_get("deleted_at")?;
-
- let op_name: Option<OauthProviderName> = row.try_get("oauth_provider_name")?;
- let op_flow: Option<String> = row.try_get("oauth_provider_flow")?;
- let op_base_url: Option<String> = row.try_get("oauth_provider_base_url")?;
- let op_response_type: Option<OauthResponseType> =
- row.try_get("oauth_provider_response_type")?;
- let op_default_scope: Option<String> = row.try_get("oauth_provider_default_scope")?;
- let op_client_id: Option<String> = row.try_get("oauth_provider_client_id")?;
- let op_client_secret: Option<String> = row.try_get("oauth_provider_client_secret")?;
- let op_redirect_url: Option<String> = row.try_get("oauth_provider_redirect_url")?;
- let op_created_at: Option<OffsetDateTime> = row.try_get("oauth_provider_created_at")?;
- let op_deleted_at: Option<OffsetDateTime> = row.try_get("oauth_provider_deleted_at")?;
-
- let op_base_url = op_base_url
- .map(|s| Url::from_str(&s).ok())
- .flatten()
- .ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_base_url".into(),
- source: "secd".into(),
- })?;
-
- let op_redirect_url = op_redirect_url
- .map(|s| Url::from_str(&s).ok())
- .flatten()
- .ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_redirect_url".into(),
- source: "secd".into(),
- })?;
-
- Ok(OauthValidation {
- id,
- identity_id,
- access_token,
- raw_response,
- created_at: created_at.ok_or(sqlx::Error::ColumnDecode {
- index: "created_at".into(),
- source: "secd".into(),
- })?,
- validated_at,
- revoked_at,
- deleted_at,
- oauth_provider: OauthProvider {
- name: op_name.unwrap(),
- flow: op_flow,
- base_url: op_base_url,
- response: op_response_type.ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_response_type".into(),
- source: "secd".into(),
- })?,
- default_scope: op_default_scope.ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_default_scope".into(),
- source: "secd".into(),
- })?,
- client_id: op_client_id.ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_client_id".into(),
- source: "secd".into(),
- })?,
- client_secret: op_client_secret.ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_client_secret".into(),
- source: "secd".into(),
- })?,
- redirect_url: op_redirect_url,
- created_at: op_created_at.ok_or(sqlx::Error::ColumnDecode {
- index: "oauth_provider_created_at".into(),
- source: "secd".into(),
- })?,
- deleted_at: op_deleted_at,
- },
- })
- }
-}
-
-impl<'a, D: Database> Decode<'a, D> for OauthProviderName
-where
- &'a str: Decode<'a, D>,
-{
- fn decode(
- value: <D as HasValueRef<'a>>::ValueRef,
- ) -> Result<Self, Box<dyn ::std::error::Error + 'static + Send + Sync>> {
- let v = <&str as Decode<D>>::decode(value)?;
- <OauthProviderName as clap::ValueEnum>::from_str(v, true)
- .map_err(|_| "OauthProviderName should exist and decode to a program value.".into())
- }
-}
-
-impl<D: Database> Type<D> for OauthProviderName
-where
- str: Type<D>,
-{
- fn type_info() -> D::TypeInfo {
- <&str as Type<D>>::type_info()
- }
-}
-
-impl<'a, D: Database> Decode<'a, D> for OauthResponseType
-where
- &'a str: Decode<'a, D>,
-{
- fn decode(
- value: <D as HasValueRef<'a>>::ValueRef,
- ) -> Result<Self, Box<dyn ::std::error::Error + 'static + Send + Sync>> {
- let v = <&str as Decode<D>>::decode(value)?;
- <OauthResponseType as clap::ValueEnum>::from_str(v, true)
- .map_err(|_| "OauthResponseType should exist and decode to a program value.".into())
- }
-}
-
-impl<D: Database> Type<D> for OauthResponseType
-where
- str: Type<D>,
-{
- fn type_info() -> D::TypeInfo {
- <&str as Type<D>>::type_info()
- }
-}
-
-#[async_trait::async_trait]
-pub trait Store {
- async fn write_email(&self, email_address: &str) -> Result<(), StoreError>;
-
- async fn find_email_validation(
- &self,
- validation_id: Option<&Uuid>,
- code: Option<&str>,
- ) -> Result<EmailValidation, StoreError>;
- async fn write_email_validation(
- &self,
- ev: &EmailValidation,
- // TODO: Make this write an EmailValidation
- ) -> anyhow::Result<Uuid>;
-
- async fn find_identity(
- &self,
- identity_id: Option<&Uuid>,
- email: Option<&str>,
- ) -> anyhow::Result<Option<Identity>>;
- async fn find_identity_by_code(&self, code: &str) -> Result<Identity, StoreError>;
- async fn write_identity(&self, i: &Identity) -> Result<(), StoreError>;
- async fn read_identity(&self, identity_id: &Uuid) -> Result<Identity, StoreError>;
-
- async fn write_session(&self, session: &Session) -> Result<(), StoreError>;
- async fn read_session(&self, secret: &SessionSecret) -> Result<Session, StoreError>;
-
- async fn write_oauth_provider(&self, provider: &OauthProvider) -> Result<(), StoreError>;
- async fn read_oauth_provider(
- &self,
- provider: &OauthProviderName,
- flow: Option<String>,
- ) -> Result<OauthProvider, StoreError>;
- async fn write_oauth_validation(
- &self,
- validation: &OauthValidation,
- ) -> anyhow::Result<ValidationRequestId>;
- async fn read_oauth_validation(
- &self,
- validation_id: &ValidationRequestId,
- ) -> anyhow::Result<OauthValidation>;
-
- async fn find_validation_type(
- &self,
- validation_id: &ValidationRequestId,
- ) -> anyhow::Result<ValidationType>;
-}
+pub(crate) mod store;