Skip to main content

nodedb_types/protocol/
auth.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Authentication method types.
4
5use serde::{Deserialize, Serialize};
6
7/// Authentication method in an `Auth` request.
8#[derive(
9    Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
10)]
11#[serde(tag = "method", rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum AuthMethod {
14    #[serde(rename = "trust")]
15    Trust { username: String },
16    #[serde(rename = "password")]
17    Password { username: String, password: String },
18    #[serde(rename = "api_key")]
19    ApiKey { token: String },
20    /// OIDC bearer token (native / HTTP clients only; NOT pgwire).
21    #[serde(rename = "oidc_bearer")]
22    OidcBearer {
23        token: String,
24        /// Optional provider name hint. When absent the provider is resolved by
25        /// the `iss` claim in the token.
26        #[serde(default)]
27        provider: Option<String>,
28    },
29}
30
31/// Successful auth response payload.
32#[derive(
33    Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
34)]
35pub struct AuthResponse {
36    pub username: String,
37    pub tenant_id: u64,
38}
39
40#[cfg(test)]
41mod tests {
42    use super::AuthMethod;
43
44    #[test]
45    fn auth_method_variants() {
46        let trust = AuthMethod::Trust {
47            username: "admin".into(),
48        };
49        let bytes = zerompk::to_msgpack_vec(&trust).unwrap();
50        let decoded: AuthMethod = zerompk::from_msgpack(&bytes).unwrap();
51        match decoded {
52            AuthMethod::Trust { username } => assert_eq!(username, "admin"),
53            _ => panic!("expected Trust variant"),
54        }
55
56        let pw = AuthMethod::Password {
57            username: "user".into(),
58            password: "secret".into(),
59        };
60        let bytes = zerompk::to_msgpack_vec(&pw).unwrap();
61        let decoded: AuthMethod = zerompk::from_msgpack(&bytes).unwrap();
62        match decoded {
63            AuthMethod::Password { username, password } => {
64                assert_eq!(username, "user");
65                assert_eq!(password, "secret");
66            }
67            _ => panic!("expected Password variant"),
68        }
69    }
70
71    /// A `trust` auth frame that omits `username` entirely must fail to
72    /// decode via serde/JSON — never silently resolve to a default identity.
73    #[test]
74    fn trust_missing_username_rejected_by_serde() {
75        let value = serde_json::json!({ "method": "trust" });
76        let decoded: Result<AuthMethod, _> = serde_json::from_value(value);
77        assert!(
78            decoded.is_err(),
79            "trust auth frame without username must not decode"
80        );
81    }
82
83    /// A well-formed trust frame with an explicit username still decodes.
84    #[test]
85    fn trust_explicit_username_accepted_by_serde() {
86        let value = serde_json::json!({ "method": "trust", "username": "alice" });
87        let decoded: AuthMethod = serde_json::from_value(value).expect("must decode");
88        match decoded {
89            AuthMethod::Trust { username } => assert_eq!(username, "alice"),
90            other => panic!("expected Trust variant, got {other:?}"),
91        }
92    }
93
94    /// Same guarantee on the zerompk wire path: a `trust` variant payload
95    /// with no `username` key must fail to decode, not default to "admin".
96    #[test]
97    fn trust_missing_username_rejected_by_zerompk() {
98        // Hand-encoded MessagePack for zerompk's externally-tagged enum repr:
99        // map-len(1) { "trust" => <payload> }, with the payload map
100        // deliberately empty (no `username` key). Markers: 0x81 = fixmap(1),
101        // 0xA5 = fixstr(5) "trust", 0x80 = fixmap(0).
102        let bytes: &[u8] = &[0x81, 0xA5, b't', b'r', b'u', b's', b't', 0x80];
103
104        let decoded: Result<AuthMethod, _> = zerompk::from_msgpack(bytes);
105        assert!(
106            decoded.is_err(),
107            "trust auth frame without username must not decode via zerompk"
108        );
109    }
110
111    /// A well-formed trust frame with an explicit username still decodes via zerompk.
112    #[test]
113    fn trust_explicit_username_accepted_by_zerompk() {
114        let trust = AuthMethod::Trust {
115            username: "alice".into(),
116        };
117        let bytes = zerompk::to_msgpack_vec(&trust).unwrap();
118        let decoded: AuthMethod = zerompk::from_msgpack(&bytes).unwrap();
119        match decoded {
120            AuthMethod::Trust { username } => assert_eq!(username, "alice"),
121            other => panic!("expected Trust variant, got {other:?}"),
122        }
123    }
124}