From 62659a7746c46988673d234e7c57c0140e51e1c9 Mon Sep 17 00:00:00 2001 From: Reimar Date: Wed, 14 Aug 2024 17:53:29 +0200 Subject: [PATCH] Implement JWT verification in rust --- rust-backend/Cargo.lock | 31 +++++++++++++++++++++++++++ rust-backend/Cargo.toml | 4 ++++ rust-backend/src/auth.rs | 45 +++++++++++++++++++++++++++++++--------- rust-backend/src/main.rs | 5 ++--- 4 files changed, 72 insertions(+), 13 deletions(-) diff --git a/rust-backend/Cargo.lock b/rust-backend/Cargo.lock index 78bf781..dbbc0e7 100644 --- a/rust-backend/Cargo.lock +++ b/rust-backend/Cargo.lock @@ -423,6 +423,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -583,6 +584,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.12" @@ -1082,6 +1092,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1103,8 +1124,12 @@ version = "0.1.0" dependencies = [ "actix-utils", "actix-web", + "base64", + "hmac", "refinery", "rusqlite", + "serde_json", + "sha2", ] [[package]] @@ -1132,6 +1157,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.74" diff --git a/rust-backend/Cargo.toml b/rust-backend/Cargo.toml index eec9403..ca84e91 100644 --- a/rust-backend/Cargo.toml +++ b/rust-backend/Cargo.toml @@ -4,6 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +base64 = "0.22.1" +sha2 = "0.10.8" +hmac = "0.12.1" +serde_json = "1.0.124" actix-web = "4" actix-utils = "3.0.1" refinery = { version = "0.8.14", features = ["rusqlite"] } diff --git a/rust-backend/src/auth.rs b/rust-backend/src/auth.rs index 28685c6..6d4dfb5 100644 --- a/rust-backend/src/auth.rs +++ b/rust-backend/src/auth.rs @@ -1,11 +1,17 @@ use actix_web::{Error, FromRequest, HttpRequest}; use actix_web::dev::Payload; use actix_web::error::ErrorUnauthorized; -use std::string::String; use actix_utils::future::{Ready, ok, err}; +use std::string::String; +use std::option::Option; +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; +use sha2::Sha256; +use hmac::{Hmac, Mac}; +use serde_json::Value; pub struct AuthorizedUser { - user_id: String, + pub user_id: String, + pub username: String, } impl FromRequest for AuthorizedUser { @@ -13,17 +19,17 @@ impl FromRequest for AuthorizedUser { type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - if is_authorized(req) { - ok(Self { - user_id: "hi".to_string(), - }) + let user = get_authorized_user(req); + + if user.is_some() { + ok(user.unwrap()) } else { err(ErrorUnauthorized("Unauthorized")) } } } -fn is_authorized(req: &HttpRequest) -> bool { +fn get_authorized_user(req: &HttpRequest) -> Option { let token = req.headers() .get("Authorization") .and_then(|value| value.to_str().ok()) @@ -31,11 +37,30 @@ fn is_authorized(req: &HttpRequest) -> bool { .and_then(|value| Some(value.replace("Bearer ", ""))); if token.is_none() { - return false; + return None; } - // TODO implement + let token = token.unwrap(); + let jwt_parts: Vec<&str> = token.split('.').collect(); - true + if jwt_parts.len() != 3 { + return None; + } + + let header: Value = serde_json::from_slice(&URL_SAFE_NO_PAD.decode(jwt_parts.get(0).unwrap()).ok()?).ok()?; + let payload: Value = serde_json::from_slice(&URL_SAFE_NO_PAD.decode(jwt_parts.get(1).unwrap()).ok()?).ok()?; + let signature = URL_SAFE_NO_PAD.decode(jwt_parts.get(2).unwrap()).ok()?; + + let mut mac = Hmac::::new_from_slice("DenHerMåAldrigVæreOffentligKunIDetteDemoProjekt".as_bytes()).ok()?; + mac.update(format!("{}.{}", jwt_parts.get(0).unwrap(), jwt_parts.get(1).unwrap()).as_bytes()); + + if mac.verify_slice(&signature).is_err() { + return None; + } + + Some(AuthorizedUser { + user_id: payload["sub"].as_str()?.to_string(), + username: payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"].as_str()?.to_string(), + }) } diff --git a/rust-backend/src/main.rs b/rust-backend/src/main.rs index 7f89646..162cef3 100644 --- a/rust-backend/src/main.rs +++ b/rust-backend/src/main.rs @@ -1,7 +1,6 @@ mod auth; use actix_web::{get, Responder, HttpResponse, HttpServer, App, web}; -use actix_web::middleware; use std::sync::{Mutex, Arc}; use crate::auth::AuthorizedUser; @@ -26,8 +25,8 @@ async fn healthcheck(data: web::Data) -> impl Responder { } #[get("/authorized")] -async fn authorized(_: AuthorizedUser) -> impl Responder { - HttpResponse::Ok().body("Authorized") +async fn authorized(auth: AuthorizedUser) -> impl Responder { + HttpResponse::Ok().body(format!("Authorized as {} ({})", auth.username, auth.user_id)) } #[actix_web::main]