Skip to main content

sylvia_iot_auth/models/mongodb/
authorization_code.rs

1use std::{error::Error as StdError, sync::Arc};
2
3use async_trait::async_trait;
4use chrono::TimeDelta;
5use futures::TryStreamExt;
6use mongodb::{
7    Database,
8    bson::{DateTime, Document, doc},
9};
10use serde::{Deserialize, Serialize};
11
12use sylvia_iot_corelib::err::E_UNKNOWN;
13
14use super::super::authorization_code::{
15    AuthorizationCode, AuthorizationCodeModel, EXPIRES, QueryCond,
16};
17
18/// Model instance.
19pub struct Model {
20    /// The associated database connection.
21    conn: Arc<Database>,
22}
23
24/// MongoDB schema.
25#[derive(Deserialize, Serialize)]
26struct Schema {
27    code: String,
28    #[serde(rename = "expiresAt")]
29    expires_at: DateTime,
30    #[serde(rename = "redirectUri")]
31    redirect_uri: String,
32    scope: Option<String>,
33    #[serde(rename = "clientId")]
34    client_id: String,
35    #[serde(rename = "userId")]
36    user_id: String,
37    #[serde(rename = "createdAt")]
38    created_at: DateTime,
39}
40
41const COL_NAME: &'static str = "authorizationCode";
42
43impl Model {
44    /// To create the model instance with a database connection.
45    pub async fn new(conn: Arc<Database>) -> Result<Self, Box<dyn StdError>> {
46        let model = Model { conn };
47        model.init().await?;
48        Ok(model)
49    }
50}
51
52#[async_trait]
53impl AuthorizationCodeModel for Model {
54    async fn init(&self) -> Result<(), Box<dyn StdError>> {
55        let indexes = vec![
56            doc! {"name": "code_1", "key": {"code": 1}, "unique": true},
57            doc! {"name": "clientId_1", "key": {"clientId": 1}},
58            doc! {"name": "userId_1", "key": {"userId": 1}},
59            doc! {"name": "ttl_1", "key": {"createdAt": 1}, "expireAfterSeconds": EXPIRES + 60},
60        ];
61        let command = doc! {
62            "createIndexes": COL_NAME,
63            "indexes": indexes,
64        };
65        self.conn.run_command(command).await?;
66        Ok(())
67    }
68
69    async fn get(&self, code: &str) -> Result<Option<AuthorizationCode>, Box<dyn StdError>> {
70        let mut cursor = self
71            .conn
72            .collection::<Schema>(COL_NAME)
73            .find(doc! {"code": code})
74            .await?;
75        if let Some(item) = cursor.try_next().await? {
76            return Ok(Some(AuthorizationCode {
77                code: item.code,
78                expires_at: item.expires_at.into(),
79                redirect_uri: item.redirect_uri,
80                scope: item.scope,
81                client_id: item.client_id,
82                user_id: item.user_id,
83            }));
84        }
85        Ok(None)
86    }
87
88    async fn add(&self, code: &AuthorizationCode) -> Result<(), Box<dyn StdError>> {
89        let item = Schema {
90            code: code.code.clone(),
91            expires_at: code.expires_at.into(),
92            redirect_uri: code.redirect_uri.clone(),
93            scope: code.scope.clone(),
94            client_id: code.client_id.clone(),
95            user_id: code.user_id.clone(),
96            created_at: match TimeDelta::try_seconds(EXPIRES) {
97                None => panic!("{}", E_UNKNOWN),
98                Some(t) => (code.expires_at - t).into(),
99            },
100        };
101        self.conn
102            .collection::<Schema>(COL_NAME)
103            .insert_one(item)
104            .await?;
105        Ok(())
106    }
107
108    async fn del(&self, cond: &QueryCond) -> Result<(), Box<dyn StdError>> {
109        let filter = get_query_filter(cond);
110        self.conn
111            .collection::<Schema>(COL_NAME)
112            .delete_many(filter)
113            .await?;
114        Ok(())
115    }
116}
117
118/// Transforms query conditions to the MongoDB document.
119fn get_query_filter(cond: &QueryCond) -> Document {
120    let mut filter = Document::new();
121    if let Some(value) = cond.code {
122        filter.insert("code", value);
123    }
124    if let Some(value) = cond.client_id {
125        filter.insert("clientId", value);
126    }
127    if let Some(value) = cond.user_id {
128        filter.insert("userId", value);
129    }
130    filter
131}