Skip to main content

mcp_postgres/
auth.rs

1//! Transport authentication helpers.
2//!
3//! The TCP and HTTP transports are network-exposed and, unlike the stdio
4//! transport, are not implicitly trusted. When an auth token is configured,
5//! every TCP connection and HTTP `/rpc` request must present it.
6
7/// Constant-time byte comparison to avoid leaking the token via timing.
8///
9/// Length is compared first (and short-circuits), which leaks only the
10/// token *length* — standard and acceptable for a shared secret.
11#[inline]
12pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
13    if a.len() != b.len() {
14        return false;
15    }
16    let mut diff = 0u8;
17    for (x, y) in a.iter().zip(b.iter()) {
18        diff |= x ^ y;
19    }
20    diff == 0
21}
22
23/// Verify a presented token against the configured secret in constant time.
24#[inline]
25pub fn verify_token(configured: &str, presented: &str) -> bool {
26    constant_time_eq(configured.as_bytes(), presented.as_bytes())
27}
28
29/// True if `host` refers to a loopback interface (or `localhost`).
30///
31/// Used to decide whether running without an auth token is safe: loopback
32/// binds are only reachable from the local machine, non-loopback binds are not.
33pub fn is_loopback_host(host: &str) -> bool {
34    if host.eq_ignore_ascii_case("localhost") {
35        return true;
36    }
37    host.parse::<std::net::IpAddr>()
38        .map(|ip| ip.is_loopback())
39        .unwrap_or(false)
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn test_constant_time_eq() {
48        assert!(constant_time_eq(b"secret", b"secret"));
49        assert!(!constant_time_eq(b"secret", b"secreu"));
50        assert!(!constant_time_eq(b"secret", b"secr"));
51        assert!(constant_time_eq(b"", b""));
52    }
53
54    #[test]
55    fn test_verify_token() {
56        assert!(verify_token("hunter2", "hunter2"));
57        assert!(!verify_token("hunter2", "Hunter2"));
58        assert!(!verify_token("hunter2", ""));
59    }
60
61    #[test]
62    fn test_is_loopback_host() {
63        assert!(is_loopback_host("127.0.0.1"));
64        assert!(is_loopback_host("::1"));
65        assert!(is_loopback_host("localhost"));
66        assert!(is_loopback_host("LOCALHOST"));
67        assert!(!is_loopback_host("0.0.0.0"));
68        assert!(!is_loopback_host("::"));
69        assert!(!is_loopback_host("192.168.1.10"));
70        assert!(!is_loopback_host("example.com"));
71    }
72}