Compare commits
No commits in common. "a7a0ea60d513ce695fbfdc1b83182e358eb1af93" and "c787ff0fa1ea88948c83a8962722c29206718617" have entirely different histories.
a7a0ea60d5
...
c787ff0fa1
2 changed files with 196 additions and 380 deletions
473
server/Cargo.lock
generated
473
server/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,68 +7,15 @@ 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,
|
||||||
#[serde(rename = "iat")]
|
pub aud: String,
|
||||||
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);
|
||||||
|
@ -78,6 +25,7 @@ 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,
|
||||||
},
|
},
|
||||||
|
@ -108,50 +56,3 @@ 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
Reference in a new issue