Skip to main content

reifydb_auth/service/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4//! Auth service handle wired up by the runtime. Owns the catalog and admin-transaction handles needed to verify
5//! credentials, mint tokens, and load identities; exposes the high-level "authenticate this request" entry point
6//! that server transports invoke. Per-method specifics (token, Solana key) sit in submodules so the public surface
7//! stays method-agnostic.
8
9mod authenticate;
10mod solana;
11mod token;
12
13use std::{collections::HashMap, ops::Deref, sync::Arc};
14
15use reifydb_catalog::{catalog::Catalog, create_token};
16use reifydb_core::interface::catalog::token::Token;
17use reifydb_runtime::context::{clock::Clock, rng::Rng as SystemRng};
18use reifydb_transaction::transaction::{admin::AdminTransaction, query::QueryTransaction};
19use reifydb_value::{
20	error::Error,
21	value::{datetime::DateTime, duration::Duration, identity::IdentityId},
22};
23
24use crate::{challenge::ChallengeStore, registry::AuthenticationRegistry};
25
26pub trait AuthEngine: Send + Sync {
27	fn begin_admin(&self) -> Result<AdminTransaction, Error>;
28	fn begin_query(&self) -> Result<QueryTransaction, Error>;
29	fn catalog(&self) -> Catalog;
30}
31
32#[derive(Debug, Clone)]
33pub enum AuthResponse {
34	Authenticated {
35		identity: IdentityId,
36		token: String,
37	},
38
39	Challenge {
40		challenge_id: String,
41		payload: HashMap<String, String>,
42	},
43
44	Failed {
45		reason: String,
46	},
47}
48
49pub struct AuthConfigurator {
50	session_ttl: Option<Duration>,
51	challenge_ttl: Duration,
52}
53
54impl Default for AuthConfigurator {
55	fn default() -> Self {
56		Self::new()
57	}
58}
59
60impl AuthConfigurator {
61	pub fn new() -> Self {
62		Self {
63			session_ttl: Some(Duration::from_seconds(24 * 60 * 60).unwrap()),
64			challenge_ttl: Duration::from_seconds(60).unwrap(),
65		}
66	}
67
68	pub fn session_ttl(mut self, ttl: Duration) -> Self {
69		self.session_ttl = Some(ttl);
70		self
71	}
72
73	pub fn no_session_ttl(mut self) -> Self {
74		self.session_ttl = None;
75		self
76	}
77
78	pub fn challenge_ttl(mut self, ttl: Duration) -> Self {
79		self.challenge_ttl = ttl;
80		self
81	}
82
83	pub fn configure(self) -> AuthServiceConfig {
84		AuthServiceConfig {
85			session_ttl: self.session_ttl,
86			challenge_ttl: self.challenge_ttl,
87		}
88	}
89}
90
91#[derive(Debug, Clone)]
92pub struct AuthServiceConfig {
93	pub session_ttl: Option<Duration>,
94
95	pub challenge_ttl: Duration,
96}
97
98impl Default for AuthServiceConfig {
99	fn default() -> Self {
100		AuthConfigurator::new().configure()
101	}
102}
103
104pub struct Inner {
105	pub(crate) engine: Arc<dyn AuthEngine>,
106	pub(crate) auth_registry: Arc<AuthenticationRegistry>,
107	pub(crate) challenges: ChallengeStore,
108	pub(crate) rng: SystemRng,
109	pub(crate) clock: Clock,
110	pub(crate) session_ttl: Option<Duration>,
111}
112
113#[derive(Clone)]
114pub struct AuthService(Arc<Inner>);
115
116impl Deref for AuthService {
117	type Target = Inner;
118	fn deref(&self) -> &Inner {
119		&self.0
120	}
121}
122
123impl AuthService {
124	pub fn new(
125		engine: Arc<dyn AuthEngine>,
126		auth_registry: Arc<AuthenticationRegistry>,
127		rng: SystemRng,
128		clock: Clock,
129		config: AuthServiceConfig,
130	) -> Self {
131		Self(Arc::new(Inner {
132			engine,
133			auth_registry,
134			challenges: ChallengeStore::new(config.challenge_ttl),
135			rng,
136			clock,
137			session_ttl: config.session_ttl,
138		}))
139	}
140
141	pub(super) fn now(&self) -> Result<DateTime, Error> {
142		Ok(DateTime::from_nanos(self.clock.now_nanos()))
143	}
144
145	pub(super) fn expires_at(&self) -> Result<Option<DateTime>, Error> {
146		match self.session_ttl {
147			Some(ttl) => {
148				let ttl_nanos = ttl.as_nanos()? as u64;
149				let nanos = self.clock.now_nanos().saturating_add(ttl_nanos);
150				Ok(Some(DateTime::from_nanos(nanos)))
151			}
152			None => Ok(None),
153		}
154	}
155
156	pub(super) fn persist_token(&self, token: &str, identity: IdentityId) -> Result<Token, Error> {
157		let mut admin = self.engine.begin_admin()?;
158
159		let def = create_token(&mut admin, token, identity, self.expires_at()?, self.now()?)?;
160
161		admin.commit()?;
162		Ok(def)
163	}
164
165	pub fn create_token(
166		&self,
167		token: &str,
168		identity: IdentityId,
169		expires_at: Option<DateTime>,
170	) -> Result<Token, Error> {
171		let mut admin = self.engine.begin_admin()?;
172		let def = create_token(&mut admin, token, identity, expires_at, self.now()?)?;
173		admin.commit()?;
174		Ok(def)
175	}
176}
177
178pub(super) fn generate_session_token(rng: &SystemRng) -> String {
179	let bytes = rng.infra_bytes_32();
180	bytes.iter().map(|b| format!("{:02x}", b)).collect()
181}