reifydb_auth/service/
mod.rs1mod authenticate;
5mod solana;
6mod token;
7
8use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration};
9
10use reifydb_catalog::{catalog::Catalog, create_token};
11use reifydb_core::interface::catalog::token::Token;
12use reifydb_runtime::context::{clock::Clock, rng::Rng as SystemRng};
13use reifydb_transaction::transaction::{admin::AdminTransaction, query::QueryTransaction};
14use reifydb_type::{
15 error::Error,
16 value::{datetime::DateTime, identity::IdentityId},
17};
18
19use crate::{challenge::ChallengeStore, registry::AuthenticationRegistry};
20
21pub trait AuthEngine: Send + Sync {
29 fn begin_admin(&self) -> Result<AdminTransaction, Error>;
30 fn begin_query(&self) -> Result<QueryTransaction, Error>;
31 fn catalog(&self) -> Catalog;
32}
33
34#[derive(Debug, Clone)]
36pub enum AuthResponse {
37 Authenticated {
39 identity: IdentityId,
40 token: String,
41 },
42 Challenge {
44 challenge_id: String,
45 payload: HashMap<String, String>,
46 },
47 Failed {
49 reason: String,
50 },
51}
52
53pub struct AuthConfigurator {
55 session_ttl: Option<Duration>,
56 challenge_ttl: Duration,
57}
58
59impl Default for AuthConfigurator {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl AuthConfigurator {
66 pub fn new() -> Self {
67 Self {
68 session_ttl: Some(Duration::from_secs(24 * 60 * 60)), challenge_ttl: Duration::from_secs(60),
70 }
71 }
72
73 pub fn session_ttl(mut self, ttl: Duration) -> Self {
74 self.session_ttl = Some(ttl);
75 self
76 }
77
78 pub fn no_session_ttl(mut self) -> Self {
79 self.session_ttl = None;
80 self
81 }
82
83 pub fn challenge_ttl(mut self, ttl: Duration) -> Self {
84 self.challenge_ttl = ttl;
85 self
86 }
87
88 pub fn configure(self) -> AuthServiceConfig {
89 AuthServiceConfig {
90 session_ttl: self.session_ttl,
91 challenge_ttl: self.challenge_ttl,
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct AuthServiceConfig {
99 pub session_ttl: Option<Duration>,
101 pub challenge_ttl: Duration,
103}
104
105impl Default for AuthServiceConfig {
106 fn default() -> Self {
107 AuthConfigurator::new().configure()
108 }
109}
110
111pub struct Inner {
112 pub(crate) engine: Arc<dyn AuthEngine>,
113 pub(crate) auth_registry: Arc<AuthenticationRegistry>,
114 pub(crate) challenges: ChallengeStore,
115 pub(crate) rng: SystemRng,
116 pub(crate) clock: Clock,
117 pub(crate) session_ttl: Option<Duration>,
118}
119
120#[derive(Clone)]
128pub struct AuthService(Arc<Inner>);
129
130impl Deref for AuthService {
131 type Target = Inner;
132 fn deref(&self) -> &Inner {
133 &self.0
134 }
135}
136
137impl AuthService {
138 pub fn new(
139 engine: Arc<dyn AuthEngine>,
140 auth_registry: Arc<AuthenticationRegistry>,
141 rng: SystemRng,
142 clock: Clock,
143 config: AuthServiceConfig,
144 ) -> Self {
145 Self(Arc::new(Inner {
146 engine,
147 auth_registry,
148 challenges: ChallengeStore::new(config.challenge_ttl),
149 rng,
150 clock,
151 session_ttl: config.session_ttl,
152 }))
153 }
154
155 pub(super) fn now(&self) -> Result<DateTime, Error> {
157 Ok(DateTime::from_nanos(self.clock.now_nanos()))
158 }
159
160 pub(super) fn expires_at(&self) -> Result<Option<DateTime>, Error> {
162 match self.session_ttl {
163 Some(ttl) => {
164 let ttl_nanos = ttl.as_nanos() as u64;
165 let nanos = self.clock.now_nanos().saturating_add(ttl_nanos);
166 Ok(Some(DateTime::from_nanos(nanos)))
167 }
168 None => Ok(None),
169 }
170 }
171
172 pub(super) fn persist_token(&self, token: &str, identity: IdentityId) -> Result<Token, Error> {
174 let mut admin = self.engine.begin_admin()?;
175
176 let def = create_token(&mut admin, token, identity, self.expires_at()?, self.now()?)?;
177
178 admin.commit()?;
179 Ok(def)
180 }
181
182 pub fn create_token(
189 &self,
190 token: &str,
191 identity: IdentityId,
192 expires_at: Option<DateTime>,
193 ) -> Result<Token, Error> {
194 let mut admin = self.engine.begin_admin()?;
195 let def = create_token(&mut admin, token, identity, expires_at, self.now()?)?;
196 admin.commit()?;
197 Ok(def)
198 }
199}
200
201pub(super) fn generate_session_token(rng: &SystemRng) -> String {
206 let bytes = rng.infra_bytes_32();
207 bytes.iter().map(|b| format!("{:02x}", b)).collect()
208}