use crate::hashing::{KeyVersion, LookupKey};
use crate::secret::{CodeId, SubjectId};
use crate::state::ClaimOutcome;
use super::error::StoreError;
#[derive(Debug, Clone)]
pub struct RedeemableCode {
pub id: CodeId,
pub key_version: KeyVersion,
pub grant: Option<String>,
pub scope: Option<String>,
pub expires_at: u64,
}
pub struct CodeRecord {
pub id: CodeId,
pub lookup_key: LookupKey,
pub key_version: KeyVersion,
pub purpose: Option<String>,
pub scope: Option<String>,
pub grant: Option<String>,
pub expires_at: u64,
}
pub struct ClaimRequest<'a> {
pub code_id: &'a CodeId,
pub subject: &'a SubjectId,
pub now: u64,
pub purpose: Option<&'a str>,
pub scope: Option<&'a str>,
}
pub trait CodeStore {
fn find_redeemable(
&self,
candidates: &[LookupKey],
now: u64,
scope: Option<&str>,
) -> impl Future<Output = Result<Option<RedeemableCode>, StoreError>>;
fn claim_code(
&self,
req: &ClaimRequest<'_>,
) -> impl Future<Output = Result<ClaimOutcome, StoreError>>;
fn insert_code(&self, record: CodeRecord) -> impl Future<Output = Result<(), StoreError>>;
fn revoke_code(
&self,
code_id: &CodeId,
scope: Option<&str>,
now: u64,
) -> impl Future<Output = Result<(), StoreError>>;
}
use std::future::Future;
pub fn expires_at_from_ttl(now: u64, ttl: std::time::Duration) -> u64 {
now.saturating_add(ttl.as_secs())
}
pub fn code_lookup_candidates<K: crate::hashing::KeyProvider>(
hasher: &crate::hashing::SecretHasher<K>,
normalized: &str,
) -> Vec<(LookupKey, KeyVersion)> {
hasher
.lookup_key(crate::hashing::SecretDomain::Code, normalized)
.map(|(lk, kv)| vec![(lk, kv)])
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn expires_at_from_ttl_adds_correctly() {
assert_eq!(expires_at_from_ttl(1_000, Duration::from_secs(3600)), 4_600);
assert_eq!(
expires_at_from_ttl(u64::MAX, Duration::from_secs(1)),
u64::MAX
);
}
}