alibi_core/store/contracts/
verification.rs1use crate::verification::{VerificationCreation, VerificationPublication, VerificationSnapshot};
2use crate::{AuthError, AuthResult, AuthSchema, CreateVerification};
3use async_trait::async_trait;
4#[async_trait]
5pub trait VerificationStore<S: AuthSchema>: Send + Sync {
6 async fn create_verification_record(
9 &self,
10 _data: VerificationCreation,
11 _publication: VerificationPublication,
12 ) -> AuthResult<Option<VerificationSnapshot>> {
13 Err(AuthError::NotImplemented(
14 "Verification publication phases are not supported by this store".into(),
15 ))
16 }
17 async fn consume_verification_snapshot(
21 &self,
22 _identifier: &str,
23 ) -> AuthResult<Option<S::Verification>> {
24 Err(AuthError::NotImplemented(
25 "Raw atomic verification consumption is not supported by this store".into(),
26 ))
27 }
28 async fn update_verification_by_identifier(
30 &self,
31 _identifier: &str,
32 _data: crate::UpdateVerification,
33 ) -> AuthResult<Option<VerificationSnapshot>> {
34 Err(AuthError::NotImplemented(
35 "Verification identifier updates are not supported by this store".into(),
36 ))
37 }
38 async fn reserve_verification_record(
41 &self,
42 _logical_identifier: &str,
43 _data: CreateVerification,
44 ) -> AuthResult<Option<S::Verification>> {
45 Err(AuthError::NotImplemented(
46 "Configured verification reservation is not supported by this store".into(),
47 ))
48 }
49 async fn create_verification(
50 &self,
51 verification: CreateVerification,
52 ) -> AuthResult<S::Verification>;
53 async fn get_verification(
54 &self,
55 identifier: &str,
56 value: &str,
57 ) -> AuthResult<Option<S::Verification>>;
58 async fn get_verification_by_value(&self, value: &str) -> AuthResult<Option<S::Verification>>;
59 async fn get_verification_by_identifier(
60 &self,
61 identifier: &str,
62 ) -> AuthResult<Option<S::Verification>>;
63 async fn consume_verification(
64 &self,
65 identifier: &str,
66 value: &str,
67 ) -> AuthResult<Option<S::Verification>>;
68 async fn get_latest_verification_by_identifier(
71 &self,
72 _identifier: &str,
73 ) -> AuthResult<Option<S::Verification>> {
74 Err(AuthError::internal(
75 "raw verification lookup is not supported by this store",
76 ))
77 }
78 async fn consume_verification_by_identifier(
82 &self,
83 _identifier: &str,
84 ) -> AuthResult<Option<S::Verification>> {
85 Err(AuthError::internal(
86 "atomic verification consumption is not supported by this store",
87 ))
88 }
89 async fn delete_verifications_by_identifier(&self, _identifier: &str) -> AuthResult<()> {
90 Err(AuthError::internal(
91 "verification invalidation is not supported by this store",
92 ))
93 }
94 async fn compare_and_swap_verification(
96 &self,
97 _id: &str,
98 _expected_value: &str,
99 _value: &str,
100 _expires_at: chrono::DateTime<chrono::Utc>,
101 ) -> AuthResult<bool> {
102 Err(AuthError::internal(
103 "atomic verification updates are not supported by this store",
104 ))
105 }
106 async fn reserve_verification(&self, _verification: CreateVerification) -> AuthResult<bool> {
109 Err(AuthError::internal(
110 "verification reservation is not supported by this store",
111 ))
112 }
113 async fn delete_verification(&self, id: &str) -> AuthResult<()>;
114 async fn delete_expired_verifications(&self) -> AuthResult<usize>;
115}
116
117#[must_use]
119pub fn verification_reservation_key(identifier: &str) -> (String, [u8; 32]) {
120 use base64::Engine;
121 use sha2::{Digest, Sha256};
122
123 let mut hash = Sha256::new();
124 hash.update(b"reserve:");
125 hash.update(identifier.as_bytes());
126 let digest: [u8; 32] = hash.finalize().into();
127 (
128 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest),
129 digest,
130 )
131}
132
133#[must_use]
138pub fn verification_reservation_id(
139 identifier: &str,
140 database: &crate::config::AdvancedDatabaseConfig,
141) -> (String, [u8; 32]) {
142 let (encoded, digest) = verification_reservation_key(identifier);
143 if !matches!(
144 database.generate_id,
145 Some(crate::config::DatabaseIdStrategy::Uuid)
146 ) {
147 return (encoded, digest);
148 }
149 let mut bytes = [0_u8; 16];
150 bytes.copy_from_slice(&digest[..16]);
151 bytes[6] = (bytes[6] & 0x0f) | 0x50;
152 bytes[8] = (bytes[8] & 0x3f) | 0x80;
153 (uuid::Uuid::from_bytes(bytes).to_string(), digest)
154}
155
156#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
163 fn uuid_strategy_reserves_a_version_5_uuid_from_the_digest() {
164 let mut database = crate::config::AdvancedDatabaseConfig::default();
165 let (plain, digest) = verification_reservation_id("siwe-email-claim-a@b.c", &database);
166 assert_eq!(
167 (plain.clone(), digest),
168 verification_reservation_key("siwe-email-claim-a@b.c")
169 );
170 database.generate_id = Some(crate::config::DatabaseIdStrategy::Uuid);
171 let (id, same) = verification_reservation_id("siwe-email-claim-a@b.c", &database);
172 assert_eq!(same, digest);
173 let parsed = uuid::Uuid::parse_str(&id).unwrap();
174 assert_eq!(parsed.get_version_num(), 5);
175 assert_eq!(parsed.get_variant(), uuid::Variant::RFC4122);
176 assert_eq!(parsed.as_bytes()[..6], digest[..6]);
177 assert_ne!(id, plain);
178 }
179}
180