mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-23 01:19:28 -07:00
Commit my work so I don't lose it again by reinstalling linux improperly i am very salty about that
This commit is contained in:
parent
6bdd8bb30d
commit
1bea02e5f4
11 changed files with 505 additions and 129 deletions
99
Cargo.lock
generated
99
Cargo.lock
generated
|
@ -1048,6 +1048,41 @@ version = "0.1.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.6.0"
|
||||
|
@ -1550,7 +1585,7 @@ dependencies = [
|
|||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -1569,7 +1604,7 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -1946,6 +1981,12 @@ dependencies = [
|
|||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
|
@ -1968,6 +2009,17 @@ dependencies = [
|
|||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
|
@ -1976,6 +2028,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
|||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2368,6 +2421,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde-email",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -2385,6 +2439,7 @@ dependencies = [
|
|||
"sea-orm",
|
||||
"serde",
|
||||
"server-migrations",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2698,7 +2753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3577,7 +3632,7 @@ version = "1.0.120"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
|
@ -3604,6 +3659,36 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "server-migrations"
|
||||
version = "0.1.0"
|
||||
|
@ -3786,7 +3871,7 @@ dependencies = [
|
|||
"futures-util",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
|
@ -4342,7 +4427,7 @@ version = "0.21.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"toml_datetime",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
@ -4353,7 +4438,7 @@ version = "0.22.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit as _};
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher as _};
|
||||
use naja_lib::{
|
||||
aspe::{
|
||||
requests::{AspeRequest, AspeRequestType, AspeRequestVariant},
|
||||
requests::{AspeRequest, AspeRequestType, AspeRequestVariant, AspeUri},
|
||||
AspeRequestFailure, AspeServer,
|
||||
},
|
||||
keys::AspKey,
|
||||
|
@ -82,7 +84,7 @@ impl NajaSubcommand for AspeDeleteCommand {
|
|||
version: 0,
|
||||
r#type: AspeRequestType::Request,
|
||||
request: AspeRequestVariant::Delete {
|
||||
aspe_uri: format!("aspe:{}:{}", server.host, key.fingerprint),
|
||||
aspe_uri: AspeUri::from_str(&format!("aspe:{}:{}", server.host, key.fingerprint))?,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::str::FromStr as _;
|
||||
|
||||
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit as _};
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher as _};
|
||||
use naja_lib::{
|
||||
aspe::{
|
||||
requests::{AspeRequest, AspeRequestType, AspeRequestVariant},
|
||||
requests::{AspeRequest, AspeRequestType, AspeRequestVariant, AspeUri},
|
||||
AspeFetchFailure, AspeRequestFailure, AspeServer,
|
||||
},
|
||||
hex_color::HexColor,
|
||||
|
@ -149,7 +151,7 @@ impl NajaSubcommand for AspeUploadCommand {
|
|||
r#type: AspeRequestType::Request,
|
||||
request: AspeRequestVariant::Update {
|
||||
profile_jws: encoded_profile,
|
||||
aspe_uri: format!("aspe:{}:{}", server.host, fingerprint),
|
||||
aspe_uri: AspeUri::from_str(&format!("aspe:{}:{}", server.host, key.fingerprint))?,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -61,7 +61,7 @@ impl NajaSubcommand for ProfilesImportCommand {
|
|||
_ => (self.profile, None),
|
||||
};
|
||||
|
||||
let (key, profile) = AriadneSignatureProfile::decode_and_verify(&profile, fingerprint)
|
||||
let (key, profile) = AriadneSignatureProfile::decode_and_verify(&profile, fingerprint.as_deref())
|
||||
.context("The provided or fetched profile was invalid and unable to be imported")?;
|
||||
|
||||
let txn = state.db.begin().await?;
|
||||
|
|
|
@ -17,6 +17,7 @@ reqwest = "0.12.5"
|
|||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde-email = "3.0.1"
|
||||
serde_json = { version = "1.0.120", features = ["preserve_order"] }
|
||||
serde_with = "3.9.0"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "1.0.61"
|
||||
tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] }
|
||||
|
|
|
@ -1,7 +1,59 @@
|
|||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use url::{Host, Url};
|
||||
|
||||
use crate::utils::jwt::JwtSerializable;
|
||||
|
||||
const BASE32_CHARS: [char; 32] = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','2','3','4','5','6','7'];
|
||||
|
||||
#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr)]
|
||||
pub struct AspeUri {
|
||||
/// The domain of the ASPE server to fetch this profile from
|
||||
domain: Host,
|
||||
/// The fingerprint of the profile to fetch (in uppercase)
|
||||
fingerprint: String
|
||||
}
|
||||
|
||||
impl AspeUri {
|
||||
pub fn domain(&self) -> &Host { &self.domain }
|
||||
pub fn fingerprint(&self) -> &String { &self.fingerprint }
|
||||
}
|
||||
|
||||
impl Display for AspeUri {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "aspe:{}:{}", self.domain, self.fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AspeUri {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let url = Url::parse(s)?;
|
||||
|
||||
if url.scheme() != "aspe" {
|
||||
bail!("Provided uri did not have a scheme of 'aspe'");
|
||||
}
|
||||
|
||||
let Some((domain, fingerprint)) = url.path().split_once(':') else {
|
||||
bail!("Uri did not have the correct amount of parts");
|
||||
};
|
||||
let fingerprint = fingerprint.to_uppercase();
|
||||
|
||||
if fingerprint.len() != 26 || !fingerprint.chars().all(|c| BASE32_CHARS.contains(&c)) {
|
||||
bail!("Fingerprint was not formatted correctly");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
domain: Host::parse(domain)?,
|
||||
fingerprint
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum AspeRequestType {
|
||||
|
@ -19,11 +71,11 @@ pub enum AspeRequestVariant {
|
|||
#[serde(rename = "http://ariadne.id/profile_jws")]
|
||||
profile_jws: String,
|
||||
#[serde(rename = "http://ariadne.id/aspe_uri")]
|
||||
aspe_uri: String,
|
||||
aspe_uri: AspeUri,
|
||||
},
|
||||
Delete {
|
||||
#[serde(rename = "http://ariadne.id/aspe_uri")]
|
||||
aspe_uri: String,
|
||||
aspe_uri: AspeUri,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -41,9 +93,11 @@ impl JwtSerializable for AspeRequest {}
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use josekit::jwk::Jwk;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{keys::AspKey, utils::jwt::JwtSerialize};
|
||||
use josekit::jwk::Jwk;
|
||||
|
||||
use crate::{aspe::requests::AspeUri, keys::AspKey, utils::jwt::JwtSerialize};
|
||||
|
||||
use super::{AspeRequest, AspeRequestType, AspeRequestVariant};
|
||||
|
||||
|
@ -107,7 +161,7 @@ mod tests {
|
|||
r#type: AspeRequestType::Request,
|
||||
request: AspeRequestVariant::Update {
|
||||
profile_jws: crate::test_constants::PROFILE.to_string(),
|
||||
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
||||
aspe_uri: AspeUri::from_str(crate::test_constants::ASPE_URI).unwrap(),
|
||||
},
|
||||
};
|
||||
let encoded = request.encode_and_sign(&key);
|
||||
|
@ -141,7 +195,7 @@ mod tests {
|
|||
r#type: AspeRequestType::Request,
|
||||
request: AspeRequestVariant::Update {
|
||||
profile_jws: crate::test_constants::PROFILE.to_string(),
|
||||
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
||||
aspe_uri: AspeUri::from_str(crate::test_constants::ASPE_URI).unwrap(),
|
||||
},
|
||||
},
|
||||
"Decoded update request should be correct"
|
||||
|
@ -157,7 +211,7 @@ mod tests {
|
|||
version: 0,
|
||||
r#type: AspeRequestType::Request,
|
||||
request: AspeRequestVariant::Delete {
|
||||
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
||||
aspe_uri: AspeUri::from_str(crate::test_constants::ASPE_URI).unwrap(),
|
||||
},
|
||||
};
|
||||
let encoded = request.encode_and_sign(&key);
|
||||
|
@ -190,7 +244,7 @@ mod tests {
|
|||
version: 0,
|
||||
r#type: AspeRequestType::Request,
|
||||
request: AspeRequestVariant::Delete {
|
||||
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
||||
aspe_uri: AspeUri::from_str(crate::test_constants::ASPE_URI).unwrap(),
|
||||
},
|
||||
},
|
||||
"Decoded delete request should be correct"
|
||||
|
|
|
@ -22,13 +22,12 @@ pub trait JwtSerializable {}
|
|||
|
||||
pub trait JwtSerialize {
|
||||
fn encode_and_sign(&self, key: &AspKey) -> Result<String, JwtSerializationError>;
|
||||
fn decode_and_verify<S>(
|
||||
fn decode_and_verify(
|
||||
jwt: &str,
|
||||
expected_fingerprint: Option<S>,
|
||||
expected_fingerprint: Option<&str>,
|
||||
) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||
where
|
||||
Self: for<'de> Deserialize<'de> + JwtSerializable,
|
||||
S: AsRef<str>;
|
||||
Self: for<'de> Deserialize<'de> + JwtSerializable;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -88,13 +87,12 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
|||
.or(Err(JwtSerializationError::SerializationError))
|
||||
}
|
||||
|
||||
fn decode_and_verify<S>(
|
||||
fn decode_and_verify(
|
||||
jwt: &str,
|
||||
expected_fingerprint: Option<S>,
|
||||
expected_fingerprint: Option<&str>,
|
||||
) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||
where
|
||||
Self: for<'de> serde::Deserialize<'de> + JwtSerializable,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
// Decode the header and check if the key id is correct
|
||||
let header = jwt::decode_header(jwt).or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||
|
@ -124,7 +122,7 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
|||
if key.fingerprint != key_id {
|
||||
return Err(JwtDeserializationError::MalformedJwkError);
|
||||
}
|
||||
if expected_fingerprint.is_some_and(|e| e.as_ref() != key_id) {
|
||||
if expected_fingerprint.is_some_and(|e| e != key_id) {
|
||||
return Err(JwtDeserializationError::WrongJwkError);
|
||||
};
|
||||
|
||||
|
|
|
@ -14,3 +14,4 @@ serde = { version = "1.0.204", features = ["derive"] }
|
|||
naja-lib = { path = "../naja-lib" }
|
||||
migrations = { path = "../server-migrations", package = "server-migrations" }
|
||||
env_logger = "0.11.3"
|
||||
thiserror = "1.0.63"
|
||||
|
|
86
crates/naja-server/src/extractors.rs
Normal file
86
crates/naja-server/src/extractors.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use std::{future::Future, pin::Pin};
|
||||
|
||||
use actix_web::{
|
||||
http::{header, StatusCode}, FromRequest, ResponseError
|
||||
};
|
||||
use naja_lib::{aspe::requests::AspeRequest, keys::AspKey, utils::jwt::{JwtDeserializationError, JwtSerialize}};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AspeRequestParseError {
|
||||
#[error(r#"Request content type was not set to "application/asp+jwt; charset=utf-8""#)]
|
||||
ContentTypeMismatch,
|
||||
#[error("Unable to parse request body as string")]
|
||||
StringParseFailure,
|
||||
#[error("The request JWT header was formatted incorrectly")]
|
||||
InvalidJwtHeader,
|
||||
#[error("The request JWT was unable to be verified")]
|
||||
VerificationError,
|
||||
#[error("The request JWT was unable to be decoded")]
|
||||
DecodeError,
|
||||
#[error("The request JWT had a different key fingerprint and key id")]
|
||||
KeyIdMismatch,
|
||||
#[error("An unknown error has occurred while parsing the request body JWT")]
|
||||
UnknownParsingFailure,
|
||||
}
|
||||
|
||||
impl ResponseError for AspeRequestParseError {
|
||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
match self {
|
||||
AspeRequestParseError::ContentTypeMismatch => StatusCode::UNSUPPORTED_MEDIA_TYPE,
|
||||
AspeRequestParseError::StringParseFailure => StatusCode::BAD_REQUEST,
|
||||
AspeRequestParseError::InvalidJwtHeader => StatusCode::BAD_REQUEST,
|
||||
AspeRequestParseError::VerificationError => StatusCode::BAD_REQUEST,
|
||||
AspeRequestParseError::DecodeError => StatusCode::BAD_REQUEST,
|
||||
AspeRequestParseError::KeyIdMismatch => StatusCode::BAD_REQUEST,
|
||||
AspeRequestParseError::UnknownParsingFailure => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement RFC9457
|
||||
}
|
||||
|
||||
pub struct AspeRequestBody {
|
||||
pub request: AspeRequest,
|
||||
pub key: AspKey
|
||||
}
|
||||
|
||||
impl FromRequest for AspeRequestBody {
|
||||
type Error = AspeRequestParseError;
|
||||
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
|
||||
|
||||
fn from_request(
|
||||
req: &actix_web::HttpRequest,
|
||||
payload: &mut actix_web::dev::Payload,
|
||||
) -> Self::Future {
|
||||
let req = req.clone();
|
||||
let mut payload = payload.take();
|
||||
|
||||
Box::pin(async move {
|
||||
if req
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
.is_some_and(|v| v == "application/asp+jwt; charset=utf-8")
|
||||
{
|
||||
return Err(AspeRequestParseError::ContentTypeMismatch);
|
||||
}
|
||||
|
||||
let Ok(string_body) = String::from_request(&req, &mut payload).await else {
|
||||
return Err(AspeRequestParseError::StringParseFailure);
|
||||
};
|
||||
|
||||
match AspeRequest::decode_and_verify(
|
||||
&string_body,
|
||||
None
|
||||
) {
|
||||
Ok((key, request)) => Ok(Self { key, request: *request }),
|
||||
Err(JwtDeserializationError::HeaderDecodeError) => Err(AspeRequestParseError::InvalidJwtHeader),
|
||||
Err(JwtDeserializationError::JwkUsageError) => Err(AspeRequestParseError::VerificationError),
|
||||
Err(JwtDeserializationError::JwtDecodeError) => Err(AspeRequestParseError::DecodeError),
|
||||
Err(JwtDeserializationError::MalformedJwkError) => Err(AspeRequestParseError::KeyIdMismatch),
|
||||
Err(JwtDeserializationError::WrongJwkError) => Err(AspeRequestParseError::UnknownParsingFailure),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
233
crates/naja-server/src/main copy.rs
Normal file
233
crates/naja-server/src/main copy.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
#[allow(warnings)]
|
||||
mod entities;
|
||||
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
use actix_web::{get, http::header, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder};
|
||||
use clap::Parser;
|
||||
use entities::{prelude::*, profiles};
|
||||
use env_logger::Env;
|
||||
use migrations::{Migrator, MigratorTrait as _};
|
||||
use naja_lib::{
|
||||
aspe::requests::{AspeRequest, AspeRequestVariant},
|
||||
profiles::AriadneSignatureProfile,
|
||||
utils::jwt::{JwtDeserializationError, JwtSerialize},
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveValue, Database, DatabaseConnection, DbErr, EntityTrait,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct VersionResponse {
|
||||
name: &'static str,
|
||||
version: &'static str,
|
||||
repository: &'static str,
|
||||
homepage: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ServerState {
|
||||
db: DatabaseConnection,
|
||||
domain: String,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// The path to store data in
|
||||
#[arg(long, env = "NAJA_DATA_PATH")]
|
||||
data_path: PathBuf,
|
||||
/// The bind address to listen on
|
||||
#[arg(long, env = "NAJA_BIND_ADDRESS")]
|
||||
bind_address: String,
|
||||
/// The port to listen on
|
||||
#[arg(long, env = "NAJA_BIND_PORT", default_value = "80")]
|
||||
port: u16,
|
||||
/// The front-facing domain for this server
|
||||
#[arg(long, env = "NAJA_DOMAIN")]
|
||||
domain: String,
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
fs::create_dir_all(&args.data_path)?;
|
||||
let db = Database::connect(format!(
|
||||
"sqlite://{}?mode=rwc",
|
||||
args.data_path.join("db.sqlite").to_str().unwrap()
|
||||
))
|
||||
.await
|
||||
.expect("Unable to connect to sqlite database, are permissions correct?");
|
||||
|
||||
Migrator::up(&db, None)
|
||||
.await
|
||||
.expect("Unable to migrate database");
|
||||
|
||||
let state = ServerState {
|
||||
db,
|
||||
domain: args.domain,
|
||||
};
|
||||
|
||||
env_logger::init_from_env(Env::default().default_filter_or("info"));
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.app_data(web::Data::new(state.clone()))
|
||||
.service(version)
|
||||
.service(get_by_fingerprint)
|
||||
.service(post_request)
|
||||
})
|
||||
.bind_auto_h2c((args.bind_address, args.port))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[get("/.well-known/aspe/version")]
|
||||
async fn version(accept: web::Header<header::Accept>) -> impl Responder {
|
||||
match accept.to_string().as_str() {
|
||||
"text/html" | "text/plain" => HttpResponse::Ok().body(format!(
|
||||
"{}/{}",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)),
|
||||
_ => HttpResponse::Ok().json(VersionResponse {
|
||||
name: env!("CARGO_PKG_NAME"),
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
homepage: env!("CARGO_PKG_HOMEPAGE"),
|
||||
repository: env!("CARGO_PKG_REPOSITORY"),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/.well-known/aspe/id/{fingerprint}")]
|
||||
async fn get_by_fingerprint(
|
||||
state: web::Data<ServerState>,
|
||||
fingerprint: web::Path<String>,
|
||||
) -> impl Responder {
|
||||
match Profiles::find_by_id(fingerprint.as_str())
|
||||
.one(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(Some(profile)) => HttpResponse::Ok()
|
||||
.content_type("application/asp+jwt; charset=UTF-8")
|
||||
.body(profile.jwt),
|
||||
Ok(None) => HttpResponse::NotFound().finish(),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/.well-known/aspe/post")]
|
||||
async fn post_request(
|
||||
state: web::Data<ServerState>,
|
||||
data: String,
|
||||
content_type: web::Header<header::ContentType>,
|
||||
) -> impl Responder {
|
||||
if content_type.to_string().as_str() != "application/asp+jwt; charset=utf-8" {
|
||||
return HttpResponse::BadRequest()
|
||||
.body("Content type header was not set to \"application/asp+jwt; charset=UTF-8\"");
|
||||
}
|
||||
|
||||
match AspeRequest::decode_and_verify::<String>(&data, None) {
|
||||
Ok((key, request)) => {
|
||||
if let AspeRequestVariant::Update { aspe_uri, .. }
|
||||
| AspeRequestVariant::Delete { aspe_uri } = &request.request
|
||||
{
|
||||
if aspe_uri != &format!("aspe:{}:{}", &state.domain, &key.fingerprint) {
|
||||
return HttpResponse::BadRequest().body("ASPE uri did not match key and domain");
|
||||
}
|
||||
}
|
||||
|
||||
match &request.request {
|
||||
AspeRequestVariant::Create { profile_jws }
|
||||
| AspeRequestVariant::Update { profile_jws, .. } => {
|
||||
match AriadneSignatureProfile::decode_and_verify(
|
||||
profile_jws,
|
||||
Some(&key.fingerprint),
|
||||
) {
|
||||
Ok(_) if matches!(request.request, AspeRequestVariant::Create { .. }) => {
|
||||
match Profiles::insert(profiles::ActiveModel {
|
||||
fingerprint: ActiveValue::Set(key.fingerprint),
|
||||
jwt: ActiveValue::Set(profile_jws.to_owned()),
|
||||
})
|
||||
.exec(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Created().finish(),
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
// Must be AspeRequestVariant::Update { .. }
|
||||
match Profiles::update(profiles::ActiveModel {
|
||||
fingerprint: ActiveValue::Unchanged(key.fingerprint.to_owned()),
|
||||
jwt: ActiveValue::Set(profile_jws.to_owned()),
|
||||
})
|
||||
.exec(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Created().finish(),
|
||||
Err(DbErr::RecordNotUpdated) => {
|
||||
HttpResponse::BadRequest()
|
||||
.body("Profile does not already exist")
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => HttpResponse::BadRequest().body(match e {
|
||||
JwtDeserializationError::HeaderDecodeError => {
|
||||
"Unable to decode profile JWT header"
|
||||
}
|
||||
JwtDeserializationError::MalformedJwkError => {
|
||||
"Profile JWT JWK was malformed"
|
||||
}
|
||||
JwtDeserializationError::WrongJwkError => {
|
||||
"The wrong JWK was encoded inside the profile JWT"
|
||||
}
|
||||
JwtDeserializationError::JwtDecodeError => {
|
||||
"Unable to decoded profile JWT"
|
||||
}
|
||||
JwtDeserializationError::JwkUsageError => {
|
||||
"Profile JWT JWK had invalid usage claim(s)"
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
AspeRequestVariant::Delete { .. } => {
|
||||
match Profiles::delete_by_id(&key.fingerprint)
|
||||
.exec(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Ok().finish(),
|
||||
Err(DbErr::RecordNotFound(_)) => {
|
||||
HttpResponse::NotFound().body("Profile does not exist")
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => HttpResponse::BadRequest().body(match e {
|
||||
JwtDeserializationError::HeaderDecodeError => "Unable to decode request JWT header",
|
||||
JwtDeserializationError::MalformedJwkError => "Request JWT JWK was malformed",
|
||||
JwtDeserializationError::WrongJwkError => {
|
||||
"The wrong JWK was encoded inside the request JWT"
|
||||
}
|
||||
JwtDeserializationError::JwtDecodeError => "Unable to decoded request JWT",
|
||||
JwtDeserializationError::JwkUsageError => "Request JWT JWK had invalid usage claim(s)",
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
#[allow(warnings)]
|
||||
mod entities;
|
||||
mod extractors;
|
||||
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
use actix_web::{get, http::header, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder};
|
||||
use actix_web::{
|
||||
get, http::header, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
use clap::Parser;
|
||||
use entities::{prelude::*, profiles};
|
||||
use env_logger::Env;
|
||||
|
@ -13,9 +16,7 @@ use naja_lib::{
|
|||
profiles::AriadneSignatureProfile,
|
||||
utils::jwt::{JwtDeserializationError, JwtSerialize},
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveValue, Database, DatabaseConnection, DbErr, EntityTrait,
|
||||
};
|
||||
use sea_orm::{ActiveValue, Database, DatabaseConnection, DbErr, EntityTrait};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -124,7 +125,7 @@ async fn get_by_fingerprint(
|
|||
#[post("/.well-known/aspe/post")]
|
||||
async fn post_request(
|
||||
state: web::Data<ServerState>,
|
||||
data: String,
|
||||
aspe_body: extractors::AspeRequestBody,
|
||||
content_type: web::Header<header::ContentType>,
|
||||
) -> impl Responder {
|
||||
if content_type.to_string().as_str() != "application/asp+jwt; charset=utf-8" {
|
||||
|
@ -132,102 +133,15 @@ async fn post_request(
|
|||
.body("Content type header was not set to \"application/asp+jwt; charset=UTF-8\"");
|
||||
}
|
||||
|
||||
match AspeRequest::decode_and_verify::<String>(&data, None) {
|
||||
Ok((key, request)) => {
|
||||
if let AspeRequestVariant::Update { aspe_uri, .. }
|
||||
| AspeRequestVariant::Delete { aspe_uri } = &request.request
|
||||
{
|
||||
if aspe_uri != &format!("aspe:{}:{}", &state.domain, &key.fingerprint) {
|
||||
return HttpResponse::BadRequest().body("ASPE uri did not match key and domain");
|
||||
}
|
||||
}
|
||||
|
||||
match &request.request {
|
||||
AspeRequestVariant::Create { profile_jws }
|
||||
| AspeRequestVariant::Update { profile_jws, .. } => {
|
||||
match AriadneSignatureProfile::decode_and_verify(
|
||||
profile_jws,
|
||||
Some(&key.fingerprint),
|
||||
) {
|
||||
Ok(_) if matches!(request.request, AspeRequestVariant::Create { .. }) => {
|
||||
match Profiles::insert(profiles::ActiveModel {
|
||||
fingerprint: ActiveValue::Set(key.fingerprint),
|
||||
jwt: ActiveValue::Set(profile_jws.to_owned()),
|
||||
})
|
||||
.exec(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Created().finish(),
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
// Must be AspeRequestvariant::Update { .. }
|
||||
match Profiles::update(profiles::ActiveModel {
|
||||
fingerprint: ActiveValue::Unchanged(key.fingerprint.to_owned()),
|
||||
jwt: ActiveValue::Set(profile_jws.to_owned()),
|
||||
})
|
||||
.exec(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Created().finish(),
|
||||
Err(DbErr::RecordNotUpdated) => {
|
||||
HttpResponse::BadRequest()
|
||||
.body("Profile does not already exist")
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => HttpResponse::BadRequest().body(match e {
|
||||
JwtDeserializationError::HeaderDecodeError => {
|
||||
"Unable to decode profile JWT header"
|
||||
}
|
||||
JwtDeserializationError::MalformedJwkError => {
|
||||
"Profile JWT JWK was malformed"
|
||||
}
|
||||
JwtDeserializationError::WrongJwkError => {
|
||||
"The wrong JWK was encoded inside the profile JWT"
|
||||
}
|
||||
JwtDeserializationError::JwtDecodeError => {
|
||||
"Unable to decoded profile JWT"
|
||||
}
|
||||
JwtDeserializationError::JwkUsageError => {
|
||||
"Profile JWT JWK had invalid usage claim(s)"
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
AspeRequestVariant::Delete { .. } => {
|
||||
match Profiles::delete_by_id(&key.fingerprint)
|
||||
.exec(&state.db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Ok().finish(),
|
||||
Err(DbErr::RecordNotFound(_)) => {
|
||||
HttpResponse::NotFound().body("Profile does not exist")
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let AspeRequestVariant::Update { aspe_uri, .. } | AspeRequestVariant::Delete { aspe_uri } =
|
||||
&aspe_body.request.request
|
||||
{
|
||||
if aspe_uri.domain().to_string() != state.domain || *aspe_uri.fingerprint() != aspe_body.key.fingerprint {
|
||||
return HttpResponse::BadRequest().body("ASPE uri did not match key and domain");
|
||||
}
|
||||
Err(e) => HttpResponse::BadRequest().body(match e {
|
||||
JwtDeserializationError::HeaderDecodeError => "Unable to decode request JWT header",
|
||||
JwtDeserializationError::MalformedJwkError => "Request JWT JWK was malformed",
|
||||
JwtDeserializationError::WrongJwkError => {
|
||||
"The wrong JWK was encoded inside the request JWT"
|
||||
}
|
||||
JwtDeserializationError::JwtDecodeError => "Unable to decoded request JWT",
|
||||
JwtDeserializationError::JwkUsageError => "Request JWT JWK had invalid usage claim(s)",
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
todo!();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue