New authentication struct draft
This commit is contained in:
parent
c787ff0fa1
commit
2d8f9f8697
1 changed files with 101 additions and 2 deletions
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue