Implement JWT verification in rust

This commit is contained in:
Reimar 2024-08-14 17:53:29 +02:00
parent 687d366f3d
commit 62659a7746
Signed by: Reimar
GPG Key ID: 93549FA07F0AE268
4 changed files with 72 additions and 13 deletions

View File

@ -423,6 +423,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@ -583,6 +584,15 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "0.2.12"
@ -1082,6 +1092,17 @@ dependencies = [
"digest", "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]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.2" version = "1.4.2"
@ -1103,8 +1124,12 @@ version = "0.1.0"
dependencies = [ dependencies = [
"actix-utils", "actix-utils",
"actix-web", "actix-web",
"base64",
"hmac",
"refinery", "refinery",
"rusqlite", "rusqlite",
"serde_json",
"sha2",
] ]
[[package]] [[package]]
@ -1132,6 +1157,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.74" version = "2.0.74"

View File

@ -4,6 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
base64 = "0.22.1"
sha2 = "0.10.8"
hmac = "0.12.1"
serde_json = "1.0.124"
actix-web = "4" actix-web = "4"
actix-utils = "3.0.1" actix-utils = "3.0.1"
refinery = { version = "0.8.14", features = ["rusqlite"] } refinery = { version = "0.8.14", features = ["rusqlite"] }

View File

@ -1,11 +1,17 @@
use actix_web::{Error, FromRequest, HttpRequest}; use actix_web::{Error, FromRequest, HttpRequest};
use actix_web::dev::Payload; use actix_web::dev::Payload;
use actix_web::error::ErrorUnauthorized; use actix_web::error::ErrorUnauthorized;
use std::string::String;
use actix_utils::future::{Ready, ok, err}; 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 { pub struct AuthorizedUser {
user_id: String, pub user_id: String,
pub username: String,
} }
impl FromRequest for AuthorizedUser { impl FromRequest for AuthorizedUser {
@ -13,17 +19,17 @@ impl FromRequest for AuthorizedUser {
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if is_authorized(req) { let user = get_authorized_user(req);
ok(Self {
user_id: "hi".to_string(), if user.is_some() {
}) ok(user.unwrap())
} else { } else {
err(ErrorUnauthorized("Unauthorized")) err(ErrorUnauthorized("Unauthorized"))
} }
} }
} }
fn is_authorized(req: &HttpRequest) -> bool { fn get_authorized_user(req: &HttpRequest) -> Option<AuthorizedUser> {
let token = req.headers() let token = req.headers()
.get("Authorization") .get("Authorization")
.and_then(|value| value.to_str().ok()) .and_then(|value| value.to_str().ok())
@ -31,11 +37,30 @@ fn is_authorized(req: &HttpRequest) -> bool {
.and_then(|value| Some(value.replace("Bearer ", ""))); .and_then(|value| Some(value.replace("Bearer ", "")));
if token.is_none() { 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::<Sha256>::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(),
})
} }

View File

@ -1,7 +1,6 @@
mod auth; mod auth;
use actix_web::{get, Responder, HttpResponse, HttpServer, App, web}; use actix_web::{get, Responder, HttpResponse, HttpServer, App, web};
use actix_web::middleware;
use std::sync::{Mutex, Arc}; use std::sync::{Mutex, Arc};
use crate::auth::AuthorizedUser; use crate::auth::AuthorizedUser;
@ -26,8 +25,8 @@ async fn healthcheck(data: web::Data<AppData>) -> impl Responder {
} }
#[get("/authorized")] #[get("/authorized")]
async fn authorized(_: AuthorizedUser) -> impl Responder { async fn authorized(auth: AuthorizedUser) -> impl Responder {
HttpResponse::Ok().body("Authorized") HttpResponse::Ok().body(format!("Authorized as {} ({})", auth.username, auth.user_id))
} }
#[actix_web::main] #[actix_web::main]