axum_supabase_auth/middleware/
decoder.rs

1use super::AuthClaims;
2use crate::AuthTypes;
3use jsonwebtoken::{DecodingKey, Validation};
4use std::marker::PhantomData;
5
6pub struct Decoder<T>
7where
8    T: AuthTypes,
9{
10    keys: Keys,
11    validation: Validation,
12
13    phantom: PhantomData<T>,
14}
15
16impl<T> Decoder<T>
17where
18    T: AuthTypes,
19{
20    pub fn new(secret: &str) -> Self {
21        let mut validation = Validation::default();
22        validation.set_audience(&["authenticated"]);
23
24        Self::new_with_validation(secret, validation)
25    }
26
27    pub fn new_with_validation(secret: &str, validation: Validation) -> Self {
28        Self {
29            keys: Keys::new(secret.as_bytes()),
30            validation,
31            phantom: PhantomData,
32        }
33    }
34
35    pub fn decode(&self, token: &str) -> Result<AuthClaims<T>, jsonwebtoken::errors::Error> {
36        jsonwebtoken::decode::<AuthClaims<T>>(token, &self.keys.decoding, &self.validation)
37            .map(|data| data.claims)
38    }
39}
40
41struct Keys {
42    decoding: DecodingKey,
43}
44
45impl Keys {
46    fn new(secret: &[u8]) -> Self {
47        Self {
48            decoding: DecodingKey::from_secret(secret),
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use crate::middleware::Empty;
57    use jsonwebtoken::Validation;
58    use serde::{Deserialize, Serialize};
59    use std::sync::LazyLock;
60
61    #[derive(Debug, Deserialize, Serialize)]
62    struct AppMetadata {
63        groups: Vec<String>,
64    }
65
66    const JWT: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjU0MzIxL2F1dGgvdjEiLCJzdWIiOiIzNGFiYzFmNy1lMzQ2LTRiMzAtYmMyNi0xYjUzZjcwN2JmNTQiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzIzOTc2ODY5LCJpYXQiOjE3MjM5NzMyNjksImVtYWlsIjoidGVzdHVzZXJAdGVzdC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJncm91cHMiOlsiZmsiXSwicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6e30sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3MjM5NzMyNjl9XSwic2Vzc2lvbl9pZCI6Ijk5OTRhNTk4LTMyNjMtNDBlNC1iMWMwLTU5YTE0ODNhODRlMiIsImlzX2Fub255bW91cyI6ZmFsc2V9.1U6LglCXAD1yJwnXndgYuvQ6muCn2MUb_ivwlKIirgk";
67    const SECRET: &str = "super-secret-jwt-token-with-at-least-32-characters-long";
68
69    static VALIDATION: LazyLock<Validation> = LazyLock::new(|| {
70        let mut validation = Validation::default();
71        validation.validate_exp = false;
72        validation.set_audience(&["authenticated"]);
73        validation
74    });
75
76    struct MyAuthTypes;
77
78    impl AuthTypes for MyAuthTypes {
79        type AppData = AppMetadata;
80        type UserData = Empty;
81        type AdditionalData = Empty;
82    }
83
84    struct EmptyAuthTypes;
85
86    impl AuthTypes for EmptyAuthTypes {
87        type AppData = Empty;
88        type UserData = Empty;
89        type AdditionalData = Empty;
90    }
91
92    #[test]
93    fn test_decode_additional_app_metadata() {
94        let decoder = Decoder::<MyAuthTypes>::new_with_validation(SECRET, VALIDATION.clone());
95        let claims = decoder.decode(JWT).unwrap();
96
97        assert_eq!(claims.app_metadata.additional.groups, vec!["fk"]);
98        assert_eq!(claims.app_metadata.provider, "email");
99    }
100
101    #[test]
102    fn test_decode_no_additional_app_metadata() {
103        let decoder = Decoder::<EmptyAuthTypes>::new_with_validation(SECRET, VALIDATION.clone());
104        let claims = decoder.decode(JWT).unwrap();
105
106        assert_eq!(claims.app_metadata.provider, "email");
107    }
108}