diff --git a/server/src/jwt.rs b/server/src/jwt.rs index 5e61fd1..63cb21a 100755 --- a/server/src/jwt.rs +++ b/server/src/jwt.rs @@ -1,12 +1,8 @@ use jsonwebtoken::{ decode, encode, errors::Result as JwtResult, DecodingKey, EncodingKey, Header, Validation, }; -use log::*; 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 { @@ -33,6 +29,7 @@ impl Claims { /// Authentication holds the data needed to encode and decode JWT tokens. // This is then passed to the AuthenticationMiddleware +#[derive(Clone)] pub struct Authentication { encoding_key: EncodingKey, decoding_key: DecodingKey, @@ -42,7 +39,7 @@ pub struct Authentication { impl Authentication { /// Create a new Authentication struct - fn new(secret: &[u8]) -> Self { + pub fn new(secret: &[u8]) -> Self { Authentication { encoding_key: EncodingKey::from_secret(secret), decoding_key: DecodingKey::from_secret(secret), @@ -57,58 +54,18 @@ impl Authentication { } /// Wrapper for encode_raw that takes a username (sub) and creates a Claims struct - fn encode(&self, sub: &str) -> JwtResult { + pub fn encode(&self, sub: &str) -> JwtResult { 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 { + pub fn decode(&self, token: &str) -> JwtResult { decode::(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 { - info!("Issuing JWT token for {}", user); - - let token = encode( - &Header::default(), - &Claims { - sub: user.to_string(), - iss: "frostbyte".to_string(), - iat: chrono::Utc::now().timestamp() as usize, - exp: (chrono::Utc::now() + chrono::Duration::days(DAYS_VALID)).timestamp() as usize, - }, - &EncodingKey::from_secret(JWT_SECRET), - ) - .unwrap(); - - Ok(token) -} - -// JwtResult is just a predefined error from the jsonwebtoken crate -// This function is incomplete and should be expanded to check for more things -pub fn validate_token(token: &str) -> JwtResult { - let token_data = decode::( - token, - &DecodingKey::from_secret(JWT_SECRET), - &Validation::default(), - ); - - match token_data { - Ok(token_data) => { - info!("Token validated for {}", token_data.claims.sub); - Ok(token_data.claims) - } - Err(e) => { - error!("Token validation failed: {}", e); - Err(e) - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/server/src/main.rs b/server/src/main.rs index 5e72109..d6792e8 100755 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -12,6 +12,7 @@ mod state; mod types; mod util; +use jwt::Authentication; use routes::{get_comments, get_posts, login, new_comment, new_post, post_by_id, register}; use state::CaptchaState; use state::ServerState; @@ -23,6 +24,7 @@ async fn main() -> std::io::Result<()> { let data = ServerState::new().await; let capt_db = CaptchaState::new(); + let auth = Authentication::new("secret".as_bytes()); #[cfg(debug_assertions)] { @@ -56,7 +58,8 @@ async fn main() -> std::io::Result<()> { .service(login) .service(register) .app_data(Data::new(data.clone())) - .app_data(Data::new(capt_db.clone())), + .app_data(Data::new(capt_db.clone())) + .app_data(Data::new(auth.clone())), ) .service( Files::new("/", "./public") diff --git a/server/src/routes/comment.rs b/server/src/routes/comment.rs index 3df7e00..dcb1afe 100644 --- a/server/src/routes/comment.rs +++ b/server/src/routes/comment.rs @@ -1,5 +1,5 @@ use crate::db::{db_get_comments, db_new_comment}; -use crate::jwt::validate_token; +use crate::jwt::Authentication; use crate::types::{CommentQueryParams, NewComment}; use crate::ServerState; @@ -31,8 +31,9 @@ pub async fn get_comments( pub async fn new_comment( data: Json, state: Data, + auth: Data, ) -> Result { - let user_claims = validate_token(&data.user_token); + let user_claims = auth.decode(&data.user_token); // Bail if the token is invalid if let Err(e) = user_claims { diff --git a/server/src/routes/post.rs b/server/src/routes/post.rs index 0eb4ec5..b54f49c 100755 --- a/server/src/routes/post.rs +++ b/server/src/routes/post.rs @@ -1,5 +1,5 @@ use crate::db::{db_get_latest_posts, db_get_post, db_new_post}; -use crate::jwt::validate_token; +use crate::jwt::Authentication; use crate::types::{NewPost, PostQueryParams}; use crate::ServerState; @@ -23,8 +23,12 @@ pub async fn get_posts( /// Creates a new post, requires a token in release mode #[post("/posts")] -pub async fn new_post(new_post: Json, state: Data) -> Result { - let user_claims = validate_token(&new_post.token); +pub async fn new_post( + new_post: Json, + state: Data, + auth: Data, +) -> Result { + let user_claims = auth.decode(&new_post.token); if let Err(e) = user_claims { info!("Error validating token: {}", e); diff --git a/server/src/routes/users.rs b/server/src/routes/users.rs index 21013dd..99f4edf 100755 --- a/server/src/routes/users.rs +++ b/server/src/routes/users.rs @@ -1,5 +1,6 @@ use crate::db::{db_new_user, db_user_login}; -use crate::jwt::token_factory; +use crate::jwt::Authentication; +// use crate::jwt::token_factory; use crate::state::CaptchaState; use crate::types::{AuthResponse, LoginData, RegisterData}; use crate::ServerState; @@ -14,6 +15,7 @@ pub async fn register( data: Json, state: Data, captcha_state: Data, + auth: Data, ) -> Result { if !captcha_state .capthca_db @@ -30,7 +32,7 @@ pub async fn register( info!("User: {} registered", &user.username); Ok(HttpResponse::Ok().json(AuthResponse { username: user.username.clone(), - token: token_factory(&user.username).unwrap(), + token: auth.encode(&user.username).unwrap(), })) } None => { @@ -41,14 +43,18 @@ pub async fn register( } #[post("/login")] -pub async fn login(data: Json, state: Data) -> Result { +pub async fn login( + data: Json, + state: Data, + auth: Data, +) -> Result { let result = db_user_login(data.username.clone(), data.password.clone(), &state.pool).await; match result { Some(_) => { return Ok(HttpResponse::Ok().json(AuthResponse { username: data.username.clone(), - token: token_factory(&data.username).unwrap(), + token: auth.encode(&data.username).unwrap(), })); } None => {