fraiseql_core/security/auth_middleware/config.rs
1//! Authentication configuration.
2
3use serde::{Deserialize, Serialize};
4
5use super::signing_key::SigningKey;
6
7// ============================================================================
8// Authentication Configuration
9// ============================================================================
10
11/// Authentication configuration
12///
13/// Defines what authentication requirements must be met for a request.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AuthConfig {
16 /// If true, authentication is required for all requests
17 pub required: bool,
18
19 /// Token lifetime in seconds (for validation purposes)
20 pub token_expiry_secs: u64,
21
22 /// Signing key for JWT signature verification.
23 ///
24 /// If `None`, signature verification is disabled (NOT RECOMMENDED for production).
25 /// Use `SigningKey::hs256()` or `SigningKey::rs256_pem()` to enable verification.
26 #[serde(skip)]
27 pub signing_key: Option<SigningKey>,
28
29 /// Expected issuer (iss claim).
30 ///
31 /// If set, tokens must have this value in their `iss` claim.
32 #[serde(default)]
33 pub issuer: Option<String>,
34
35 /// Expected audience (aud claim).
36 ///
37 /// If set, tokens must have this value in their `aud` claim.
38 #[serde(default)]
39 pub audience: Option<String>,
40
41 /// Clock skew tolerance in seconds.
42 ///
43 /// Allow this many seconds of clock difference when validating exp/nbf claims.
44 /// Default: 60 seconds
45 #[serde(default = "default_clock_skew")]
46 pub clock_skew_secs: u64,
47}
48
49pub(super) const fn default_clock_skew() -> u64 {
50 60
51}
52
53impl AuthConfig {
54 /// Create a permissive authentication configuration (auth optional)
55 ///
56 /// - Authentication optional
57 /// - Token expiry: 3600 seconds (1 hour)
58 /// - No signature verification (for testing only)
59 #[must_use]
60 pub const fn permissive() -> Self {
61 Self {
62 required: false,
63 token_expiry_secs: 3600,
64 signing_key: None,
65 issuer: None,
66 audience: None,
67 clock_skew_secs: default_clock_skew(),
68 }
69 }
70
71 /// Create a standard authentication configuration (auth required)
72 ///
73 /// - Authentication required
74 /// - Token expiry: 3600 seconds (1 hour)
75 /// - No signature verification (configure `signing_key` for production)
76 #[must_use]
77 pub const fn standard() -> Self {
78 Self {
79 required: true,
80 token_expiry_secs: 3600,
81 signing_key: None,
82 issuer: None,
83 audience: None,
84 clock_skew_secs: default_clock_skew(),
85 }
86 }
87
88 /// Create a strict authentication configuration (auth required, short expiry)
89 ///
90 /// - Authentication required
91 /// - Token expiry: 1800 seconds (30 minutes)
92 /// - No signature verification (configure `signing_key` for production)
93 #[must_use]
94 pub const fn strict() -> Self {
95 Self {
96 required: true,
97 token_expiry_secs: 1800,
98 signing_key: None,
99 issuer: None,
100 audience: None,
101 clock_skew_secs: default_clock_skew(),
102 }
103 }
104
105 /// Create a configuration with HS256 signing key.
106 ///
107 /// This is the recommended configuration for production when using
108 /// symmetric key signing (internal services).
109 #[must_use]
110 pub fn with_hs256(secret: &str) -> Self {
111 Self {
112 required: true,
113 token_expiry_secs: 3600,
114 signing_key: Some(SigningKey::hs256(secret)),
115 issuer: None,
116 audience: None,
117 clock_skew_secs: default_clock_skew(),
118 }
119 }
120
121 /// Create a configuration with RS256 signing key from PEM.
122 ///
123 /// This is the recommended configuration for production when using
124 /// asymmetric key signing (external identity providers).
125 #[must_use]
126 pub fn with_rs256_pem(pem: &str) -> Self {
127 Self {
128 required: true,
129 token_expiry_secs: 3600,
130 signing_key: Some(SigningKey::rs256_pem(pem)),
131 issuer: None,
132 audience: None,
133 clock_skew_secs: default_clock_skew(),
134 }
135 }
136
137 /// Set the expected issuer.
138 #[must_use]
139 pub fn with_issuer(mut self, issuer: &str) -> Self {
140 self.issuer = Some(issuer.to_string());
141 self
142 }
143
144 /// Set the expected audience.
145 #[must_use]
146 pub fn with_audience(mut self, audience: &str) -> Self {
147 self.audience = Some(audience.to_string());
148 self
149 }
150
151 /// Check if signature verification is enabled.
152 #[must_use]
153 pub const fn has_signing_key(&self) -> bool {
154 self.signing_key.is_some()
155 }
156}