Skip to main content

modkit_auth/
plugin_traits.rs

1use crate::{claims::Claims, claims_error::ClaimsError};
2use async_trait::async_trait;
3use jsonwebtoken::Header;
4use serde_json::Value;
5
6/// Plugin that knows how to normalize provider-specific claims into standard Claims format
7pub trait ClaimsPlugin: Send + Sync {
8    /// Returns the name of this plugin (for debugging/logging)
9    fn name(&self) -> &str;
10
11    /// Normalize provider-specific claims into our standard format
12    ///
13    /// Extract:
14    /// - sub (must be UUID)
15    /// - issuer
16    /// - audiences
17    /// - expiration/not-before times
18    /// - tenants (must be UUIDs)
19    /// - roles
20    /// - any extra provider-specific fields
21    ///
22    /// # Errors
23    /// Returns `ClaimsError` if required claims are missing or have invalid format.
24    fn normalize(&self, raw: &Value) -> Result<Claims, ClaimsError>;
25}
26
27/// Plugin that can validate JWT signatures and decode tokens
28#[async_trait]
29pub trait KeyProvider: Send + Sync {
30    /// Returns the name of this provider (for debugging/logging)
31    fn name(&self) -> &str;
32
33    /// Attempt to validate the JWT signature and decode its header and claims
34    ///
35    /// Returns the JWT header and raw claims as JSON if validation succeeds.
36    /// Returns an error if the signature is invalid or decoding fails.
37    ///
38    /// This method should:
39    /// - Decode the JWT header
40    /// - Find the appropriate key (e.g., by kid)
41    /// - Validate the signature
42    /// - Return raw claims for further processing
43    async fn validate_and_decode(&self, token: &str) -> Result<(Header, Value), ClaimsError>;
44
45    /// Optional: refresh keys if this provider supports it (e.g., JWKS)
46    async fn refresh_keys(&self) -> Result<(), ClaimsError> {
47        Ok(())
48    }
49}
50
51/// Plugin that can introspect opaque tokens (RFC 7662)
52#[async_trait]
53pub trait IntrospectionProvider: Send + Sync {
54    /// Returns the name of this provider (for debugging/logging)
55    fn name(&self) -> &str;
56
57    /// Introspect an opaque token and return the claims
58    ///
59    /// This should call the OAuth 2.0 Token Introspection endpoint
60    /// and return the introspection response as JSON.
61    async fn introspect(&self, token: &str) -> Result<Value, ClaimsError>;
62}
63
64#[cfg(test)]
65#[cfg_attr(coverage_nightly, coverage(off))]
66mod tests {
67    use super::*;
68
69    struct TestPlugin;
70
71    impl ClaimsPlugin for TestPlugin {
72        fn name(&self) -> &'static str {
73            "test"
74        }
75
76        fn normalize(&self, _raw: &Value) -> Result<Claims, ClaimsError> {
77            Err(ClaimsError::Malformed("test plugin".into()))
78        }
79    }
80
81    #[test]
82    fn test_plugin_name() {
83        let plugin = TestPlugin;
84        assert_eq!(plugin.name(), "test");
85    }
86}