1
0
Fork 0
mirror of https://codeberg.org/tyy/aspm synced 2024-12-22 20:39:29 -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:
Tyler Beckman 2024-08-25 22:58:10 -06:00
parent 6bdd8bb30d
commit 1bea02e5f4
Signed by: Ty
GPG key ID: 2813440C772555A4
11 changed files with 505 additions and 129 deletions

99
Cargo.lock generated
View file

@ -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",

View file

@ -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))?,
},
};

View file

@ -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))?,
},
},
};

View file

@ -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?;

View file

@ -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"] }

View file

@ -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"

View file

@ -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);
};

View file

@ -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"

View 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),
}
})
}
}

View 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)",
}),
}
}

View file

@ -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!();
}