use super::{Door, SIGN_IN_FOR};
use super::{Issuer, SignedIn, Started};
use lys_pass::Error;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Lys(AtomicUsize);
impl Lys {
const fn new() -> Self {
Self(AtomicUsize::new(0))
}
}
impl Issuer for Lys {
fn start(&self) -> Result<Started, Error> {
let n = self.0.fetch_add(1, Ordering::Relaxed) + 1;
Ok(Started {
url: "https://lys.example".into(),
state: format!("s{n}"),
verifier: "v1".into(),
browser: "b1".into(),
})
}
fn finish(
&self,
_code: &str,
_state: &str,
_browser: &str,
_started: &Started,
) -> Result<SignedIn, Error> {
Ok(SignedIn {
token: "fixture".into(),
holder: "fixture".into(),
expires_at: 1600,
refresh: None,
})
}
fn refresh(&self, _refresh: &str) -> Result<SignedIn, Error> {
Err(Error::CannotAsk("fixture"))
}
}
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn expired_pending_signins_are_removed_on_access() -> TestResult {
let door = Door::new(Lys::new());
door.begin(1_000)?;
let refused = door
.pass("unknown", 1_000 + SIGN_IN_FOR + 1)
.err()
.ok_or("unknown session was accepted")?;
assert_eq!(refused.code, "session_unknown");
assert!(
super::lock(&door.sessions.pending)?.is_empty(),
"expired pending sign-in retained on access"
);
Ok(())
}
#[test]
fn expired_sessions_are_removed_on_insert() -> TestResult {
let door = Door::new(Lys::new());
door.begin(1_000)?;
let session = door.callback("code", "s1", "b1", 1_000)?;
door.begin(1_000 + 12 * 60 * 60 + 1)?;
assert!(
!super::lock(&door.sessions.live)?.contains_key(&session),
"expired session retained on insert"
);
Ok(())
}
#[test]
fn pending_signins_are_bounded_before_asking_the_issuer() -> TestResult {
let door = Door::new(Lys::new());
let mut refused = None;
for _ in 0..513 {
if let Err(failure) = door.begin(1_000) {
refused = Some(failure);
break;
}
}
let refusal = refused.ok_or("pending sign-ins grew past the default bound of 512")?;
assert_eq!(refusal.code, "busy_pending_signins");
assert_eq!(
door.issuer().0.load(Ordering::Relaxed),
512,
"full pending map still asked the issuer"
);
Ok(())
}
struct LastRefresh(Lys);
impl Issuer for LastRefresh {
fn start(&self) -> Result<Started, Error> {
self.0.start()
}
fn finish(
&self,
code: &str,
state: &str,
browser: &str,
started: &Started,
) -> Result<SignedIn, Error> {
let mut signed_in = self.0.finish(code, state, browser, started)?;
signed_in.refresh = Some("last-refresh".to_owned());
Ok(signed_in)
}
fn refresh(&self, _refresh: &str) -> Result<SignedIn, Error> {
Ok(SignedIn {
token: "fresh".to_owned(),
holder: "fixture".to_owned(),
expires_at: 1700,
refresh: None,
})
}
}
#[test]
fn a_session_without_a_new_refresh_token_expires_with_its_fresh_pass() -> TestResult {
let door = Door::new(LastRefresh(Lys::new()));
door.begin(1000)?;
let id = door.callback("code", "s1", "b1", 1000)?;
assert_eq!(door.pass(&id, 1590)?, "fresh");
door.begin(1701)?;
assert!(
!super::lock(&door.sessions.live)?.contains_key(&id),
"expired refreshed session survived the next access"
);
Ok(())
}