mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-04 00:47:01 +01:00
test: cover session tokens, and run the suite in make check
auth.rs was the largest untested surface and the one where a mistake is worth the most: the tests pin the round trip, expiry, secret rotation, forged expiries, flipped signature bytes, malformed input, and that credentials have to match in full rather than by prefix. Also documents autosave, :export! and the security headers in the README.
This commit is contained in:
1 parent
41d2d7ef77
commit
c606169b01
3 files changed
+95
No files matched your search
+74
@@ -94,3 +94,77 @@ impl FromRequestParts<AppState> for Session {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{credentials_match, issue_token, verify_token};
|
||||
use crate::config::Config;
|
||||
|
||||
fn config(session_days: i64) -> Config {
|
||||
Config {
|
||||
username: "narl".into(),
|
||||
password: "hunter2".into(),
|
||||
secret: b"a-secret-of-at-least-16-bytes".to_vec(),
|
||||
bind: "127.0.0.1:0".parse().unwrap(),
|
||||
data_dir: ".".into(),
|
||||
session_days,
|
||||
max_upload_bytes: 1,
|
||||
secure_cookie: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fresh_token_verifies() {
|
||||
let config = config(30);
|
||||
assert!(verify_token(&config, &issue_token(&config)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_expired_token_does_not() {
|
||||
let config = config(-1);
|
||||
assert!(!verify_token(&config, &issue_token(&config)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotating_the_secret_invalidates_every_session() {
|
||||
let token = issue_token(&config(30));
|
||||
let mut rotated = config(30);
|
||||
rotated.secret = b"a-different-secret-entirely".to_vec();
|
||||
assert!(!verify_token(&rotated, &token));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tampered_token_does_not_verify() {
|
||||
let config = config(30);
|
||||
let token = issue_token(&config);
|
||||
let (payload, signature) = token.split_once('.').unwrap();
|
||||
|
||||
// A far-future expiry the holder signed themselves.
|
||||
let forged = base64::Engine::encode(&super::B64, "99999999999");
|
||||
assert!(!verify_token(&config, &format!("{forged}.{signature}")));
|
||||
|
||||
// The real expiry with the last byte of the signature flipped.
|
||||
let mut bytes = signature.as_bytes().to_vec();
|
||||
bytes[0] = if bytes[0] == b'A' { b'B' } else { b'A' };
|
||||
let flipped = String::from_utf8(bytes).unwrap();
|
||||
assert!(!verify_token(&config, &format!("{payload}.{flipped}")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_tokens_are_rejected_rather_than_panicking() {
|
||||
let config = config(30);
|
||||
for token in ["", ".", "no-dot", "!!!.!!!", "a.b.c", &".".repeat(100)] {
|
||||
assert!(!verify_token(&config, token), "{token:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credentials_must_match_exactly() {
|
||||
let config = config(30);
|
||||
assert!(credentials_match(&config, "narl", "hunter2"));
|
||||
assert!(!credentials_match(&config, "narl", "hunter"));
|
||||
assert!(!credentials_match(&config, "narl", "hunter22"));
|
||||
assert!(!credentials_match(&config, "nar", "hunter2"));
|
||||
assert!(!credentials_match(&config, "", ""));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user