aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 7856d5c7da1e03bf3328a095d523c46f0e8cc97b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
mod client;
mod util;

use std::sync::Arc;

use client::{sqldb::SqliteClient, Store, StoreError};
use derive_more::Display;
use email_address::EmailAddress;
use log::{error, info};
use serde::Serialize;
use time::OffsetDateTime;
use uuid::Uuid;

#[derive(Copy, Display, Clone, Debug)]
pub enum OauthProvider {
    Amazon,
    Apple,
    Dropbox,
    Facebook,
    Github,
    Gitlab,
    Google,
    Instagram,
    LinkedIn,
    Microsoft,
    Paypal,
    Reddit,
    Spotify,
    Strava,
    Stripe,
    Twitch,
    Twitter,
    WeChat,
}

#[derive(Display, Debug)]
pub enum AuthStore {
    Sqlite,
    Postgres,
    MySql,
    Mongo,
    Dynamo,
    Redis,
}

pub type OauthClientId = String;
pub type OauthClientSecretEncrypted = String;
pub type OauthConsentUri = String;

pub type IdentityId = Uuid;

//////////////////////////////////////////////////
// Resources
#[derive(sqlx::FromRow, Debug, Serialize)]
pub struct Identity {
    #[sqlx(rename = "identity_public_id")]
    id: Uuid,
    created_at: sqlx::types::time::OffsetDateTime,
    #[serde(skip_serializing_if = "Option::is_none")]
    data: Option<String>,
}

pub struct ApiKey {}

pub struct Session {}

pub struct ValidationRequest {}
//////////////////////////////////////////////////
#[derive(Debug, derive_more::Display, thiserror::Error)]
pub enum SecdError {
    InvalidEmailAddress,
    SqliteInitializationFailure(sqlx::Error),
    StoreError(#[from] StoreError),
    EmailValidationRequestError,
    Unknown,
}

pub struct Secd {
    store: Arc<dyn Store + Send + Sync + 'static>,
}

impl Secd {
    pub async fn init(
        auth_store: AuthStore,
        conn_string: Option<String>,
        // TODO: Turn Secd into a trait and impl separately.
        // TODO: initialize email and SMS templates with secd
    ) -> Result<Self, SecdError> {
        let store = match auth_store {
            AuthStore::Sqlite => SqliteClient::new(
                sqlx::sqlite::SqlitePoolOptions::new()
                    .connect(conn_string.unwrap_or("sqlite::memory:".into()).as_str())
                    .await
                    .map_err(|e| SecdError::SqliteInitializationFailure(e))?,
            ),
            // TODO: if AuthStore is provided, then configure the client.
            _ => return Err(SecdError::Unknown),
        }
        .await;

        Ok(Secd {
            store: Arc::new(store),
        })
    }

    pub async fn create_identity(&self) -> Result<Uuid, SecdError> {
        let id = Uuid::new_v4();
        self.store
            .write_identity(&Identity {
                id,
                created_at: OffsetDateTime::now_utc(),
                data: None,
            })
            .await?;

        Ok(id)
    }

    pub async fn create_validation_request(&self, email: Option<&str>) -> Result<(), SecdError> {
        // TODO: refactor based on email, phone, or some other template? Or break up the API?
        let email = match email {
            Some(ea) => {
                if EmailAddress::is_valid(ea) {
                    ea
                } else {
                    return Err(SecdError::InvalidEmailAddress);
                }
            }
            None => return Err(SecdError::InvalidEmailAddress),
        };

        match self.store.find_identity(None, Some(email)).await? {
            Some(identity) => {
                error!("TODO: implement email send with LOGIN template");
                error!("TODO: send to: {}", email);
                let req_id = self
                    .store
                    .write_email_validation_request(identity.id, email)
                    .await?;

                // TODO: provide some dummy email handlers that are used when testing locally...
                error!("TODO: when the request comes back, it needs to hit something like /iam/identity/1234/email-validation/1234?code=2345");
                error!("TODO: consequently, we may want to shorten the url by providing a quick access code and/or /iam/email-validation/1234/validate");
            }
            None => {
                let identity = Identity {
                    id: Uuid::new_v4(),
                    created_at: OffsetDateTime::now_utc(),
                    data: None,
                };
                self.store.write_identity(&identity).await?;
                self.store.write_email(identity.id, email).await?;
                error!("TODO: implement email send with SIGN_UP template");
                self.store
                    .write_email_validation_request(identity.id, email)
                    .await?;
            }
        }

        error!("TODO: think about returning the identity id for which this validation request was created");
        Ok(())
    }

    pub async fn get_identity(&self, id: IdentityId) -> Result<Identity, SecdError> {
        Ok(self.store.read_identity(&id).await?)
    }

    pub async fn create_email_validation(email: String) -> Result<(), SecdError> {
        Ok(())
    }
}