mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-22 21:49: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},
|
||||
StatusCode,
|
||||
};
|
||||
pub use url::Host;
|
||||
use url::Host;
|
||||
|
||||
/// An ASPE-compatible server
|
||||
pub struct AspeServer {
|
||||
|
@ -100,14 +100,14 @@ impl AspeServer {
|
|||
|
||||
pub async fn fetch_profile(
|
||||
&self,
|
||||
fingerprint: impl Into<String>,
|
||||
fingerprint: impl AsRef<str>,
|
||||
) -> Result<String, AspeFetchFailure> {
|
||||
let res = self
|
||||
.client
|
||||
.get(format!(
|
||||
"https://{host}/.well-known/aspe/id/{fingerprint}",
|
||||
host = self.host,
|
||||
fingerprint = fingerprint.into()
|
||||
fingerprint = fingerprint.as_ref()
|
||||
))
|
||||
.header(
|
||||
header::ACCEPT,
|
||||
|
|
|
@ -72,12 +72,14 @@ mod tests {
|
|||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).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!(
|
||||
decoded.is_ok(),
|
||||
"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!(
|
||||
*decoded,
|
||||
|
@ -105,7 +107,7 @@ mod tests {
|
|||
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!(
|
||||
encoded.is_ok(),
|
||||
"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())
|
||||
.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!(
|
||||
decoded.is_ok(),
|
||||
"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!(
|
||||
*decoded,
|
||||
|
@ -150,7 +154,7 @@ mod tests {
|
|||
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!(
|
||||
encoded.is_ok(),
|
||||
"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())
|
||||
.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!(
|
||||
decoded.is_ok(),
|
||||
"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!(
|
||||
*decoded,
|
||||
|
|
|
@ -18,7 +18,7 @@ use serde_json::Map;
|
|||
use sha2::{Digest, Sha512};
|
||||
|
||||
/// An enum representing the possible types of JWK for ASPs
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AspKeyType {
|
||||
Ed25519,
|
||||
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
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct AspKey {
|
||||
pub key_type: AspKeyType,
|
||||
pub fingerprint: String,
|
||||
|
|
|
@ -3,20 +3,18 @@ pub mod keys;
|
|||
pub mod profiles;
|
||||
pub mod utils;
|
||||
|
||||
pub use hex_color;
|
||||
pub use serde_email;
|
||||
pub use url;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_constants {
|
||||
// NOTE: This key is taken from the example keys in RFC 7517
|
||||
pub(crate) static KEY: &str = r#"
|
||||
{"kty":"EC",
|
||||
"crv":"P-256",
|
||||
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
|
||||
"#;
|
||||
pub(crate) static ASPE_URI: &str = "aspe:example.com:452JFAI6B3KOLKBAUX3MC73DAU";
|
||||
pub(crate) static KEY: &str = r#"{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM","d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}"#;
|
||||
pub(crate) static ASPE_URI: &str = "aspe:example.com:6O6CWLNM66Z7CYONKDONKLYPAQ";
|
||||
|
||||
pub(crate) static PROFILE: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicHJvZmlsZSIsImh0dHA6Ly9hcmlhZG5lLmlkL25hbWUiOiJFeGFtcGxlIG5hbWUiLCJodHRwOi8vYXJpYWRuZS5pZC9jbGFpbXMiOlsiZG5zOmV4YW1wbGUuY29tP3R5cGU9VFhUIiwiaHR0cHM6Ly9naXQuZXhhbXBsZS5jb20vZXhhbXBsZS9mb3JnZWpvX3Byb29mIl0sImh0dHA6Ly9hcmlhZG5lLmlkL2NvbG9yIjoiI0E0MzRFQiJ9.u5AbAqRpyXetXwU_QqpZrieNzwZGCRZ0tFTL4FoIwPRiZZ9iIGBnqs7PWbsd0iHQpYT_Q7s1GmwggGssM9ttxQ";
|
||||
pub(crate) static CREATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImNyZWF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalExTWtwR1FVazJRak5MVDB4TFFrRlZXRE5OUXpjelJFRlZJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyTnZiRzl5SWpvaUkwRTBNelJGUWlKOS51NUFiQXFScHlYZXRYd1VfUXFwWnJpZU56d1pHQ1JaMHRGVEw0Rm9Jd1BSaVpaOWlJR0JucXM3UFdic2QwaUhRcFlUX1E3czFHbXdnZ0dzc005dHR4USJ9.f8NdVzrjCZKT2R5MzUZkgcnNIJWo6ftQj6MCvXF5cgpjYt3suTqOGoBs6EKvtsgVGs12uS4ZxNnVAnFMsKKGlQ";
|
||||
pub(crate) static UPDATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6InVwZGF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalExTWtwR1FVazJRak5MVDB4TFFrRlZXRE5OUXpjelJFRlZJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyTnZiRzl5SWpvaUkwRTBNelJGUWlKOS51NUFiQXFScHlYZXRYd1VfUXFwWnJpZU56d1pHQ1JaMHRGVEw0Rm9Jd1BSaVpaOWlJR0JucXM3UFdic2QwaUhRcFlUX1E3czFHbXdnZ0dzc005dHR4USIsImh0dHA6Ly9hcmlhZG5lLmlkL2FzcGVfdXJpIjoiYXNwZTpleGFtcGxlLmNvbTo0NTJKRkFJNkIzS09MS0JBVVgzTUM3M0RBVSJ9.044vzbhefes8bFJFrXLwU2RNYhNvK_rNDDqM7NjEaC8alyFl-5Fh_Obj-pIKUkcxD-HL27y2objt_-lbDqvw4g";
|
||||
pub(crate) static DELETE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImRlbGV0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL2FzcGVfdXJpIjoiYXNwZTpleGFtcGxlLmNvbTo0NTJKRkFJNkIzS09MS0JBVVgzTUM3M0RBVSJ9.DJNuN-wTXxOW3VZHcN_tlUIFOHfI0GeD_uzs1RplwsGTBe0Z4KpIojEQ85N7tSnuLxuGlsR8kd1SrbcvxhkWaw";
|
||||
pub(crate) static PROFILE: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicHJvZmlsZSIsImh0dHA6Ly9hcmlhZG5lLmlkL25hbWUiOiJFeGFtcGxlIG5hbWUiLCJodHRwOi8vYXJpYWRuZS5pZC9jbGFpbXMiOlsiZG5zOmV4YW1wbGUuY29tP3R5cGU9VFhUIiwiaHR0cHM6Ly9naXQuZXhhbXBsZS5jb20vZXhhbXBsZS9mb3JnZWpvX3Byb29mIl0sImh0dHA6Ly9hcmlhZG5lLmlkL2Rlc2NyaXB0aW9uIjoiVGhpcyBpcyBhbiBleGFtcGxlIHByb2ZpbGUiLCJodHRwOi8vYXJpYWRuZS5pZC9lbWFpbCI6ImV4YW1wbGVAZXhhbXBsZS5jb20iLCJodHRwOi8vYXJpYWRuZS5pZC9hdmF0YXJfdXJsIjoiaHR0cHM6Ly9wbGFjZWhvbGQuY28vMjU2IiwiaHR0cDovL2FyaWFkbmUuaWQvY29sb3IiOiIjQTQzNEVCIn0.aVdOWTIjdRo8riTiepNIadLNXJxPnOnmKzKzv6C8Abt7hQSlRYrEFg42PpF7R7juz5INuJauAv-xKj2s9kweDA";
|
||||
pub(crate) static CREATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6ImNyZWF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalpQTmtOWFRFNU5OalphTjBOWlQwNUxSRTlPUzB4WlVFRlJJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyUmxjMk55YVhCMGFXOXVJam9pVkdocGN5QnBjeUJoYmlCbGVHRnRjR3hsSUhCeWIyWnBiR1VpTENKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5bGJXRnBiQ0k2SW1WNFlXMXdiR1ZBWlhoaGJYQnNaUzVqYjIwaUxDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOWhkbUYwWVhKZmRYSnNJam9pYUhSMGNITTZMeTl3YkdGalpXaHZiR1F1WTI4dk1qVTJJaXdpYUhSMGNEb3ZMMkZ5YVdGa2JtVXVhV1F2WTI5c2IzSWlPaUlqUVRRek5FVkNJbjAuYVZkT1dUSWpkUm84cmlUaWVwTklhZExOWEp4UG5Pbm1Lekt6djZDOEFidDdoUVNsUllyRUZnNDJQcEY3UjdqdXo1SU51SmF1QXYteEtqMnM5a3dlREEifQ.G5Asc4gswiU0iw8oBZGz4dmPREKmUmHykKfjXY-89sVm_HJPfO4XRmODIqaNkonlehsS23jgry5_Dt4X6fO6PA";
|
||||
pub(crate) static UPDATE_REQUEST: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjZPNkNXTE5NNjZaN0NZT05LRE9OS0xZUEFRIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicmVxdWVzdCIsImh0dHA6Ly9hcmlhZG5lLmlkL2FjdGlvbiI6InVwZGF0ZSIsImh0dHA6Ly9hcmlhZG5lLmlkL3Byb2ZpbGVfandzIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlV6STFOaUlzSW10cFpDSTZJalpQTmtOWFRFNU5OalphTjBOWlQwNUxSRTlPUzB4WlVFRlJJaXdpYW5kcklqcDdJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pVFV0Q1ExUk9TV05MVlZORWFXa3hNWGxUY3pNMU1qWnBSRm80UVdsVWJ6ZFVkVFpMVUVGeGRqZEVOQ0lzSW5raU9pSTBSWFJzTmxOU1Z6SlphVXhWY2s0MWRtWjJWa2gxYUhBM2VEaFFlR3gwYlZkWGJHSmlUVFJKUm5sTkluMTkuZXlKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5MlpYSnphVzl1SWpvd0xDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOTBlWEJsSWpvaWNISnZabWxzWlNJc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyNWhiV1VpT2lKRmVHRnRjR3hsSUc1aGJXVWlMQ0pvZEhSd09pOHZZWEpwWVdSdVpTNXBaQzlqYkdGcGJYTWlPbHNpWkc1ek9tVjRZVzF3YkdVdVkyOXRQM1I1Y0dVOVZGaFVJaXdpYUhSMGNITTZMeTluYVhRdVpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTOW1iM0puWldwdlgzQnliMjltSWwwc0ltaDBkSEE2THk5aGNtbGhaRzVsTG1sa0wyUmxjMk55YVhCMGFXOXVJam9pVkdocGN5QnBjeUJoYmlCbGVHRnRjR3hsSUhCeWIyWnBiR1VpTENKb2RIUndPaTh2WVhKcFlXUnVaUzVwWkM5bGJXRnBiQ0k2SW1WNFlXMXdiR1ZBWlhoaGJYQnNaUzVqYjIwaUxDSm9kSFJ3T2k4dllYSnBZV1J1WlM1cFpDOWhkbUYwWVhKZmRYSnNJam9pYUhSMGNITTZMeTl3YkdGalpXaHZiR1F1WTI4dk1qVTJJaXdpYUhSMGNEb3ZMMkZ5YVdGa2JtVXVhV1F2WTI5c2IzSWlPaUlqUVRRek5FVkNJbjAuYVZkT1dUSWpkUm84cmlUaWVwTklhZExOWEp4UG5Pbm1Lekt6djZDOEFidDdoUVNsUllyRUZnNDJQcEY3UjdqdXo1SU51SmF1QXYteEtqMnM5a3dlREEiLCJodHRwOi8vYXJpYWRuZS5pZC9hc3BlX3VyaSI6ImFzcGU6ZXhhbXBsZS5jb206Nk82Q1dMTk02Nlo3Q1lPTktET05LTFlQQVEifQ.jGAmCQwGZ82iBleew2E1QNohe29HFgnSJO-UxwwYNI4g1n-_wRKVhE2Xw3Tah5UMBByEAbViLmlCMTS_hg6hPA";
|
||||
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};
|
||||
pub use serde_email::Email;
|
||||
pub use url::Url;
|
||||
use serde_email::Email;
|
||||
use url::Url;
|
||||
|
||||
use crate::utils::jwt::{AspJwsType, JwtSerializable};
|
||||
|
||||
|
@ -45,6 +45,8 @@ mod tests {
|
|||
|
||||
use hex_color::HexColor;
|
||||
use josekit::jwk::Jwk;
|
||||
use serde_email::Email;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
keys::AspKey,
|
||||
|
@ -82,9 +84,9 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
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");
|
||||
let profile = profile.unwrap();
|
||||
let (_, profile) = profile.unwrap();
|
||||
assert_eq!(
|
||||
*profile,
|
||||
AriadneSignatureProfile {
|
||||
|
@ -95,9 +97,9 @@ mod tests {
|
|||
"dns:example.com?type=TXT".to_string(),
|
||||
"https://git.example.com/example/forgejo_proof".to_string()
|
||||
],
|
||||
description: None,
|
||||
avatar_url: None,
|
||||
email: None,
|
||||
description: Some("This is an example profile".to_string()),
|
||||
avatar_url: Some(Url::from_str("https://placehold.co/256").unwrap()),
|
||||
email: Some(Email::from_str("example@example.com").unwrap()),
|
||||
color: Some(HexColor::from_str("#a434eb").unwrap()),
|
||||
},
|
||||
"Profile should decode correctly"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::Context;
|
||||
use josekit::{
|
||||
jwk::Jwk,
|
||||
jws::JwsHeader,
|
||||
jwt::{self, JwtPayload},
|
||||
};
|
||||
|
@ -19,9 +20,10 @@ pub trait JwtSerializable {}
|
|||
|
||||
pub trait JwtSerialize {
|
||||
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
|
||||
Self: for<'de> Deserialize<'de> + JwtSerializable;
|
||||
Self: for<'de> Deserialize<'de> + JwtSerializable,
|
||||
S: AsRef<str>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -36,13 +38,15 @@ pub enum JwtSerializationError {
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
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,
|
||||
#[error("jwt header was unable to be decoded")]
|
||||
HeaderDecodeError,
|
||||
#[error("jwt was unable to be decoded and verified")]
|
||||
JwtDecodeError,
|
||||
#[error("provided jwk was unable to be used")]
|
||||
#[error("nested jwk was unable to be used for verification")]
|
||||
JwkUsageError,
|
||||
}
|
||||
|
||||
|
@ -76,9 +80,10 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
|||
.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
|
||||
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))?;
|
||||
|
@ -89,8 +94,26 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
|||
.as_str()
|
||||
.context("kid value on header was not a string")
|
||||
.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 {
|
||||
return Err(JwtDeserializationError::MalformedJwkError);
|
||||
}
|
||||
if expected_fingerprint.is_some_and(|e| e.as_ref() != key_id) {
|
||||
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()))
|
||||
.or(Err(JwtDeserializationError::JwtDecodeError))?;
|
||||
|
||||
Ok(Box::new(claims))
|
||||
Ok((key, Box::new(claims)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub struct KeysDeleteCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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
|
||||
let entry = Keys::query_key(&state.db, &self.key)
|
||||
.await
|
||||
|
|
|
@ -61,7 +61,7 @@ pub struct KeysExportCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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
|
||||
let entry = Keys::query_key(&state.db, &self.key)
|
||||
.await
|
||||
|
|
|
@ -34,7 +34,7 @@ pub struct KeysGenerateCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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 {
|
||||
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?")
|
||||
|
|
|
@ -27,9 +27,10 @@ pub enum KeyImportFormat {
|
|||
/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct KeysImportCommand {
|
||||
/// The format of key to import
|
||||
/// The format of key to import.
|
||||
///
|
||||
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,
|
||||
/// 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)]
|
||||
|
@ -38,7 +39,7 @@ pub struct KeysImportCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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 {
|
||||
alias.clone()
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,7 @@ pub struct KeysListCommand;
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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()
|
||||
.all(&state.db)
|
||||
.await
|
||||
|
|
|
@ -9,11 +9,11 @@ use crate::entities::keys::{Column as KeysColumn, Entity as KeysEntity, Model as
|
|||
use crate::AspmState;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait AspmSubcommand: Parser + Sync {
|
||||
async fn execute(&self, _state: AspmState) -> Result<(), anyhow::Error> {
|
||||
pub trait AspmSubcommand: Parser + Sync + Send {
|
||||
async fn execute(self, _state: AspmState) -> Result<(), anyhow::Error> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
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 dialoguer::{theme::ColorfulTheme, Input, Select};
|
||||
use sea_orm::{ActiveValue, EntityTrait};
|
||||
|
@ -27,7 +30,7 @@ pub struct ProfilesCreateCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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 alias = if let Some(alias) = &self.profile_alias {
|
||||
|
|
|
@ -21,7 +21,7 @@ pub struct ProfilesDeleteCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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 {
|
||||
Some(key) => Profiles::find_by_id(key)
|
||||
.one(&state.db)
|
||||
|
@ -101,7 +101,8 @@ impl AspmSubcommand for ProfilesDeleteCommand {
|
|||
}
|
||||
}
|
||||
|
||||
profile.delete(&state.db)
|
||||
profile
|
||||
.delete(&state.db)
|
||||
.await
|
||||
.context("Unable to delete profile and claims from database")?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anstyle::{AnsiColor, Reset, Style as Anstyle};
|
||||
use anyhow::{bail, Context};
|
||||
use asp::profiles::{Email, HexColor, Url};
|
||||
use asp::{hex_color::HexColor, serde_email::Email, url::Url};
|
||||
use clap::Parser;
|
||||
use dialoguer::{theme::ColorfulTheme, Input, Select};
|
||||
use indoc::writedoc;
|
||||
|
@ -22,7 +22,7 @@ pub struct ProfilesEditCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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 {
|
||||
Some(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 argon2::{password_hash::SaltString, Argon2, PasswordHasher as _};
|
||||
use asp::{
|
||||
hex_color::HexColor,
|
||||
keys::AspKey,
|
||||
profiles::{AriadneSignatureProfile, Email, HexColor, Url},
|
||||
profiles::AriadneSignatureProfile,
|
||||
serde_email::Email,
|
||||
url::Url,
|
||||
utils::jwt::{AspJwsType, JwtSerialize},
|
||||
};
|
||||
use clap::Parser;
|
||||
|
@ -23,7 +26,7 @@ pub struct ProfilesExportCommand {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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)
|
||||
.one(&state.db)
|
||||
.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]
|
||||
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
|
||||
let profiles = Profiles::find()
|
||||
.find_with_related(Claims)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod edit;
|
||||
pub mod export;
|
||||
pub mod import;
|
||||
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.
|
||||
#[derive(Parser)]
|
||||
|
@ -20,4 +21,5 @@ pub enum ProfilesSubcommands {
|
|||
List(list::ProfilesListCommand),
|
||||
Edit(edit::ProfilesEditCommand),
|
||||
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 data_dir: PathBuf,
|
||||
pub db: DatabaseConnection,
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
|
@ -142,26 +143,27 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
|
|||
.context("Unable to check database for keys table")?);
|
||||
|
||||
// Make the state
|
||||
let state = AspmState { data_dir, db };
|
||||
let state = AspmState { data_dir, db, verbose: parsed.verbose };
|
||||
|
||||
Ok::<AspmState, anyhow::Error>(state)
|
||||
})?;
|
||||
|
||||
// Call the subcommand
|
||||
match &parsed.subcommand {
|
||||
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
||||
match parsed.subcommand {
|
||||
AspmSubcommands::Keys(subcommand) => match subcommand.subcommand {
|
||||
KeysSubcommands::Generate(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
KeysSubcommands::Delete(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::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
ProfilesSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
ProfilesSubcommands::Edit(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