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}