Skip to main content

openauth_oauth_provider/
options.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::{Arc, RwLock};
5
6use openauth_core::db::{Session, User};
7use openauth_core::error::OpenAuthError;
8use openauth_core::options::RateLimitRule;
9use openauth_core::plugin::AuthPlugin;
10use serde_json::{Map, Value};
11use thiserror::Error;
12
13use crate::models::SchemaClient;
14
15type ClientReferenceFuture =
16    Pin<Box<dyn Future<Output = Result<Option<String>, OpenAuthError>> + Send>>;
17type ClientPrivilegesFuture = Pin<Box<dyn Future<Output = Result<bool, OpenAuthError>> + Send>>;
18type JsonObjectFuture =
19    Pin<Box<dyn Future<Output = Result<Map<String, Value>, OpenAuthError>> + Send>>;
20type OptionalStringFuture =
21    Pin<Box<dyn Future<Output = Result<Option<String>, OpenAuthError>> + Send>>;
22type RequestUriFuture =
23    Pin<Box<dyn Future<Output = Result<Option<Vec<(String, String)>>, OpenAuthError>> + Send>>;
24type StringGeneratorFuture = Pin<Box<dyn Future<Output = Result<String, OpenAuthError>> + Send>>;
25type BoolResolverFuture = Pin<Box<dyn Future<Output = Result<bool, OpenAuthError>> + Send>>;
26type RefreshTokenEncodeFuture = Pin<Box<dyn Future<Output = Result<String, OpenAuthError>> + Send>>;
27type RefreshTokenDecodeFuture =
28    Pin<Box<dyn Future<Output = Result<RefreshTokenFormatDecodeOutput, OpenAuthError>> + Send>>;
29
30/// Input passed to the OAuth client reference resolver.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct ClientReferenceInput {
33    pub user: Option<User>,
34    pub session: Option<Session>,
35}
36
37/// Async callback that resolves the non-user owner of OAuth clients.
38#[derive(Clone)]
39pub struct ClientReferenceResolver {
40    resolver: Arc<dyn Fn(ClientReferenceInput) -> ClientReferenceFuture + Send + Sync>,
41}
42
43impl ClientReferenceResolver {
44    /// Create a resolver from an async function.
45    pub fn new<F, Fut>(resolver: F) -> Self
46    where
47        F: Fn(ClientReferenceInput) -> Fut + Send + Sync + 'static,
48        Fut: Future<Output = Result<Option<String>, OpenAuthError>> + Send + 'static,
49    {
50        Self {
51            resolver: Arc::new(move |input| Box::pin(resolver(input))),
52        }
53    }
54
55    pub async fn resolve(
56        &self,
57        input: ClientReferenceInput,
58    ) -> Result<Option<String>, OpenAuthError> {
59        (self.resolver)(input).await
60    }
61}
62
63impl std::fmt::Debug for ClientReferenceResolver {
64    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        formatter.write_str("ClientReferenceResolver(..)")
66    }
67}
68
69impl PartialEq for ClientReferenceResolver {
70    fn eq(&self, _other: &Self) -> bool {
71        true
72    }
73}
74
75impl Eq for ClientReferenceResolver {}
76
77#[derive(Clone, Default)]
78pub struct TrustedClientCache {
79    clients: Arc<RwLock<BTreeMap<String, SchemaClient>>>,
80}
81
82impl TrustedClientCache {
83    pub fn get(&self, client_id: &str) -> Result<Option<SchemaClient>, OpenAuthError> {
84        let clients = self
85            .clients
86            .read()
87            .map_err(|_| OpenAuthError::Api("trusted client cache lock poisoned".to_owned()))?;
88        Ok(clients.get(client_id).cloned())
89    }
90
91    pub fn insert(&self, client: SchemaClient) -> Result<(), OpenAuthError> {
92        let mut clients = self
93            .clients
94            .write()
95            .map_err(|_| OpenAuthError::Api("trusted client cache lock poisoned".to_owned()))?;
96        clients.insert(client.client_id.clone(), client);
97        Ok(())
98    }
99}
100
101impl std::fmt::Debug for TrustedClientCache {
102    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        formatter.write_str("TrustedClientCache(..)")
104    }
105}
106
107impl PartialEq for TrustedClientCache {
108    fn eq(&self, _other: &Self) -> bool {
109        true
110    }
111}
112
113impl Eq for TrustedClientCache {}
114
115/// OAuth client-management action checked by [`ClientPrivilegesResolver`].
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum ClientPrivilegeAction {
118    Create,
119    Read,
120    Update,
121    Delete,
122    List,
123    Rotate,
124}
125
126/// Input passed to the OAuth client privileges resolver.
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct ClientPrivilegesInput {
129    pub action: ClientPrivilegeAction,
130    pub user: Option<User>,
131    pub session: Option<Session>,
132}
133
134/// Async callback that authorizes OAuth client-management actions.
135#[derive(Clone)]
136pub struct ClientPrivilegesResolver {
137    resolver: Arc<dyn Fn(ClientPrivilegesInput) -> ClientPrivilegesFuture + Send + Sync>,
138}
139
140impl ClientPrivilegesResolver {
141    pub fn new<F, Fut>(resolver: F) -> Self
142    where
143        F: Fn(ClientPrivilegesInput) -> Fut + Send + Sync + 'static,
144        Fut: Future<Output = Result<bool, OpenAuthError>> + Send + 'static,
145    {
146        Self {
147            resolver: Arc::new(move |input| Box::pin(resolver(input))),
148        }
149    }
150
151    pub async fn resolve(&self, input: ClientPrivilegesInput) -> Result<bool, OpenAuthError> {
152        (self.resolver)(input).await
153    }
154}
155
156impl std::fmt::Debug for ClientPrivilegesResolver {
157    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        formatter.write_str("ClientPrivilegesResolver(..)")
159    }
160}
161
162impl PartialEq for ClientPrivilegesResolver {
163    fn eq(&self, _other: &Self) -> bool {
164        true
165    }
166}
167
168impl Eq for ClientPrivilegesResolver {}
169
170/// Input passed to custom client secret hash callbacks.
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct ClientSecretHashInput {
173    pub secret: String,
174}
175
176/// Async callback that hashes client secrets before persistence.
177#[derive(Clone)]
178pub struct ClientSecretHashResolver {
179    resolver: Arc<dyn Fn(ClientSecretHashInput) -> StringGeneratorFuture + Send + Sync>,
180}
181
182impl ClientSecretHashResolver {
183    pub fn new<F, Fut>(resolver: F) -> Self
184    where
185        F: Fn(ClientSecretHashInput) -> Fut + Send + Sync + 'static,
186        Fut: Future<Output = Result<String, OpenAuthError>> + Send + 'static,
187    {
188        Self {
189            resolver: Arc::new(move |input| Box::pin(resolver(input))),
190        }
191    }
192
193    pub async fn resolve(&self, input: ClientSecretHashInput) -> Result<String, OpenAuthError> {
194        (self.resolver)(input).await
195    }
196}
197
198impl std::fmt::Debug for ClientSecretHashResolver {
199    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        formatter.write_str("ClientSecretHashResolver(..)")
201    }
202}
203
204impl PartialEq for ClientSecretHashResolver {
205    fn eq(&self, _other: &Self) -> bool {
206        true
207    }
208}
209
210impl Eq for ClientSecretHashResolver {}
211
212/// Input passed to custom client secret verification callbacks.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct ClientSecretVerifyInput {
215    pub secret: String,
216    pub stored_hash: String,
217}
218
219/// Async callback that verifies client secrets against stored values.
220#[derive(Clone)]
221pub struct ClientSecretVerifyResolver {
222    resolver: Arc<dyn Fn(ClientSecretVerifyInput) -> BoolResolverFuture + Send + Sync>,
223}
224
225impl ClientSecretVerifyResolver {
226    pub fn new<F, Fut>(resolver: F) -> Self
227    where
228        F: Fn(ClientSecretVerifyInput) -> Fut + Send + Sync + 'static,
229        Fut: Future<Output = Result<bool, OpenAuthError>> + Send + 'static,
230    {
231        Self {
232            resolver: Arc::new(move |input| Box::pin(resolver(input))),
233        }
234    }
235
236    pub async fn resolve(&self, input: ClientSecretVerifyInput) -> Result<bool, OpenAuthError> {
237        (self.resolver)(input).await
238    }
239}
240
241impl std::fmt::Debug for ClientSecretVerifyResolver {
242    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        formatter.write_str("ClientSecretVerifyResolver(..)")
244    }
245}
246
247impl PartialEq for ClientSecretVerifyResolver {
248    fn eq(&self, _other: &Self) -> bool {
249        true
250    }
251}
252
253impl Eq for ClientSecretVerifyResolver {}
254
255/// Input passed to custom OAuth token hash callbacks.
256#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct TokenHashInput {
258    pub token: String,
259    pub token_type: String,
260}
261
262/// Async callback that hashes OAuth tokens before lookup or persistence.
263#[derive(Clone)]
264pub struct TokenHashResolver {
265    resolver: Arc<dyn Fn(TokenHashInput) -> StringGeneratorFuture + Send + Sync>,
266}
267
268impl TokenHashResolver {
269    pub fn new<F, Fut>(resolver: F) -> Self
270    where
271        F: Fn(TokenHashInput) -> Fut + Send + Sync + 'static,
272        Fut: Future<Output = Result<String, OpenAuthError>> + Send + 'static,
273    {
274        Self {
275            resolver: Arc::new(move |input| Box::pin(resolver(input))),
276        }
277    }
278
279    pub async fn resolve(&self, input: TokenHashInput) -> Result<String, OpenAuthError> {
280        (self.resolver)(input).await
281    }
282}
283
284impl std::fmt::Debug for TokenHashResolver {
285    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        formatter.write_str("TokenHashResolver(..)")
287    }
288}
289
290impl PartialEq for TokenHashResolver {
291    fn eq(&self, _other: &Self) -> bool {
292        true
293    }
294}
295
296impl Eq for TokenHashResolver {}
297
298/// Input passed to advanced prompt redirect callbacks.
299#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct PromptRedirectInput {
301    pub user: User,
302    pub session: Session,
303    pub scopes: Vec<String>,
304}
305
306/// Async callback that may redirect an advanced prompt step to a page.
307#[derive(Clone)]
308pub struct PromptRedirectResolver {
309    resolver: Arc<dyn Fn(PromptRedirectInput) -> OptionalStringFuture + Send + Sync>,
310}
311
312impl PromptRedirectResolver {
313    pub fn new<F, Fut>(resolver: F) -> Self
314    where
315        F: Fn(PromptRedirectInput) -> Fut + Send + Sync + 'static,
316        Fut: Future<Output = Result<Option<String>, OpenAuthError>> + Send + 'static,
317    {
318        Self {
319            resolver: Arc::new(move |input| Box::pin(resolver(input))),
320        }
321    }
322
323    pub async fn resolve(
324        &self,
325        input: PromptRedirectInput,
326    ) -> Result<Option<String>, OpenAuthError> {
327        (self.resolver)(input).await
328    }
329}
330
331impl std::fmt::Debug for PromptRedirectResolver {
332    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        formatter.write_str("PromptRedirectResolver(..)")
334    }
335}
336
337impl PartialEq for PromptRedirectResolver {
338    fn eq(&self, _other: &Self) -> bool {
339        true
340    }
341}
342
343impl Eq for PromptRedirectResolver {}
344
345/// Input passed to custom ID token claim callbacks.
346#[derive(Debug, Clone, PartialEq)]
347pub struct CustomIdTokenClaimsInput {
348    pub user: User,
349    pub scopes: Vec<String>,
350    pub metadata: Option<Value>,
351}
352
353/// Input passed to custom access token claim callbacks.
354#[derive(Debug, Clone, PartialEq)]
355pub struct CustomAccessTokenClaimsInput {
356    pub user: Option<User>,
357    pub reference_id: Option<String>,
358    pub scopes: Vec<String>,
359    pub resource: Vec<String>,
360    pub metadata: Option<Value>,
361}
362
363/// Async callback that provides additional access token or introspection claims.
364#[derive(Clone)]
365pub struct CustomAccessTokenClaimsResolver {
366    resolver: Arc<dyn Fn(CustomAccessTokenClaimsInput) -> JsonObjectFuture + Send + Sync>,
367}
368
369impl CustomAccessTokenClaimsResolver {
370    pub fn new<F, Fut>(resolver: F) -> Self
371    where
372        F: Fn(CustomAccessTokenClaimsInput) -> Fut + Send + Sync + 'static,
373        Fut: Future<Output = Result<Map<String, Value>, OpenAuthError>> + Send + 'static,
374    {
375        Self {
376            resolver: Arc::new(move |input| Box::pin(resolver(input))),
377        }
378    }
379
380    pub async fn resolve(
381        &self,
382        input: CustomAccessTokenClaimsInput,
383    ) -> Result<Map<String, Value>, OpenAuthError> {
384        (self.resolver)(input).await
385    }
386}
387
388impl std::fmt::Debug for CustomAccessTokenClaimsResolver {
389    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390        formatter.write_str("CustomAccessTokenClaimsResolver(..)")
391    }
392}
393
394impl PartialEq for CustomAccessTokenClaimsResolver {
395    fn eq(&self, _other: &Self) -> bool {
396        true
397    }
398}
399
400impl Eq for CustomAccessTokenClaimsResolver {}
401
402/// Async callback that provides additional ID token claims.
403#[derive(Clone)]
404pub struct CustomIdTokenClaimsResolver {
405    resolver: Arc<dyn Fn(CustomIdTokenClaimsInput) -> JsonObjectFuture + Send + Sync>,
406}
407
408impl CustomIdTokenClaimsResolver {
409    pub fn new<F, Fut>(resolver: F) -> Self
410    where
411        F: Fn(CustomIdTokenClaimsInput) -> Fut + Send + Sync + 'static,
412        Fut: Future<Output = Result<Map<String, Value>, OpenAuthError>> + Send + 'static,
413    {
414        Self {
415            resolver: Arc::new(move |input| Box::pin(resolver(input))),
416        }
417    }
418
419    pub async fn resolve(
420        &self,
421        input: CustomIdTokenClaimsInput,
422    ) -> Result<Map<String, Value>, OpenAuthError> {
423        (self.resolver)(input).await
424    }
425}
426
427impl std::fmt::Debug for CustomIdTokenClaimsResolver {
428    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429        formatter.write_str("CustomIdTokenClaimsResolver(..)")
430    }
431}
432
433impl PartialEq for CustomIdTokenClaimsResolver {
434    fn eq(&self, _other: &Self) -> bool {
435        true
436    }
437}
438
439impl Eq for CustomIdTokenClaimsResolver {}
440
441/// Input passed to custom token response field callbacks.
442#[derive(Debug, Clone, PartialEq)]
443pub struct CustomTokenResponseFieldsInput {
444    pub grant_type: GrantType,
445    pub user: Option<User>,
446    pub scopes: Vec<String>,
447    pub metadata: Option<Value>,
448}
449
450/// Async callback that provides extra token response fields.
451#[derive(Clone)]
452pub struct CustomTokenResponseFieldsResolver {
453    resolver: Arc<dyn Fn(CustomTokenResponseFieldsInput) -> JsonObjectFuture + Send + Sync>,
454}
455
456impl CustomTokenResponseFieldsResolver {
457    pub fn new<F, Fut>(resolver: F) -> Self
458    where
459        F: Fn(CustomTokenResponseFieldsInput) -> Fut + Send + Sync + 'static,
460        Fut: Future<Output = Result<Map<String, Value>, OpenAuthError>> + Send + 'static,
461    {
462        Self {
463            resolver: Arc::new(move |input| Box::pin(resolver(input))),
464        }
465    }
466
467    pub async fn resolve(
468        &self,
469        input: CustomTokenResponseFieldsInput,
470    ) -> Result<Map<String, Value>, OpenAuthError> {
471        (self.resolver)(input).await
472    }
473}
474
475impl std::fmt::Debug for CustomTokenResponseFieldsResolver {
476    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        formatter.write_str("CustomTokenResponseFieldsResolver(..)")
478    }
479}
480
481impl PartialEq for CustomTokenResponseFieldsResolver {
482    fn eq(&self, _other: &Self) -> bool {
483        true
484    }
485}
486
487impl Eq for CustomTokenResponseFieldsResolver {}
488
489/// Input passed to custom userinfo claim callbacks.
490#[derive(Debug, Clone, PartialEq)]
491pub struct CustomUserInfoClaimsInput {
492    pub user: User,
493    pub scopes: Vec<String>,
494    pub jwt: Value,
495}
496
497/// Async callback that provides additional userinfo claims.
498#[derive(Clone)]
499pub struct CustomUserInfoClaimsResolver {
500    resolver: Arc<dyn Fn(CustomUserInfoClaimsInput) -> JsonObjectFuture + Send + Sync>,
501}
502
503impl CustomUserInfoClaimsResolver {
504    pub fn new<F, Fut>(resolver: F) -> Self
505    where
506        F: Fn(CustomUserInfoClaimsInput) -> Fut + Send + Sync + 'static,
507        Fut: Future<Output = Result<Map<String, Value>, OpenAuthError>> + Send + 'static,
508    {
509        Self {
510            resolver: Arc::new(move |input| Box::pin(resolver(input))),
511        }
512    }
513
514    pub async fn resolve(
515        &self,
516        input: CustomUserInfoClaimsInput,
517    ) -> Result<Map<String, Value>, OpenAuthError> {
518        (self.resolver)(input).await
519    }
520}
521
522impl std::fmt::Debug for CustomUserInfoClaimsResolver {
523    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524        formatter.write_str("CustomUserInfoClaimsResolver(..)")
525    }
526}
527
528impl PartialEq for CustomUserInfoClaimsResolver {
529    fn eq(&self, _other: &Self) -> bool {
530        true
531    }
532}
533
534impl Eq for CustomUserInfoClaimsResolver {}
535
536/// Input passed to request URI resolution.
537#[derive(Debug, Clone, PartialEq, Eq)]
538pub struct RequestUriResolverInput {
539    pub request_uri: String,
540    pub client_id: Option<String>,
541}
542
543/// Async callback that resolves pushed authorization request parameters.
544#[derive(Clone)]
545pub struct RequestUriResolver {
546    resolver: Arc<dyn Fn(RequestUriResolverInput) -> RequestUriFuture + Send + Sync>,
547}
548
549impl RequestUriResolver {
550    pub fn new<F, Fut>(resolver: F) -> Self
551    where
552        F: Fn(RequestUriResolverInput) -> Fut + Send + Sync + 'static,
553        Fut: Future<Output = Result<Option<Vec<(String, String)>>, OpenAuthError>> + Send + 'static,
554    {
555        Self {
556            resolver: Arc::new(move |input| Box::pin(resolver(input))),
557        }
558    }
559
560    pub async fn resolve(
561        &self,
562        input: RequestUriResolverInput,
563    ) -> Result<Option<Vec<(String, String)>>, OpenAuthError> {
564        (self.resolver)(input).await
565    }
566}
567
568impl std::fmt::Debug for RequestUriResolver {
569    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570        formatter.write_str("RequestUriResolver(..)")
571    }
572}
573
574impl PartialEq for RequestUriResolver {
575    fn eq(&self, _other: &Self) -> bool {
576        true
577    }
578}
579
580impl Eq for RequestUriResolver {}
581
582/// Async callback used to generate OAuth identifiers and token secrets.
583#[derive(Clone)]
584pub struct StringGeneratorResolver {
585    resolver: Arc<dyn Fn() -> StringGeneratorFuture + Send + Sync>,
586}
587
588impl StringGeneratorResolver {
589    pub fn new<F, Fut>(resolver: F) -> Self
590    where
591        F: Fn() -> Fut + Send + Sync + 'static,
592        Fut: Future<Output = Result<String, OpenAuthError>> + Send + 'static,
593    {
594        Self {
595            resolver: Arc::new(move || Box::pin(resolver())),
596        }
597    }
598
599    pub async fn generate(&self) -> Result<String, OpenAuthError> {
600        (self.resolver)().await
601    }
602}
603
604impl std::fmt::Debug for StringGeneratorResolver {
605    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
606        formatter.write_str("StringGeneratorResolver(..)")
607    }
608}
609
610impl PartialEq for StringGeneratorResolver {
611    fn eq(&self, _other: &Self) -> bool {
612        true
613    }
614}
615
616impl Eq for StringGeneratorResolver {}
617
618/// Input passed to custom refresh token formatters.
619#[derive(Debug, Clone, PartialEq, Eq)]
620pub struct RefreshTokenFormatEncodeInput {
621    pub token: String,
622    pub session_id: Option<String>,
623}
624
625/// Output returned from custom refresh token decoders.
626#[derive(Debug, Clone, PartialEq, Eq)]
627pub struct RefreshTokenFormatDecodeOutput {
628    pub session_id: Option<String>,
629    pub token: String,
630}
631
632/// Async callbacks that encode and decode refresh tokens returned to OAuth clients.
633#[derive(Clone)]
634pub struct RefreshTokenFormatter {
635    encoder: Arc<dyn Fn(RefreshTokenFormatEncodeInput) -> RefreshTokenEncodeFuture + Send + Sync>,
636    decoder: Arc<dyn Fn(String) -> RefreshTokenDecodeFuture + Send + Sync>,
637}
638
639impl RefreshTokenFormatter {
640    pub fn new<Encode, EncodeFuture, Decode, DecodeFuture>(encoder: Encode, decoder: Decode) -> Self
641    where
642        Encode: Fn(RefreshTokenFormatEncodeInput) -> EncodeFuture + Send + Sync + 'static,
643        EncodeFuture: Future<Output = Result<String, OpenAuthError>> + Send + 'static,
644        Decode: Fn(String) -> DecodeFuture + Send + Sync + 'static,
645        DecodeFuture:
646            Future<Output = Result<RefreshTokenFormatDecodeOutput, OpenAuthError>> + Send + 'static,
647    {
648        Self {
649            encoder: Arc::new(move |input| Box::pin(encoder(input))),
650            decoder: Arc::new(move |token| Box::pin(decoder(token))),
651        }
652    }
653
654    pub async fn encode(
655        &self,
656        input: RefreshTokenFormatEncodeInput,
657    ) -> Result<String, OpenAuthError> {
658        (self.encoder)(input).await
659    }
660
661    pub async fn decode(
662        &self,
663        token: String,
664    ) -> Result<RefreshTokenFormatDecodeOutput, OpenAuthError> {
665        (self.decoder)(token).await
666    }
667}
668
669impl std::fmt::Debug for RefreshTokenFormatter {
670    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671        formatter.write_str("RefreshTokenFormatter(..)")
672    }
673}
674
675impl PartialEq for RefreshTokenFormatter {
676    fn eq(&self, _other: &Self) -> bool {
677        true
678    }
679}
680
681impl Eq for RefreshTokenFormatter {}
682
683/// Optional public prefixes applied to generated OAuth secrets before returning them.
684#[derive(Debug, Clone, Default, PartialEq, Eq)]
685pub struct OAuthTokenPrefixes {
686    pub opaque_access_token: Option<String>,
687    pub refresh_token: Option<String>,
688    pub client_secret: Option<String>,
689}
690
691/// Supported token endpoint grant types.
692#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
693#[serde(rename_all = "snake_case")]
694pub enum GrantType {
695    AuthorizationCode,
696    ClientCredentials,
697    RefreshToken,
698}
699
700impl GrantType {
701    pub fn as_str(self) -> &'static str {
702        match self {
703            Self::AuthorizationCode => "authorization_code",
704            Self::ClientCredentials => "client_credentials",
705            Self::RefreshToken => "refresh_token",
706        }
707    }
708}
709
710/// OAuth token endpoint client authentication method.
711#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
712#[serde(rename_all = "snake_case")]
713pub enum TokenEndpointAuthMethod {
714    None,
715    ClientSecretBasic,
716    ClientSecretPost,
717}
718
719impl TokenEndpointAuthMethod {
720    pub fn as_str(self) -> &'static str {
721        match self {
722            Self::None => "none",
723            Self::ClientSecretBasic => "client_secret_basic",
724            Self::ClientSecretPost => "client_secret_post",
725        }
726    }
727}
728
729/// Storage strategy for OAuth provider secrets and tokens.
730#[derive(Debug, Clone, Copy, PartialEq, Eq)]
731pub enum SecretStorage {
732    /// Choose the upstream default from the JWT plugin setting.
733    Auto,
734    /// Store only a hash of the value.
735    Hashed,
736    /// Store an encrypted value.
737    Encrypted,
738}
739
740/// Per-endpoint OAuth provider rate-limit behavior.
741#[derive(Debug, Clone, PartialEq, Eq)]
742pub enum OAuthProviderRateLimit {
743    /// Use the provider's built-in default for this endpoint.
744    Default,
745    /// Do not contribute a plugin rate-limit rule for this endpoint.
746    Disabled,
747    /// Override the built-in default with a custom rule.
748    Custom(RateLimitRule),
749}
750
751/// Rate-limit settings for OAuth provider endpoints.
752#[derive(Debug, Clone, PartialEq, Eq)]
753pub struct OAuthProviderRateLimits {
754    pub token: OAuthProviderRateLimit,
755    pub authorize: OAuthProviderRateLimit,
756    pub introspect: OAuthProviderRateLimit,
757    pub revoke: OAuthProviderRateLimit,
758    pub register: OAuthProviderRateLimit,
759    pub userinfo: OAuthProviderRateLimit,
760}
761
762impl Default for OAuthProviderRateLimits {
763    fn default() -> Self {
764        Self {
765            token: OAuthProviderRateLimit::Default,
766            authorize: OAuthProviderRateLimit::Default,
767            introspect: OAuthProviderRateLimit::Default,
768            revoke: OAuthProviderRateLimit::Default,
769            register: OAuthProviderRateLimit::Default,
770            userinfo: OAuthProviderRateLimit::Default,
771        }
772    }
773}
774
775/// User-facing OAuth provider plugin options.
776#[derive(Debug, Clone, PartialEq, Eq)]
777pub struct OAuthProviderOptions {
778    pub scopes: Vec<String>,
779    pub client_registration_default_scopes: Vec<String>,
780    pub client_registration_allowed_scopes: Vec<String>,
781    pub grant_types: Vec<GrantType>,
782    pub login_page: String,
783    pub consent_page: String,
784    pub signup_page: Option<String>,
785    pub select_account_page: Option<String>,
786    pub post_login_page: Option<String>,
787    pub signup_redirect: Option<PromptRedirectResolver>,
788    pub select_account_redirect: Option<PromptRedirectResolver>,
789    pub post_login_redirect: Option<PromptRedirectResolver>,
790    pub code_expires_in: u64,
791    pub access_token_expires_in: u64,
792    pub m2m_access_token_expires_in: u64,
793    pub id_token_expires_in: u64,
794    pub refresh_token_expires_in: u64,
795    pub client_credential_grant_default_scopes: Vec<String>,
796    pub scope_expirations: BTreeMap<String, u64>,
797    pub client_registration_client_secret_expiration: Option<u64>,
798    pub allow_unauthenticated_client_registration: bool,
799    pub allow_dynamic_client_registration: bool,
800    pub allow_public_client_prelogin: bool,
801    pub cached_trusted_clients: BTreeSet<String>,
802    pub client_reference: Option<ClientReferenceResolver>,
803    pub client_privileges: Option<ClientPrivilegesResolver>,
804    pub custom_access_token_claims: Option<CustomAccessTokenClaimsResolver>,
805    pub custom_id_token_claims: Option<CustomIdTokenClaimsResolver>,
806    pub custom_token_response_fields: Option<CustomTokenResponseFieldsResolver>,
807    pub custom_userinfo_claims: Option<CustomUserInfoClaimsResolver>,
808    pub request_uri_resolver: Option<RequestUriResolver>,
809    pub prefixes: OAuthTokenPrefixes,
810    pub generate_client_id: Option<StringGeneratorResolver>,
811    pub generate_client_secret: Option<StringGeneratorResolver>,
812    pub generate_opaque_access_token: Option<StringGeneratorResolver>,
813    pub generate_refresh_token: Option<StringGeneratorResolver>,
814    pub format_refresh_token: Option<RefreshTokenFormatter>,
815    pub disable_jwt_plugin: bool,
816    pub store_client_secret: SecretStorage,
817    pub store_tokens: SecretStorage,
818    pub hash_client_secret: Option<ClientSecretHashResolver>,
819    pub verify_client_secret_hash: Option<ClientSecretVerifyResolver>,
820    pub hash_token: Option<TokenHashResolver>,
821    pub pairwise_secret: Option<String>,
822    pub advertised_scopes_supported: Vec<String>,
823    pub advertised_claims_supported: Vec<String>,
824    pub valid_audiences: Vec<String>,
825    pub rate_limits: OAuthProviderRateLimits,
826}
827
828impl Default for OAuthProviderOptions {
829    fn default() -> Self {
830        Self {
831            scopes: Vec::new(),
832            client_registration_default_scopes: Vec::new(),
833            client_registration_allowed_scopes: Vec::new(),
834            grant_types: Vec::new(),
835            login_page: String::new(),
836            consent_page: String::new(),
837            signup_page: None,
838            select_account_page: None,
839            post_login_page: None,
840            signup_redirect: None,
841            select_account_redirect: None,
842            post_login_redirect: None,
843            code_expires_in: 600,
844            access_token_expires_in: 3600,
845            m2m_access_token_expires_in: 3600,
846            id_token_expires_in: 36000,
847            refresh_token_expires_in: 2_592_000,
848            client_credential_grant_default_scopes: Vec::new(),
849            scope_expirations: BTreeMap::new(),
850            client_registration_client_secret_expiration: None,
851            allow_unauthenticated_client_registration: false,
852            allow_dynamic_client_registration: false,
853            allow_public_client_prelogin: false,
854            cached_trusted_clients: BTreeSet::new(),
855            client_reference: None,
856            client_privileges: None,
857            custom_access_token_claims: None,
858            custom_id_token_claims: None,
859            custom_token_response_fields: None,
860            custom_userinfo_claims: None,
861            request_uri_resolver: None,
862            prefixes: OAuthTokenPrefixes::default(),
863            generate_client_id: None,
864            generate_client_secret: None,
865            generate_opaque_access_token: None,
866            generate_refresh_token: None,
867            format_refresh_token: None,
868            disable_jwt_plugin: false,
869            store_client_secret: SecretStorage::Auto,
870            store_tokens: SecretStorage::Hashed,
871            hash_client_secret: None,
872            verify_client_secret_hash: None,
873            hash_token: None,
874            pairwise_secret: None,
875            advertised_scopes_supported: Vec::new(),
876            advertised_claims_supported: Vec::new(),
877            valid_audiences: Vec::new(),
878            rate_limits: OAuthProviderRateLimits::default(),
879        }
880    }
881}
882
883/// Fully resolved OAuth provider options after upstream-compatible defaults.
884#[derive(Debug, Clone, PartialEq, Eq)]
885pub struct ResolvedOAuthProviderOptions {
886    pub scopes: Vec<String>,
887    pub claims: Vec<String>,
888    pub client_registration_allowed_scopes: Vec<String>,
889    pub grant_types: Vec<GrantType>,
890    pub login_page: String,
891    pub consent_page: String,
892    pub signup_page: Option<String>,
893    pub select_account_page: Option<String>,
894    pub post_login_page: Option<String>,
895    pub signup_redirect: Option<PromptRedirectResolver>,
896    pub select_account_redirect: Option<PromptRedirectResolver>,
897    pub post_login_redirect: Option<PromptRedirectResolver>,
898    pub code_expires_in: u64,
899    pub access_token_expires_in: u64,
900    pub m2m_access_token_expires_in: u64,
901    pub id_token_expires_in: u64,
902    pub refresh_token_expires_in: u64,
903    pub client_credential_grant_default_scopes: Vec<String>,
904    pub scope_expirations: BTreeMap<String, u64>,
905    pub client_registration_default_scopes: Vec<String>,
906    pub client_registration_client_secret_expiration: Option<u64>,
907    pub allow_unauthenticated_client_registration: bool,
908    pub allow_dynamic_client_registration: bool,
909    pub allow_public_client_prelogin: bool,
910    pub cached_trusted_clients: BTreeSet<String>,
911    pub trusted_client_cache: TrustedClientCache,
912    pub client_reference: Option<ClientReferenceResolver>,
913    pub client_privileges: Option<ClientPrivilegesResolver>,
914    pub custom_access_token_claims: Option<CustomAccessTokenClaimsResolver>,
915    pub custom_id_token_claims: Option<CustomIdTokenClaimsResolver>,
916    pub custom_token_response_fields: Option<CustomTokenResponseFieldsResolver>,
917    pub custom_userinfo_claims: Option<CustomUserInfoClaimsResolver>,
918    pub request_uri_resolver: Option<RequestUriResolver>,
919    pub prefixes: OAuthTokenPrefixes,
920    pub generate_client_id: Option<StringGeneratorResolver>,
921    pub generate_client_secret: Option<StringGeneratorResolver>,
922    pub generate_opaque_access_token: Option<StringGeneratorResolver>,
923    pub generate_refresh_token: Option<StringGeneratorResolver>,
924    pub format_refresh_token: Option<RefreshTokenFormatter>,
925    pub disable_jwt_plugin: bool,
926    pub store_client_secret: SecretStorage,
927    pub store_tokens: SecretStorage,
928    pub hash_client_secret: Option<ClientSecretHashResolver>,
929    pub verify_client_secret_hash: Option<ClientSecretVerifyResolver>,
930    pub hash_token: Option<TokenHashResolver>,
931    pub pairwise_secret: Option<String>,
932    pub advertised_scopes_supported: Vec<String>,
933    pub advertised_claims_supported: Vec<String>,
934    pub valid_audiences: Vec<String>,
935    pub rate_limits: OAuthProviderRateLimits,
936}
937
938/// OAuth provider extension returned by [`crate::oauth_provider`].
939#[derive(Debug, Clone)]
940pub struct OAuthProviderPlugin {
941    pub id: String,
942    pub version: String,
943    pub options: ResolvedOAuthProviderOptions,
944    pub(crate) auth_plugin: AuthPlugin,
945}
946
947impl OAuthProviderPlugin {
948    /// Convert this typed extension into the generic OpenAuth plugin contract.
949    pub fn into_auth_plugin(self) -> AuthPlugin {
950        self.auth_plugin
951    }
952
953    /// Borrow the generic OpenAuth plugin contract.
954    pub fn as_auth_plugin(&self) -> &AuthPlugin {
955        &self.auth_plugin
956    }
957}
958
959/// OAuth provider configuration errors.
960#[derive(Debug, Clone, PartialEq, Eq, Error)]
961pub enum OAuthProviderConfigError {
962    #[error("login_page is required")]
963    MissingLoginPage,
964    #[error("consent_page is required")]
965    MissingConsentPage,
966    #[error("clientRegistrationAllowedScope {0} not found in scopes")]
967    UnknownClientRegistrationScope(String),
968    #[error("clientCredentialGrantDefaultScopes {0} not found in scopes")]
969    UnknownClientCredentialGrantScope(String),
970    #[error("advertisedMetadata.scopes_supported {0} not found in scopes")]
971    UnknownAdvertisedScope(String),
972    #[error(
973        "pairwiseSecret must be at least 32 characters long for adequate HMAC-SHA256 security"
974    )]
975    PairwiseSecretTooShort,
976    #[error("refresh_token grant requires authorization_code grant")]
977    RefreshTokenRequiresAuthorizationCode,
978    #[error("unable to store hashed secrets because id tokens will be signed with secret")]
979    HashedClientSecretsRequireJwtPlugin,
980    #[error("encryption method not recommended, please use 'hashed' or the 'hash' function")]
981    EncryptedClientSecretsWithJwtPlugin,
982    #[error("unable to initialize jwt plugin: {0}")]
983    JwtPlugin(String),
984}