Skip to content

Commit

Permalink
#13 client auth headers
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Nov 15, 2021
1 parent 45b31aa commit 2aa9939
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 19 deletions.
51 changes: 43 additions & 8 deletions lib/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,55 @@
//! Functions for interacting with an Atomic Server
use url::Url;

use crate::{errors::AtomicResult, parse::parse_json_ad_resource, Resource, Storelike};
use crate::{
agents::Agent, commit::sign_message, errors::AtomicResult, parse::parse_json_ad_resource,
Resource, Storelike,
};

/// Fetches a resource, makes sure its subject matches.
/// Checks the datatypes for the Values.
/// Ignores all atoms where the subject is different.
/// WARNING: Calls store methods, and is called by store methods, might get stuck in a loop!
pub fn fetch_resource(subject: &str, store: &impl Storelike) -> AtomicResult<Resource> {
let body = fetch_body(subject, crate::parse::JSON_AD_MIME)?;
pub fn fetch_resource(
subject: &str,
store: &impl Storelike,
for_agent: Option<Agent>,
) -> AtomicResult<Resource> {
let body = fetch_body(subject, crate::parse::JSON_AD_MIME, for_agent)?;
let resource = parse_json_ad_resource(&body, store)
.map_err(|e| format!("Error parsing body of {}. {}", subject, e))?;
Ok(resource)
}

/// Fetches a URL, returns its body
pub fn fetch_body(url: &str, content_type: &str) -> AtomicResult<String> {
/// Returns the various x-atomic authentication headers, includign agent signature
pub fn get_authentication_headers(url: &str, agent: &Agent) -> AtomicResult<Vec<(String, String)>> {
let mut headers = Vec::new();
let now = crate::datetime_helpers::now().to_string();
let message = format!("{} {}", url, now);
let signature = sign_message(
&message,
agent
.private_key
.as_ref()
.ok_or("No private key in agent")?,
&agent.public_key,
)?;
headers.push(("x-atomic-public-key".into(), agent.public_key.to_string()));
headers.push(("x-atomic-signature".into(), signature));
headers.push(("x-atomic-timestamp".into(), now));
headers.push(("x-atomic-agent".into(), agent.subject.to_string()));
Ok(headers)
}

/// Fetches a URL, returns its body.
/// Uses the store's Agent agent (if set) to sign the request.
pub fn fetch_body(url: &str, content_type: &str, for_agent: Option<Agent>) -> AtomicResult<String> {
if !url.starts_with("http") {
return Err(format!("Could not fetch url '{}', must start with http.", url).into());
}
if let Some(agent) = for_agent {
get_authentication_headers(url, &agent)?;
}
let resp = ureq::get(url)
.set("Accept", content_type)
.timeout_read(2000)
Expand All @@ -28,7 +59,7 @@ pub fn fetch_body(url: &str, content_type: &str) -> AtomicResult<String> {
};
let body = resp
.into_string()
.map_err(|e| format!("Could not parse response {}: {}", url, e))?;
.map_err(|e| format!("Could not parse HTTP response for {}: {}", url, e))?;
Ok(body)
}

Expand All @@ -50,7 +81,11 @@ pub fn fetch_tpf(
if let Some(val) = q_value {
url.query_pairs_mut().append_pair("value", val);
}
let body = fetch_body(url.as_str(), "application/ad+json")?;
let body = fetch_body(
url.as_str(),
"application/ad+json",
store.get_default_agent().ok(),
)?;
crate::parse::parse_json_ad_array(&body, store, false)
}

Expand Down Expand Up @@ -97,7 +132,7 @@ mod test {
#[ignore]
fn fetch_resource_basic() {
let store = crate::Store::init().unwrap();
let resource = fetch_resource(crate::urls::SHORTNAME, &store).unwrap();
let resource = fetch_resource(crate::urls::SHORTNAME, &store, None).unwrap();
let shortname = resource.get(crate::urls::SHORTNAME).unwrap();
assert!(shortname.to_string() == "shortname");
}
Expand Down
11 changes: 4 additions & 7 deletions lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::collections::{HashMap, HashSet};
use urls::{SET, SIGNER};

use crate::{
datatype::DataType, errors::AtomicResult, resources::PropVals, urls, Atom, Resource, Storelike,
Value,
datatype::DataType, datetime_helpers, errors::AtomicResult, resources::PropVals, urls, Atom,
Resource, Storelike, Value,
};

/// Contains two resources. The first is the Resource representation of the applied Commits.
Expand Down Expand Up @@ -426,7 +426,7 @@ fn sign_at(
}

/// Signs a string using a base64 encoded ed25519 private key. Outputs a base64 encoded ed25519 signature.
fn sign_message(message: &str, private_key: &str, public_key: &str) -> AtomicResult<String> {
pub fn sign_message(message: &str, private_key: &str, public_key: &str) -> AtomicResult<String> {
let private_key_bytes = base64::decode(private_key.to_string()).map_err(|e| {
format!(
"Failed decoding private key {}: {}",
Expand Down Expand Up @@ -457,10 +457,7 @@ fn sign_message(message: &str, private_key: &str, public_key: &str) -> AtomicRes
const ACCEPTABLE_TIME_DIFFERENCE: i64 = 10000;

pub fn check_timestamp(timestamp: i64) -> AtomicResult<()> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as i64;
let now = datetime_helpers::now();
if timestamp > now + ACCEPTABLE_TIME_DIFFERENCE {
return Err(format!(
"Commit CreatedAt timestamp must lie in the past. Check your clock. Timestamp now: {} CreatedAt is: {}",
Expand Down
2 changes: 1 addition & 1 deletion lib/src/datetime_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// Returns the current UNIX timestamp in milliseconds
/// Returns the current timestamp in milliseconds since UNIX epoch
pub fn now() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
Expand Down
4 changes: 3 additions & 1 deletion lib/src/storelike.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,11 @@ pub trait Storelike: Sized {
}

/// Fetches a resource, makes sure its subject matches.
/// Uses the default agent to sign the request.
/// Save to the store.
fn fetch_resource(&self, subject: &str) -> AtomicResult<Resource> {
let resource: Resource = crate::client::fetch_resource(subject, self)?;
let resource: Resource =
crate::client::fetch_resource(subject, self, self.get_default_agent().ok())?;
self.add_resource_opts(&resource, true, true, true)?;
Ok(resource)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub fn validate_store(
resource_count += 1;

if fetch_items {
match crate::client::fetch_resource(subject, store) {
match crate::client::fetch_resource(subject, store, store.get_default_agent().ok()) {
Ok(_) => {}
Err(e) => unfetchable.push((subject.clone(), e.to_string())),
}
Expand Down
2 changes: 1 addition & 1 deletion server/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async fn init_server() {
println!("response: {:?}", resp);
assert!(resp.status().is_success());
let body = resp.take_body();
assert!(body.as_str().contains("html"));
assert!(body.as_str().contains("html"), "no html in response");

// Should 404
let req = test::TestRequest::with_uri("/doesnotexist")
Expand Down

0 comments on commit 2aa9939

Please sign in to comment.