reifydb_auth/service/
authenticate.rs1use std::collections::HashMap;
5
6use reifydb_core::interface::auth::AuthStep;
7use reifydb_transaction::transaction::Transaction;
8use reifydb_type::error::Error;
9use tracing::instrument;
10
11use super::{AuthResponse, AuthService, generate_session_token};
12use crate::error::AuthError;
13
14impl AuthService {
15 #[instrument(name = "auth::authenticate", level = "debug", skip(self, credentials))]
25 pub fn authenticate(&self, method: &str, credentials: HashMap<String, String>) -> Result<AuthResponse, Error> {
26 if let Some(challenge_id) = credentials.get("challenge_id").cloned() {
27 return self.authenticate_challenge_response(&challenge_id, credentials);
28 }
29
30 if method == "token" {
31 return self.authenticate_token(credentials);
32 }
33
34 let identifier = credentials.get("identifier").map(|s| s.as_str()).unwrap_or("");
35
36 let mut txn = self.engine.begin_query()?;
37 let catalog = self.engine.catalog();
38
39 let ident = match catalog.find_identity_by_name(&mut Transaction::Query(&mut txn), identifier)? {
40 Some(u) => u,
41 None => {
42 drop(txn);
43
44 if method == "solana"
45 && let Some(public_key) = credentials.get("public_key").cloned()
46 {
47 return self.auto_provision_solana(identifier, &public_key, &credentials);
48 }
49 return Ok(AuthResponse::Failed {
50 reason: "invalid credentials".to_string(),
51 });
52 }
53 };
54
55 if !ident.enabled {
56 return Ok(AuthResponse::Failed {
57 reason: "identity is disabled".to_string(),
58 });
59 }
60
61 let stored_auth = match catalog.find_authentication_by_identity_and_method(
62 &mut Transaction::Query(&mut txn),
63 ident.id,
64 method,
65 )? {
66 Some(a) => a,
67 None => {
68 return Ok(AuthResponse::Failed {
69 reason: "invalid credentials".to_string(),
70 });
71 }
72 };
73
74 let provider = self.auth_registry.get(method).ok_or_else(|| {
75 Error::from(AuthError::UnknownMethod {
76 method: method.to_string(),
77 })
78 })?;
79
80 match provider.authenticate(&stored_auth.properties, &credentials)? {
81 AuthStep::Authenticated => {
82 let token = generate_session_token(&self.rng);
83 self.persist_token(&token, ident.id)?;
84 Ok(AuthResponse::Authenticated {
85 identity: ident.id,
86 token,
87 })
88 }
89 AuthStep::Failed => Ok(AuthResponse::Failed {
90 reason: "invalid credentials".to_string(),
91 }),
92 AuthStep::Challenge {
93 payload,
94 } => {
95 let challenge_id = self.challenges.create(
96 identifier.to_string(),
97 method.to_string(),
98 payload.clone(),
99 &self.clock,
100 &self.rng,
101 );
102 Ok(AuthResponse::Challenge {
103 challenge_id,
104 payload,
105 })
106 }
107 }
108 }
109
110 fn authenticate_token(&self, credentials: HashMap<String, String>) -> Result<AuthResponse, Error> {
111 let token_value = match credentials.get("token") {
112 Some(t) if !t.is_empty() => t,
113 _ => {
114 return Ok(AuthResponse::Failed {
115 reason: "invalid credentials".to_string(),
116 });
117 }
118 };
119
120 match self.validate_token(token_value) {
121 Some(token) => {
122 let session_token = generate_session_token(&self.rng);
123 self.persist_token(&session_token, token.identity)?;
124 Ok(AuthResponse::Authenticated {
125 identity: token.identity,
126 token: session_token,
127 })
128 }
129 None => Ok(AuthResponse::Failed {
130 reason: "invalid credentials".to_string(),
131 }),
132 }
133 }
134
135 fn authenticate_challenge_response(
137 &self,
138 challenge_id: &str,
139 mut credentials: HashMap<String, String>,
140 ) -> Result<AuthResponse, Error> {
141 let challenge = match self.challenges.consume(challenge_id) {
142 Some(c) => c,
143 None => {
144 return Ok(AuthResponse::Failed {
145 reason: "invalid or expired challenge".to_string(),
146 });
147 }
148 };
149
150 for (k, v) in &challenge.payload {
152 credentials.entry(k.clone()).or_insert_with(|| v.clone());
153 }
154
155 credentials.remove("challenge_id");
157
158 let mut txn = self.engine.begin_query()?;
160 let catalog = self.engine.catalog();
161
162 let ident = match catalog
163 .find_identity_by_name(&mut Transaction::Query(&mut txn), &challenge.identifier)?
164 {
165 Some(u) if u.enabled => u,
166 _ => {
167 return Ok(AuthResponse::Failed {
168 reason: "invalid credentials".to_string(),
169 });
170 }
171 };
172
173 let stored_auth = match catalog.find_authentication_by_identity_and_method(
174 &mut Transaction::Query(&mut txn),
175 ident.id,
176 &challenge.method,
177 )? {
178 Some(a) => a,
179 None => {
180 return Ok(AuthResponse::Failed {
181 reason: "invalid credentials".to_string(),
182 });
183 }
184 };
185
186 let provider = self.auth_registry.get(&challenge.method).ok_or_else(|| {
187 Error::from(AuthError::UnknownMethod {
188 method: challenge.method.clone(),
189 })
190 })?;
191
192 match provider.authenticate(&stored_auth.properties, &credentials)? {
193 AuthStep::Authenticated => {
194 let token = generate_session_token(&self.rng);
195 self.persist_token(&token, ident.id)?;
196 Ok(AuthResponse::Authenticated {
197 identity: ident.id,
198 token,
199 })
200 }
201 AuthStep::Failed => Ok(AuthResponse::Failed {
202 reason: "invalid credentials".to_string(),
203 }),
204 AuthStep::Challenge {
205 ..
206 } => Ok(AuthResponse::Failed {
207 reason: "nested challenges are not supported".to_string(),
208 }),
209 }
210 }
211}