use crate::verification::{VerificationCreation, VerificationPublication, VerificationSnapshot};
use crate::{AuthError, AuthResult, AuthSchema, CreateVerification};
use async_trait::async_trait;
#[async_trait]
pub trait VerificationStore<S: AuthSchema>: Send + Sync {
async fn create_verification_record(
&self,
_data: VerificationCreation,
_publication: VerificationPublication,
) -> AuthResult<Option<VerificationSnapshot>> {
Err(AuthError::NotImplemented(
"Verification publication phases are not supported by this store".into(),
))
}
async fn consume_verification_snapshot(
&self,
_identifier: &str,
) -> AuthResult<Option<S::Verification>> {
Err(AuthError::NotImplemented(
"Raw atomic verification consumption is not supported by this store".into(),
))
}
async fn update_verification_by_identifier(
&self,
_identifier: &str,
_data: crate::UpdateVerification,
) -> AuthResult<Option<VerificationSnapshot>> {
Err(AuthError::NotImplemented(
"Verification identifier updates are not supported by this store".into(),
))
}
async fn reserve_verification_record(
&self,
_logical_identifier: &str,
_data: CreateVerification,
) -> AuthResult<Option<S::Verification>> {
Err(AuthError::NotImplemented(
"Configured verification reservation is not supported by this store".into(),
))
}
async fn create_verification(
&self,
verification: CreateVerification,
) -> AuthResult<S::Verification>;
async fn get_verification(
&self,
identifier: &str,
value: &str,
) -> AuthResult<Option<S::Verification>>;
async fn get_verification_by_value(&self, value: &str) -> AuthResult<Option<S::Verification>>;
async fn get_verification_by_identifier(
&self,
identifier: &str,
) -> AuthResult<Option<S::Verification>>;
async fn consume_verification(
&self,
identifier: &str,
value: &str,
) -> AuthResult<Option<S::Verification>>;
async fn get_latest_verification_by_identifier(
&self,
_identifier: &str,
) -> AuthResult<Option<S::Verification>> {
Err(AuthError::internal(
"raw verification lookup is not supported by this store",
))
}
async fn consume_verification_by_identifier(
&self,
_identifier: &str,
) -> AuthResult<Option<S::Verification>> {
Err(AuthError::internal(
"atomic verification consumption is not supported by this store",
))
}
async fn delete_verifications_by_identifier(&self, _identifier: &str) -> AuthResult<()> {
Err(AuthError::internal(
"verification invalidation is not supported by this store",
))
}
async fn compare_and_swap_verification(
&self,
_id: &str,
_expected_value: &str,
_value: &str,
_expires_at: chrono::DateTime<chrono::Utc>,
) -> AuthResult<bool> {
Err(AuthError::internal(
"atomic verification updates are not supported by this store",
))
}
async fn reserve_verification(&self, _verification: CreateVerification) -> AuthResult<bool> {
Err(AuthError::internal(
"verification reservation is not supported by this store",
))
}
async fn delete_verification(&self, id: &str) -> AuthResult<()>;
async fn delete_expired_verifications(&self) -> AuthResult<usize>;
}
#[must_use]
pub fn verification_reservation_key(identifier: &str) -> (String, [u8; 32]) {
use base64::Engine;
use sha2::{Digest, Sha256};
let mut hash = Sha256::new();
hash.update(b"reserve:");
hash.update(identifier.as_bytes());
let digest: [u8; 32] = hash.finalize().into();
(
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest),
digest,
)
}
#[must_use]
pub fn verification_reservation_id(
identifier: &str,
database: &crate::config::AdvancedDatabaseConfig,
) -> (String, [u8; 32]) {
let (encoded, digest) = verification_reservation_key(identifier);
if !matches!(
database.generate_id,
Some(crate::config::DatabaseIdStrategy::Uuid)
) {
return (encoded, digest);
}
let mut bytes = [0_u8; 16];
bytes.copy_from_slice(&digest[..16]);
bytes[6] = (bytes[6] & 0x0f) | 0x50;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
(uuid::Uuid::from_bytes(bytes).to_string(), digest)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuid_strategy_reserves_a_version_5_uuid_from_the_digest() {
let mut database = crate::config::AdvancedDatabaseConfig::default();
let (plain, digest) = verification_reservation_id("siwe-email-claim-a@b.c", &database);
assert_eq!(
(plain.clone(), digest),
verification_reservation_key("siwe-email-claim-a@b.c")
);
database.generate_id = Some(crate::config::DatabaseIdStrategy::Uuid);
let (id, same) = verification_reservation_id("siwe-email-claim-a@b.c", &database);
assert_eq!(same, digest);
let parsed = uuid::Uuid::parse_str(&id).unwrap();
assert_eq!(parsed.get_version_num(), 5);
assert_eq!(parsed.get_variant(), uuid::Variant::RFC4122);
assert_eq!(parsed.as_bytes()[..6], digest[..6]);
assert_ne!(id, plain);
}
}