Skip to main content

rustauth_oauth_provider/
options.rs

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