From 06f10d659bfdc969b857cd22b9872b1090ca6efc Mon Sep 17 00:00:00 2001 From: Emil Monrad Laursen Date: Tue, 14 Nov 2023 09:39:48 +0100 Subject: [PATCH 1/5] Adds documentation for multiple auth methods --- src/api/auth.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index 5fcab01..cb63a95 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -14,6 +14,8 @@ pub struct Claims { exp: usize, } +/// This method is used to create a new access token based on the uid. +/// The token is valid for 20 minutes. pub fn create_access_token(uid: &str) -> Result { let secret = env::var("ACCESS_TOKEN_HS512_SECRET") .expect("Expected ACCESS_TOKEN_HS512_SECRET to be set."); @@ -37,6 +39,8 @@ pub fn create_access_token(uid: &str) -> Result { .map_err(|_| ErrorKind::InvalidToken.into()) } +/// This method is used to create a new refresh token based on the uid. +/// The token is valid for 90 days. pub fn create_refresh_token(uid: &str) -> Result { let secret = env::var("REFRESH_TOKEN_HS512_SECRET") .expect("Expected REFRESH_TOKEN_HS512_SECRET to be set."); @@ -60,6 +64,7 @@ pub fn create_refresh_token(uid: &str) -> Result { .map_err(|_| ErrorKind::InvalidToken.into()) } +/// This method is used to validate the access token (not refresh). pub fn validation_interceptor(mut req: Request<()>) -> Result, Status> { let token = match get_token_from_request(&req) { Ok(token) => token, @@ -78,6 +83,7 @@ pub fn validation_interceptor(mut req: Request<()>) -> Result, Statu } } +/// This method is used to get a token (access or refresh) from the request metadata. pub fn get_token_from_request(req: &Request) -> Result { let token = match req.metadata().get("authorization") { Some(token) => token.to_str(), @@ -93,6 +99,8 @@ pub fn get_token_from_request(req: &Request) -> Result { } } +/// This method is used to validate a token (access or refresh). +/// It returns the token data if the token is valid. pub fn validate_token(token: String, is_refresh_token: bool) -> Result, Status> { let secret: String; @@ -104,7 +112,7 @@ pub fn validate_token(token: String, is_refresh_token: bool) -> Result( &token, From 28d39a373abb99c3d325127d9076216122d03bcd Mon Sep 17 00:00:00 2001 From: sabotack Date: Tue, 14 Nov 2023 09:59:31 +0100 Subject: [PATCH 2/5] Refactor auth to merge token creating methods into one --- src/api/auth.rs | 65 ++++++++++++++++++++++++-------------------- src/api/ecdar_api.rs | 4 +-- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index 5fcab01..eef0927 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -1,4 +1,4 @@ -use chrono::Utc; +use chrono::{Utc, Duration}; use jsonwebtoken::{ decode, encode, errors::{Error, ErrorKind}, @@ -14,37 +14,38 @@ pub struct Claims { exp: usize, } -pub fn create_access_token(uid: &str) -> Result { - let secret = env::var("ACCESS_TOKEN_HS512_SECRET") - .expect("Expected ACCESS_TOKEN_HS512_SECRET to be set."); - - let expiration = Utc::now() - .checked_add_signed(chrono::Duration::minutes(20)) - .expect("valid timestamp") - .timestamp(); - - let claims = Claims { - sub: uid.to_owned(), - exp: expiration as usize, - }; - - let header = Header::new(Algorithm::HS512); - encode( - &header, - &claims, - &EncodingKey::from_secret(secret.as_bytes()), - ) - .map_err(|_| ErrorKind::InvalidToken.into()) +pub enum TokenType { + AccessToken, + RefreshToken, } -pub fn create_refresh_token(uid: &str) -> Result { - let secret = env::var("REFRESH_TOKEN_HS512_SECRET") - .expect("Expected REFRESH_TOKEN_HS512_SECRET to be set."); - - let expiration = Utc::now() - .checked_add_signed(chrono::Duration::days(90)) - .expect("valid timestamp") - .timestamp(); +pub fn create_token(token_type: TokenType, uid: &str) -> Result { + const ACCESS_TOKEN_DURATION_MINS: i64 = 20; + const REFRESH_TOKEN_DURATION_DAYS: i64 = 90; + + let secret: String; + let expiration: i64; + + match token_type { + TokenType::AccessToken => { + secret = env::var("ACCESS_TOKEN_HS512_SECRET") + .expect("Expected ACCESS_TOKEN_HS512_SECRET to be set."); + + expiration = Utc::now() + .checked_add_signed(Duration::minutes(ACCESS_TOKEN_DURATION_MINS)) + .expect("valid timestamp") + .timestamp(); + }, + TokenType::RefreshToken => { + secret = env::var("REFRESH_TOKEN_HS512_SECRET") + .expect("Expected REFRESH_TOKEN_HS512_SECRET to be set."); + + expiration = Utc::now() + .checked_add_signed(Duration::days(REFRESH_TOKEN_DURATION_DAYS)) + .expect("valid timestamp") + .timestamp(); + }, + }; let claims = Claims { sub: uid.to_owned(), @@ -125,3 +126,7 @@ pub fn validate_token(token: String, is_refresh_token: bool) -> Result token.to_owned(), Err(e) => return Err(Status::new(Code::Internal, e.to_string())), }; - let refresh_token = match auth::create_refresh_token(&uid) { + let refresh_token = match auth::create_token(auth::TokenType::RefreshToken, &uid) { Ok(token) => token.to_owned(), Err(e) => return Err(Status::new(Code::Internal, e.to_string())), }; From 7ed562d36e9d9dbf639fe8b9a6d964ac0dc2a5e6 Mon Sep 17 00:00:00 2001 From: sabotack Date: Tue, 14 Nov 2023 10:01:41 +0100 Subject: [PATCH 3/5] cargo fmt --- src/api/auth.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index eef0927..3eb6b72 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -1,4 +1,4 @@ -use chrono::{Utc, Duration}; +use chrono::{Duration, Utc}; use jsonwebtoken::{ decode, encode, errors::{Error, ErrorKind}, @@ -22,29 +22,29 @@ pub enum TokenType { pub fn create_token(token_type: TokenType, uid: &str) -> Result { const ACCESS_TOKEN_DURATION_MINS: i64 = 20; const REFRESH_TOKEN_DURATION_DAYS: i64 = 90; - + let secret: String; let expiration: i64; - + match token_type { TokenType::AccessToken => { secret = env::var("ACCESS_TOKEN_HS512_SECRET") - .expect("Expected ACCESS_TOKEN_HS512_SECRET to be set."); + .expect("Expected ACCESS_TOKEN_HS512_SECRET to be set."); expiration = Utc::now() - .checked_add_signed(Duration::minutes(ACCESS_TOKEN_DURATION_MINS)) - .expect("valid timestamp") - .timestamp(); - }, + .checked_add_signed(Duration::minutes(ACCESS_TOKEN_DURATION_MINS)) + .expect("valid timestamp") + .timestamp(); + } TokenType::RefreshToken => { secret = env::var("REFRESH_TOKEN_HS512_SECRET") - .expect("Expected REFRESH_TOKEN_HS512_SECRET to be set."); + .expect("Expected REFRESH_TOKEN_HS512_SECRET to be set."); expiration = Utc::now() - .checked_add_signed(Duration::days(REFRESH_TOKEN_DURATION_DAYS)) - .expect("valid timestamp") - .timestamp(); - }, + .checked_add_signed(Duration::days(REFRESH_TOKEN_DURATION_DAYS)) + .expect("valid timestamp") + .timestamp(); + } }; let claims = Claims { @@ -129,4 +129,4 @@ pub fn validate_token(token: String, is_refresh_token: bool) -> Result Date: Tue, 14 Nov 2023 10:05:05 +0100 Subject: [PATCH 4/5] fix test --- src/tests/api/auth.rs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/tests/api/auth.rs diff --git a/src/tests/api/auth.rs b/src/tests/api/auth.rs new file mode 100644 index 0000000..c744738 --- /dev/null +++ b/src/tests/api/auth.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod auth {} From c6de60c431961d92fd4aac9a09ccf99f7bfbeb30 Mon Sep 17 00:00:00 2001 From: sabotack Date: Tue, 14 Nov 2023 12:23:57 +0100 Subject: [PATCH 5/5] Add tests for auth --- src/tests/api/auth.rs | 90 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/tests/api/auth.rs b/src/tests/api/auth.rs index c744738..d4651c7 100644 --- a/src/tests/api/auth.rs +++ b/src/tests/api/auth.rs @@ -1,2 +1,90 @@ #[cfg(test)] -mod auth {} +mod auth { + use crate::api::auth; + use std::{env, str::FromStr}; + use tonic::{metadata::MetadataValue, Request}; + + #[tokio::test] + async fn gtfr_bearer_token_trims_token() { + let token = "Bearer 1234567890"; + let mut request = Request::new(()); + request + .metadata_mut() + .insert("authorization", MetadataValue::from_str(token).unwrap()); + + let result = auth::get_token_from_request(&request).unwrap(); + + assert_eq!(result, token.trim_start_matches("Bearer ")); + } + + #[tokio::test] + async fn gtfr_no_token_returns_err() { + let request = Request::new(()); + + let result = auth::get_token_from_request(&request); + + assert!(result.is_err()); + } + + #[tokio::test] + async fn create_token_access_returns_token() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let uid = "1"; + let result = auth::create_token(auth::TokenType::AccessToken, uid); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn create_token_refresh_returns_token() { + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let uid = "1"; + let result = auth::create_token(auth::TokenType::RefreshToken, uid); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn validate_token_valid_access_returns_tokendata() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = auth::create_token(auth::TokenType::AccessToken, "1").unwrap(); + let result = auth::validate_token(token, false); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn validate_token_valid_refresh_returns_tokendata() { + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let token = auth::create_token(auth::TokenType::RefreshToken, "1").unwrap(); + let result = auth::validate_token(token, true); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn validate_token_invalid_returns_err() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let result_access = auth::validate_token("invalid_token".to_string(), false); + let result_refresh = auth::validate_token("invalid_token".to_string(), true); + assert!(result_access.is_err() && result_refresh.is_err()); + } + + #[tokio::test] + async fn validate_token_wrong_signature_returns_err() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let token = auth::create_token(auth::TokenType::AccessToken, "1").unwrap(); + let result_access = auth::validate_token(token, true); + + let token = auth::create_token(auth::TokenType::RefreshToken, "1").unwrap(); + let result_refresh = auth::validate_token(token, false); + + assert!(result_access.is_err() && result_refresh.is_err()); + } +}