Skip to content

Commit

Permalink
Merge branch '9-setup-api-server' of https://github.com/ECDAR-AAU-SW-…
Browse files Browse the repository at this point in the history
…P5/Ecdar-API into 9-setup-api-server
  • Loading branch information
AlexanderManich committed Nov 14, 2023
2 parents 18852b1 + 993254e commit 15dd3b6
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 31 deletions.
69 changes: 40 additions & 29 deletions src/api/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use chrono::Utc;
use chrono::{Duration, Utc};
use jsonwebtoken::{
decode, encode,
errors::{Error, ErrorKind},
Expand All @@ -14,37 +14,40 @@ pub struct Claims {
exp: usize,
}

pub fn create_access_token(uid: &str) -> Result<String, Error> {
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();
pub enum TokenType {
AccessToken,
RefreshToken,
}

let claims = Claims {
sub: uid.to_owned(),
exp: expiration as usize,
};
/// This method is used to create a new access or refresh token based on the token type and uid.
/// An access token is valid for 20 minutes and a refresh token is valid for 90 days.
pub fn create_token(token_type: TokenType, uid: &str) -> Result<String, Error> {
const ACCESS_TOKEN_DURATION_MINS: i64 = 20;
const REFRESH_TOKEN_DURATION_DAYS: i64 = 90;

let header = Header::new(Algorithm::HS512);
encode(
&header,
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.map_err(|_| ErrorKind::InvalidToken.into())
}
let secret: String;
let expiration: i64;

pub fn create_refresh_token(uid: &str) -> Result<String, Error> {
let secret = env::var("REFRESH_TOKEN_HS512_SECRET")
.expect("Expected REFRESH_TOKEN_HS512_SECRET to be set.");
match token_type {
TokenType::AccessToken => {
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::days(90))
.expect("valid timestamp")
.timestamp();
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(),
Expand All @@ -60,6 +63,7 @@ pub fn create_refresh_token(uid: &str) -> Result<String, Error> {
.map_err(|_| ErrorKind::InvalidToken.into())
}

/// This method is used to validate the access token (not refresh).
pub fn validation_interceptor(mut req: Request<()>) -> Result<Request<()>, Status> {
let token = match get_token_from_request(&req) {
Ok(token) => token,
Expand All @@ -78,6 +82,7 @@ pub fn validation_interceptor(mut req: Request<()>) -> Result<Request<()>, Statu
}
}

/// This method is used to get a token (access or refresh) from the request metadata.
pub fn get_token_from_request<T>(req: &Request<T>) -> Result<String, Status> {
let token = match req.metadata().get("authorization") {
Some(token) => token.to_str(),
Expand All @@ -93,6 +98,8 @@ pub fn get_token_from_request<T>(req: &Request<T>) -> Result<String, Status> {
}
}

/// 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<TokenData<Claims>, Status> {
let secret: String;

Expand All @@ -104,7 +111,7 @@ pub fn validate_token(token: String, is_refresh_token: bool) -> Result<TokenData

let mut validation = Validation::new(Algorithm::HS512);

validation.validate_exp = true;
validation.validate_exp = true; // This might be redundant as this should be defualt, however, it doesn't seem to work without it.

match decode::<Claims>(
&token,
Expand All @@ -125,3 +132,7 @@ pub fn validate_token(token: String, is_refresh_token: bool) -> Result<TokenData
},
}
}

#[cfg(test)]
#[path = "../tests/api/auth.rs"]
mod tests;
4 changes: 2 additions & 2 deletions src/api/ecdar_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,11 @@ impl EcdarApiAuth for ConcreteEcdarApi {
}

// Create new access and refresh token with user id
let access_token = match auth::create_access_token(&uid) {
let access_token = match auth::create_token(auth::TokenType::AccessToken, &uid) {
Ok(token) => 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())),
};
Expand Down
90 changes: 90 additions & 0 deletions src/tests/api/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#[cfg(test)]
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());
}
}

0 comments on commit 15dd3b6

Please sign in to comment.