Skip to main content

openclaw_gateway/auth/
config.rs

1//! Authentication configuration.
2
3use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6
7/// Default token expiry in hours.
8const DEFAULT_TOKEN_EXPIRY_HOURS: u64 = 24;
9/// Default refresh token expiry in days.
10const DEFAULT_REFRESH_EXPIRY_DAYS: u64 = 7;
11
12/// Authentication configuration.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct AuthConfig {
15    /// Whether authentication is enabled.
16    #[serde(default = "default_enabled")]
17    pub enabled: bool,
18
19    /// JWT secret (hex-encoded). Auto-generated if not set.
20    #[serde(default)]
21    pub jwt_secret: Option<String>,
22
23    /// Access token expiry in hours.
24    #[serde(default = "default_token_expiry")]
25    pub token_expiry_hours: u64,
26
27    /// Refresh token expiry in days.
28    #[serde(default = "default_refresh_expiry")]
29    pub refresh_expiry_days: u64,
30
31    /// Require auth for RPC calls.
32    #[serde(default = "default_true")]
33    pub require_auth_for_rpc: bool,
34
35    /// Require auth for WebSocket connections.
36    #[serde(default = "default_true")]
37    pub require_auth_for_ws: bool,
38
39    /// Methods that don't require authentication.
40    #[serde(default = "default_public_methods")]
41    pub public_methods: Vec<String>,
42}
43
44const fn default_enabled() -> bool {
45    true
46}
47
48const fn default_true() -> bool {
49    true
50}
51
52const fn default_token_expiry() -> u64 {
53    DEFAULT_TOKEN_EXPIRY_HOURS
54}
55
56const fn default_refresh_expiry() -> u64 {
57    DEFAULT_REFRESH_EXPIRY_DAYS
58}
59
60fn default_public_methods() -> Vec<String> {
61    vec![
62        "auth.login".to_string(),
63        "setup.status".to_string(),
64        "setup.init".to_string(),
65        "system.health".to_string(),
66        "system.version".to_string(),
67    ]
68}
69
70impl Default for AuthConfig {
71    fn default() -> Self {
72        Self {
73            enabled: default_enabled(),
74            jwt_secret: None,
75            token_expiry_hours: default_token_expiry(),
76            refresh_expiry_days: default_refresh_expiry(),
77            require_auth_for_rpc: default_true(),
78            require_auth_for_ws: default_true(),
79            public_methods: default_public_methods(),
80        }
81    }
82}
83
84impl AuthConfig {
85    /// Create a new auth config builder.
86    #[must_use]
87    pub fn builder() -> AuthConfigBuilder {
88        AuthConfigBuilder::default()
89    }
90
91    /// Get token expiry as Duration.
92    #[must_use]
93    pub const fn token_expiry(&self) -> Duration {
94        Duration::from_secs(self.token_expiry_hours * 3600)
95    }
96
97    /// Get refresh token expiry as Duration.
98    #[must_use]
99    pub const fn refresh_expiry(&self) -> Duration {
100        Duration::from_secs(self.refresh_expiry_days * 24 * 3600)
101    }
102
103    /// Check if a method is public (doesn't require auth).
104    #[must_use]
105    pub fn is_public_method(&self, method: &str) -> bool {
106        self.public_methods.iter().any(|m| m == method)
107    }
108
109    /// Load config from environment variables (overrides).
110    #[must_use]
111    pub fn with_env_overrides(mut self) -> Self {
112        if let Ok(secret) = std::env::var("OPENCLAW_JWT_SECRET") {
113            self.jwt_secret = Some(secret);
114        }
115
116        if std::env::var("OPENCLAW_AUTH_DISABLED")
117            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
118            .unwrap_or(false)
119        {
120            self.enabled = false;
121        }
122
123        self
124    }
125}
126
127/// Builder for `AuthConfig`.
128#[derive(Debug, Default)]
129pub struct AuthConfigBuilder {
130    config: AuthConfig,
131}
132
133impl AuthConfigBuilder {
134    /// Set whether auth is enabled.
135    #[must_use]
136    pub const fn enabled(mut self, enabled: bool) -> Self {
137        self.config.enabled = enabled;
138        self
139    }
140
141    /// Set the JWT secret.
142    #[must_use]
143    pub fn jwt_secret(mut self, secret: impl Into<String>) -> Self {
144        self.config.jwt_secret = Some(secret.into());
145        self
146    }
147
148    /// Set token expiry in hours.
149    #[must_use]
150    pub const fn token_expiry_hours(mut self, hours: u64) -> Self {
151        self.config.token_expiry_hours = hours;
152        self
153    }
154
155    /// Set refresh token expiry in days.
156    #[must_use]
157    pub const fn refresh_expiry_days(mut self, days: u64) -> Self {
158        self.config.refresh_expiry_days = days;
159        self
160    }
161
162    /// Set whether RPC requires auth.
163    #[must_use]
164    pub const fn require_auth_for_rpc(mut self, required: bool) -> Self {
165        self.config.require_auth_for_rpc = required;
166        self
167    }
168
169    /// Set whether WebSocket requires auth.
170    #[must_use]
171    pub const fn require_auth_for_ws(mut self, required: bool) -> Self {
172        self.config.require_auth_for_ws = required;
173        self
174    }
175
176    /// Add a public method (doesn't require auth).
177    #[must_use]
178    pub fn public_method(mut self, method: impl Into<String>) -> Self {
179        self.config.public_methods.push(method.into());
180        self
181    }
182
183    /// Build the config.
184    #[must_use]
185    pub fn build(self) -> AuthConfig {
186        self.config
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_default_config() {
196        let config = AuthConfig::default();
197        assert!(config.enabled);
198        assert!(config.jwt_secret.is_none());
199        assert_eq!(config.token_expiry_hours, 24);
200        assert_eq!(config.refresh_expiry_days, 7);
201    }
202
203    #[test]
204    fn test_public_methods() {
205        let config = AuthConfig::default();
206        assert!(config.is_public_method("auth.login"));
207        assert!(config.is_public_method("system.health"));
208        assert!(!config.is_public_method("session.create"));
209    }
210
211    #[test]
212    fn test_builder() {
213        let config = AuthConfig::builder()
214            .enabled(false)
215            .token_expiry_hours(12)
216            .build();
217
218        assert!(!config.enabled);
219        assert_eq!(config.token_expiry_hours, 12);
220    }
221
222    #[test]
223    fn test_durations() {
224        let config = AuthConfig::default();
225        assert_eq!(config.token_expiry(), Duration::from_secs(24 * 3600));
226        assert_eq!(config.refresh_expiry(), Duration::from_secs(7 * 24 * 3600));
227    }
228}