Add API tokens support #9
26
virtweb_backend/Cargo.lock
generated
26
virtweb_backend/Cargo.lock
generated
@ -598,6 +598,20 @@ version = "1.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "basic-jwt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20108a39851e5d33abc309904eb8f65c9b7c08c1ef3f107dfbe455de75111699"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"elliptic-curve",
|
||||||
|
"jsonwebtoken",
|
||||||
|
"p384",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "2.0.0-rc.3"
|
version = "2.0.0-rc.3"
|
||||||
@ -2720,18 +2734,18 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.198"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.197"
|
version = "1.0.198"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3372,16 +3386,15 @@ dependencies = [
|
|||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"basic-jwt",
|
||||||
"bytes",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"elliptic-curve",
|
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"image",
|
"image",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
"jsonwebtoken",
|
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"light-openid",
|
"light-openid",
|
||||||
@ -3389,7 +3402,6 @@ dependencies = [
|
|||||||
"mime_guess",
|
"mime_guess",
|
||||||
"nix",
|
"nix",
|
||||||
"num",
|
"num",
|
||||||
"p384",
|
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -45,6 +45,4 @@ rust-embed = { version = "8.3.0" }
|
|||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
nix = { version = "0.28.0", features = ["net"] }
|
nix = { version = "0.28.0", features = ["net"] }
|
||||||
jsonwebtoken = "9.3.0"
|
basic-jwt = "0.1.0"
|
||||||
elliptic-curve = { version = "0.13.8", features = ["pkcs8","pem" ] }
|
|
||||||
p384 = { version = "0.13.0", features = ["ecdsa", "pkcs8", "pem"] }
|
|
@ -1,10 +1,10 @@
|
|||||||
|
use basic_jwt::{sign_jwt, TokenPrivKey};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::os::unix::prelude::CommandExt;
|
use std::os::unix::prelude::CommandExt;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use virtweb_backend::api_tokens::TokenVerb;
|
use virtweb_backend::api_tokens::TokenVerb;
|
||||||
use virtweb_backend::extractors::api_auth_extractor::TokenClaims;
|
use virtweb_backend::extractors::api_auth_extractor::TokenClaims;
|
||||||
use virtweb_backend::utils::jwt_utils::{sign_jwt, TokenPrivKey};
|
|
||||||
use virtweb_backend::utils::time_utils::time;
|
use virtweb_backend::utils::time_utils::time;
|
||||||
|
|
||||||
/// curl wrapper to query Virtweb backend API
|
/// curl wrapper to query Virtweb backend API
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
use crate::app_config::AppConfig;
|
use crate::app_config::AppConfig;
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::utils::jwt_utils;
|
|
||||||
use crate::utils::jwt_utils::{TokenPrivKey, TokenPubKey};
|
|
||||||
use crate::utils::time_utils::time;
|
use crate::utils::time_utils::time;
|
||||||
use actix_http::Method;
|
use actix_http::Method;
|
||||||
|
use basic_jwt::{TokenPrivKey, TokenPubKey};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -197,7 +196,7 @@ impl NewToken {
|
|||||||
|
|
||||||
/// Create a new Token
|
/// Create a new Token
|
||||||
pub async fn create(t: &NewToken) -> anyhow::Result<(Token, TokenPrivKey)> {
|
pub async fn create(t: &NewToken) -> anyhow::Result<(Token, TokenPrivKey)> {
|
||||||
let (pub_key, priv_key) = jwt_utils::generate_key_pair()?;
|
let (pub_key, priv_key) = basic_jwt::generate_ec384_keypair()?;
|
||||||
|
|
||||||
let token = Token {
|
let token = Token {
|
||||||
name: t.name.to_string(),
|
name: t.name.to_string(),
|
||||||
|
@ -4,8 +4,8 @@ use crate::api_tokens;
|
|||||||
use crate::api_tokens::{NewToken, TokenID, TokenRights};
|
use crate::api_tokens::{NewToken, TokenID, TokenRights};
|
||||||
use crate::controllers::api_tokens_controller::rest_token::RestToken;
|
use crate::controllers::api_tokens_controller::rest_token::RestToken;
|
||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
use crate::utils::jwt_utils::TokenPrivKey;
|
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
|
use basic_jwt::TokenPrivKey;
|
||||||
|
|
||||||
/// Create a special module for REST token to enforce usage of constructor function
|
/// Create a special module for REST token to enforce usage of constructor function
|
||||||
mod rest_token {
|
mod rest_token {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use crate::api_tokens::{Token, TokenID, TokenVerb};
|
use crate::api_tokens::{Token, TokenID, TokenVerb};
|
||||||
|
|
||||||
use crate::api_tokens;
|
use crate::api_tokens;
|
||||||
use crate::utils::jwt_utils;
|
|
||||||
use crate::utils::time_utils::time;
|
use crate::utils::time_utils::time;
|
||||||
use actix_remote_ip::RemoteIP;
|
use actix_remote_ip::RemoteIP;
|
||||||
use actix_web::dev::Payload;
|
use actix_web::dev::Payload;
|
||||||
@ -72,7 +71,7 @@ impl FromRequest for ApiAuthExtractor {
|
|||||||
return Err(ErrorBadRequest("Unable to validate token!"));
|
return Err(ErrorBadRequest("Unable to validate token!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let claims = match jwt_utils::validate_jwt::<TokenClaims>(
|
let claims = match basic_jwt::validate_jwt::<TokenClaims>(
|
||||||
&token
|
&token
|
||||||
.pub_key
|
.pub_key
|
||||||
.clone()
|
.clone()
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
use elliptic_curve::pkcs8::EncodePublicKey;
|
|
||||||
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Validation};
|
|
||||||
use p384::ecdsa::{SigningKey, VerifyingKey};
|
|
||||||
use p384::pkcs8::{EncodePrivateKey, LineEnding};
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
|
|
||||||
#[serde(tag = "alg")]
|
|
||||||
pub enum TokenPubKey {
|
|
||||||
/// ECDSA with SHA2-384 variant
|
|
||||||
ES384 { r#pub: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
|
||||||
#[serde(tag = "alg")]
|
|
||||||
pub enum TokenPrivKey {
|
|
||||||
ES384 { r#priv: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a new token keypair
|
|
||||||
pub fn generate_key_pair() -> anyhow::Result<(TokenPubKey, TokenPrivKey)> {
|
|
||||||
let signing_key = SigningKey::random(&mut OsRng);
|
|
||||||
let priv_pem = signing_key
|
|
||||||
.to_pkcs8_der()?
|
|
||||||
.to_pem("PRIVATE KEY", LineEnding::LF)?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let pub_key = VerifyingKey::from(signing_key);
|
|
||||||
let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
TokenPubKey::ES384 { r#pub: pub_pem },
|
|
||||||
TokenPrivKey::ES384 { r#priv: priv_pem },
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sign JWT with a private key
|
|
||||||
pub fn sign_jwt<C: Serialize>(key: &TokenPrivKey, claims: &C) -> anyhow::Result<String> {
|
|
||||||
match key {
|
|
||||||
TokenPrivKey::ES384 { r#priv } => {
|
|
||||||
let encoding_key = EncodingKey::from_ec_pem(r#priv.as_bytes())?;
|
|
||||||
|
|
||||||
Ok(jsonwebtoken::encode(
|
|
||||||
&jsonwebtoken::Header::new(Algorithm::ES384),
|
|
||||||
&claims,
|
|
||||||
&encoding_key,
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate a given JWT
|
|
||||||
pub fn validate_jwt<E: DeserializeOwned>(key: &TokenPubKey, token: &str) -> anyhow::Result<E> {
|
|
||||||
match key {
|
|
||||||
TokenPubKey::ES384 { r#pub } => {
|
|
||||||
let decoding_key = DecodingKey::from_ec_pem(r#pub.as_bytes())?;
|
|
||||||
|
|
||||||
let validation = Validation::new(Algorithm::ES384);
|
|
||||||
Ok(jsonwebtoken::decode::<E>(token, &decoding_key, &validation)?.claims)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::utils::jwt_utils::{generate_key_pair, sign_jwt, validate_jwt};
|
|
||||||
use crate::utils::time_utils::time;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
|
||||||
pub struct Claims {
|
|
||||||
sub: String,
|
|
||||||
exp: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Claims {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
sub: "my-sub".to_string(),
|
|
||||||
exp: time() + 100,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn jwt_encode_sign_verify_valid() {
|
|
||||||
let (pub_key, priv_key) = generate_key_pair().unwrap();
|
|
||||||
let claims = Claims::default();
|
|
||||||
let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!");
|
|
||||||
let claims_out = validate_jwt(&pub_key, &jwt).expect("Failed to validate JWT!");
|
|
||||||
|
|
||||||
assert_eq!(claims, claims_out)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn jwt_encode_sign_verify_invalid_key() {
|
|
||||||
let (_pub_key, priv_key) = generate_key_pair().unwrap();
|
|
||||||
let (pub_key_2, _priv_key_2) = generate_key_pair().unwrap();
|
|
||||||
let claims = Claims::default();
|
|
||||||
let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!");
|
|
||||||
validate_jwt::<Claims>(&pub_key_2, &jwt).expect_err("JWT should not have validated!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn jwt_verify_random_string() {
|
|
||||||
let (pub_key, _priv_key) = generate_key_pair().unwrap();
|
|
||||||
validate_jwt::<Claims>(&pub_key, "random_string")
|
|
||||||
.expect_err("JWT should not have validated!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn jwt_expired() {
|
|
||||||
let (pub_key, priv_key) = generate_key_pair().unwrap();
|
|
||||||
let claims = Claims {
|
|
||||||
exp: time() - 100,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!");
|
|
||||||
validate_jwt::<Claims>(&pub_key, &jwt).expect_err("JWT should not have validated!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn jwt_invalid_signature() {
|
|
||||||
let (pub_key, priv_key) = generate_key_pair().unwrap();
|
|
||||||
let claims = Claims::default();
|
|
||||||
let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!");
|
|
||||||
validate_jwt::<Claims>(&pub_key, &format!("{jwt}bad"))
|
|
||||||
.expect_err("JWT should not have validated!");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
pub mod disks_utils;
|
pub mod disks_utils;
|
||||||
pub mod files_utils;
|
pub mod files_utils;
|
||||||
pub mod jwt_utils;
|
|
||||||
pub mod net_utils;
|
pub mod net_utils;
|
||||||
pub mod rand_utils;
|
pub mod rand_utils;
|
||||||
pub mod time_utils;
|
pub mod time_utils;
|
||||||
|
Loading…
Reference in New Issue
Block a user