#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod common;
use std::sync::Arc;
use std::time::Duration;
use common::MemoryEpochRevocation;
use ppoppo_token::access_token::{AuthError, EpochRevocation, IssueConfig, IssueRequest, VerifyConfig, issue, verify};
use ppoppo_token::{SigningKey};
const TEST_SUB: &str = "01HSAB00000000000000000000";
const TEST_CLIENT_ID: &str = "ppoppo-internal";
const TTL_15M: Duration = Duration::from_secs(900);
const ISSUER: &str = "https://accounts.ppoppo.com";
const AUDIENCE: &str = "ppoppo";
fn mint_token_with_sv(signer: &SigningKey, sv: u64) -> String {
let issue_cfg = IssueConfig::access_token(ISSUER, AUDIENCE, signer.kid());
let req = IssueRequest::new(TEST_SUB, TEST_CLIENT_ID, TTL_15M).with_session_version(sv);
issue(&req, &issue_cfg, signer, time::OffsetDateTime::now_utc().unix_timestamp()).expect("issue should succeed")
}
fn mint_token_without_sv(signer: &SigningKey) -> String {
let issue_cfg = IssueConfig::access_token(ISSUER, AUDIENCE, signer.kid());
let req = IssueRequest::new(TEST_SUB, TEST_CLIENT_ID, TTL_15M);
issue(&req, &issue_cfg, signer, time::OffsetDateTime::now_utc().unix_timestamp()).expect("issue should succeed")
}
#[tokio::test]
async fn epoch_admits_when_cfg_epoch_is_none() {
let (signer, key_set) = SigningKey::test_pair();
let token = mint_token_with_sv(&signer, 1);
let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE);
verify(&token, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp())
.await
.expect("None port must admit");
}
#[tokio::test]
async fn epoch_admits_when_token_has_no_sv() {
let (signer, key_set) = SigningKey::test_pair();
let token = mint_token_without_sv(&signer);
let port = Arc::new(MemoryEpochRevocation::new());
port.bump(TEST_SUB, 99); let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
verify(&token, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp())
.await
.expect("token without sv must admit (R6)");
}
#[tokio::test]
async fn epoch_admits_when_token_sv_meets_current() {
let (signer, key_set) = SigningKey::test_pair();
let token = mint_token_with_sv(&signer, 5);
let port = Arc::new(MemoryEpochRevocation::new());
port.bump(TEST_SUB, 5);
let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
verify(&token, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp())
.await
.expect("token sv >= current must admit");
}
#[tokio::test]
async fn epoch_admits_when_token_sv_exceeds_current() {
let (signer, key_set) = SigningKey::test_pair();
let token = mint_token_with_sv(&signer, 10);
let port = Arc::new(MemoryEpochRevocation::new()); let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
verify(&token, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp())
.await
.expect("token sv > current must admit");
}
#[tokio::test]
async fn epoch_rejects_stale_token_with_session_version_stale() {
let (signer, key_set) = SigningKey::test_pair();
let token = mint_token_with_sv(&signer, 3);
let port = Arc::new(MemoryEpochRevocation::new());
port.bump(TEST_SUB, 7); let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
let result = verify(&token, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp()).await;
assert_eq!(
result,
Err(AuthError::SessionVersionStale),
"stale sv must reject as SessionVersionStale",
);
}
#[tokio::test]
async fn epoch_substrate_transient_maps_to_session_version_lookup_unavailable() {
let (signer, key_set) = SigningKey::test_pair();
let token = mint_token_with_sv(&signer, 1);
let port = Arc::new(MemoryEpochRevocation::failing());
let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
let result = verify(&token, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp()).await;
assert_eq!(
result,
Err(AuthError::SessionVersionLookupUnavailable),
"failing substrate must map to SessionVersionLookupUnavailable, not admit",
);
}
#[tokio::test]
async fn epoch_axis_distinct_from_session() {
let (signer, key_set) = SigningKey::test_pair();
let token_old = mint_token_with_sv(&signer, 2);
let token_new = mint_token_with_sv(&signer, 5);
let port = Arc::new(MemoryEpochRevocation::new());
port.bump(TEST_SUB, 5); let cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
assert_eq!(
verify(&token_old, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp()).await,
Err(AuthError::SessionVersionStale),
"sv=2 < current=5 must reject",
);
verify(&token_new, &cfg, &key_set, time::OffsetDateTime::now_utc().unix_timestamp())
.await
.expect("sv=5 >= current=5 must admit");
}
#[tokio::test]
async fn epoch_dyn_compatibility_via_arc_in_verify_config() {
let port: Arc<dyn EpochRevocation> = Arc::new(MemoryEpochRevocation::new());
let _cfg = VerifyConfig::access_token(ISSUER, AUDIENCE).with_epoch_revocation(port);
}