New authentication struct draft

This commit is contained in:
Imbus 2023-12-22 21:39:14 +01:00
parent c787ff0fa1
commit 2d8f9f8697

View file

@ -7,15 +7,68 @@ use serde::{Deserialize, Serialize};
const DAYS_VALID: i64 = 7;
const JWT_SECRET: &[u8] = "secret".as_bytes();
/// Claims holds the data that will be encoded into the JWT token.
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
#[serde(rename = "sub")]
pub sub: String,
#[serde(rename = "iss")]
pub iss: String,
pub aud: String,
#[serde(rename = "iat")]
pub iat: usize,
#[serde(rename = "exp")]
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
pub fn token_factory(user: &str) -> JwtResult<String> {
info!("Issuing JWT token for {}", user);
@ -25,7 +78,6 @@ pub fn token_factory(user: &str) -> JwtResult<String> {
&Claims {
sub: user.to_string(),
iss: "frostbyte".to_string(),
aud: "frostbyte".to_string(),
iat: chrono::Utc::now().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());
}
}