use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::sync::{Arc, Mutex};
use crate::authorization::AuthorizationCodeRecord;
use crate::client::{Client, ClientId};
use crate::device::{DeviceGrant, DeviceGrantState};
use crate::token::{IssuedToken, RefreshTokenRecord};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StorageError(pub String);
impl StorageError {
pub fn new(msg: impl Into<String>) -> Self {
StorageError(msg.into())
}
}
impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "storage error: {}", self.0)
}
}
impl std::error::Error for StorageError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RevocationBarrier {
Client(ClientId),
TokenFamily(Box<str>),
Consent {
client_id: ClientId,
subject: Box<str>,
},
}
pub(crate) fn reject_empty_scope(what: &str, value: &str) -> Result<(), StorageError> {
if value.is_empty() {
return Err(StorageError::new(format!(
"a revocation needs a non-empty {what}; the empty string does not name an identity a \
barrier can be recorded for"
)));
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RevocationWindow {
pub recorded_at: std::time::SystemTime,
pub until: std::time::SystemTime,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use = "a refused write means the record was NOT stored; the caller must undo what it was issuing"]
pub enum WriteOutcome {
Applied,
RefusedRevoked,
}
impl WriteOutcome {
pub fn is_applied(self) -> bool {
matches!(self, WriteOutcome::Applied)
}
pub fn is_refused(self) -> bool {
matches!(self, WriteOutcome::RefusedRevoked)
}
}
pub trait Storage: Send + Sync {
fn get_client(
&self,
client_id: &ClientId,
) -> impl Future<Output = Result<Option<Arc<Client>>, StorageError>> + Send;
fn put_client(&self, client: Client) -> impl Future<Output = Result<(), StorageError>> + Send;
fn compare_and_swap_client(
&self,
expected: &Client,
updated: Client,
) -> impl Future<Output = Result<bool, StorageError>> + Send;
fn delete_client(
&self,
client_id: &ClientId,
window: RevocationWindow,
) -> impl Future<Output = Result<bool, StorageError>> + Send;
fn put_device_grant(
&self,
grant: DeviceGrant,
) -> impl Future<Output = Result<(), StorageError>> + Send;
fn get_device_grant(
&self,
device_code: &str,
) -> impl Future<Output = Result<Option<DeviceGrant>, StorageError>> + Send;
fn find_device_grant_by_user_code(
&self,
normalized_user_code: &str,
) -> impl Future<Output = Result<Option<DeviceGrant>, StorageError>> + Send;
fn take_device_grant(
&self,
device_code: &str,
) -> impl Future<Output = Result<Option<DeviceGrant>, StorageError>> + Send;
fn compare_and_swap_device_grant(
&self,
expected: &DeviceGrantState,
updated: DeviceGrant,
) -> impl Future<Output = Result<bool, StorageError>> + Send;
fn put_authorization_code(
&self,
record: AuthorizationCodeRecord,
) -> impl Future<Output = Result<(), StorageError>> + Send;
fn compare_and_swap_authorization_code(
&self,
expected: &crate::authorization::AuthorizationCodeState,
updated: AuthorizationCodeRecord,
) -> impl Future<Output = Result<bool, StorageError>> + Send;
fn take_authorization_code(
&self,
code: &str,
) -> impl Future<Output = Result<Option<AuthorizationCodeRecord>, StorageError>> + Send;
#[cfg(feature = "par")]
fn put_pushed_authorization_request(
&self,
record: crate::par::PushedAuthorizationRequest,
) -> impl Future<Output = Result<WriteOutcome, StorageError>> + Send;
#[cfg(feature = "par")]
fn take_pushed_authorization_request(
&self,
request_uri: &str,
) -> impl Future<Output = Result<Option<crate::par::PushedAuthorizationRequest>, StorageError>> + Send;
fn put_token(
&self,
token: IssuedToken,
) -> impl Future<Output = Result<WriteOutcome, StorageError>> + Send;
fn get_token(
&self,
access_token: &str,
) -> impl Future<Output = Result<Option<Arc<IssuedToken>>, StorageError>> + Send;
fn delete_token(
&self,
access_token: &str,
) -> impl Future<Output = Result<(), StorageError>> + Send;
fn put_refresh_token(
&self,
record: RefreshTokenRecord,
) -> impl Future<Output = Result<WriteOutcome, StorageError>> + Send;
fn get_refresh_token(
&self,
refresh_token: &str,
) -> impl Future<Output = Result<Option<Arc<RefreshTokenRecord>>, StorageError>> + Send;
fn take_refresh_token(
&self,
refresh_token: &str,
) -> impl Future<Output = Result<Option<RefreshTokenRecord>, StorageError>> + Send;
fn revoke_token_family(
&self,
family_id: &str,
window: RevocationWindow,
) -> impl Future<Output = Result<u64, StorageError>> + Send;
#[cfg(feature = "consent")]
fn put_consent(
&self,
record: crate::consent::ConsentRecord,
) -> impl Future<Output = Result<(), StorageError>> + Send;
#[cfg(feature = "consent")]
fn compare_and_swap_consent(
&self,
expected: Option<&crate::consent::ConsentRecord>,
updated: crate::consent::ConsentRecord,
) -> impl Future<Output = Result<bool, StorageError>> + Send;
#[cfg(feature = "consent")]
fn get_consent(
&self,
consent_id: &str,
) -> impl Future<Output = Result<Option<Arc<crate::consent::ConsentRecord>>, StorageError>> + Send;
#[cfg(feature = "consent")]
fn find_consent(
&self,
client_id: &ClientId,
subject: &str,
) -> impl Future<Output = Result<Option<Arc<crate::consent::ConsentRecord>>, StorageError>> + Send;
#[cfg(feature = "consent")]
fn consents_for_subject(
&self,
subject: &str,
) -> impl Future<Output = Result<Vec<Arc<crate::consent::ConsentRecord>>, StorageError>> + Send;
#[cfg(feature = "consent")]
fn revoke_consent(
&self,
consent_id: &str,
window: RevocationWindow,
) -> impl Future<Output = Result<u64, StorageError>> + Send;
#[cfg(any(feature = "client-assertion", feature = "dpop"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "client-assertion", feature = "dpop"))))]
fn claim_replay_id(
&self,
id: &str,
expires_at: std::time::SystemTime,
) -> impl Future<Output = Result<bool, StorageError>> + Send;
fn sweep_expired(
&self,
now: std::time::SystemTime,
) -> impl Future<Output = Result<u64, StorageError>> + Send;
}
#[derive(Default)]
struct MemoryInner {
clients: HashMap<String, Arc<Client>>,
device_by_code: HashMap<String, DeviceGrant>,
user_code_index: HashMap<String, String>,
codes: HashMap<String, AuthorizationCodeRecord>,
#[cfg(feature = "par")]
pushed: HashMap<String, crate::par::PushedAuthorizationRequest>,
tokens: HashMap<String, Arc<IssuedToken>>,
refresh: HashMap<String, Arc<RefreshTokenRecord>>,
#[cfg(feature = "consent")]
consents: HashMap<String, Arc<crate::consent::ConsentRecord>>,
#[cfg(any(feature = "client-assertion", feature = "dpop"))]
replay_ids: HashMap<String, std::time::SystemTime>,
barriers: HashMap<String, ScopeBarriers>,
}
#[derive(Default)]
struct ScopeBarriers {
client: Option<BarrierTimes>,
family: Option<BarrierTimes>,
consents: HashMap<String, BarrierTimes>,
}
impl ScopeBarriers {
fn is_empty(&self) -> bool {
self.client.is_none() && self.family.is_none() && self.consents.is_empty()
}
}
#[derive(Clone, Copy)]
struct BarrierTimes {
recorded_at: std::time::SystemTime,
until: std::time::SystemTime,
}
impl BarrierTimes {
fn new(recorded_at: std::time::SystemTime, until: std::time::SystemTime) -> Self {
BarrierTimes { recorded_at, until }
}
fn merge(&mut self, recorded_at: std::time::SystemTime, until: std::time::SystemTime) {
if until > self.until {
self.until = until;
}
if recorded_at > self.recorded_at {
self.recorded_at = recorded_at;
}
}
fn merge_into(
slot: &mut Option<BarrierTimes>,
recorded_at: std::time::SystemTime,
until: std::time::SystemTime,
) {
match slot {
Some(existing) => existing.merge(recorded_at, until),
None => *slot = Some(BarrierTimes::new(recorded_at, until)),
}
}
fn covers(&self, established: std::time::SystemTime) -> bool {
established <= self.recorded_at
}
}
impl MemoryInner {
fn is_revoked(
&self,
client_id: &ClientId,
family_id: Option<&str>,
subject: Option<&str>,
grant_established_at: std::time::SystemTime,
) -> bool {
if let Some(scopes) = self.barriers.get(client_id.as_str()) {
if scopes
.client
.is_some_and(|t| t.covers(grant_established_at))
{
return true;
}
if let Some(subject) = subject {
if scopes
.consents
.get(subject)
.is_some_and(|t| t.covers(grant_established_at))
{
return true;
}
}
}
family_id.is_some_and(|f| {
self.barriers
.get(f)
.is_some_and(|scopes| scopes.family.is_some())
})
}
fn record_barrier(
&mut self,
barrier: RevocationBarrier,
recorded_at: std::time::SystemTime,
until: std::time::SystemTime,
) {
match barrier {
RevocationBarrier::Client(client_id) => {
let entry = self
.barriers
.entry(client_id.as_str().to_string())
.or_default();
BarrierTimes::merge_into(&mut entry.client, recorded_at, until);
}
RevocationBarrier::TokenFamily(family_id) => {
let entry = self.barriers.entry(family_id.into_string()).or_default();
BarrierTimes::merge_into(&mut entry.family, recorded_at, until);
}
RevocationBarrier::Consent { client_id, subject } => {
let entry = self
.barriers
.entry(client_id.as_str().to_string())
.or_default();
entry
.consents
.entry(subject.into_string())
.or_insert(BarrierTimes::new(recorded_at, until))
.merge(recorded_at, until);
}
}
}
}
#[derive(Default)]
pub struct MemoryStorage {
inner: Mutex<MemoryInner>,
}
impl MemoryStorage {
pub fn new() -> Self {
Self::default()
}
fn lock(&self) -> std::sync::MutexGuard<'_, MemoryInner> {
self.inner.lock().unwrap_or_else(|e| e.into_inner())
}
}
impl Storage for MemoryStorage {
async fn get_client(&self, client_id: &ClientId) -> Result<Option<Arc<Client>>, StorageError> {
Ok(self.lock().clients.get(client_id.as_str()).cloned())
}
async fn put_client(&self, client: Client) -> Result<(), StorageError> {
self.lock()
.clients
.insert(client.client_id.as_str().to_string(), Arc::new(client));
Ok(())
}
async fn compare_and_swap_client(
&self,
expected: &Client,
updated: Client,
) -> Result<bool, StorageError> {
let mut g = self.lock();
match g.clients.get(updated.client_id.as_str()) {
Some(current) if **current == *expected => {}
_ => return Ok(false),
}
g.clients
.insert(updated.client_id.as_str().to_string(), Arc::new(updated));
Ok(true)
}
async fn delete_client(
&self,
client_id: &ClientId,
window: RevocationWindow,
) -> Result<bool, StorageError> {
reject_empty_scope("client_id", client_id.as_str())?;
let mut g = self.lock();
let existed = g.clients.remove(client_id.as_str()).is_some();
g.record_barrier(
RevocationBarrier::Client(client_id.clone()),
window.recorded_at,
window.until,
);
g.tokens.retain(|_, t| &t.client_id != client_id);
g.refresh.retain(|_, r| &r.client_id != client_id);
g.codes.retain(|_, c| &c.client_id != client_id);
#[cfg(feature = "par")]
g.pushed.retain(|_, p| &p.client_id != client_id);
g.device_by_code.retain(|_, d| &d.client_id != client_id);
#[cfg(feature = "consent")]
g.consents.retain(|_, c| &c.client_id != client_id);
let live = &g.device_by_code;
let stale: Vec<String> = g
.user_code_index
.iter()
.filter(|(_, dc)| !live.contains_key(*dc))
.map(|(uc, _)| uc.clone())
.collect();
for uc in stale {
g.user_code_index.remove(&uc);
}
Ok(existed)
}
async fn put_device_grant(&self, grant: DeviceGrant) -> Result<(), StorageError> {
let mut g = self.lock();
let normalized = crate::device::normalize_user_code(&grant.user_code);
if let Some(owner) = g.user_code_index.get(&normalized) {
if owner != &grant.device_code {
return Err(StorageError::new(
"user code is already indexed for a different device_code",
));
}
}
if let Some(previous) = g.device_by_code.get(&grant.device_code) {
let previous_normalized = crate::device::normalize_user_code(&previous.user_code);
if previous_normalized != normalized {
g.user_code_index.remove(&previous_normalized);
}
}
g.user_code_index
.insert(normalized, grant.device_code.clone());
g.device_by_code.insert(grant.device_code.clone(), grant);
Ok(())
}
async fn get_device_grant(
&self,
device_code: &str,
) -> Result<Option<DeviceGrant>, StorageError> {
Ok(self.lock().device_by_code.get(device_code).cloned())
}
async fn find_device_grant_by_user_code(
&self,
normalized_user_code: &str,
) -> Result<Option<DeviceGrant>, StorageError> {
let g = self.lock();
Ok(g.user_code_index
.get(normalized_user_code)
.and_then(|dc| g.device_by_code.get(dc))
.cloned())
}
async fn take_device_grant(
&self,
device_code: &str,
) -> Result<Option<DeviceGrant>, StorageError> {
let mut g = self.lock();
let grant = g.device_by_code.remove(device_code);
if let Some(grant) = &grant {
let normalized = crate::device::normalize_user_code(&grant.user_code);
g.user_code_index.remove(&normalized);
}
Ok(grant)
}
async fn compare_and_swap_device_grant(
&self,
expected: &DeviceGrantState,
updated: DeviceGrant,
) -> Result<bool, StorageError> {
let mut g = self.lock();
match g.device_by_code.get(&updated.device_code) {
Some(current) if current.state == *expected => {}
_ => return Ok(false),
}
let normalized = crate::device::normalize_user_code(&updated.user_code);
if let Some(owner) = g.user_code_index.get(&normalized) {
if owner != &updated.device_code {
return Err(StorageError::new(
"user code is already indexed for a different device_code",
));
}
}
if let Some(previous) = g.device_by_code.get(&updated.device_code) {
let previous_normalized = crate::device::normalize_user_code(&previous.user_code);
if previous_normalized != normalized {
g.user_code_index.remove(&previous_normalized);
}
}
g.user_code_index
.insert(normalized, updated.device_code.clone());
g.device_by_code
.insert(updated.device_code.clone(), updated);
Ok(true)
}
async fn put_authorization_code(
&self,
record: AuthorizationCodeRecord,
) -> Result<(), StorageError> {
self.lock().codes.insert(record.code.clone(), record);
Ok(())
}
async fn compare_and_swap_authorization_code(
&self,
expected: &crate::authorization::AuthorizationCodeState,
updated: AuthorizationCodeRecord,
) -> Result<bool, StorageError> {
let mut g = self.lock();
match g.codes.get(&updated.code) {
Some(current) if current.state == *expected => {}
_ => return Ok(false),
}
g.codes.insert(updated.code.clone(), updated);
Ok(true)
}
async fn take_authorization_code(
&self,
code: &str,
) -> Result<Option<AuthorizationCodeRecord>, StorageError> {
Ok(self.lock().codes.remove(code))
}
#[cfg(feature = "par")]
async fn put_pushed_authorization_request(
&self,
record: crate::par::PushedAuthorizationRequest,
) -> Result<WriteOutcome, StorageError> {
let mut g = self.lock();
if g.is_revoked(&record.client_id, None, None, record.pushed_at) {
return Ok(WriteOutcome::RefusedRevoked);
}
g.pushed.insert(record.request_uri.clone(), record);
Ok(WriteOutcome::Applied)
}
#[cfg(feature = "par")]
async fn take_pushed_authorization_request(
&self,
request_uri: &str,
) -> Result<Option<crate::par::PushedAuthorizationRequest>, StorageError> {
Ok(self.lock().pushed.remove(request_uri))
}
async fn put_token(&self, token: IssuedToken) -> Result<WriteOutcome, StorageError> {
let mut g = self.lock();
if g.is_revoked(
&token.client_id,
token.family_id.as_deref(),
token.subject.as_deref(),
token.grant_established_at,
) {
return Ok(WriteOutcome::RefusedRevoked);
}
g.tokens.insert(token.access_token.clone(), Arc::new(token));
Ok(WriteOutcome::Applied)
}
async fn get_token(
&self,
access_token: &str,
) -> Result<Option<Arc<IssuedToken>>, StorageError> {
Ok(self.lock().tokens.get(access_token).cloned())
}
async fn delete_token(&self, access_token: &str) -> Result<(), StorageError> {
self.lock().tokens.remove(access_token);
Ok(())
}
async fn put_refresh_token(
&self,
record: RefreshTokenRecord,
) -> Result<WriteOutcome, StorageError> {
let mut g = self.lock();
if g.is_revoked(
&record.client_id,
Some(&record.family_id),
record.subject.as_deref(),
record.grant_established_at,
) {
return Ok(WriteOutcome::RefusedRevoked);
}
g.refresh
.insert(record.refresh_token.clone(), Arc::new(record));
Ok(WriteOutcome::Applied)
}
async fn get_refresh_token(
&self,
refresh_token: &str,
) -> Result<Option<Arc<RefreshTokenRecord>>, StorageError> {
Ok(self.lock().refresh.get(refresh_token).cloned())
}
async fn take_refresh_token(
&self,
refresh_token: &str,
) -> Result<Option<RefreshTokenRecord>, StorageError> {
Ok(self
.lock()
.refresh
.remove(refresh_token)
.map(|a| Arc::try_unwrap(a).unwrap_or_else(|a| (*a).clone())))
}
async fn revoke_token_family(
&self,
family_id: &str,
window: RevocationWindow,
) -> Result<u64, StorageError> {
reject_empty_scope("family_id", family_id)?;
let mut g = self.lock();
g.record_barrier(
RevocationBarrier::TokenFamily(family_id.into()),
window.recorded_at,
window.until,
);
let before = g.tokens.len() + g.refresh.len();
g.tokens
.retain(|_, t| t.family_id.as_deref() != Some(family_id));
g.refresh.retain(|_, r| r.family_id != family_id);
Ok((before - (g.tokens.len() + g.refresh.len())) as u64)
}
#[cfg(feature = "consent")]
async fn put_consent(&self, record: crate::consent::ConsentRecord) -> Result<(), StorageError> {
self.lock()
.consents
.insert(record.consent_id.to_string(), Arc::new(record));
Ok(())
}
#[cfg(feature = "consent")]
async fn compare_and_swap_consent(
&self,
expected: Option<&crate::consent::ConsentRecord>,
updated: crate::consent::ConsentRecord,
) -> Result<bool, StorageError> {
let mut g = self.lock();
let current = g
.consents
.values()
.find(|c| c.client_id == updated.client_id && c.subject == updated.subject)
.cloned();
match (current.as_deref(), expected) {
(Some(live), Some(expected)) if live == expected => {}
(None, None) => {}
_ => return Ok(false),
}
g.consents
.insert(updated.consent_id.to_string(), Arc::new(updated));
Ok(true)
}
#[cfg(feature = "consent")]
async fn get_consent(
&self,
consent_id: &str,
) -> Result<Option<Arc<crate::consent::ConsentRecord>>, StorageError> {
Ok(self.lock().consents.get(consent_id).cloned())
}
#[cfg(feature = "consent")]
async fn find_consent(
&self,
client_id: &ClientId,
subject: &str,
) -> Result<Option<Arc<crate::consent::ConsentRecord>>, StorageError> {
Ok(self
.lock()
.consents
.values()
.find(|c| &c.client_id == client_id && c.subject.as_ref() == subject)
.cloned())
}
#[cfg(feature = "consent")]
async fn consents_for_subject(
&self,
subject: &str,
) -> Result<Vec<Arc<crate::consent::ConsentRecord>>, StorageError> {
Ok(self
.lock()
.consents
.values()
.filter(|c| c.subject.as_ref() == subject)
.cloned()
.collect())
}
#[cfg(feature = "consent")]
async fn revoke_consent(
&self,
consent_id: &str,
window: RevocationWindow,
) -> Result<u64, StorageError> {
let mut g = self.lock();
let Some(peek) = g.consents.get(consent_id) else {
return Ok(0);
};
reject_empty_scope("client_id", peek.client_id.as_str())?;
reject_empty_scope("subject", peek.subject.as_ref())?;
let consent = g
.consents
.remove(consent_id)
.expect("the peek above holds the same guard");
let client_id = &consent.client_id;
let subject: &str = consent.subject.as_ref();
g.record_barrier(
RevocationBarrier::Consent {
client_id: client_id.clone(),
subject: subject.into(),
},
window.recorded_at,
window.until,
);
let before = g.tokens.len() + g.refresh.len() + g.codes.len() + g.device_by_code.len();
g.tokens
.retain(|_, t| !(&t.client_id == client_id && t.subject.as_deref() == Some(subject)));
g.refresh
.retain(|_, r| !(&r.client_id == client_id && r.subject.as_deref() == Some(subject)));
g.codes
.retain(|_, c| !(&c.client_id == client_id && c.subject == subject));
g.device_by_code.retain(|_, d| {
!(&d.client_id == client_id
&& matches!(&d.state, DeviceGrantState::Approved { subject: s } if s == subject))
});
let live = &g.device_by_code;
let stale: Vec<String> = g
.user_code_index
.iter()
.filter(|(_, dc)| !live.contains_key(*dc))
.map(|(uc, _)| uc.clone())
.collect();
for uc in stale {
g.user_code_index.remove(&uc);
}
let after = g.tokens.len() + g.refresh.len() + g.codes.len() + g.device_by_code.len();
Ok((before - after) as u64)
}
#[cfg(any(feature = "client-assertion", feature = "dpop"))]
async fn claim_replay_id(
&self,
id: &str,
expires_at: std::time::SystemTime,
) -> Result<bool, StorageError> {
let mut g = self.lock();
if g.replay_ids.contains_key(id) {
return Ok(false);
}
g.replay_ids.insert(id.to_string(), expires_at);
Ok(true)
}
async fn sweep_expired(&self, now: std::time::SystemTime) -> Result<u64, StorageError> {
let mut g = self.lock();
let mut removed = 0u64;
let before = g.device_by_code.len();
g.device_by_code.retain(|_, grant| now < grant.expires_at);
removed += (before - g.device_by_code.len()) as u64;
let live = &g.device_by_code;
let stale: Vec<String> = g
.user_code_index
.iter()
.filter(|(_, dc)| !live.contains_key(*dc))
.map(|(uc, _)| uc.clone())
.collect();
for uc in stale {
g.user_code_index.remove(&uc);
}
let before = g.codes.len();
g.codes.retain(|_, c| now < c.expires_at);
removed += (before - g.codes.len()) as u64;
#[cfg(feature = "par")]
{
let before = g.pushed.len();
g.pushed.retain(|_, p| now < p.expires_at);
removed += (before - g.pushed.len()) as u64;
}
let before = g.tokens.len();
g.tokens.retain(|_, t| now < t.expires_at);
removed += (before - g.tokens.len()) as u64;
let before = g.refresh.len();
g.refresh.retain(|_, r| match r.expires_at {
Some(exp) => now < exp,
None => true,
});
removed += (before - g.refresh.len()) as u64;
#[cfg(any(feature = "client-assertion", feature = "dpop"))]
{
let before = g.replay_ids.len();
g.replay_ids.retain(|_, exp| now < *exp);
removed += (before - g.replay_ids.len()) as u64;
}
g.barriers.retain(|_, scopes| {
if scopes.client.is_some_and(|t| now >= t.until) {
scopes.client = None;
removed += 1;
}
if scopes.family.is_some_and(|t| now >= t.until) {
scopes.family = None;
removed += 1;
}
let before = scopes.consents.len();
scopes.consents.retain(|_, t| now < t.until);
removed += (before - scopes.consents.len()) as u64;
!scopes.is_empty()
});
Ok(removed)
}
}