Compare commits
	
		
			2 commits
		
	
	
		
			c787ff0fa1
			...
			a7a0ea60d5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							 | 
						a7a0ea60d5 | ||
| 
							 | 
						2d8f9f8697 | 
					 2 changed files with 381 additions and 197 deletions
				
			
		
							
								
								
									
										475
									
								
								server/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										475
									
								
								server/Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -7,15 +7,68 @@ use serde::{Deserialize, Serialize};
 | 
				
			||||||
const DAYS_VALID: i64 = 7;
 | 
					const DAYS_VALID: i64 = 7;
 | 
				
			||||||
const JWT_SECRET: &[u8] = "secret".as_bytes();
 | 
					const JWT_SECRET: &[u8] = "secret".as_bytes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Claims holds the data that will be encoded into the JWT token.
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize)]
 | 
					#[derive(Debug, Serialize, Deserialize)]
 | 
				
			||||||
pub struct Claims {
 | 
					pub struct Claims {
 | 
				
			||||||
 | 
					    #[serde(rename = "sub")]
 | 
				
			||||||
    pub sub: String,
 | 
					    pub sub: String,
 | 
				
			||||||
 | 
					    #[serde(rename = "iss")]
 | 
				
			||||||
    pub iss: String,
 | 
					    pub iss: String,
 | 
				
			||||||
    pub aud: String,
 | 
					    #[serde(rename = "iat")]
 | 
				
			||||||
    pub iat: usize,
 | 
					    pub iat: usize,
 | 
				
			||||||
 | 
					    #[serde(rename = "exp")]
 | 
				
			||||||
    pub exp: usize,
 | 
					    pub exp: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Claims {
 | 
				
			||||||
 | 
					    pub fn new(sub: &str, days: i64) -> Self {
 | 
				
			||||||
 | 
					        Claims {
 | 
				
			||||||
 | 
					            sub: sub.to_string(),
 | 
				
			||||||
 | 
					            iss: "frostbyte".to_string(),
 | 
				
			||||||
 | 
					            iat: chrono::Utc::now().timestamp() as usize,
 | 
				
			||||||
 | 
					            exp: (chrono::Utc::now() + chrono::Duration::days(days)).timestamp() as usize,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Authentication holds the data needed to encode and decode JWT tokens.
 | 
				
			||||||
 | 
					// This is then passed to the AuthenticationMiddleware
 | 
				
			||||||
 | 
					pub struct Authentication {
 | 
				
			||||||
 | 
					    encoding_key: EncodingKey,
 | 
				
			||||||
 | 
					    decoding_key: DecodingKey,
 | 
				
			||||||
 | 
					    validation: Validation,
 | 
				
			||||||
 | 
					    days_valid: i64, // chrono::Duration::days() takes an i64, we don't want to cast it every time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Authentication {
 | 
				
			||||||
 | 
					    /// Create a new Authentication struct
 | 
				
			||||||
 | 
					    fn new(secret: &[u8]) -> Self {
 | 
				
			||||||
 | 
					        Authentication {
 | 
				
			||||||
 | 
					            encoding_key: EncodingKey::from_secret(secret),
 | 
				
			||||||
 | 
					            decoding_key: DecodingKey::from_secret(secret),
 | 
				
			||||||
 | 
					            validation: Validation::default(),
 | 
				
			||||||
 | 
					            days_valid: 7,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Encode the Claims struct into a JWT token, this is the raw version
 | 
				
			||||||
 | 
					    fn encode_raw(&self, claims: Claims) -> JwtResult<String> {
 | 
				
			||||||
 | 
					        encode(&Header::default(), &claims, &self.encoding_key)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Wrapper for encode_raw that takes a username (sub) and creates a Claims struct
 | 
				
			||||||
 | 
					    fn encode(&self, sub: &str) -> JwtResult<String> {
 | 
				
			||||||
 | 
					        let claims = Claims::new(sub, self.days_valid);
 | 
				
			||||||
 | 
					        self.encode_raw(claims)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Decode a JWT token into a Claims struct
 | 
				
			||||||
 | 
					    // If this faie, it means the token is invalid
 | 
				
			||||||
 | 
					    fn decode(&self, token: &str) -> JwtResult<Claims> {
 | 
				
			||||||
 | 
					        decode::<Claims>(token, &self.decoding_key, &self.validation).map(|data| data.claims)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// JwtResult is just a predefined error from the jsonwebtoken crate
 | 
					// JwtResult is just a predefined error from the jsonwebtoken crate
 | 
				
			||||||
pub fn token_factory(user: &str) -> JwtResult<String> {
 | 
					pub fn token_factory(user: &str) -> JwtResult<String> {
 | 
				
			||||||
    info!("Issuing JWT token for {}", user);
 | 
					    info!("Issuing JWT token for {}", user);
 | 
				
			||||||
| 
						 | 
					@ -25,7 +78,6 @@ pub fn token_factory(user: &str) -> JwtResult<String> {
 | 
				
			||||||
        &Claims {
 | 
					        &Claims {
 | 
				
			||||||
            sub: user.to_string(),
 | 
					            sub: user.to_string(),
 | 
				
			||||||
            iss: "frostbyte".to_string(),
 | 
					            iss: "frostbyte".to_string(),
 | 
				
			||||||
            aud: "frostbyte".to_string(),
 | 
					 | 
				
			||||||
            iat: chrono::Utc::now().timestamp() as usize,
 | 
					            iat: chrono::Utc::now().timestamp() as usize,
 | 
				
			||||||
            exp: (chrono::Utc::now() + chrono::Duration::days(DAYS_VALID)).timestamp() as usize,
 | 
					            exp: (chrono::Utc::now() + chrono::Duration::days(DAYS_VALID)).timestamp() as usize,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -56,3 +108,50 @@ pub fn validate_token(token: &str) -> JwtResult<Claims> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_auth() {
 | 
				
			||||||
 | 
					        let username: &str = "testuser";
 | 
				
			||||||
 | 
					        let auth = Authentication::new("secret".as_bytes());
 | 
				
			||||||
 | 
					        assert!(auth.encode(username).is_ok());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let token = auth.encode(username);
 | 
				
			||||||
 | 
					        assert!(!token.is_err());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let token = token.unwrap();
 | 
				
			||||||
 | 
					        assert!(!token.is_empty());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_validate() {
 | 
				
			||||||
 | 
					        let username: &str = "testuser";
 | 
				
			||||||
 | 
					        let auth = Authentication::new("secret".as_bytes());
 | 
				
			||||||
 | 
					        let token = auth.encode(username).unwrap();
 | 
				
			||||||
 | 
					        let claims = auth.decode(&token).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(claims.sub, username);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_invalid() {
 | 
				
			||||||
 | 
					        let auth = Authentication::new("secret".as_bytes());
 | 
				
			||||||
 | 
					        let token = auth.encode("testuser").unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove the first character should invalidate the token
 | 
				
			||||||
 | 
					        let token = token[1..].to_string();
 | 
				
			||||||
 | 
					        assert!(auth.decode(&token).is_err());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_expired() {
 | 
				
			||||||
 | 
					        let auth = Authentication::new("secret".as_bytes());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Chrono::duration allows negative durations, -1 is yesterday in this case
 | 
				
			||||||
 | 
					        let claims = Claims::new("testuser", -1);
 | 
				
			||||||
 | 
					        let token = auth.encode_raw(claims).unwrap();
 | 
				
			||||||
 | 
					        assert!(auth.decode(&token).is_err());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue