use anyhow::Result;
use rusqlite::{Connection, OptionalExtension, Row};
use super::{Outcome, Store};
pub(super) const SCHEMA: &str = "
CREATE TABLE IF NOT EXISTS admin_credentials (
-- The WebAuthn credential id, base64url without padding.
id TEXT PRIMARY KEY,
-- The WebAuthn user handle, a UUID. Every passkey has the same one:
-- there is one owner, and an authenticator given the same handle
-- twice for a site replaces its passkey rather than adding one.
user_handle TEXT NOT NULL,
-- What the owner called it, such as 'iPhone'.
name TEXT NOT NULL,
-- webauthn-rs's Passkey, as JSON: the public key and its flags.
passkey TEXT NOT NULL,
-- The authenticator's signature counter at the last sign-in. Kept
-- beside the JSON so that checking it and moving it forward is one
-- conditional UPDATE, which two sign-ins at once cannot both pass.
sign_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
last_used_at TEXT
);
CREATE TABLE IF NOT EXISTS admin_sessions (
-- SHA-256 of the cookie's value, lowercase hex. The value itself is
-- never stored, so a copy of the database signs nobody in.
token_sha256 TEXT PRIMARY KEY,
-- The passkey that signed in: removing it ends its sessions.
credential_id TEXT NOT NULL,
created_at TEXT NOT NULL,
last_used_at TEXT NOT NULL,
-- The absolute limit, however recently it was used.
expires_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS admin_bootstrap (
-- One row at most: a new code replaces the last.
id INTEGER PRIMARY KEY CHECK (id = 1),
-- SHA-256 of the code, lowercase hex, as `bootstrap_code` reads
-- it. The code itself is never stored.
code_sha256 TEXT NOT NULL,
created_at TEXT NOT NULL,
-- Registering the first passkey with it must happen before this.
expires_at TEXT NOT NULL
);
";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminCredential {
pub id: String,
pub user_handle: String,
pub name: String,
pub passkey: String,
pub sign_count: u32,
pub created_at: String,
pub last_used_at: Option<String>,
}
#[derive(Debug, Clone)]
pub struct NewAdminCredential<'a> {
pub id: &'a str,
pub user_handle: &'a str,
pub name: &'a str,
pub passkey: &'a str,
pub sign_count: u32,
pub created_at: &'a str,
}
#[derive(Debug, Clone, Copy)]
pub struct FirstPasskey<'a> {
pub code_sha256: &'a str,
pub now: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddedCredential {
Added,
NotFirst,
Duplicate,
Code(BootstrapCode),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootstrapCode {
Valid,
Expired,
Wrong,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RemovedCredential {
Removed(AdminCredential),
Last,
NotFound,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminSession {
pub credential_id: String,
pub created_at: String,
pub last_used_at: String,
pub expires_at: String,
}
const CREDENTIAL_COLUMNS: &str =
"id, user_handle, name, passkey, sign_count, created_at, last_used_at";
fn credential_from(r: &Row<'_>) -> rusqlite::Result<AdminCredential> {
Ok(AdminCredential {
id: r.get(0)?,
user_handle: r.get(1)?,
name: r.get(2)?,
passkey: r.get(3)?,
sign_count: r.get(4)?,
created_at: r.get(5)?,
last_used_at: r.get(6)?,
})
}
fn get_credential(conn: &Connection, id: &str) -> Result<Option<AdminCredential>> {
Ok(conn
.query_row(
&format!("SELECT {CREDENTIAL_COLUMNS} FROM admin_credentials WHERE id = ?1"),
(id,),
credential_from,
)
.optional()?)
}
fn bootstrap_code(conn: &Connection, code_sha256: &str, now: &str) -> Result<BootstrapCode> {
let expires_at: Option<String> = conn
.query_row(
"SELECT expires_at FROM admin_bootstrap WHERE code_sha256 = ?1",
(code_sha256,),
|r| r.get(0),
)
.optional()?;
Ok(match expires_at {
Some(expires_at) if expires_at.as_str() > now => BootstrapCode::Valid,
Some(_) => BootstrapCode::Expired,
None => BootstrapCode::Wrong,
})
}
fn set_bootstrap_code(
conn: &Connection,
code_sha256: &str,
now: &str,
expires_at: &str,
) -> Result<()> {
conn.execute(
"INSERT OR REPLACE INTO admin_bootstrap (id, code_sha256, created_at, expires_at)
VALUES (1, ?1, ?2, ?3)",
(code_sha256, now, expires_at),
)?;
Ok(())
}
impl Store {
pub fn has_admin_credentials(&self) -> Result<bool> {
let conn = self.lock();
let n: i64 = conn.query_row("SELECT COUNT(*) FROM admin_credentials", [], |r| r.get(0))?;
Ok(n > 0)
}
pub fn add_admin_credential_audited(
&self,
c: &NewAdminCredential<'_>,
first: Option<FirstPasskey<'_>>,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<AddedCredential> {
self.audited(
|tx, _| {
if get_credential(tx, c.id)?.is_some() {
return Ok(Outcome::Refuse(AddedCredential::Duplicate));
}
if let Some(first) = first {
let n: i64 =
tx.query_row("SELECT COUNT(*) FROM admin_credentials", [], |r| r.get(0))?;
if n > 0 {
return Ok(Outcome::Refuse(AddedCredential::NotFirst));
}
match bootstrap_code(tx, first.code_sha256, first.now)? {
BootstrapCode::Valid => {}
refused => return Ok(Outcome::Refuse(AddedCredential::Code(refused))),
}
tx.execute("DELETE FROM admin_bootstrap", [])?;
}
tx.execute(
"INSERT INTO admin_credentials
(id, user_handle, name, passkey, sign_count, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
(
c.id,
c.user_handle,
c.name,
c.passkey,
c.sign_count,
c.created_at,
),
)?;
Ok(Outcome::Commit(AddedCredential::Added))
},
|seq, at, _| build_leaf(seq, at),
)
}
pub fn check_bootstrap_code(&self, code_sha256: &str, now: &str) -> Result<BootstrapCode> {
bootstrap_code(&self.lock(), code_sha256, now)
}
pub fn set_bootstrap_code_audited(
&self,
code_sha256: &str,
now: &str,
expires_at: &str,
build_leaf: impl FnOnce(u64, &str) -> Vec<u8>,
) -> Result<()> {
self.audited(
|tx, _| {
set_bootstrap_code(tx, code_sha256, now, expires_at)?;
Ok(Outcome::Commit(()))
},
|seq, at, ()| build_leaf(seq, at),
)
}
pub fn admin_credential(&self, id: &str) -> Result<Option<AdminCredential>> {
get_credential(&self.lock(), id)
}
pub fn admin_credentials(&self) -> Result<Vec<AdminCredential>> {
let conn = self.lock();
let mut stmt = conn.prepare(&format!(
"SELECT {CREDENTIAL_COLUMNS} FROM admin_credentials ORDER BY created_at, id"
))?;
let rows = stmt.query_map([], credential_from)?;
Ok(rows.collect::<rusqlite::Result<_>>()?)
}
pub fn record_admin_sign_in(
&self,
id: &str,
sign_count: u32,
passkey: &str,
now: &str,
) -> Result<bool> {
let conn = self.lock();
let updated = conn.execute(
"UPDATE admin_credentials
SET sign_count = ?2, passkey = ?3, last_used_at = ?4
WHERE id = ?1 AND (sign_count < ?2 OR (sign_count = 0 AND ?2 = 0))",
(id, sign_count, passkey, now),
)?;
Ok(updated == 1)
}
pub fn remove_admin_credential_audited(
&self,
id: &str,
build_leaf: impl FnOnce(u64, &str, &AdminCredential) -> Vec<u8>,
) -> Result<RemovedCredential> {
self.audited(
|tx, _| {
let Some(credential) = get_credential(tx, id)? else {
return Ok(Outcome::Refuse(RemovedCredential::NotFound));
};
let n: i64 =
tx.query_row("SELECT COUNT(*) FROM admin_credentials", [], |r| r.get(0))?;
if n <= 1 {
return Ok(Outcome::Refuse(RemovedCredential::Last));
}
tx.execute("DELETE FROM admin_credentials WHERE id = ?1", (id,))?;
tx.execute("DELETE FROM admin_sessions WHERE credential_id = ?1", (id,))?;
Ok(Outcome::Commit(RemovedCredential::Removed(credential)))
},
|seq, at, removed| match removed {
RemovedCredential::Removed(credential) => build_leaf(seq, at, credential),
_ => unreachable!("a leaf only for a removal"),
},
)
}
pub fn reset_admin_credentials_audited(
&self,
code_sha256: &str,
now: &str,
expires_at: &str,
build_leaf: impl FnOnce(u64, &str, usize) -> Vec<u8>,
) -> Result<usize> {
self.lock().execute_batch(super::audit::SCHEMA)?;
self.audited(
|tx, _| {
tx.execute_batch(SCHEMA)?;
let n = tx.execute("DELETE FROM admin_credentials", [])?;
tx.execute("DELETE FROM admin_sessions", [])?;
set_bootstrap_code(tx, code_sha256, now, expires_at)?;
Ok(Outcome::Commit(n))
},
|seq, at, n| build_leaf(seq, at, *n),
)
}
pub fn create_admin_session(
&self,
token_sha256: &str,
credential_id: &str,
now: &str,
expires_at: &str,
) -> Result<bool> {
let conn = self.lock();
let inserted = conn.execute(
"INSERT INTO admin_sessions
(token_sha256, credential_id, created_at, last_used_at, expires_at)
SELECT ?1, ?2, ?3, ?3, ?4
WHERE EXISTS (SELECT 1 FROM admin_credentials WHERE id = ?2)",
(token_sha256, credential_id, now, expires_at),
)?;
Ok(inserted == 1)
}
pub fn admin_session(&self, token_sha256: &str) -> Result<Option<AdminSession>> {
let conn = self.lock();
Ok(conn
.query_row(
"SELECT credential_id, created_at, last_used_at, expires_at
FROM admin_sessions WHERE token_sha256 = ?1",
(token_sha256,),
|r| {
Ok(AdminSession {
credential_id: r.get(0)?,
created_at: r.get(1)?,
last_used_at: r.get(2)?,
expires_at: r.get(3)?,
})
},
)
.optional()?)
}
pub fn touch_admin_session(&self, token_sha256: &str, now: &str) -> Result<()> {
let conn = self.lock();
conn.execute(
"UPDATE admin_sessions SET last_used_at = ?2 WHERE token_sha256 = ?1",
(token_sha256, now),
)?;
Ok(())
}
pub fn delete_other_admin_sessions_audited(
&self,
keep: &str,
build_leaf: impl FnOnce(u64, &str, usize) -> Vec<u8>,
) -> Result<usize> {
self.audited(
|tx, _| {
let n = tx.execute(
"DELETE FROM admin_sessions WHERE token_sha256 != ?1",
(keep,),
)?;
Ok(if n == 0 {
Outcome::Refuse(0)
} else {
Outcome::Commit(n)
})
},
|seq, at, n| build_leaf(seq, at, *n),
)
}
pub fn delete_admin_session(&self, token_sha256: &str) -> Result<()> {
let conn = self.lock();
conn.execute(
"DELETE FROM admin_sessions WHERE token_sha256 = ?1",
(token_sha256,),
)?;
Ok(())
}
pub fn sweep_admin_sessions(&self, now: &str, idle_before: &str) -> Result<usize> {
let conn = self.lock();
Ok(conn.execute(
"DELETE FROM admin_sessions WHERE expires_at <= ?1 OR last_used_at < ?2",
(now, idle_before),
)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::store::test_leaf;
impl Store {
fn add_admin_credential(
&self,
c: &NewAdminCredential<'_>,
first: Option<FirstPasskey<'_>>,
) -> Result<AddedCredential> {
self.add_admin_credential_audited(c, first, test_leaf)
}
fn set_bootstrap_code(&self, code_sha256: &str, now: &str, expires_at: &str) -> Result<()> {
self.set_bootstrap_code_audited(code_sha256, now, expires_at, test_leaf)
}
fn remove_admin_credential(&self, id: &str) -> Result<RemovedCredential> {
self.remove_admin_credential_audited(id, |seq, at, _| test_leaf(seq, at))
}
fn reset_admin_credentials(
&self,
code_sha256: &str,
now: &str,
expires_at: &str,
) -> Result<usize> {
self.reset_admin_credentials_audited(code_sha256, now, expires_at, |seq, at, _| {
test_leaf(seq, at)
})
}
fn delete_other_admin_sessions(&self, keep: &str) -> Result<usize> {
self.delete_other_admin_sessions_audited(keep, |seq, at, _| test_leaf(seq, at))
}
}
fn credential<'a>(id: &'a str, created_at: &'a str) -> NewAdminCredential<'a> {
NewAdminCredential {
id,
user_handle: "u",
name: "phone",
passkey: "{}",
sign_count: 0,
created_at,
}
}
const T0: &str = "2026-09-23T10:00:00.000Z";
const T1: &str = "2026-09-23T11:00:00.000Z";
const T2: &str = "2026-09-23T12:00:00.000Z";
fn first(code_sha256: &str) -> Option<FirstPasskey<'_>> {
Some(FirstPasskey {
code_sha256,
now: T0,
})
}
fn bootstrapping() -> Store {
let st = Store::open_in_memory().unwrap();
st.set_bootstrap_code("code", T0, T1).unwrap();
st
}
#[test]
fn only_the_first_passkey_can_be_added_as_the_first() {
let st = bootstrapping();
assert!(!st.has_admin_credentials().unwrap());
assert_eq!(
st.add_admin_credential(&credential("a", T0), first("code"))
.unwrap(),
AddedCredential::Added
);
assert!(st.has_admin_credentials().unwrap());
st.set_bootstrap_code("again", T0, T1).unwrap();
assert_eq!(
st.add_admin_credential(&credential("b", T0), first("again"))
.unwrap(),
AddedCredential::NotFirst
);
assert_eq!(
st.add_admin_credential(&credential("a", T0), None).unwrap(),
AddedCredential::Duplicate
);
assert_eq!(
st.add_admin_credential(&credential("b", T1), None).unwrap(),
AddedCredential::Added
);
let ids: Vec<String> = st
.admin_credentials()
.unwrap()
.into_iter()
.map(|c| c.id)
.collect();
assert_eq!(ids, ["a", "b"]);
}
#[test]
fn the_first_passkey_needs_the_bootstrap_code_once() {
let st = bootstrapping();
assert_eq!(
st.check_bootstrap_code("code", T0).unwrap(),
BootstrapCode::Valid
);
assert_eq!(
st.check_bootstrap_code("code", T1).unwrap(),
BootstrapCode::Expired
);
assert_eq!(
st.check_bootstrap_code("other", T0).unwrap(),
BootstrapCode::Wrong
);
assert_eq!(
st.add_admin_credential(&credential("a", T0), first("other"))
.unwrap(),
AddedCredential::Code(BootstrapCode::Wrong)
);
let late = Some(FirstPasskey {
code_sha256: "code",
now: T1,
});
assert_eq!(
st.add_admin_credential(&credential("a", T0), late).unwrap(),
AddedCredential::Code(BootstrapCode::Expired)
);
assert!(!st.has_admin_credentials().unwrap(), "nothing stored");
assert_eq!(
st.add_admin_credential(&credential("a", T0), first("code"))
.unwrap(),
AddedCredential::Added
);
assert_eq!(
st.check_bootstrap_code("code", T0).unwrap(),
BootstrapCode::Wrong,
"used up"
);
assert_eq!(st.reset_admin_credentials("new", T0, T2).unwrap(), 1);
assert_eq!(
st.check_bootstrap_code("new", T1).unwrap(),
BootstrapCode::Valid
);
st.set_bootstrap_code("newer", T0, T2).unwrap();
assert_eq!(
st.check_bootstrap_code("new", T1).unwrap(),
BootstrapCode::Wrong,
"replaced"
);
}
#[test]
fn a_sign_in_is_recorded_only_if_its_counter_moved_forward() {
let st = bootstrapping();
st.add_admin_credential(&credential("a", T0), first("code"))
.unwrap();
assert!(st.record_admin_sign_in("a", 0, "{}", T1).unwrap());
assert!(st.record_admin_sign_in("a", 0, "{}", T1).unwrap());
assert!(st.record_admin_sign_in("a", 5, "{}", T1).unwrap());
assert!(!st.record_admin_sign_in("a", 5, "{}", T1).unwrap(), "equal");
assert!(!st.record_admin_sign_in("a", 4, "{}", T1).unwrap(), "lower");
assert!(!st.record_admin_sign_in("a", 0, "{}", T1).unwrap(), "reset");
assert!(st.record_admin_sign_in("a", 6, "{}", T1).unwrap());
let stored = st.admin_credential("a").unwrap().unwrap();
assert_eq!(stored.sign_count, 6);
assert_eq!(stored.last_used_at.as_deref(), Some(T1));
assert!(!st.record_admin_sign_in("nobody", 9, "{}", T1).unwrap());
}
#[test]
fn the_last_passkey_cannot_be_removed_and_removing_one_ends_its_sessions() {
let st = bootstrapping();
st.add_admin_credential(&credential("a", T0), first("code"))
.unwrap();
assert_eq!(
st.remove_admin_credential("a").unwrap(),
RemovedCredential::Last
);
st.add_admin_credential(&credential("b", T1), None).unwrap();
assert!(st.create_admin_session("s1", "a", T1, T1).unwrap());
assert!(st.create_admin_session("s2", "b", T1, T1).unwrap());
assert!(matches!(
st.remove_admin_credential("a").unwrap(),
RemovedCredential::Removed(c) if c.id == "a"
));
assert_eq!(st.admin_session("s1").unwrap(), None);
assert!(st.admin_session("s2").unwrap().is_some());
assert_eq!(
st.remove_admin_credential("a").unwrap(),
RemovedCredential::NotFound
);
assert!(
!st.create_admin_session("s3", "a", T1, T1).unwrap(),
"a removed passkey signs nobody in"
);
}
#[test]
fn signing_out_the_others_keeps_this_session() {
let st = bootstrapping();
st.add_admin_credential(&credential("a", T0), first("code"))
.unwrap();
for s in ["mine", "theirs", "old"] {
st.create_admin_session(s, "a", T0, T2).unwrap();
}
assert_eq!(st.delete_other_admin_sessions("mine").unwrap(), 2);
assert!(st.admin_session("mine").unwrap().is_some());
assert_eq!(st.admin_session("theirs").unwrap(), None);
assert_eq!(st.delete_other_admin_sessions("mine").unwrap(), 0);
}
#[test]
fn sessions_are_swept_when_idle_or_past_their_limit() {
let st = bootstrapping();
st.add_admin_credential(&credential("a", T0), first("code"))
.unwrap();
st.create_admin_session("idle", "a", T0, "2026-10-23T10:00:00.000Z")
.unwrap();
st.create_admin_session("old", "a", T1, T1).unwrap();
st.create_admin_session("fine", "a", T1, "2026-10-23T10:00:00.000Z")
.unwrap();
assert_eq!(
st.sweep_admin_sessions(T1, "2026-09-23T10:30:00.000Z")
.unwrap(),
2
);
assert!(st.admin_session("fine").unwrap().is_some());
assert_eq!(st.reset_admin_credentials("new", T0, T2).unwrap(), 1);
assert_eq!(st.admin_session("fine").unwrap(), None);
assert!(!st.has_admin_credentials().unwrap());
}
}