Skip to main content

reifydb_auth/service/
token.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::collections::HashMap;
5
6use reifydb_catalog::{drop_expired_tokens, drop_token, drop_tokens_by_identity, find_token_by_value};
7use reifydb_core::interface::{auth::AuthStep, catalog::token::Token};
8use reifydb_transaction::transaction::Transaction;
9use reifydb_type::value::{datetime::DateTime, identity::IdentityId};
10
11use super::AuthService;
12
13impl AuthService {
14	/// Validate a bearer token and return the associated token definition.
15	///
16	/// Checks in order:
17	/// 1. Persisted session tokens (from login)
18	/// 2. Catalog tokens (from `CREATE AUTHENTICATION ... { method: token }`)
19	pub fn validate_token(&self, token: &str) -> Option<Token> {
20		// 1. Check persisted session tokens
21		let mut txn = self.engine.begin_query().ok()?;
22
23		if let Ok(Some(def)) = find_token_by_value(&mut Transaction::Query(&mut txn), token) {
24			// Check expiry
25			if let Some(expires_at) = def.expires_at {
26				if expires_at < self.now().ok()? {
27					return None;
28				}
29			}
30			return Some(def);
31		}
32
33		// 2. Fall back to catalog-stored tokens
34		self.validate_catalog_token(token)
35	}
36
37	/// Check if a token matches any catalog-stored token authentication.
38	fn validate_catalog_token(&self, token: &str) -> Option<Token> {
39		let provider = self.auth_registry.get("token")?;
40
41		let mut txn = self.engine.begin_query().ok()?;
42		let catalog = self.engine.catalog();
43
44		let auths = catalog.list_authentications_by_method(&mut Transaction::Query(&mut txn), "token").ok()?;
45
46		let creds = HashMap::from([("token".to_string(), token.to_string())]);
47
48		for auth in auths {
49			if let Ok(AuthStep::Authenticated) = provider.authenticate(&auth.properties, &creds) {
50				// Look up the identity via materialized catalog (no transaction needed)
51				if let Some(ident) = catalog.materialized.find_identity(auth.identity) {
52					if ident.enabled {
53						return Some(Token {
54							id: 0,
55							token: token.to_string(),
56							identity: ident.id,
57							expires_at: None,
58							created_at: DateTime::default(),
59						});
60					}
61				}
62			}
63		}
64
65		None
66	}
67
68	/// Revoke a specific session token.
69	pub fn revoke_token(&self, token: &str) -> bool {
70		let mut txn = match self.engine.begin_query() {
71			Ok(txn) => txn,
72			Err(_) => return false,
73		};
74
75		let def = match find_token_by_value(&mut Transaction::Query(&mut txn), token) {
76			Ok(Some(def)) => def,
77			_ => return false,
78		};
79		drop(txn);
80
81		let mut admin = match self.engine.begin_admin() {
82			Ok(a) => a,
83			Err(_) => return false,
84		};
85
86		if drop_token(&mut admin, def.id).is_err() {
87			return false;
88		}
89
90		admin.commit().is_ok()
91	}
92
93	/// Revoke all session tokens for a given identity.
94	pub fn revoke_all(&self, identity: IdentityId) {
95		if let Ok(mut admin) = self.engine.begin_admin() {
96			if drop_tokens_by_identity(&mut admin, identity).is_ok() {
97				let _ = admin.commit();
98			}
99		}
100	}
101
102	/// Clean up expired sessions and challenges.
103	pub fn cleanup_expired(&self) {
104		if let (Ok(mut admin), Ok(now)) = (self.engine.begin_admin(), self.now()) {
105			if drop_expired_tokens(&mut admin, now).is_ok() {
106				let _ = admin.commit();
107			}
108		}
109		self.challenges.cleanup_expired();
110	}
111}