mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-23 01:19:28 -07:00
A whole bunch of changes, I forgor to split it up
This commit is contained in:
parent
561cc3fb42
commit
8af322b2ad
20 changed files with 210 additions and 63 deletions
|
@ -4,7 +4,7 @@ use reqwest::{
|
||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
pub use url::Host;
|
use url::Host;
|
||||||
|
|
||||||
/// An ASPE-compatible server
|
/// An ASPE-compatible server
|
||||||
pub struct AspeServer {
|
pub struct AspeServer {
|
||||||
|
@ -100,14 +100,14 @@ impl AspeServer {
|
||||||
|
|
||||||
pub async fn fetch_profile(
|
pub async fn fetch_profile(
|
||||||
&self,
|
&self,
|
||||||
fingerprint: impl Into<String>,
|
fingerprint: impl AsRef<str>,
|
||||||
) -> Result<String, AspeFetchFailure> {
|
) -> Result<String, AspeFetchFailure> {
|
||||||
let res = self
|
let res = self
|
||||||
.client
|
.client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"https://{host}/.well-known/aspe/id/{fingerprint}",
|
"https://{host}/.well-known/aspe/id/{fingerprint}",
|
||||||
host = self.host,
|
host = self.host,
|
||||||
fingerprint = fingerprint.into()
|
fingerprint = fingerprint.as_ref()
|
||||||
))
|
))
|
||||||
.header(
|
.header(
|
||||||
header::ACCEPT,
|
header::ACCEPT,
|
||||||
|
|
|
@ -72,12 +72,14 @@ mod tests {
|
||||||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let decoded = AspeRequest::decode_and_verify(crate::test_constants::CREATE_REQUEST, &key);
|
let decoded = AspeRequest::decode_and_verify(crate::test_constants::CREATE_REQUEST, Some(&key.fingerprint));
|
||||||
assert!(
|
assert!(
|
||||||
decoded.is_ok(),
|
decoded.is_ok(),
|
||||||
"ASPE request JWS should verify and decode successfully"
|
"ASPE request JWS should verify and decode successfully"
|
||||||
);
|
);
|
||||||
let decoded = decoded.unwrap();
|
let (parsed_key, decoded) = decoded.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parsed_key.fingerprint, key.fingerprint); // Comparison by fingerprint makes public == private comparison succeed
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*decoded,
|
*decoded,
|
||||||
|
@ -105,7 +107,7 @@ mod tests {
|
||||||
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let encoded = dbg!(request.encode_and_sign(&key));
|
let encoded = request.encode_and_sign(&key);
|
||||||
assert!(
|
assert!(
|
||||||
encoded.is_ok(),
|
encoded.is_ok(),
|
||||||
"ASPE request JWS should sign and encode successfully"
|
"ASPE request JWS should sign and encode successfully"
|
||||||
|
@ -117,12 +119,14 @@ mod tests {
|
||||||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let decoded = AspeRequest::decode_and_verify(crate::test_constants::UPDATE_REQUEST, &key);
|
let decoded = AspeRequest::decode_and_verify(crate::test_constants::UPDATE_REQUEST, Some(&key.fingerprint));
|
||||||
assert!(
|
assert!(
|
||||||
decoded.is_ok(),
|
decoded.is_ok(),
|
||||||
"ASPE request JWS should verify and decode successfully"
|
"ASPE request JWS should verify and decode successfully"
|
||||||
);
|
);
|
||||||
let decoded = decoded.unwrap();
|
let (parsed_key, decoded) = decoded.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parsed_key.fingerprint, key.fingerprint); // Comparison by fingerprint makes public == private comparison succeed
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*decoded,
|
*decoded,
|
||||||
|
@ -150,7 +154,7 @@ mod tests {
|
||||||
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
aspe_uri: crate::test_constants::ASPE_URI.to_string(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let encoded = dbg!(request.encode_and_sign(&key));
|
let encoded = request.encode_and_sign(&key);
|
||||||
assert!(
|
assert!(
|
||||||
encoded.is_ok(),
|
encoded.is_ok(),
|
||||||
"ASPE request JWS should sign and encode successfully"
|
"ASPE request JWS should sign and encode successfully"
|
||||||
|
@ -162,12 +166,14 @@ mod tests {
|
||||||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let decoded = AspeRequest::decode_and_verify(crate::test_constants::DELETE_REQUEST, &key);
|
let decoded = AspeRequest::decode_and_verify(crate::test_constants::DELETE_REQUEST, Some(&key.fingerprint));
|
||||||
assert!(
|
assert!(
|
||||||
decoded.is_ok(),
|
decoded.is_ok(),
|
||||||
"ASPE request JWS should verify and decode successfully"
|
"ASPE request JWS should verify and decode successfully"
|
||||||
);
|
);
|
||||||
let decoded = decoded.unwrap();
|
let (parsed_key, decoded) = decoded.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parsed_key.fingerprint, key.fingerprint); // Comparison by fingerprint makes public == private comparison succeed
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*decoded,
|
*decoded,
|
||||||
|
|
|
@ -18,7 +18,7 @@ use serde_json::Map;
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
|
|
||||||
/// An enum representing the possible types of JWK for ASPs
|
/// An enum representing the possible types of JWK for ASPs
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum AspKeyType {
|
pub enum AspKeyType {
|
||||||
Ed25519,
|
Ed25519,
|
||||||
ES256,
|
ES256,
|
||||||
|
@ -54,7 +54,7 @@ impl TryFrom<i32> for AspKeyType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct representing a key that can be used to create profiles or ASPE requests
|
/// A struct representing a key that can be used to create profiles or ASPE requests
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct AspKey {
|
pub struct AspKey {
|
||||||
pub key_type: AspKeyType,
|
pub key_type: AspKeyType,
|
||||||
pub fingerprint: String,
|
pub fingerprint: String,
|
||||||
|
|
|
@ -3,20 +3,18 @@ pub mod keys;
|
||||||
pub mod profiles;
|
pub mod profiles;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
pub use hex_color;
|
||||||
|
pub use serde_email;
|
||||||
|
pub use url;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test_constants {
|
pub(crate) mod test_constants {
|
||||||
// NOTE: This key is taken from the example keys in RFC 7517
|
// NOTE: This key is taken from the example keys in RFC 7517
|
||||||
pub(crate) static KEY: &str = r#"
|
pub(crate) static KEY: &str = r#"{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM","d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}"#;
|
||||||
{"kty":"EC",
|
pub(crate) static ASPE_URI: &str = "aspe:example.com:6O6CWLNM66Z7CYONKDONKLYPAQ";
|
||||||
"crv":"P-256",
|
|
||||||
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
|
||||||
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
|
||||||
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
|
|
||||||
"#;
|
|
||||||
pub(crate) static ASPE_URI: &str = "aspe:example.com:452JFAI6B3KOLKBAUX3MC73DAU";
|
|
||||||
|
|
||||||
pub(crate) static PROFILE: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicHJvZmlsZSIsImh0dHA6Ly9hcmlhZG5lLmlkL25hbWUiOiJFeGFtcGxlIG5hbWUiLCJodHRwOi8vYXJpYWRuZS5pZC9jbGFpbXMiOlsiZG5zOmV4YW1wbGUuY29tP3R5cGU9VFhUIiwiaHR0cHM6Ly9naXQuZXhhbXBsZS5jb20vZXhhbXBsZS9mb3JnZWpvX3Byb29mIl0sImh0dHA6Ly9hcmlhZG5lLmlkL2NvbG9yIjoiI0E0MzRFQiJ9.u5AbAqRpyXetXwU_QqpZrieNzwZGCRZ0tFTL4FoIwPRiZZ9iIGBnqs7PWbsd0iHQpYT_Q7s1GmwggGssM9ttxQ";
|
pub(crate) static PROFILE: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicHJvZmlsZSIsImh0dHA6Ly9hcmlhZG5lLmlkL25hbWUiOiJFeGFtcGxlIG5hbWUiLCJodHRwOi8vYXJpYWRuZS5pZC9jbGFpbXMiOlsiZG5zOmV4YW1wbGUuY29tP3R5cGU9VFhUIiwiaHR0cHM6Ly9naXQuZXhhbXBsZS5jb20vZXhhbXBsZS9mb3JnZWpvX3Byb29mIl0sImh0dHA6Ly9hcmlhZG5lLmlkL2Rlc2NyaXB0aW9uIjoiVGhpcyBpcyBhbiBleGFtcGxlIHByb2ZpbGUiLCJodHRwOi8vYXJpYWRuZS5pZC9lbWFpbCI6ImV4YW1wbGVAZXhhbXBsZS5jb20iLCJodHRwOi8vYXJpYWRuZS5pZC9hdmF0YXJfdXJsIjoiaHR0cHM6Ly9wbGFjZWhvbGQuY28vMjU2IiwiaHR0cDovL2FyaWFkbmUuaWQvY29sb3IiOiIjQTQzNEVCIn0.aVdOWTIjdRo8riTiepNIadLNXJxPnOnmKzKzv6C8Abt7hQSlRYrEFg42PpF7R7juz5INuJauAv-xKj2s9kweDA";
|
||||||
pub(crate) static CREATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImNyZWF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalExTWtwR1FVazJRak5MVDB4TFFrRlZXRE5OUXpjelJFRlZJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyTnZiRzl5SWpvaUkwRTBNelJGUWlKOS51NUFiQXFScHlYZXRYd1VfUXFwWnJpZU56d1pHQ1JaMHRGVEw0Rm9Jd1BSaVpaOWlJR0JucXM3UFdic2QwaUhRcFlUX1E3czFHbXdnZ0dzc005dHR4USJ9.f8NdVzrjCZKT2R5MzUZkgcnNIJWo6ftQj6MCvXF5cgpjYt3suTqOGoBs6EKvtsgVGs12uS4ZxNnVAnFMsKKGlQ";
|
pub(crate) static CREATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImNyZWF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalpQTmtOWFRFNU5OalphTjBOWlQwNUxSRTlPUzB4WlVFRlJJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyUmxjMk55YVhCMGFXOXVJam9pVkdocGN5QnBjeUJoYmlCbGVHRnRjR3hsSUhCeWIyWnBiR1VpTENKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5bGJXRnBiQ0k2SW1WNFlXMXdiR1ZBWlhoaGJYQnNaUzVqYjIwaUxDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOWhkbUYwWVhKZmRYSnNJam9pYUhSMGNITTZMeTl3YkdGalpXaHZiR1F1WTI4dk1qVTJJaXdpYUhSMGNEb3ZMMkZ5YVdGa2JtVXVhV1F2WTI5c2IzSWlPaUlqUVRRek5FVkNJbjAuYVZkT1dUSWpkUm84cmlUaWVwTklhZExOWEp4UG5Pbm1Lekt6djZDOEFidDdoUVNsUllyRUZnNDJQcEY3UjdqdXo1SU51SmF1QXYteEtqMnM5a3dlREEifQ.G5Asc4gswiU0iw8oBZGz4dmPREKmUmHykKfjXY-89sVm_HJPfO4XRmODIqaNkonlehsS23jgry5_Dt4X6fO6PA";
|
||||||
pub(crate) static UPDATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6InVwZGF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalExTWtwR1FVazJRak5MVDB4TFFrRlZXRE5OUXpjelJFRlZJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyTnZiRzl5SWpvaUkwRTBNelJGUWlKOS51NUFiQXFScHlYZXRYd1VfUXFwWnJpZU56d1pHQ1JaMHRGVEw0Rm9Jd1BSaVpaOWlJR0JucXM3UFdic2QwaUhRcFlUX1E3czFHbXdnZ0dzc005dHR4USIsImh0dHA6Ly9hcmlhZG5lLmlkL2FzcGVfdXJpIjoiYXNwZTpleGFtcGxlLmNvbTo0NTJKRkFJNkIzS09MS0JBVVgzTUM3M0RBVSJ9.044vzbhefes8bFJFrXLwU2RNYhNvK_rNDDqM7NjEaC8alyFl-5Fh_Obj-pIKUkcxD-HL27y2objt_-lbDqvw4g";
|
pub(crate) static UPDATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6InVwZGF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalpQTmtOWFRFNU5OalphTjBOWlQwNUxSRTlPUzB4WlVFRlJJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyUmxjMk55YVhCMGFXOXVJam9pVkdocGN5QnBjeUJoYmlCbGVHRnRjR3hsSUhCeWIyWnBiR1VpTENKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5bGJXRnBiQ0k2SW1WNFlXMXdiR1ZBWlhoaGJYQnNaUzVqYjIwaUxDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOWhkbUYwWVhKZmRYSnNJam9pYUhSMGNITTZMeTl3YkdGalpXaHZiR1F1WTI4dk1qVTJJaXdpYUhSMGNEb3ZMMkZ5YVdGa2JtVXVhV1F2WTI5c2IzSWlPaUlqUVRRek5FVkNJbjAuYVZkT1dUSWpkUm84cmlUaWVwTklhZExOWEp4UG5Pbm1Lekt6djZDOEFidDdoUVNsUllyRUZnNDJQcEY3UjdqdXo1SU51SmF1QXYteEtqMnM5a3dlREEiLCJodHRwOi8vYXJpYWRuZS5pZC9hc3BlX3VyaSI6ImFzcGU6ZXhhbXBsZS5jb206Nk82Q1dMTk02Nlo3Q1lPTktET05LTFlQQVEifQ.jGAmCQwGZ82iBleew2E1QNohe29HFgnSJO-UxwwYNI4g1n-_wRKVhE2Xw3Tah5UMBByEAbViLmlCMTS_hg6hPA";
|
||||||
pub(crate) static DELETE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImRlbGV0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL2FzcGVfdXJpIjoiYXNwZTpleGFtcGxlLmNvbTo0NTJKRkFJNkIzS09MS0JBVVgzTUM3M0RBVSJ9.DJNuN-wTXxOW3VZHcN_tlUIFOHfI0GeD_uzs1RplwsGTBe0Z4KpIojEQ85N7tSnuLxuGlsR8kd1SrbcvxhkWaw";
|
pub(crate) static DELETE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImRlbGV0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL2FzcGVfdXJpIjoiYXNwZTpleGFtcGxlLmNvbTo2TzZDV0xOTTY2WjdDWU9OS0RPTktMWVBBUSJ9.Z_cZOKHK-QwpejGMU_iAl_5a2teJUmtqwxVfElP-fGL25qZWhFRL5cG-B08YrwBc1vGS5rO31RY1RKtqUiiuKQ";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
pub use hex_color::HexColor;
|
use hex_color::HexColor;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use serde_email::Email;
|
use serde_email::Email;
|
||||||
pub use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::utils::jwt::{AspJwsType, JwtSerializable};
|
use crate::utils::jwt::{AspJwsType, JwtSerializable};
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ mod tests {
|
||||||
|
|
||||||
use hex_color::HexColor;
|
use hex_color::HexColor;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
|
use serde_email::Email;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
keys::AspKey,
|
keys::AspKey,
|
||||||
|
@ -82,9 +84,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let profile =
|
let profile =
|
||||||
AriadneSignatureProfile::decode_and_verify(crate::test_constants::PROFILE, &key);
|
AriadneSignatureProfile::decode_and_verify(crate::test_constants::PROFILE, Some(&key.fingerprint));
|
||||||
assert!(profile.is_ok(), "Profile should parse and verify correctly");
|
assert!(profile.is_ok(), "Profile should parse and verify correctly");
|
||||||
let profile = profile.unwrap();
|
let (_, profile) = profile.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*profile,
|
*profile,
|
||||||
AriadneSignatureProfile {
|
AriadneSignatureProfile {
|
||||||
|
@ -95,9 +97,9 @@ mod tests {
|
||||||
"dns:example.com?type=TXT".to_string(),
|
"dns:example.com?type=TXT".to_string(),
|
||||||
"https://git.example.com/example/forgejo_proof".to_string()
|
"https://git.example.com/example/forgejo_proof".to_string()
|
||||||
],
|
],
|
||||||
description: None,
|
description: Some("This is an example profile".to_string()),
|
||||||
avatar_url: None,
|
avatar_url: Some(Url::from_str("https://placehold.co/256").unwrap()),
|
||||||
email: None,
|
email: Some(Email::from_str("example@example.com").unwrap()),
|
||||||
color: Some(HexColor::from_str("#a434eb").unwrap()),
|
color: Some(HexColor::from_str("#a434eb").unwrap()),
|
||||||
},
|
},
|
||||||
"Profile should decode correctly"
|
"Profile should decode correctly"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use josekit::{
|
use josekit::{
|
||||||
|
jwk::Jwk,
|
||||||
jws::JwsHeader,
|
jws::JwsHeader,
|
||||||
jwt::{self, JwtPayload},
|
jwt::{self, JwtPayload},
|
||||||
};
|
};
|
||||||
|
@ -19,9 +20,10 @@ pub trait JwtSerializable {}
|
||||||
|
|
||||||
pub trait JwtSerialize {
|
pub trait JwtSerialize {
|
||||||
fn encode_and_sign(&self, key: &AspKey) -> Result<String, JwtSerializationError>;
|
fn encode_and_sign(&self, key: &AspKey) -> Result<String, JwtSerializationError>;
|
||||||
fn decode_and_verify(jwt: &str, key: &AspKey) -> Result<Box<Self>, JwtDeserializationError>
|
fn decode_and_verify<S>(jwt: &str, expected_fingerprint: Option<S>) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||||
where
|
where
|
||||||
Self: for<'de> Deserialize<'de> + JwtSerializable;
|
Self: for<'de> Deserialize<'de> + JwtSerializable,
|
||||||
|
S: AsRef<str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -36,13 +38,15 @@ pub enum JwtSerializationError {
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum JwtDeserializationError {
|
pub enum JwtDeserializationError {
|
||||||
#[error("provided jwk was not the correct key for the provided jwt")]
|
#[error("nested jwk had an incorrect kid")]
|
||||||
|
MalformedJwkError,
|
||||||
|
#[error("nested jwk fingerprint did not match expected fingerprint")]
|
||||||
WrongJwkError,
|
WrongJwkError,
|
||||||
#[error("jwt header was unable to be decoded")]
|
#[error("jwt header was unable to be decoded")]
|
||||||
HeaderDecodeError,
|
HeaderDecodeError,
|
||||||
#[error("jwt was unable to be decoded and verified")]
|
#[error("jwt was unable to be decoded and verified")]
|
||||||
JwtDecodeError,
|
JwtDecodeError,
|
||||||
#[error("provided jwk was unable to be used")]
|
#[error("nested jwk was unable to be used for verification")]
|
||||||
JwkUsageError,
|
JwkUsageError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,9 +80,10 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
||||||
.or(Err(JwtSerializationError::SerializationError))
|
.or(Err(JwtSerializationError::SerializationError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_and_verify(jwt: &str, key: &AspKey) -> Result<Box<Self>, JwtDeserializationError>
|
fn decode_and_verify<S>(jwt: &str, expected_fingerprint: Option<S>) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||||
where
|
where
|
||||||
Self: for<'de> serde::Deserialize<'de> + JwtSerializable,
|
Self: for<'de> serde::Deserialize<'de> + JwtSerializable,
|
||||||
|
S: AsRef<str>
|
||||||
{
|
{
|
||||||
// Decode the header and check if the key id is correct
|
// Decode the header and check if the key id is correct
|
||||||
let header = jwt::decode_header(jwt).or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
let header = jwt::decode_header(jwt).or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||||
|
@ -89,8 +94,26 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
||||||
.as_str()
|
.as_str()
|
||||||
.context("kid value on header was not a string")
|
.context("kid value on header was not a string")
|
||||||
.or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||||
|
let key = AspKey::from_jwk(
|
||||||
|
Jwk::from_map(
|
||||||
|
header
|
||||||
|
.claim("jwk")
|
||||||
|
.context("jwk value on header was missing")
|
||||||
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?
|
||||||
|
.as_object()
|
||||||
|
.context("kid value on header was not an object")
|
||||||
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.context("jwk value on header was not a valid jwk")
|
||||||
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?,
|
||||||
|
)
|
||||||
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||||
|
|
||||||
if key.fingerprint != key_id {
|
if key.fingerprint != key_id {
|
||||||
|
return Err(JwtDeserializationError::MalformedJwkError);
|
||||||
|
}
|
||||||
|
if expected_fingerprint.is_some_and(|e| e.as_ref() != key_id) {
|
||||||
return Err(JwtDeserializationError::WrongJwkError);
|
return Err(JwtDeserializationError::WrongJwkError);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,6 +129,6 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
||||||
serde_json::from_value(serde_json::Value::Object(payload.claims_set().clone()))
|
serde_json::from_value(serde_json::Value::Object(payload.claims_set().clone()))
|
||||||
.or(Err(JwtDeserializationError::JwtDecodeError))?;
|
.or(Err(JwtDeserializationError::JwtDecodeError))?;
|
||||||
|
|
||||||
Ok(Box::new(claims))
|
Ok((key, Box::new(claims)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub struct KeysDeleteCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysDeleteCommand {
|
impl AspmSubcommand for KeysDeleteCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
// Fetch key from db
|
// Fetch key from db
|
||||||
let entry = Keys::query_key(&state.db, &self.key)
|
let entry = Keys::query_key(&state.db, &self.key)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub struct KeysExportCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysExportCommand {
|
impl AspmSubcommand for KeysExportCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
// Fetch key from db
|
// Fetch key from db
|
||||||
let entry = Keys::query_key(&state.db, &self.key)
|
let entry = Keys::query_key(&state.db, &self.key)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub struct KeysGenerateCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysGenerateCommand {
|
impl AspmSubcommand for KeysGenerateCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
if self.key_type == KeyGenerationType::Ed25519 {
|
if self.key_type == KeyGenerationType::Ed25519 {
|
||||||
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
|
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
|
||||||
.with_prompt("You are creating an Ed25519 key. Before confirming, please make sure you are aware that this may not be supported in browser environments, such as being viewed on https://keyoxide.org. Are you sure you want to create an Ed25519 key?")
|
.with_prompt("You are creating an Ed25519 key. Before confirming, please make sure you are aware that this may not be supported in browser environments, such as being viewed on https://keyoxide.org. Are you sure you want to create an Ed25519 key?")
|
||||||
|
|
|
@ -27,9 +27,10 @@ pub enum KeyImportFormat {
|
||||||
/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves.
|
/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct KeysImportCommand {
|
pub struct KeysImportCommand {
|
||||||
/// The format of key to import
|
/// The format of key to import.
|
||||||
|
///
|
||||||
format: KeyImportFormat,
|
format: KeyImportFormat,
|
||||||
/// The key to import, as a file or "-" for stdin. This must be a valid JWK
|
/// The key to import, as a string.
|
||||||
key: String,
|
key: String,
|
||||||
/// The alias of the key to import. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint.
|
/// The alias of the key to import. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint.
|
||||||
#[arg(short = 'n', long)]
|
#[arg(short = 'n', long)]
|
||||||
|
@ -38,7 +39,7 @@ pub struct KeysImportCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysImportCommand {
|
impl AspmSubcommand for KeysImportCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let alias = if let Some(alias) = &self.key_alias {
|
let alias = if let Some(alias) = &self.key_alias {
|
||||||
alias.clone()
|
alias.clone()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub struct KeysListCommand;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysListCommand {
|
impl AspmSubcommand for KeysListCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let entries = Keys::find()
|
let entries = Keys::find()
|
||||||
.all(&state.db)
|
.all(&state.db)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -9,11 +9,11 @@ use crate::entities::keys::{Column as KeysColumn, Entity as KeysEntity, Model as
|
||||||
use crate::AspmState;
|
use crate::AspmState;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AspmSubcommand: Parser + Sync {
|
pub trait AspmSubcommand: Parser + Sync + Send {
|
||||||
async fn execute(&self, _state: AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, _state: AspmState) -> Result<(), anyhow::Error> {
|
||||||
panic!("Not implemented")
|
panic!("Not implemented")
|
||||||
}
|
}
|
||||||
fn execute_sync(&self, state: AspmState, runtime: Runtime) -> Result<(), anyhow::Error> {
|
fn execute_sync(self, state: AspmState, runtime: Runtime) -> Result<(), anyhow::Error> {
|
||||||
runtime.block_on(self.execute(state))
|
runtime.block_on(self.execute(state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use asp::{keys::AspKeyType, profiles::*, utils::jwt::AspJwsType};
|
use asp::{
|
||||||
|
hex_color::HexColor, keys::AspKeyType, profiles::*, serde_email::Email, url::Url,
|
||||||
|
utils::jwt::AspJwsType,
|
||||||
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use dialoguer::{theme::ColorfulTheme, Input, Select};
|
use dialoguer::{theme::ColorfulTheme, Input, Select};
|
||||||
use sea_orm::{ActiveValue, EntityTrait};
|
use sea_orm::{ActiveValue, EntityTrait};
|
||||||
|
@ -27,7 +30,7 @@ pub struct ProfilesCreateCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for ProfilesCreateCommand {
|
impl AspmSubcommand for ProfilesCreateCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let theme = ColorfulTheme::default();
|
let theme = ColorfulTheme::default();
|
||||||
|
|
||||||
let alias = if let Some(alias) = &self.profile_alias {
|
let alias = if let Some(alias) = &self.profile_alias {
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct ProfilesDeleteCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for ProfilesDeleteCommand {
|
impl AspmSubcommand for ProfilesDeleteCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let profile = match &self.profile {
|
let profile = match &self.profile {
|
||||||
Some(key) => Profiles::find_by_id(key)
|
Some(key) => Profiles::find_by_id(key)
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
|
@ -101,7 +101,8 @@ impl AspmSubcommand for ProfilesDeleteCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.delete(&state.db)
|
profile
|
||||||
|
.delete(&state.db)
|
||||||
.await
|
.await
|
||||||
.context("Unable to delete profile and claims from database")?;
|
.context("Unable to delete profile and claims from database")?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anstyle::{AnsiColor, Reset, Style as Anstyle};
|
use anstyle::{AnsiColor, Reset, Style as Anstyle};
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use asp::profiles::{Email, HexColor, Url};
|
use asp::{hex_color::HexColor, serde_email::Email, url::Url};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use dialoguer::{theme::ColorfulTheme, Input, Select};
|
use dialoguer::{theme::ColorfulTheme, Input, Select};
|
||||||
use indoc::writedoc;
|
use indoc::writedoc;
|
||||||
|
@ -22,7 +22,7 @@ pub struct ProfilesEditCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for ProfilesEditCommand {
|
impl AspmSubcommand for ProfilesEditCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let (profile, claims) = match &self.profile {
|
let (profile, claims) = match &self.profile {
|
||||||
Some(fingerprint) => {
|
Some(fingerprint) => {
|
||||||
let mut profiles = Profiles::find_by_id(fingerprint)
|
let mut profiles = Profiles::find_by_id(fingerprint)
|
||||||
|
|
|
@ -2,8 +2,11 @@ use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit as _};
|
||||||
use anyhow::{anyhow, bail, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher as _};
|
use argon2::{password_hash::SaltString, Argon2, PasswordHasher as _};
|
||||||
use asp::{
|
use asp::{
|
||||||
|
hex_color::HexColor,
|
||||||
keys::AspKey,
|
keys::AspKey,
|
||||||
profiles::{AriadneSignatureProfile, Email, HexColor, Url},
|
profiles::AriadneSignatureProfile,
|
||||||
|
serde_email::Email,
|
||||||
|
url::Url,
|
||||||
utils::jwt::{AspJwsType, JwtSerialize},
|
utils::jwt::{AspJwsType, JwtSerialize},
|
||||||
};
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
@ -23,7 +26,7 @@ pub struct ProfilesExportCommand {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for ProfilesExportCommand {
|
impl AspmSubcommand for ProfilesExportCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let Some(profile) = Profiles::find_by_id(&self.fingerprint)
|
let Some(profile) = Profiles::find_by_id(&self.fingerprint)
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await
|
.await
|
||||||
|
|
106
src/commands/profiles/import.rs
Normal file
106
src/commands/profiles/import.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use asp::{
|
||||||
|
aspe::{self, AspeFetchFailure},
|
||||||
|
profiles::AriadneSignatureProfile,
|
||||||
|
url::{Host, Url},
|
||||||
|
utils::jwt::JwtSerialize,
|
||||||
|
};
|
||||||
|
use clap::Parser;
|
||||||
|
use sea_orm::{EntityTrait, IntoActiveValue, SqlErr};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commands::AspmSubcommand,
|
||||||
|
entities::{claims, prelude::*, profiles},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Imports a profile either directly or from an ASPE server. To import from an aspe server, pass a valid ASPE URI. The relevant key must be imported seperately beforehand.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct ProfilesImportCommand {
|
||||||
|
/// Either the encoded profile, or an ASPE URI to fetch from
|
||||||
|
profile: String,
|
||||||
|
/// The alias to give to the imported profile
|
||||||
|
#[clap(trailing_var_arg = true)]
|
||||||
|
alias: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AspmSubcommand for ProfilesImportCommand {
|
||||||
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
|
let (profile, fingerprint) = match Url::parse(&self.profile).ok().and_then(|url| {
|
||||||
|
Some({
|
||||||
|
let (host, fingerprint) = url.path().split_once(':')?;
|
||||||
|
(host.to_string(), fingerprint.to_string())
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
Some((host, fingerprint)) => match aspe::AspeServer::new(Host::parse(&host).unwrap())?
|
||||||
|
.fetch_profile(&fingerprint)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(profile) => (profile, Some(fingerprint)),
|
||||||
|
Err(AspeFetchFailure::NotFound) => {
|
||||||
|
eprintln!(
|
||||||
|
"A profile could not be found with the specified host and fingerprint"
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Err(AspeFetchFailure::RateLimited) => {
|
||||||
|
eprintln!("The requested profile could not be fetched due to ratelimiting, try again later");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Err(AspeFetchFailure::TooLarge) => {
|
||||||
|
eprintln!("The server rejected the request as it was deemed too large");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Err(AspeFetchFailure::Unknown(e)) => return Err(e.into()),
|
||||||
|
},
|
||||||
|
_ => (self.profile, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (key, profile) = AriadneSignatureProfile::decode_and_verify(&profile, fingerprint)
|
||||||
|
.context("The provided or fetched profile was invalid and unable to be imported")?;
|
||||||
|
|
||||||
|
match Profiles::insert(profiles::ActiveModel {
|
||||||
|
alias: self.alias.join(" ").into_active_value(),
|
||||||
|
key: key.fingerprint.clone().into_active_value(),
|
||||||
|
name: profile.name.into_active_value(),
|
||||||
|
description: profile.description.into_active_value(),
|
||||||
|
email: profile
|
||||||
|
.email
|
||||||
|
.map(|email| email.to_string())
|
||||||
|
.into_active_value(),
|
||||||
|
avatar_url: profile
|
||||||
|
.avatar_url
|
||||||
|
.map(|avatar_url| avatar_url.to_string())
|
||||||
|
.into_active_value(),
|
||||||
|
color: profile
|
||||||
|
.color
|
||||||
|
.map(|color| color.to_string())
|
||||||
|
.into_active_value(),
|
||||||
|
})
|
||||||
|
.exec(&state.db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
Claims::insert_many(profile.claims.into_iter().map(|claim| claims::ActiveModel {
|
||||||
|
profile: key.fingerprint.clone().into_active_value(),
|
||||||
|
uri: claim.into_active_value(),
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.exec(&state.db)
|
||||||
|
.await
|
||||||
|
.context("Unable to insert claims into database")?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Successfully imported profile with fingerprint {}",
|
||||||
|
key.fingerprint
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(e) if matches!(e.sql_err(), Some(SqlErr::ForeignKeyConstraintViolation(_))) => {
|
||||||
|
eprintln!("Unable to import profile as the key has not first been imported. Import the secret key and then try again.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ pub struct ProfilesListCommand {}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for ProfilesListCommand {
|
impl AspmSubcommand for ProfilesListCommand {
|
||||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
// Fetch data
|
// Fetch data
|
||||||
let profiles = Profiles::find()
|
let profiles = Profiles::find()
|
||||||
.find_with_related(Claims)
|
.find_with_related(Claims)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
pub mod create;
|
pub mod create;
|
||||||
|
pub mod delete;
|
||||||
pub mod edit;
|
pub mod edit;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
|
pub mod import;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod delete;
|
|
||||||
|
|
||||||
/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles.
|
/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -20,4 +21,5 @@ pub enum ProfilesSubcommands {
|
||||||
List(list::ProfilesListCommand),
|
List(list::ProfilesListCommand),
|
||||||
Edit(edit::ProfilesEditCommand),
|
Edit(edit::ProfilesEditCommand),
|
||||||
Delete(delete::ProfilesDeleteCommand),
|
Delete(delete::ProfilesDeleteCommand),
|
||||||
|
Import(import::ProfilesImportCommand),
|
||||||
}
|
}
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -23,6 +23,7 @@ const DATABASE_URL: &str = "sqlite://DB_PATH?mode=rwc";
|
||||||
pub struct AspmState {
|
pub struct AspmState {
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub db: DatabaseConnection,
|
pub db: DatabaseConnection,
|
||||||
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -142,26 +143,27 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
|
||||||
.context("Unable to check database for keys table")?);
|
.context("Unable to check database for keys table")?);
|
||||||
|
|
||||||
// Make the state
|
// Make the state
|
||||||
let state = AspmState { data_dir, db };
|
let state = AspmState { data_dir, db, verbose: parsed.verbose };
|
||||||
|
|
||||||
Ok::<AspmState, anyhow::Error>(state)
|
Ok::<AspmState, anyhow::Error>(state)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Call the subcommand
|
// Call the subcommand
|
||||||
match &parsed.subcommand {
|
match parsed.subcommand {
|
||||||
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
AspmSubcommands::Keys(subcommand) => match subcommand.subcommand {
|
||||||
KeysSubcommands::Generate(subcommand) => subcommand.execute_sync(state, runtime),
|
KeysSubcommands::Generate(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
|
KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
KeysSubcommands::Import(subcommand) => subcommand.execute_sync(state, runtime),
|
KeysSubcommands::Import(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
},
|
},
|
||||||
AspmSubcommands::Profiles(subcommand) => match &subcommand.subcommand {
|
AspmSubcommands::Profiles(subcommand) => match subcommand.subcommand {
|
||||||
ProfilesSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime),
|
ProfilesSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
ProfilesSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
ProfilesSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
ProfilesSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
ProfilesSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
ProfilesSubcommands::Edit(subcommand) => subcommand.execute_sync(state, runtime),
|
ProfilesSubcommands::Edit(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
ProfilesSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
|
ProfilesSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
|
ProfilesSubcommands::Import(subcommand) => subcommand.execute_sync(state, runtime),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue