rustical_store/auth/
mod.rs

1pub mod middleware;
2mod principal;
3use crate::error::Error;
4use async_trait::async_trait;
5
6mod principal_type;
7pub use principal_type::*;
8
9mod error;
10pub use error::UnauthorizedError;
11
12pub use principal::{AppToken, Principal};
13
14/// The `AuthenticationProvider` is the principal store for rustical.
15#[async_trait]
16pub trait AuthenticationProvider: Send + Sync + 'static {
17    /// Returns a list of all principals
18    async fn get_principals(&self) -> Result<Vec<Principal>, crate::Error>;
19
20    /// Returns a principal by its `id` as an `Option<Principal>`.
21    /// If the principal does not exist `Ok(None)` is returned.
22    async fn get_principal(&self, id: &str) -> Result<Option<Principal>, crate::Error>;
23
24    async fn remove_principal(&self, id: &str) -> Result<(), crate::Error>;
25
26    /// Inserts a principal and upserts it if `overwrite=true`.
27    /// Ignores `Principal.membership` field which is instead managed
28    /// via the `*_membership´ methods.
29    async fn insert_principal(&self, user: Principal, overwrite: bool) -> Result<(), crate::Error>;
30
31    /// Validates a password input for a principal.
32    /// If the password input is correct returns `Ok(Some(principal))`.
33    async fn validate_password(
34        &self,
35        user_id: &str,
36        password_input: &str,
37    ) -> Result<Option<Principal>, Error> {
38        let user: Principal = match self.get_principal(user_id).await? {
39            Some(user) => user,
40            None => return Ok(None),
41        };
42        let Some(password) = &user.password else {
43            return Ok(None);
44        };
45
46        if password_auth::verify_password(password_input, password.as_ref()).is_ok() {
47            return Ok(Some(user));
48        }
49        Ok(None)
50    }
51
52    /// Validates an app token for a given principal id.
53    async fn validate_app_token(
54        &self,
55        user_id: &str,
56        token: &str,
57    ) -> Result<Option<Principal>, Error> {
58        // Allow to specify the token id to use to make validation faster
59        // Doesn't match the whole length of the token id to keep the length in bounds
60        // Example: asd_selgkh
61        // where the app token id starts with asd and its value is selgkh
62        let (token_id_prefix, token) = token.split_once('_').unwrap_or(("", token));
63
64        for app_token in &self.get_app_tokens(user_id).await? {
65            // Wrong token id
66            if !app_token.id.starts_with(token_id_prefix) {
67                continue;
68            }
69            if password_auth::verify_password(token, app_token.token.as_ref()).is_ok() {
70                return self.get_principal(user_id).await;
71            }
72        }
73        Ok(None)
74    }
75
76    /// Returns a token identifier
77    async fn add_app_token(
78        &self,
79        user_id: &str,
80        name: String,
81        token: String,
82    ) -> Result<String, Error>;
83    async fn remove_app_token(&self, user_id: &str, token_id: &str) -> Result<(), Error>;
84
85    async fn get_app_tokens(&self, principal: &str) -> Result<Vec<AppToken>, Error>;
86
87    async fn add_membership(&self, principal: &str, member_of: &str) -> Result<(), Error>;
88
89    async fn remove_membership(&self, principal: &str, member_of: &str) -> Result<(), Error>;
90
91    async fn list_members(&self, principal: &str) -> Result<Vec<String>, Error>;
92}
93
94pub use middleware::AuthenticationMiddleware;