use crate::application::Application;
use crate::auth::{
BasicAuthLayer, Claims, JwtLayer, base64_decode, base64_encode, base64url_encode,
build_jwt, extract_bearer_token, verify_jwt,
};
use crate::core::New;
use crate::error::IntoResponse;
use crate::header::Header;
use crate::http::VERSION;
use crate::middleware::{Middleware, WithMiddleware};
use crate::request::{METHOD, Request};
use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
use crate::server::{Address, ConnectionInfo};
fn conn() -> ConnectionInfo {
ConnectionInfo {
client: Address { ip: "127.0.0.1".to_string(), port: 0 },
server: Address { ip: "127.0.0.1".to_string(), port: 7878 },
request_size: 16000,
}
}
fn get(uri: &str) -> Request {
Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers: vec![],
body: vec![],
}
}
fn with_header(mut req: Request, name: &str, value: &str) -> Request {
req.headers.push(Header { name: name.to_string(), value: value.to_string() });
req
}
fn basic_header(user: &str, pass: &str) -> String {
format!("Basic {}", base64_encode(format!("{}:{}", user, pass).as_bytes()))
}
struct OkApp;
impl Application for OkApp {
fn execute(&self, _: &Request, _: &ConnectionInfo) -> Result<Response, String> {
let mut r = Response::new();
r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
Ok(r)
}
}
#[test]
fn base64_roundtrip_standard() {
let original = b"Hello, World!";
let encoded = base64_encode(original);
let decoded = base64_decode(&encoded).unwrap();
assert_eq!(original, decoded.as_slice());
}
#[test]
fn base64_roundtrip_url_safe() {
let original = b"\xfb\xff\xfe"; let encoded = base64url_encode(original);
let decoded = base64_decode(&encoded).unwrap();
assert_eq!(original, decoded.as_slice());
}
#[test]
fn base64_decode_with_padding() {
assert_eq!(base64_decode("TWFu").unwrap(), b"Man");
assert_eq!(base64_decode("TWE=").unwrap(), b"Ma");
assert_eq!(base64_decode("TQ==").unwrap(), b"M");
}
const SECRET: &[u8] = b"test-secret";
const FAR_FUTURE: u64 = 9_999_999_999;
#[test]
fn valid_jwt_returns_claims() {
let claims_json = format!(r#"{{"sub":"user1","exp":{}}}"#, FAR_FUTURE);
let token = build_jwt(&claims_json, SECRET);
let claims = verify_jwt(&token, SECRET).unwrap();
assert_eq!(Some("user1".to_string()), claims.sub);
assert_eq!(Some(FAR_FUTURE), claims.exp);
}
#[test]
fn wrong_secret_returns_none() {
let token = build_jwt(r#"{"sub":"u"}"#, SECRET);
assert!(verify_jwt(&token, b"wrong-secret").is_none());
}
#[test]
fn tampered_payload_returns_none() {
let token = build_jwt(r#"{"sub":"u"}"#, SECRET);
let mut parts: Vec<&str> = token.splitn(3, '.').collect();
parts[1] = "dGFtcGVyZWQ"; let tampered = parts.join(".");
assert!(verify_jwt(&tampered, SECRET).is_none());
}
#[test]
fn expired_token_returns_none() {
let token = build_jwt(r#"{"sub":"u","exp":1}"#, SECRET); assert!(verify_jwt(&token, SECRET).is_none());
}
#[test]
fn token_without_exp_is_accepted() {
let token = build_jwt(r#"{"sub":"no-exp"}"#, SECRET);
assert!(verify_jwt(&token, SECRET).is_some());
}
#[test]
fn wrong_algorithm_returns_none() {
let header = base64url_encode(br#"{"alg":"RS256","typ":"JWT"}"#);
let payload = base64url_encode(br#"{"sub":"u"}"#);
let fake_sig = base64url_encode(b"not-a-real-sig");
let token = format!("{}.{}.{}", header, payload, fake_sig);
assert!(verify_jwt(&token, SECRET).is_none());
}
#[test]
fn malformed_token_returns_none() {
assert!(verify_jwt("not.a.jwt.with.extra.dots", SECRET).is_none());
assert!(verify_jwt("onlytwoparts", SECRET).is_none());
assert!(verify_jwt("", SECRET).is_none());
}
#[test]
fn claims_is_valid_at_before_exp() {
let c = Claims { sub: None, exp: Some(FAR_FUTURE), raw: String::new() };
assert!(c.is_valid_at(1_000_000));
}
#[test]
fn claims_is_valid_at_after_exp() {
let c = Claims { sub: None, exp: Some(1), raw: String::new() };
assert!(!c.is_valid_at(1_000_000));
}
#[test]
fn claims_no_exp_always_valid() {
let c = Claims { sub: None, exp: None, raw: String::new() };
assert!(c.is_valid_at(u64::MAX));
}
#[test]
fn extract_bearer_token_from_header() {
let req = with_header(get("/"), "Authorization", "Bearer tok123");
assert_eq!(Some("tok123".to_string()), extract_bearer_token(&req));
}
#[test]
fn extract_bearer_token_absent_header() {
assert!(extract_bearer_token(&get("/")).is_none());
}
#[test]
fn basic_auth_missing_header_returns_401_with_challenge() {
let layer = BasicAuthLayer::new(|_, _| true);
let resp = layer.handle(&get("/"), &conn(), &OkApp).unwrap();
assert_eq!(401, resp.status_code);
let has_challenge = resp.headers.iter().any(|h| h.name == "WWW-Authenticate");
assert!(has_challenge, "expected WWW-Authenticate header");
}
#[test]
fn basic_auth_wrong_password_returns_401() {
let layer = BasicAuthLayer::new(|user, pass| user == "admin" && pass == "correct");
let req = with_header(get("/"), "Authorization", &basic_header("admin", "wrong"));
let resp = layer.handle(&req, &conn(), &OkApp).unwrap();
assert_eq!(401, resp.status_code);
}
#[test]
fn basic_auth_correct_credentials_passes_through() {
let layer = BasicAuthLayer::new(|user, pass| user == "admin" && pass == "secret");
let req = with_header(get("/"), "Authorization", &basic_header("admin", "secret"));
let resp = layer.handle(&req, &conn(), &OkApp).unwrap();
assert_eq!(200, resp.status_code);
}
#[test]
fn basic_auth_password_with_colon() {
let layer = BasicAuthLayer::new(|user, pass| user == "u" && pass == "p:with:colons");
let req = with_header(get("/"), "Authorization", &basic_header("u", "p:with:colons"));
let resp = layer.handle(&req, &conn(), &OkApp).unwrap();
assert_eq!(200, resp.status_code);
}
#[test]
fn basic_auth_via_middleware_stack() {
use crate::app::App;
let app = WithMiddleware::new(OkApp).wrap(
BasicAuthLayer::new(|user, pass| user == "a" && pass == "b"),
);
let req = with_header(get("/does-not-exist"), "Authorization", &basic_header("a", "b"));
let resp = app.execute(&req, &conn()).unwrap();
assert_eq!(200, resp.status_code);
}
#[test]
fn jwt_layer_valid_token_passes_through() {
let token = build_jwt(&format!(r#"{{"sub":"u","exp":{}}}"#, FAR_FUTURE), SECRET);
let layer = JwtLayer::new(SECRET);
let req = with_header(get("/"), "Authorization", &format!("Bearer {}", token));
let resp = layer.handle(&req, &conn(), &OkApp).unwrap();
assert_eq!(200, resp.status_code);
}
#[test]
fn jwt_layer_missing_token_returns_401() {
let layer = JwtLayer::new(SECRET);
let resp = layer.handle(&get("/"), &conn(), &OkApp).unwrap();
assert_eq!(401, resp.status_code);
}
#[test]
fn jwt_layer_invalid_token_returns_401() {
let layer = JwtLayer::new(SECRET);
let req = with_header(get("/"), "Authorization", "Bearer not.a.valid.token");
let resp = layer.handle(&req, &conn(), &OkApp).unwrap();
assert_eq!(401, resp.status_code);
}
#[test]
fn jwt_layer_expired_token_returns_401() {
let token = build_jwt(r#"{"sub":"u","exp":1}"#, SECRET);
let layer = JwtLayer::new(SECRET);
let req = with_header(get("/"), "Authorization", &format!("Bearer {}", token));
let resp = layer.handle(&req, &conn(), &OkApp).unwrap();
assert_eq!(401, resp.status_code);
}