Skip to main content

rustauth_plugins/jwt/
options.rs

1use std::fmt;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use rustauth_core::context::AuthContext;
7use rustauth_core::db::{Session, User};
8use rustauth_core::error::RustAuthError;
9
10use super::{Jwk, JwkAlgorithm, JwtClaims};
11
12pub type JwtClaimsFuture<'a> =
13    Pin<Box<dyn Future<Output = Result<JwtClaims, RustAuthError>> + Send + 'a>>;
14pub type JwtStringFuture<'a> =
15    Pin<Box<dyn Future<Output = Result<String, RustAuthError>> + Send + 'a>>;
16pub type JwtJwksFuture<'a> =
17    Pin<Box<dyn Future<Output = Result<Vec<Jwk>, RustAuthError>> + Send + 'a>>;
18pub type JwtJwkFuture<'a> = Pin<Box<dyn Future<Output = Result<Jwk, RustAuthError>> + Send + 'a>>;
19
20pub type JwtDefinePayloadHandler =
21    Arc<dyn for<'a> Fn(&'a JwtSessionContext) -> JwtClaimsFuture<'a> + Send + Sync>;
22pub type JwtGetSubjectHandler =
23    Arc<dyn for<'a> Fn(&'a JwtSessionContext) -> JwtStringFuture<'a> + Send + Sync>;
24pub type JwtSignHandler = Arc<dyn Fn(JwtClaims) -> JwtStringFuture<'static> + Send + Sync>;
25pub type JwtGetJwksHandler =
26    Arc<dyn for<'a> Fn(&'a AuthContext) -> JwtJwksFuture<'a> + Send + Sync>;
27pub type JwtCreateJwkHandler =
28    Arc<dyn for<'a> Fn(&'a AuthContext, Jwk) -> JwtJwkFuture<'a> + Send + Sync>;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct JwtSessionContext {
32    pub session: Session,
33    pub user: User,
34}
35
36#[derive(Clone, Default)]
37pub struct JwtOptions {
38    pub jwks: JwtJwksOptions,
39    pub jwt: JwtSigningOptions,
40    pub adapter: JwtAdapterOptions,
41    pub disable_setting_jwt_header: bool,
42    pub schema: super::schema::JwtSchemaOptions,
43}
44
45#[derive(Debug, Clone)]
46pub struct JwtJwksOptions {
47    pub remote_url: Option<String>,
48    pub key_pair_algorithm: Option<JwkAlgorithm>,
49    pub rsa_modulus_length: Option<u32>,
50    pub disable_private_key_encryption: bool,
51    pub rotation_interval: Option<i64>,
52    pub grace_period: i64,
53    pub jwks_path: String,
54}
55
56impl Default for JwtJwksOptions {
57    fn default() -> Self {
58        Self {
59            remote_url: None,
60            key_pair_algorithm: Some(JwkAlgorithm::EdDsa),
61            rsa_modulus_length: None,
62            disable_private_key_encryption: false,
63            rotation_interval: None,
64            grace_period: 60 * 60 * 24 * 30,
65            jwks_path: "/jwks".to_owned(),
66        }
67    }
68}
69
70#[derive(Clone, Default)]
71pub struct JwtSigningOptions {
72    pub issuer: Option<String>,
73    pub audience: Option<Vec<String>>,
74    pub expiration_time: Option<super::TimeInput>,
75    pub define_payload: Option<JwtDefinePayloadHandler>,
76    pub get_subject: Option<JwtGetSubjectHandler>,
77    pub sign: Option<JwtSignHandler>,
78}
79
80impl fmt::Debug for JwtSigningOptions {
81    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82        formatter
83            .debug_struct("JwtSigningOptions")
84            .field("issuer", &self.issuer)
85            .field("audience", &self.audience)
86            .field("expiration_time", &self.expiration_time)
87            .field(
88                "define_payload",
89                &self.define_payload.as_ref().map(|_| "<define-payload>"),
90            )
91            .field(
92                "get_subject",
93                &self.get_subject.as_ref().map(|_| "<get-subject>"),
94            )
95            .field("sign", &self.sign.as_ref().map(|_| "<sign-handler>"))
96            .finish()
97    }
98}
99
100#[derive(Clone, Default)]
101pub struct JwtAdapterOptions {
102    pub get_jwks: Option<JwtGetJwksHandler>,
103    pub create_jwk: Option<JwtCreateJwkHandler>,
104}
105
106impl fmt::Debug for JwtAdapterOptions {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        formatter
109            .debug_struct("JwtAdapterOptions")
110            .field("get_jwks", &self.get_jwks.as_ref().map(|_| "<get-jwks>"))
111            .field(
112                "create_jwk",
113                &self.create_jwk.as_ref().map(|_| "<create-jwk>"),
114            )
115            .finish()
116    }
117}
118
119impl JwtOptions {
120    #[must_use]
121    pub fn builder() -> JwtOptionsBuilder {
122        JwtOptionsBuilder::default()
123    }
124
125    pub fn validate(&self) -> Result<(), RustAuthError> {
126        if self.jwt.sign.is_some() && self.jwks.remote_url.is_none() {
127            return Err(RustAuthError::InvalidConfig(
128                "options.jwks.remoteUrl must be set when using options.jwt.sign".to_owned(),
129            ));
130        }
131        if self.jwks.remote_url.is_some() && self.jwks.key_pair_algorithm.is_none() {
132            return Err(RustAuthError::InvalidConfig(
133                "options.jwks.keyPairConfig.alg must be specified when using remoteUrl".to_owned(),
134            ));
135        }
136        if let Some(modulus_length) = self.jwks.rsa_modulus_length {
137            if modulus_length < 2048 {
138                return Err(RustAuthError::InvalidConfig(
139                    "options.jwks.keyPairConfig.modulusLength must be at least 2048".to_owned(),
140                ));
141            }
142        }
143        let path = &self.jwks.jwks_path;
144        if path.is_empty() || !path.starts_with('/') || path.contains("..") {
145            return Err(RustAuthError::InvalidConfig(
146                "options.jwks.jwksPath must be a non-empty string starting with '/' and not contain '..'"
147                    .to_owned(),
148            ));
149        }
150        Ok(())
151    }
152
153    pub(crate) fn algorithm(&self) -> JwkAlgorithm {
154        self.jwks.key_pair_algorithm.unwrap_or(JwkAlgorithm::EdDsa)
155    }
156}
157
158#[derive(Clone, Default)]
159pub struct JwtOptionsBuilder {
160    jwks: Option<JwtJwksOptions>,
161    jwt: Option<JwtSigningOptions>,
162    adapter: Option<JwtAdapterOptions>,
163    disable_setting_jwt_header: Option<bool>,
164    schema: Option<super::schema::JwtSchemaOptions>,
165}
166
167impl JwtOptionsBuilder {
168    #[must_use]
169    pub fn jwks(mut self, jwks: JwtJwksOptions) -> Self {
170        self.jwks = Some(jwks);
171        self
172    }
173
174    #[must_use]
175    pub fn jwt(mut self, jwt: JwtSigningOptions) -> Self {
176        self.jwt = Some(jwt);
177        self
178    }
179
180    #[must_use]
181    pub fn adapter(mut self, adapter: JwtAdapterOptions) -> Self {
182        self.adapter = Some(adapter);
183        self
184    }
185
186    #[must_use]
187    pub fn disable_setting_jwt_header(mut self, disabled: bool) -> Self {
188        self.disable_setting_jwt_header = Some(disabled);
189        self
190    }
191
192    #[must_use]
193    pub fn schema(mut self, schema: super::schema::JwtSchemaOptions) -> Self {
194        self.schema = Some(schema);
195        self
196    }
197
198    pub fn build(self) -> Result<JwtOptions, RustAuthError> {
199        let defaults = JwtOptions::default();
200        let options = JwtOptions {
201            jwks: self.jwks.unwrap_or(defaults.jwks),
202            jwt: self.jwt.unwrap_or(defaults.jwt),
203            adapter: self.adapter.unwrap_or(defaults.adapter),
204            disable_setting_jwt_header: self
205                .disable_setting_jwt_header
206                .unwrap_or(defaults.disable_setting_jwt_header),
207            schema: self.schema.unwrap_or(defaults.schema),
208        };
209        options.validate()?;
210        Ok(options)
211    }
212}