1mod authenticate;
8mod github;
9mod solana;
10mod token;
11
12use std::{collections::HashMap, ops::Deref, sync::Arc};
13
14use reifydb_catalog::{catalog::Catalog, create_token};
15use reifydb_core::interface::catalog::token::Token;
16use reifydb_runtime::context::{clock::Clock, rng::Rng as SystemRng};
17use reifydb_transaction::transaction::{Transaction, admin::AdminTransaction, query::QueryTransaction};
18use reifydb_value::{
19 error::Error,
20 value::{Value, datetime::DateTime, duration::Duration, identity::IdentityId, value_type::ValueType},
21};
22
23use crate::{
24 challenge::ChallengeStore,
25 github::{GithubApi, GithubConfig, default_api},
26 registry::AuthenticationRegistry,
27};
28
29pub trait AuthEngine: Send + Sync {
30 fn begin_admin(&self) -> Result<AdminTransaction, Error>;
31 fn begin_query(&self) -> Result<QueryTransaction, Error>;
32 fn catalog(&self) -> Catalog;
33}
34
35#[derive(Debug, Clone)]
36pub enum AuthResponse {
37 Authenticated {
38 identity: IdentityId,
39 token: String,
40 },
41
42 Challenge {
43 challenge_id: String,
44 payload: HashMap<String, String>,
45 },
46
47 Failed {
48 reason: String,
49 },
50}
51
52pub struct AuthConfigurator {
53 session_ttl: Option<Duration>,
54 challenge_ttl: Duration,
55 github: Option<GithubConfig>,
56}
57
58impl Default for AuthConfigurator {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl AuthConfigurator {
65 pub fn new() -> Self {
66 Self {
67 session_ttl: Some(Duration::from_seconds(24 * 60 * 60).unwrap()),
68 challenge_ttl: Duration::from_seconds(60).unwrap(),
69 github: None,
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 github(mut self, config: GithubConfig) -> Self {
89 self.github = Some(config);
90 self
91 }
92
93 pub fn configure(self) -> AuthServiceConfig {
94 AuthServiceConfig {
95 session_ttl: self.session_ttl,
96 challenge_ttl: self.challenge_ttl,
97 github: self.github,
98 }
99 }
100}
101
102#[derive(Debug, Clone)]
103pub struct AuthServiceConfig {
104 pub session_ttl: Option<Duration>,
105
106 pub challenge_ttl: Duration,
107
108 pub github: Option<GithubConfig>,
109}
110
111impl Default for AuthServiceConfig {
112 fn default() -> Self {
113 AuthConfigurator::new().configure()
114 }
115}
116
117pub struct Inner {
118 pub(crate) engine: Arc<dyn AuthEngine>,
119 pub(crate) auth_registry: Arc<AuthenticationRegistry>,
120 pub(crate) challenges: ChallengeStore,
121 pub(crate) rng: SystemRng,
122 pub(crate) clock: Clock,
123 pub(crate) session_ttl: Option<Duration>,
124 pub(crate) github: Option<GithubAuth>,
125}
126
127pub(crate) struct GithubAuth {
128 pub(crate) config: GithubConfig,
129 pub(crate) api: Arc<dyn GithubApi>,
130}
131
132#[derive(Clone)]
133pub struct AuthService(Arc<Inner>);
134
135impl Deref for AuthService {
136 type Target = Inner;
137 fn deref(&self) -> &Inner {
138 &self.0
139 }
140}
141
142impl AuthService {
143 pub fn new(
144 engine: Arc<dyn AuthEngine>,
145 auth_registry: Arc<AuthenticationRegistry>,
146 rng: SystemRng,
147 clock: Clock,
148 config: AuthServiceConfig,
149 ) -> Self {
150 Self::with_github_api(engine, auth_registry, rng, clock, config, default_api())
151 }
152
153 pub fn with_github_api(
154 engine: Arc<dyn AuthEngine>,
155 auth_registry: Arc<AuthenticationRegistry>,
156 rng: SystemRng,
157 clock: Clock,
158 config: AuthServiceConfig,
159 api: Arc<dyn GithubApi>,
160 ) -> Self {
161 Self(Arc::new(Inner {
162 engine,
163 auth_registry,
164 challenges: ChallengeStore::new(config.challenge_ttl),
165 rng,
166 clock,
167 session_ttl: config.session_ttl,
168 github: config.github.map(|config| GithubAuth {
169 config,
170 api,
171 }),
172 }))
173 }
174
175 pub(super) fn now(&self) -> Result<DateTime, Error> {
176 Ok(self.clock.now())
177 }
178
179 pub(super) fn expires_at(&self) -> Result<Option<DateTime>, Error> {
180 match self.session_ttl {
181 Some(ttl) => {
182 let ttl_nanos = ttl.as_nanos()? as u64;
183 let nanos = self.clock.now().to_nanos().saturating_add(ttl_nanos);
184 Ok(Some(DateTime::from_nanos(nanos)))
185 }
186 None => Ok(None),
187 }
188 }
189
190 pub(super) fn persist_token(&self, token: &str, identity: IdentityId) -> Result<Token, Error> {
191 let mut admin = self.engine.begin_admin()?;
192
193 let def = create_token(&mut admin, token, identity, self.expires_at()?, self.now()?)?;
194
195 admin.commit()?;
196 Ok(def)
197 }
198
199 pub fn create_token(
200 &self,
201 token: &str,
202 identity: IdentityId,
203 expires_at: Option<DateTime>,
204 ) -> Result<Token, Error> {
205 let mut admin = self.engine.begin_admin()?;
206 let def = create_token(&mut admin, token, identity, expires_at, self.now()?)?;
207 admin.commit()?;
208 Ok(def)
209 }
210
211 pub(super) fn set_lookup_attribute(
212 &self,
213 admin: &mut AdminTransaction,
214 identity: IdentityId,
215 name: &str,
216 value: &str,
217 ) -> Result<(), Error> {
218 let catalog = self.engine.catalog();
219 let attribute =
220 match catalog.find_identity_attribute_by_name(&mut Transaction::Admin(&mut *admin), name)? {
221 Some(attribute) => attribute,
222 None => catalog.create_identity_attribute(admin, name, ValueType::Utf8)?,
223 };
224 catalog.set_identity_attribute_value(admin, identity, &attribute, Value::Utf8(value.to_string()))?;
225 Ok(())
226 }
227}
228
229pub(super) fn generate_session_token(rng: &SystemRng) -> String {
230 let bytes = rng.infra_bytes_32();
231 bytes.iter().map(|b| format!("{:02x}", b)).collect()
232}