use async_trait::async_trait;
use serde::Serialize;
use std::fmt::Debug;
use crate::auth::session::Session;
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("forbidden")]
Forbidden,
#[error("did method rejected")]
DidMethodRejected,
#[error("too many pending challenges")]
PendingChallengeLimitReached,
#[error("session not found")]
SessionNotFound,
#[error("session replay or state mismatch")]
SessionStateMismatch,
#[error("challenge mismatch")]
ChallengeMismatch,
#[error("challenge expired")]
ChallengeExpired,
#[error("signer DID does not match session DID")]
SignerMismatch,
#[error("message created_time outside freshness window")]
StaleMessage,
#[error("refresh token not found or consumed")]
RefreshTokenInvalid,
#[error("refresh token expired")]
RefreshTokenExpired,
#[error("tee attestation failed: {0}")]
AttestationFailed(String),
#[error("internal: {0}")]
Internal(String),
}
#[async_trait]
pub trait SessionStore: Send + Sync + 'static {
type Error: Debug + Send + Sync + 'static;
async fn store_session(&self, session: &Session) -> Result<(), Self::Error>;
async fn get_session(&self, session_id: &str) -> Result<Option<Session>, Self::Error>;
async fn delete_session(&self, session_id: &str) -> Result<(), Self::Error>;
async fn store_refresh_index(
&self,
refresh_token: &str,
session_id: &str,
) -> Result<(), Self::Error>;
async fn take_session_id_by_refresh(
&self,
refresh_token: &str,
) -> Result<Option<String>, Self::Error>;
async fn count_pending_challenges(&self, did: &str) -> Result<usize, Self::Error>;
}
#[async_trait]
pub trait AuthBackend: Send + Sync + 'static {
type Store: SessionStore;
type Error: From<AuthError> + Debug + Send + Sync + 'static;
type Role: std::fmt::Display + Serialize + Clone + Send + Sync + 'static;
fn sessions(&self) -> &Self::Store;
async fn mint_access_token(
&self,
subject: &str,
session_id: &str,
role: &Self::Role,
contexts: &[String],
amr: &[String],
acr: &str,
tee_attested: bool,
ttl_secs: u64,
) -> Result<String, Self::Error>;
async fn check_acl(&self, did: &str) -> Result<RoleResolution<Self::Role>, Self::Error>;
async fn validate_did(&self, _did: &str) -> Result<(), Self::Error> {
Ok(())
}
async fn attest_challenge(
&self,
_challenge_bytes: &[u8; 32],
) -> Result<AttestationOutcome, Self::Error> {
Ok(AttestationOutcome::not_attested())
}
fn max_pending_challenges_per_did(&self) -> usize {
10
}
fn audit(&self, event: AuthAuditEvent<'_>) {
match event {
AuthAuditEvent::ChallengeIssued { did, session_id } => {
tracing::info!(audit = true, %did, %session_id, "auth challenge issued");
}
AuthAuditEvent::Authenticated {
did, session_id, ..
} => {
tracing::info!(audit = true, %did, %session_id, "auth successful");
}
AuthAuditEvent::Refreshed {
did,
old_session_id,
new_session_id,
..
} => {
tracing::info!(
audit = true,
%did,
%old_session_id,
%new_session_id,
"token refreshed",
);
}
}
}
fn challenge_ttl(&self) -> u64;
fn access_token_ttl(&self) -> u64;
fn access_token_ttl_for_aal2(&self) -> u64 {
let base = self.access_token_ttl();
std::cmp::max(60, base / 3)
}
fn refresh_token_ttl(&self) -> u64;
fn didcomm_freshness_window(&self) -> u64 {
60
}
}
#[derive(Debug, Clone)]
pub struct RoleResolution<R> {
pub role: R,
pub contexts: Vec<String>,
}
impl<R> RoleResolution<R> {
pub fn new(role: R) -> Self {
Self {
role,
contexts: Vec::new(),
}
}
pub fn with_contexts(role: R, contexts: Vec<String>) -> Self {
Self { role, contexts }
}
}
#[derive(Debug, Clone)]
pub struct AttestationOutcome {
pub report: Option<serde_json::Value>,
pub attested: bool,
}
impl AttestationOutcome {
pub fn not_attested() -> Self {
Self {
report: None,
attested: false,
}
}
pub fn attested(report: serde_json::Value) -> Self {
Self {
report: Some(report),
attested: true,
}
}
}
#[derive(Debug)]
pub enum AuthAuditEvent<'a> {
ChallengeIssued { did: &'a str, session_id: &'a str },
Authenticated {
did: &'a str,
session_id: &'a str,
amr: &'a [String],
acr: &'a str,
},
Refreshed {
did: &'a str,
old_session_id: &'a str,
new_session_id: &'a str,
amr: &'a [String],
acr: &'a str,
},
}
#[derive(Debug, Clone)]
pub struct ChallengeInput {
pub did: String,
pub session_pubkey_b58btc: Option<String>,
}
#[derive(Debug, Clone)]
pub struct AuthenticateInput {
pub session_id: String,
pub challenge: String,
pub signer_did: String,
pub created_time: Option<u64>,
pub session_pubkey_b58btc: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RefreshInput {
pub refresh_token: String,
pub signer_did: Option<String>,
}