Files
rss-reader/src/auth/jwt.rs
T
2026-06-07 15:43:43 +02:00

112 lines
3.3 KiB
Rust
Executable File

extern crate hmac;
extern crate jwt;
extern crate sha2;
use std::collections::BTreeMap;
use std::env;
use actix_web::HttpRequest;
use dotenv::dotenv;
use hmac::{Hmac, Mac};
use jwt::{Header, SignWithKey, Token, VerifyWithKey};
use sha2::Sha256;
pub struct JwtToken {
pub user_id: i32,
}
type HmacSha256 = Hmac<Sha256>;
fn signing_key() -> HmacSha256 {
dotenv().ok();
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
HmacSha256::new_from_slice(secret.as_bytes()).unwrap()
}
impl JwtToken {
pub fn encode(user_id: i32) -> String {
let key: HmacSha256 = signing_key();
let mut claims = BTreeMap::new();
claims.insert("user_id", user_id);
claims.sign_with_key(&key).unwrap()
}
pub fn decode(encoded_token: String) -> Result<JwtToken, &'static str> {
let key: HmacSha256 = signing_key();
let token_str: &str = encoded_token.as_str();
let token: Result<Token<Header, BTreeMap<String, i32>, jwt::Verified>, jwt::Error> =
VerifyWithKey::verify_with_key(token_str, &key);
match token {
Ok(token) => {
let _header = token.header();
let claims = token.claims();
Ok(JwtToken {
user_id: claims["user_id"],
})
}
Err(_err) => Err("could not decode token"),
}
}
#[allow(dead_code)]
pub fn decode_from_request(request: HttpRequest) -> Result<JwtToken, &'static str> {
match request.headers().get("user-token") {
Some(token) => JwtToken::decode(String::from(token.to_str().unwrap())),
None => Err("There is no token"),
}
}
}
#[cfg(test)]
mod jwt_test {
use actix_web::{http::header, test};
use super::JwtToken;
#[test]
async fn encode_decode() {
let encoded_token: String = JwtToken::encode(32);
let decoded_token: JwtToken = JwtToken::decode(encoded_token).unwrap();
assert_eq!(32, decoded_token.user_id);
}
#[test]
async fn decode_incorrect_token() {
let encoded_token: String = String::from("test");
match JwtToken::decode(encoded_token) {
Err(message) => assert_eq!(message, "could not decode token"),
_ => panic!("Incorrect token should not be able to decode."),
}
}
#[actix_web::test]
async fn decode_from_request_with_correct_token() {
let encoded_token: String = JwtToken::encode(32);
let request = test::TestRequest::default()
.insert_header(header::ContentType::json())
.insert_header(("user-token", encoded_token))
.to_http_request();
let out_come = JwtToken::decode_from_request(request);
match out_come {
Ok(token) => assert_eq!(32, token.user_id),
_ => panic!("Token is not returned with it should be."),
}
}
#[actix_web::test]
async fn decode_from_request_with_no_token() {
let request = test::TestRequest::default()
.insert_header(("test", "test"))
.to_http_request();
let out_come = JwtToken::decode_from_request(request);
match out_come {
Err(message) => assert_eq!("There is no token", message),
_ => panic!("Token should not be returned when it is not present in the header."),
}
}
}