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 if let Some(public_key) = credentials.get("public_key").cloned() {
46 return self.auto_provision_solana(
47 identifier,
48 &public_key,
49 &credentials,
50 );
51 }
52 }
53 return Ok(AuthResponse::Failed {
54 reason: "invalid credentials".to_string(),
55 });
56 }
57 };
58
59 if !ident.enabled {
60 return Ok(AuthResponse::Failed {
61 reason: "identity is disabled".to_string(),
62 });
63 }
64
65 let stored_auth = match catalog.find_authentication_by_identity_and_method(
66 &mut Transaction::Query(&mut txn),
67 ident.id,
68 method,
69 )? {
70 Some(a) => a,
71 None => {
72 return Ok(AuthResponse::Failed {
73 reason: "invalid credentials".to_string(),
74 });
75 }
76 };
77
78 let provider = self.auth_registry.get(method).ok_or_else(|| {
79 Error::from(AuthError::UnknownMethod {
80 method: method.to_string(),
81 })
82 })?;
83
84 match provider.authenticate(&stored_auth.properties, &credentials)? {
85 AuthStep::Authenticated => {
86 let token = generate_session_token(&self.rng);
87 self.persist_token(&token, ident.id)?;
88 Ok(AuthResponse::Authenticated {
89 identity: ident.id,
90 token,
91 })
92 }
93 AuthStep::Failed => Ok(AuthResponse::Failed {
94 reason: "invalid credentials".to_string(),
95 }),
96 AuthStep::Challenge {
97 payload,
98 } => {
99 let challenge_id = self.challenges.create(
100 identifier.to_string(),
101 method.to_string(),
102 payload.clone(),
103 );
104 Ok(AuthResponse::Challenge {
105 challenge_id,
106 payload,
107 })
108 }
109 }
110 }
111
112 fn authenticate_token(&self, credentials: HashMap<String, String>) -> Result<AuthResponse, Error> {
113 let token_value = match credentials.get("token") {
114 Some(t) if !t.is_empty() => t,
115 _ => {
116 return Ok(AuthResponse::Failed {
117 reason: "invalid credentials".to_string(),
118 });
119 }
120 };
121
122 match self.validate_token(token_value) {
123 Some(token) => {
124 let session_token = generate_session_token(&self.rng);
125 self.persist_token(&session_token, token.identity)?;
126 Ok(AuthResponse::Authenticated {
127 identity: token.identity,
128 token: session_token,
129 })
130 }
131 None => Ok(AuthResponse::Failed {
132 reason: "invalid credentials".to_string(),
133 }),
134 }
135 }
136
137 fn authenticate_challenge_response(
139 &self,
140 challenge_id: &str,
141 mut credentials: HashMap<String, String>,
142 ) -> Result<AuthResponse, Error> {
143 let challenge = match self.challenges.consume(challenge_id) {
144 Some(c) => c,
145 None => {
146 return Ok(AuthResponse::Failed {
147 reason: "invalid or expired challenge".to_string(),
148 });
149 }
150 };
151
152 for (k, v) in &challenge.payload {
154 credentials.entry(k.clone()).or_insert_with(|| v.clone());
155 }
156
157 credentials.remove("challenge_id");
159
160 let mut txn = self.engine.begin_query()?;
162 let catalog = self.engine.catalog();
163
164 let ident = match catalog
165 .find_identity_by_name(&mut Transaction::Query(&mut txn), &challenge.identifier)?
166 {
167 Some(u) if u.enabled => u,
168 _ => {
169 return Ok(AuthResponse::Failed {
170 reason: "invalid credentials".to_string(),
171 });
172 }
173 };
174
175 let stored_auth = match catalog.find_authentication_by_identity_and_method(
176 &mut Transaction::Query(&mut txn),
177 ident.id,
178 &challenge.method,
179 )? {
180 Some(a) => a,
181 None => {
182 return Ok(AuthResponse::Failed {
183 reason: "invalid credentials".to_string(),
184 });
185 }
186 };
187
188 let provider = self.auth_registry.get(&challenge.method).ok_or_else(|| {
189 Error::from(AuthError::UnknownMethod {
190 method: challenge.method.clone(),
191 })
192 })?;
193
194 match provider.authenticate(&stored_auth.properties, &credentials)? {
195 AuthStep::Authenticated => {
196 let token = generate_session_token(&self.rng);
197 self.persist_token(&token, ident.id)?;
198 Ok(AuthResponse::Authenticated {
199 identity: ident.id,
200 token,
201 })
202 }
203 AuthStep::Failed => Ok(AuthResponse::Failed {
204 reason: "invalid credentials".to_string(),
205 }),
206 AuthStep::Challenge {
207 ..
208 } => Ok(AuthResponse::Failed {
209 reason: "nested challenges are not supported".to_string(),
210 }),
211 }
212 }
213}