1use super::*;
2impl JwtPlugin {
3 pub(in crate::plugins::jwt) async fn keys(
4 &self,
5 request: Option<&AuthRequest>,
6 ctx: &AuthContext<impl AuthSchema>,
7 ) -> AuthResult<Vec<Jwk>> {
8 let endpoint = alibi_core::endpoint::current_endpoint_call_context();
9 let path = endpoint
10 .as_ref()
11 .and_then(alibi_core::endpoint::EndpointCall::path)
12 .unwrap_or_else(|| request.map_or("virtual:", AuthRequest::path));
13 self.keys_at_path(request, path, ctx).await
14 }
15
16 pub(in crate::plugins::jwt) async fn keys_in_transaction<S: AuthSchema>(
17 &self,
18 request: Option<&AuthRequest>,
19 ctx: &AuthContext<S>,
20 transaction: Option<&dyn alibi_core::store::AuthTransaction<S>>,
21 ) -> AuthResult<Vec<Jwk>> {
22 if self.config.keyring.is_none()
23 && let Some(transaction) = transaction
24 {
25 transaction.list_jwks().await
26 } else {
27 self.keys(request, ctx).await
28 }
29 }
30
31 pub(in crate::plugins::jwt) async fn keys_at_path(
32 &self,
33 request: Option<&AuthRequest>,
34 path: &str,
35 ctx: &AuthContext<impl AuthSchema>,
36 ) -> AuthResult<Vec<Jwk>> {
37 let endpoint = alibi_core::endpoint::current_endpoint_call_context();
38 match &self.config.keyring {
39 Some(keyring) => {
40 keyring
41 .keys(&JwtKeyringContext {
42 path,
43 request,
44 endpoint: endpoint.as_ref(),
45 })
46 .await
47 }
48 None => ctx.database.list_jwks().await,
49 }
50 }
51
52 pub async fn create_jwk<S: AuthSchema>(
58 &self,
59 config: Option<&JwtKeyPairConfig>,
60 request: Option<&AuthRequest>,
61 ctx: &AuthContext<S>,
62 ) -> AuthResult<Jwk> {
63 self.create_jwk_in_transaction(config, request, ctx, None)
64 .await
65 }
66
67 pub(in crate::plugins::jwt) async fn create_jwk_in_transaction<S: AuthSchema>(
68 &self,
69 config: Option<&JwtKeyPairConfig>,
70 request: Option<&AuthRequest>,
71 ctx: &AuthContext<S>,
72 transaction: Option<&dyn alibi_core::store::AuthTransaction<S>>,
73 ) -> AuthResult<Jwk> {
74 let config = config.unwrap_or(&self.config.key_pair);
75 let (public, private) = crypto::generate(config)?;
76 let private = serde_json::to_string(&private)?;
77 let private_key = if self.config.disable_private_key_encryption {
78 private
79 } else {
80 serde_json::to_string(&encrypt_with_config(&private, &ctx.config)?)?
81 };
82 let now = Utc::now();
83 let data = CreateJwk {
84 id: None,
85 public_key: serde_json::to_string(&public)?,
86 private_key,
87 created_at: now,
88 expires_at: self
89 .config
90 .rotation_interval
91 .filter(|duration| !duration.is_zero())
92 .map(|duration| now + duration),
93 alg: Some(config.algorithm.as_str().to_owned()),
94 crv: config.algorithm.curve().map(str::to_owned),
95 };
96 let endpoint = alibi_core::endpoint::current_endpoint_call_context();
97 match &self.config.keyring {
98 Some(keyring) => {
99 keyring
100 .create_key(
101 data,
102 &JwtKeyringContext {
103 path: endpoint
104 .as_ref()
105 .and_then(alibi_core::endpoint::EndpointCall::path)
106 .unwrap_or_else(|| request.map_or("virtual:", AuthRequest::path)),
107 request,
108 endpoint: endpoint.as_ref(),
109 },
110 )
111 .await
112 }
113 None => match transaction {
114 Some(transaction) => transaction.create_jwk(data).await,
115 None => ctx.database.create_jwk(data).await,
116 },
117 }
118 }
119
120 pub async fn resolve_signing_key<S: AuthSchema>(
126 &self,
127 options: &JwtSignOptions,
128 request: Option<&AuthRequest>,
129 ctx: &AuthContext<S>,
130 ) -> AuthResult<Option<ResolvedJwtSigningKey>> {
131 self.resolve_signing_key_in_transaction(options, request, ctx, None)
132 .await
133 }
134
135 pub(in crate::plugins::jwt) async fn resolve_signing_key_in_transaction<S: AuthSchema>(
136 &self,
137 options: &JwtSignOptions,
138 request: Option<&AuthRequest>,
139 ctx: &AuthContext<S>,
140 transaction: Option<&dyn alibi_core::store::AuthTransaction<S>>,
141 ) -> AuthResult<Option<ResolvedJwtSigningKey>> {
142 if self.config.remote_signer.is_some() {
143 return Ok(None);
144 }
145 let mut keys = if options.signing_key_id.is_some() {
148 Vec::new()
149 } else {
150 self.keys_in_transaction(request, ctx, transaction).await?
151 };
152 keys.sort_by_key(|key| std::cmp::Reverse(key.created_at));
153 let primary = self.config.key_pair.algorithm;
154 let key_alg = |key: &Jwk| {
155 key.alg
156 .as_deref()
157 .map_or(Ok(primary), JwtAlgorithm::from_str)
158 };
159 let now = Utc::now();
160 let live = |key: &&Jwk| key.expires_at.is_none_or(|expiry| expiry > now);
161 let mut minted_unpinned_key = false;
162 let mut key = if let Some(id) = &options.signing_key_id {
163 let key = match &self.config.keyring {
164 Some(_) => self.keys_in_transaction(request, ctx, transaction).await?.into_iter().find(|key| &key.id == id),
165 None => match transaction {Some(transaction)=>transaction.get_jwk_by_id(id).await?,None=>ctx.database.get_jwk_by_id(id).await?},
166 }.ok_or_else(|| AuthError::config(format!("signJWT: signingKeyId \"{id}\" not found in JWKS. The key must be provisioned before it can be referenced.")))?;
167 if let Some(algorithm) = options.signing_algorithm
168 && key_alg(&key)? != algorithm
169 {
170 return Err(AuthError::config(format!(
171 "signJWT: signingKeyId \"{id}\" has a different algorithm than {}",
172 algorithm.as_str()
173 )));
174 }
175 key
176 } else if let Some(algorithm) = options.signing_algorithm {
177 if let Some(key) = keys
178 .iter()
179 .filter(live)
180 .find(|key| key_alg(key).is_ok_and(|alg| alg == algorithm))
181 .cloned()
182 {
183 key
184 } else {
185 let config = self
186 .config
187 .additional_key_pairs
188 .iter()
189 .find(|config| config.algorithm == algorithm)
190 .or_else(|| (primary == algorithm).then_some(&self.config.key_pair))
191 .ok_or_else(|| {
192 AuthError::config(format!(
193 "No signing key configured for {}",
194 algorithm.as_str()
195 ))
196 })?;
197 self.create_jwk_in_transaction(Some(config), request, ctx, transaction)
198 .await?
199 }
200 } else {
201 if let Some(key) = keys
202 .iter()
203 .filter(live)
204 .find(|key| key_alg(key).is_ok_and(|alg| alg == primary))
205 .cloned()
206 {
207 key
208 } else {
209 let mut fallback = self.keys_in_transaction(request, ctx, transaction).await?;
212 fallback.sort_by_key(|key| std::cmp::Reverse(key.created_at));
213 if let Some(key) = fallback
214 .into_iter()
215 .find(|key| key.expires_at.is_none_or(|expiry| expiry > Utc::now()))
216 {
217 key
218 } else {
219 minted_unpinned_key = true;
220 self.create_jwk_in_transaction(None, request, ctx, transaction)
221 .await?
222 }
223 }
224 };
225 if !minted_unpinned_key && key.expires_at.is_some_and(|expiry| expiry < Utc::now()) {
226 if options.signing_key_id.is_some() || options.signing_algorithm.is_some() {
227 return Err(AuthError::config(
228 "signJWT: requested signing key is expired and an explicit kid/alg was provided; not auto-minting a replacement. Rotate the key explicitly.",
229 ));
230 }
231 key = self
232 .create_jwk_in_transaction(None, request, ctx, transaction)
233 .await?;
234 }
235 let private = if self.config.disable_private_key_encryption {
236 key.private_key
237 } else {
238 let encrypted: String = serde_json::from_str(&key.private_key)?;
239 decrypt_with_config(&encrypted, &ctx.config).map_err(|_error| AuthError::config("Failed to decrypt private key. Make sure the secret currently in use is the same as the one used to encrypt the private key. If you are using a different secret, either clean up your JWKS or disable private key encryption."))?
240 };
241 Ok(Some(ResolvedJwtSigningKey {
242 algorithm: key
243 .alg
244 .as_deref()
245 .map(JwtAlgorithm::from_str)
246 .unwrap_or(Ok(primary))?,
247 key_id: key.id,
248 private_key: serde_json::from_str(&private)?,
249 }))
250 }
251}