From 85c9c6e08cd45a7294918c663a90acbfbfb043d5 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 28 Sep 2022 22:14:02 +0200 Subject: [PATCH 01/45] #288 #489 Register Endpoint and true URLs --- lib/Cargo.toml | 1 + lib/src/collections.rs | 2 +- lib/src/commit.rs | 2 +- lib/src/db.rs | 21 +++++----- lib/src/db/query_index.rs | 2 +- lib/src/endpoints.rs | 1 + lib/src/plugins/mod.rs | 1 + lib/src/plugins/register.rs | 73 +++++++++++++++++++++++++++++++++++ lib/src/populate.rs | 56 +++++++++++++++++++++------ lib/src/resources.rs | 8 +--- lib/src/store.rs | 14 +++++-- lib/src/storelike.rs | 53 +++++++++++++++++-------- lib/src/urls.rs | 4 +- server/src/appstate.rs | 4 +- server/src/bin.rs | 2 +- server/src/commit_monitor.rs | 5 ++- server/src/handlers/commit.rs | 8 +--- server/src/helpers.rs | 1 + 18 files changed, 195 insertions(+), 63 deletions(-) create mode 100644 lib/src/plugins/register.rs diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 8d93bdc41..ab3c55d6f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -23,6 +23,7 @@ html2md = { version = "0.2.14", optional = true } kuchikiki = { version = "0.8.2", optional = true } lol_html = { version = "1", optional = true } rand = { version = "0.8" } +lazy_static = "1" regex = "1" ring = "0.17.6" rio_api = { version = "0.8", optional = true } diff --git a/lib/src/collections.rs b/lib/src/collections.rs index fa52713c0..a8d585a14 100644 --- a/lib/src/collections.rs +++ b/lib/src/collections.rs @@ -416,7 +416,7 @@ pub fn create_collection_resource_for_class( // Let the Collections collection be the top level item let parent = if class.subject == urls::COLLECTION { - drive + drive.to_string() } else { format!("{}/collections", drive) }; diff --git a/lib/src/commit.rs b/lib/src/commit.rs index 30cc04cf9..a9a7b4d79 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -181,7 +181,7 @@ impl Commit { let default_parent = store.get_self_url().ok_or("There is no self_url set, and no parent in the Commit. The commit can not be applied.")?; resource_old.set( urls::PARENT.into(), - Value::AtomicUrl(default_parent), + Value::AtomicUrl(default_parent.to_string()), store, )?; } diff --git a/lib/src/db.rs b/lib/src/db.rs index 3584d6d7f..656ebe84a 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -14,7 +14,8 @@ use std::{ vec, }; -use tracing::{info, instrument}; +use tracing::instrument; +use url::Url; use crate::{ agents::ForAgent, @@ -80,7 +81,7 @@ pub struct Db { /// A list of all the Collections currently being used. Is used to update `query_index`. watched_queries: sled::Tree, /// The address where the db will be hosted, e.g. http://localhost/ - server_url: String, + server_url: Url, /// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it. endpoints: Vec, /// Function called whenever a Commit is applied. @@ -197,6 +198,7 @@ impl Db { } fn map_sled_item_to_resource( + &self, item: Result<(sled::IVec, sled::IVec), sled::Error>, self_url: String, include_external: bool, @@ -204,7 +206,7 @@ impl Db { let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG); let subject: String = String::from_utf8_lossy(&subject).to_string(); - if !include_external && !subject.starts_with(&self_url) { + if !include_external && self.is_external_subject(&subject).ok()? { return None; } @@ -424,14 +426,14 @@ impl Storelike for Db { Ok(()) } - fn get_server_url(&self) -> &str { + fn get_server_url(&self) -> &Url { &self.server_url } - // Since the DB is often also the server, this should make sense. - // Some edge cases might appear later on (e.g. a slave DB that only stores copies?) - fn get_self_url(&self) -> Option { - Some(self.get_server_url().into()) + fn get_self_url(&self) -> Option<&Url> { + // Since the DB is often also the server, this should make sense. + // Some edge cases might appear later on (e.g. a slave DB that only stores copies?) + Some(self.get_server_url()) } fn get_default_agent(&self) -> AtomicResult { @@ -600,7 +602,7 @@ impl Storelike for Db { .expect("No self URL set, is required in DB"); let result = self.resources.into_iter().filter_map(move |item| { - Db::map_sled_item_to_resource(item, self_url.clone(), include_external) + Db::map_sled_item_to_resource(self, item, self_url.to_string(), include_external) }); Box::new(result) @@ -654,6 +656,7 @@ impl Storelike for Db { fn populate(&self) -> AtomicResult<()> { crate::populate::populate_all(self) + crate::populate::create_drive(self, None, &default_agent.subject, true) } #[instrument(skip(self))] diff --git a/lib/src/db/query_index.rs b/lib/src/db/query_index.rs index 442272624..0e5af9e8f 100644 --- a/lib/src/db/query_index.rs +++ b/lib/src/db/query_index.rs @@ -115,7 +115,7 @@ pub fn query_sorted_indexed( let (_q_filter, _val, subject) = parse_collection_members_key(&k)?; // If no external resources should be included, skip this one if it's an external resource - if !q.include_external && !subject.starts_with(&self_url) { + if !q.include_external && store.is_external_subject(subject)? { continue; } diff --git a/lib/src/endpoints.rs b/lib/src/endpoints.rs index 1ce1c0811..e97ddc005 100644 --- a/lib/src/endpoints.rs +++ b/lib/src/endpoints.rs @@ -81,6 +81,7 @@ pub fn default_endpoints() -> Vec { plugins::path::path_endpoint(), plugins::search::search_endpoint(), plugins::files::upload_endpoint(), + plugins::register::register_endpoint(), #[cfg(feature = "html")] plugins::bookmark::bookmark_endpoint(), plugins::importer::import_endpoint(), diff --git a/lib/src/plugins/mod.rs b/lib/src/plugins/mod.rs index 31a2bdcf1..569b4989b 100644 --- a/lib/src/plugins/mod.rs +++ b/lib/src/plugins/mod.rs @@ -45,5 +45,6 @@ pub mod files; pub mod path; pub mod prunetests; pub mod query; +pub mod register; pub mod search; pub mod versioning; diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs new file mode 100644 index 000000000..b097cb045 --- /dev/null +++ b/lib/src/plugins/register.rs @@ -0,0 +1,73 @@ +//! Creates a new Drive and optionally also an Agent. + +use crate::{agents::Agent, endpoints::Endpoint, errors::AtomicResult, urls, Resource, Storelike}; + +pub fn register_endpoint() -> Endpoint { + Endpoint { + path: "/register".to_string(), + params: [ + urls::INVITE_PUBKEY.to_string(), + urls::NAME.to_string(), + ].into(), + description: "Allows new users to easily, in one request, make both an Agent and a Drive. This drive will be created at the subdomain of `name`.".to_string(), + shortname: "register".to_string(), + handle: Some(construct_register_redirect), + handle_post: None, + } +} + +#[tracing::instrument(skip(store))] +pub fn construct_register_redirect( + url: url::Url, + store: &impl Storelike, + for_agent: Option<&str>, +) -> AtomicResult { + let requested_subject = url.to_string(); + let mut pub_key = None; + let mut name_option = None; + for (k, v) in url.query_pairs() { + match k.as_ref() { + "public-key" | urls::INVITE_PUBKEY => pub_key = Some(v.to_string()), + "name" | urls::NAME => name_option = Some(v.to_string()), + _ => {} + } + } + if pub_key.is_none() && name_option.is_none() { + return register_endpoint().to_resource(store); + } + + let name = if let Some(n) = name_option { + n + } else { + return Err("No name provided".into()); + }; + + let mut new_agent = None; + + let drive_creator_agent: String = if let Some(key) = pub_key { + let new = Agent::new_from_public_key(store, &key)?.subject; + new_agent = Some(new.clone()); + new + } else if let Some(agent) = for_agent { + agent.to_string() + } else { + return Err("No `public-key` provided".into()); + }; + + // Create the new Drive + let drive = crate::populate::create_drive(store, Some(&name), &drive_creator_agent, false)?; + + // Construct the Redirect Resource, which might provide the Client with a Subject for his Agent. + let mut redirect = Resource::new_instance(urls::REDIRECT, store)?; + redirect.set_string(urls::DESTINATION.into(), drive.get_subject(), store)?; + if let Some(agent) = new_agent { + redirect.set( + urls::REDIRECT_AGENT.into(), + crate::Value::AtomicUrl(agent), + store, + )?; + } + // The front-end requires the @id to be the same as requested + redirect.set_subject(requested_subject); + Ok(redirect) +} diff --git a/lib/src/populate.rs b/lib/src/populate.rs index 829850830..ad860c2b4 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -9,7 +9,7 @@ use crate::{ parse::ParseOpts, schema::{Class, Property}, storelike::Query, - urls, Storelike, Value, + urls, Resource, Storelike, Value, }; const DEFAULT_ONTOLOGY_PATH: &str = "defaultOntology"; @@ -153,17 +153,43 @@ pub fn populate_base_models(store: &impl Storelike) -> AtomicResult<()> { Ok(()) } -/// Creates a Drive resource at the base URL. Does not set rights. Use set_drive_rights for that. -pub fn create_drive(store: &impl Storelike) -> AtomicResult<()> { - let self_url = store - .get_self_url() - .ok_or("No self_url set, cannot populate store with Drive")?; - let mut drive = store.get_resource_new(&self_url); +/// Creates a Drive resource at the base URL if no name is passed. +#[tracing::instrument(skip(store), level = "info")] +pub fn create_drive( + store: &impl Storelike, + drive_name: Option<&str>, + for_agent: &str, + public_read: bool, +) -> AtomicResult { + let mut self_url = if let Some(url) = store.get_self_url() { + url.to_owned() + } else { + return Err("No self URL set. Cannot create drive.".into()); + }; + let drive_subject: String = if let Some(name) = drive_name { + // Let's make a subdomain + let host = self_url.host().expect("No host in server_url"); + let subdomain_host = format!("{}.{}", name, host); + self_url.set_host(Some(&subdomain_host))?; + self_url.to_string() + } else { + self_url.to_string() + }; + + let mut drive = if drive_name.is_some() { + if store.get_resource(&drive_subject).is_ok() { + return Err("Drive URL is already taken".into()); + } + Resource::new(drive_subject) + } else { + // Only for the base URL (of no drive name is passed), we should not check if the drive exists. + // This is because we use `create_drive` in the `--initialize` command. + store.get_resource_new(&drive_subject) + }; drive.set_class(urls::DRIVE); - let server_url = url::Url::parse(store.get_server_url())?; drive.set_string( urls::NAME.into(), - server_url.host_str().ok_or("Can't use current base URL")?, + drive_name.unwrap_or_else(|| self_url.host_str().unwrap()), store, )?; drive.save_locally(store)?; @@ -236,8 +262,10 @@ To use the data in your web apps checkout our client libraries: [@tomic/lib](htt Use [@tomic/cli](https://docs.atomicdata.dev/js-cli) to generate typed ontologies inside your code. "#, store.get_server_url(), &format!("{}/{}", drive.get_subject(), DEFAULT_ONTOLOGY_PATH)), store)?; } + drive.save_locally(store)?; - Ok(()) + + Ok(drive) } /// Imports the Atomic Data Core items (the entire atomicdata.dev Ontology / Vocabulary) @@ -312,7 +340,11 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> { .ok_or("No self URL in this Store - required for populating importer")?; let mut importer = crate::Resource::new(urls::construct_path_import(&base)); importer.set_class(urls::IMPORTER); - importer.set(urls::PARENT.into(), Value::AtomicUrl(base), store)?; + importer.set( + urls::PARENT.into(), + Value::AtomicUrl(base.to_string()), + store, + )?; importer.set(urls::NAME.into(), Value::String("Import".into()), store)?; importer.save_locally(store)?; Ok(()) @@ -323,7 +355,7 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> { /// Useful for helping a new user get started. pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> { let base = store.get_self_url().ok_or("No self_url")?; - let mut drive = store.get_resource(&base)?; + let mut drive = store.get_resource(base.as_str())?; let arr = vec![ format!("{}/setup", base), format!("{}/import", base), diff --git a/lib/src/resources.rs b/lib/src/resources.rs index 59dc990b3..0ec8ff89b 100644 --- a/lib/src/resources.rs +++ b/lib/src/resources.rs @@ -342,13 +342,7 @@ impl Resource { let commit_builder = self.get_commit_builder().clone(); let commit = commit_builder.sign(&agent, store, self)?; // If the current client is a server, and the subject is hosted here, don't post - let should_post = if let Some(self_url) = store.get_self_url() { - !self.subject.starts_with(&self_url) - } else { - // Current client is not a server, has no own persisted store - true - }; - if should_post { + if store.is_external_subject(&commit.subject)? { crate::client::post_commit(&commit, store)?; } let opts = CommitOpts { diff --git a/lib/src/store.rs b/lib/src/store.rs index 8aeb82913..7402615cb 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -1,6 +1,8 @@ //! In-memory store of Atomic data. //! This provides many methods for finding, changing, serializing and parsing Atomic Data. +use url::Url; + use crate::agents::Agent; use crate::storelike::QueryResult; use crate::Value; @@ -17,6 +19,10 @@ pub struct Store { default_agent: Arc>>, } +lazy_static::lazy_static! { + static ref LOCAL_STORE_URL: Url = Url::parse("local:store").unwrap(); +} + impl Store { /// Creates an empty Store. /// Run `.populate()` to get useful standard models loaded into your store. @@ -158,14 +164,14 @@ impl Storelike for Store { Box::new(self.hashmap.lock().unwrap().clone().into_values()) } - fn get_server_url(&self) -> &str { + fn get_server_url(&self) -> &Url { // TODO Should be implemented later when companion functionality is here // https://github.com/atomicdata-dev/atomic-server/issues/6 - "local:store" + &LOCAL_STORE_URL } - fn get_self_url(&self) -> Option { - Some(self.get_server_url().into()) + fn get_self_url(&self) -> Option<&Url> { + Some(self.get_server_url()) } fn get_default_agent(&self) -> AtomicResult { diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index 095b17583..c9adc80be 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -1,5 +1,7 @@ //! The Storelike Trait contains many useful methods for maniupulting / retrieving data. +use url::Url; + use crate::{ agents::{Agent, ForAgent}, commit::CommitResponse, @@ -86,12 +88,12 @@ pub trait Storelike: Sized { /// E.g. `https://example.com` /// This is where deltas should be sent to. /// Also useful for Subject URL generation. - fn get_server_url(&self) -> &str; + fn get_server_url(&self) -> &Url; /// Returns the root URL where this instance of the store is hosted. /// Should return `None` if this is simply a client and not a server. /// E.g. `https://example.com` - fn get_self_url(&self) -> Option { + fn get_self_url(&self) -> Option<&Url> { None } @@ -201,21 +203,16 @@ pub trait Storelike: Sized { /// Implement this if you want to have custom handlers for Commits. fn handle_commit(&self, _commit_response: &CommitResponse) {} - fn handle_not_found( - &self, - subject: &str, - _error: AtomicError, - for_agent: Option<&Agent>, - ) -> AtomicResult { - if let Some(self_url) = self.get_self_url() { - if subject.starts_with(&self_url) { - return Err(AtomicError::not_found(format!( - "Failed to retrieve locally: '{}'", - subject - ))); - } + fn handle_not_found(&self, subject: &str, error: AtomicError) -> AtomicResult { + // This does not work for subdomains + if self.is_external_subject(subject)? { + self.fetch_resource(subject, None) + } else { + Err(AtomicError::not_found(format!( + "Failed to retrieve locally: '{}'. {}", + subject, error + ))) } - self.fetch_resource(subject, for_agent) } /// Imports a JSON-AD string, returns the amount of imported resources. @@ -225,6 +222,30 @@ pub trait Storelike: Sized { Ok(len) } + /// Checks if the URL of some resource is owned by some external store. + /// If true, then the Subject points to a different server. + /// If you're using `Storelike` on something that does not persist (e.g. a client app), + /// the answer should always be `true`. + fn is_external_subject(&self, subject: &str) -> AtomicResult { + if let Some(self_url) = self.get_self_url() { + if subject.starts_with(&self_url.as_str()) { + return Ok(false); + } else { + let subject_url = url::Url::parse(subject)?; + let subject_host = subject_url.host().ok_or_else(|| { + AtomicError::not_found(format!("Subject URL has no host: {}", subject)) + })?; + let self_host = self_url.host().ok_or_else(|| { + AtomicError::not_found(format!("Self URL has no host: {}", self_url)) + })?; + if subject_host == self_host { + return Ok(false); + } + } + } + Ok(true) + } + /// Removes a resource from the store. Errors if not present. fn remove_resource(&self, subject: &str) -> AtomicResult<()>; diff --git a/lib/src/urls.rs b/lib/src/urls.rs index 335150d09..180bc21e8 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -1,5 +1,7 @@ //! Contains some of the most important Atomic Data URLs. +use url::Url; + // Classes pub const CLASS: &str = "https://atomicdata.dev/classes/Class"; pub const PROPERTY: &str = "https://atomicdata.dev/classes/Property"; @@ -151,7 +153,7 @@ pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent"; pub const SUDO_AGENT: &str = "sudo:agent"; // Paths -pub fn construct_path_import(base: &str) -> String { +pub fn construct_path_import(base: &Url) -> String { format!("{base}{PATH_IMPORT}") } diff --git a/server/src/appstate.rs b/server/src/appstate.rs index 945c833ad..e64f166f3 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -179,12 +179,12 @@ fn set_up_initial_invite(store: &impl Storelike) -> AtomicServerResult<()> { )?; invite.set( atomic_lib::urls::TARGET.into(), - atomic_lib::Value::AtomicUrl(store.get_server_url().into()), + atomic_lib::Value::AtomicUrl(store.get_server_url().to_string()), store, )?; invite.set( atomic_lib::urls::PARENT.into(), - atomic_lib::Value::AtomicUrl(store.get_server_url().into()), + atomic_lib::Value::AtomicUrl(store.get_server_url().to_string()), store, )?; invite.set( diff --git a/server/src/bin.rs b/server/src/bin.rs index 30cc513b7..a8be8d7e7 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -68,7 +68,7 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { let importer_subject = if let Some(i) = &import_opts.parent { i.into() } else { - urls::construct_path_import(&appstate.store.get_self_url().expect("No self url")) + urls::construct_path_import(appstate.store.get_self_url().expect("No self url")) }; let parse_opts = atomic_lib::parse::ParseOpts { importer: Some(importer_subject), diff --git a/server/src/commit_monitor.rs b/server/src/commit_monitor.rs index cf4d472b7..b75dd0ffe 100644 --- a/server/src/commit_monitor.rs +++ b/server/src/commit_monitor.rs @@ -54,8 +54,9 @@ impl Handler for CommitMonitor { fields(to = %msg.subject, agent = %msg.agent) )] fn handle(&mut self, msg: Subscribe, _ctx: &mut Context) { - // check if the agent has the rights to subscribe to this resource - if !msg.subject.starts_with(&self.store.get_self_url().unwrap()) { + // check if the agent has the rights to subscribe to this resourceif let Some(self_url) = self.get_self_url() { + + if self.store.is_external_subject(&msg.subject).unwrap_or(true) { tracing::warn!("can't subscribe to external resource"); return; } diff --git a/server/src/handlers/commit.rs b/server/src/handlers/commit.rs index 2ef23b35b..61c094ecf 100644 --- a/server/src/handlers/commit.rs +++ b/server/src/handlers/commit.rs @@ -19,12 +19,8 @@ pub async fn post_commit( let mut builder = HttpResponse::Ok(); let incoming_commit_resource = parse_json_ad_commit_resource(&body, store)?; let incoming_commit = Commit::from_resource(incoming_commit_resource)?; - if !incoming_commit.subject.contains( - &store - .get_self_url() - .ok_or("Cannot apply commits to this store. No self_url is set.")?, - ) { - return Err("Subject of commit should be sent to other domain - this store can not own this resource.".into()); + if store.is_external_subject(&incoming_commit.subject)? { + return Err("Subject of commit is external, and should be sent to its origin domain. This store can not own this resource. See https://github.com/atomicdata-dev/atomic-data-rust/issues/509".into()); } let opts = CommitOpts { validate_schema: true, diff --git a/server/src/helpers.rs b/server/src/helpers.rs index 4fc776de6..e2476328a 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -164,6 +164,7 @@ pub fn get_auth( /// Checks for authentication headers and returns Some agent's subject if everything is well. /// Skips these checks in public_mode and returns Ok(None). +/// Returns the Agent's subject or the Public Agent. #[tracing::instrument(skip(appstate))] pub fn get_client_agent( headers: &HeaderMap, From 0cff112e2172b0c930889ff3a4a878eee847a0e3 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 28 Sep 2022 22:22:13 +0200 Subject: [PATCH 02/45] Fix some tests --- lib/src/store.rs | 5 ++++- lib/src/storelike.rs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/store.rs b/lib/src/store.rs index 7402615cb..92eecdc19 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -19,8 +19,11 @@ pub struct Store { default_agent: Arc>>, } +/// The URL used for stores that are not accessible on the web. +pub const LOCAL_STORE_URL_STR: &str = "local:store"; + lazy_static::lazy_static! { - static ref LOCAL_STORE_URL: Url = Url::parse("local:store").unwrap(); + static ref LOCAL_STORE_URL: Url = Url::parse(LOCAL_STORE_URL_STR).unwrap(); } impl Store { diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index c9adc80be..c69079a44 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -8,6 +8,7 @@ use crate::{ errors::AtomicError, hierarchy, schema::{Class, Property}, + store::LOCAL_STORE_URL_STR, urls, }; use crate::{errors::AtomicResult, parse::parse_json_ad_string}; @@ -228,6 +229,9 @@ pub trait Storelike: Sized { /// the answer should always be `true`. fn is_external_subject(&self, subject: &str) -> AtomicResult { if let Some(self_url) = self.get_self_url() { + if self_url.as_str() == LOCAL_STORE_URL_STR { + return Ok(true); + } if subject.starts_with(&self_url.as_str()) { return Ok(false); } else { From f5c5882bb91f9cd28da1e9c9ea0a8c560b4d23ee Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 12 Oct 2022 21:50:19 +0200 Subject: [PATCH 03/45] #502 refactor to AtomicUrl WIP Path URL #502 URLs --- .cargo/config.toml | 32 ++++ Cargo.lock | 1 + cli/src/main.rs | 3 +- lib/src/agents.rs | 18 ++- lib/src/atomic_url.rs | 195 +++++++++++++++++++++++++ lib/src/collections.rs | 8 +- lib/src/commit.rs | 21 ++- lib/src/db.rs | 25 ++-- lib/src/db/test.rs | 11 +- lib/src/lib.rs | 2 + lib/src/parse.rs | 2 +- lib/src/populate.rs | 21 +-- lib/src/store.rs | 14 +- lib/src/storelike.rs | 26 ++-- lib/src/urls.rs | 12 +- lib/src/values.rs | 12 +- server/Cargo.toml | 1 + server/src/appstate.rs | 5 +- server/src/bin.rs | 7 +- server/src/errors.rs | 20 ++- server/src/handlers/search.rs | 17 ++- server/src/handlers/single_page_app.rs | 1 + server/src/tests.rs | 19 ++- 23 files changed, 384 insertions(+), 89 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 lib/src/atomic_url.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..10dda65b3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,32 @@ +[build] +rustc-wrapper = '/Users/joep/.cargo/bin/sccache' +[target.x86_64-unknown-linux-gnu] +rustflags = [ + '-Clink-arg=-fuse-ld=lld', + '-Zshare-generics=y', +] +linker = '/usr/bin/clang' + +[target.x86_64-pc-windows-msvc] +rustflags = ['-Zshare-generics=y'] +linker = 'rust-lld.exe' + +[target.x86_64-apple-darwin] +rustflags = [ + '-C', + 'link-arg=-fuse-ld=/usr/local/bin/zld', + '-Zshare-generics=y', + '-Csplit-debuginfo=unpacked', +] +[profile.dev] +opt-level = 0 +debug = 2 +incremental = true +codegen-units = 512 + +[profile.release] +opt-level = 3 +debug = 0 +incremental = false +codegen-units = 256 +split-debuginfo = '...' diff --git a/Cargo.lock b/Cargo.lock index c6ce0bb80..3c0cb1c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,7 @@ dependencies = [ "tracing-opentelemetry", "tracing-subscriber", "ureq", + "url", "urlencoding", "walkdir", ] diff --git a/cli/src/main.rs b/cli/src/main.rs index 756432f5c..f10ad2acb 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,4 +1,5 @@ use atomic_lib::serialize::Format; +use atomic_lib::atomic_url::Routes; use atomic_lib::{agents::generate_public_key, mapping::Mapping}; use atomic_lib::{agents::Agent, config::Config}; use atomic_lib::{errors::AtomicResult, Storelike}; @@ -165,7 +166,7 @@ fn set_agent_config() -> CLIResult { "No config found at {:?}. Let's create one!", &agent_config_path ); - let server = promptly::prompt("What's the base url of your Atomic Server?")?; + let server: String = promptly::prompt("What's the base url of your Atomic Server?")?; let agent = promptly::prompt("What's the URL of your Agent?")?; let private_key = promptly::prompt("What's the private key of this Agent?")?; let config = atomic_lib::config::Config { diff --git a/lib/src/agents.rs b/lib/src/agents.rs index fcf5facae..e1dafdd01 100644 --- a/lib/src/agents.rs +++ b/lib/src/agents.rs @@ -85,7 +85,7 @@ impl Agent { pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult { let keypair = generate_keypair()?; - Ok(Agent::new_from_private_key(name, store, &keypair.private)) + Agent::new_from_private_key(name, store, &keypair.private) } /// Creates a new Agent on this server, using the server's Server URL. @@ -94,16 +94,22 @@ impl Agent { name: Option<&str>, store: &impl Storelike, private_key: &str, - ) -> Agent { + ) -> AtomicResult { let keypair = generate_public_key(private_key); + println!("server url: {}", store.get_server_url()); + let subject = store + .get_server_url() + .url() + .join(&format!("agents/{}", &keypair.public))? + .to_string(); - Agent { + Ok(Agent { private_key: Some(keypair.private), - public_key: keypair.public.clone(), - subject: format!("{}/agents/{}", store.get_server_url(), keypair.public), + public_key: keypair.public, + subject, name: name.map(|x| x.to_owned()), created_at: crate::utils::now(), - } + }) } /// Creates a new Agent on this server, using the server's Server URL. diff --git a/lib/src/atomic_url.rs b/lib/src/atomic_url.rs new file mode 100644 index 000000000..babdc71a4 --- /dev/null +++ b/lib/src/atomic_url.rs @@ -0,0 +1,195 @@ +use serde::{Deserialize, Serialize, Serializer}; +use url::Url; + +use crate::{errors::AtomicResult, utils::random_string}; + +pub enum Routes { + Agents, + AllVersions, + Collections, + Commits, + CommitsUnsigned, + Endpoints, + Import, + Tpf, + Version, + Setup, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Wrapper for URLs / subjects. +/// Has a bunch of methods for finding or creating commonly used paths. +pub struct AtomicUrl { + url: Url, +} + +impl AtomicUrl { + pub fn new(url: Url) -> Self { + Self { url } + } + + pub fn as_str(&self) -> &str { + self.url.as_str() + } + + /// Returns the route to some common Endpoint + pub fn set_route(&self, route: Routes) -> Self { + let path = match route { + Routes::AllVersions => "/all-versions".to_string(), + Routes::Agents => "/agents".to_string(), + Routes::Collections => "/collections".to_string(), + Routes::Commits => "/commits".to_string(), + Routes::CommitsUnsigned => "/commits-unsigned".to_string(), + Routes::Endpoints => "/endpoints".to_string(), + Routes::Import => "/import".to_string(), + Routes::Tpf => "/tpf".to_string(), + Routes::Version => "/version".to_string(), + Routes::Setup => "/setup".to_string(), + }; + let mut new = self.url.clone(); + new.set_path(&path); + Self::new(new) + } + + /// Returns a new URL generated from the provided path_shortname and a random string. + /// ``` + /// let url = atomic_lib::AtomicUrl::try_from("https://example.com").unwrap(); + /// let generated = url.generate_random("my-type"); + /// assert!(generated.to_string().starts_with("https://example.com/my-type/")); + /// ``` + pub fn generate_random(&self, path_shortname: &str) -> Self { + let mut url = self.url.clone(); + let path = format!("{path_shortname}/{}", random_string(10)); + url.set_path(&path); + Self { url } + } + + /// Adds a sub-path to a URL. + /// Adds a slash to the existing URL, followed by the passed path. + /// + /// ``` + /// use atomic_lib::AtomicUrl; + /// let start = "http://localhost"; + /// let mut url = AtomicUrl::try_from(start).unwrap(); + /// assert_eq!(url.to_string(), "http://localhost/"); + /// url.append("/"); + /// assert_eq!(url.to_string(), "http://localhost/"); + /// url.append("someUrl/123"); + /// assert_eq!(url.to_string(), "http://localhost/someUrl/123"); + /// url.append("/345"); + /// assert_eq!(url.to_string(), "http://localhost/someUrl/123/345"); + /// ``` + pub fn append(&mut self, path: &str) -> &Self { + let mut new_path = self.url.path().to_string(); + match (new_path.ends_with('/'), path.starts_with('/')) { + (true, true) => { + new_path.pop(); + } + (false, false) => new_path.push('/'), + _other => {} + }; + + // Remove first slash if it exists + if new_path.starts_with('/') { + new_path.remove(0); + } + + new_path.push_str(path); + + self.url.set_path(&new_path); + self + } + + /// Sets the subdomain of the URL. + /// Removes an existing subdomain if the subdomain is None + pub fn set_subdomain(&mut self, subdomain: Option<&str>) -> AtomicResult<&Self> { + let mut host = self.url.host_str().unwrap().to_string(); + if let Some(subdomain) = subdomain { + host = format!("{}.{}", subdomain, host); + } + self.url.set_host(Some(host.as_str()))?; + Ok(self) + } + + pub fn set_path(&mut self, path: &str) -> &Self { + self.url.set_path(path); + self + } + + pub fn subdomain(&self) -> Option { + let url = self.url.clone(); + let host = url.host_str().unwrap(); + let parts: Vec<&str> = host.split('.').collect(); + if parts.len() > 2 { + Some(parts[0].to_string()) + } else { + None + } + } + + /// Returns the inner {url::Url} struct that has a bunch of regular URL methods + /// Useful if you need the host or something. + pub fn url(&self) -> Url { + self.url.clone() + } +} + +impl TryFrom<&str> for AtomicUrl { + type Error = url::ParseError; + + fn try_from(value: &str) -> Result { + let url = Url::parse(value)?; + Ok(Self { url }) + } +} + +impl Serialize for AtomicUrl { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.url.as_str()) + } +} + +impl<'de> Deserialize<'de> for AtomicUrl { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let url = Url::parse(&s).map_err(serde::de::Error::custom)?; + Ok(Self { url }) + } +} + +impl std::fmt::Display for AtomicUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.url) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_url() { + let _should_fail = AtomicUrl::try_from("nonsense").unwrap_err(); + let _should_succeed = AtomicUrl::try_from("http://localhost/someUrl").unwrap(); + } + + #[test] + fn subdomain() { + let sub = "http://test.example.com"; + assert_eq!( + AtomicUrl::try_from(sub).unwrap().subdomain(), + Some("test".to_string()) + ); + let mut no_sub = AtomicUrl::try_from("http://example.com").unwrap(); + assert_eq!(no_sub.subdomain(), None); + + let to_sub = no_sub.set_subdomain(Some("mysub")).unwrap(); + assert_eq!(to_sub.subdomain(), Some("mysub".to_string())); + } +} diff --git a/lib/src/collections.rs b/lib/src/collections.rs index a8d585a14..0e767748a 100644 --- a/lib/src/collections.rs +++ b/lib/src/collections.rs @@ -88,7 +88,7 @@ impl CollectionBuilder { store: &impl Storelike, ) -> CollectionBuilder { CollectionBuilder { - subject: format!("{}/{}", store.get_server_url(), path), + subject: store.get_server_url().clone().set_path(path).to_string(), property: Some(urls::IS_A.into()), value: Some(class_url.into()), sort_by: None, @@ -418,7 +418,9 @@ pub fn create_collection_resource_for_class( let parent = if class.subject == urls::COLLECTION { drive.to_string() } else { - format!("{}/collections", drive) + drive + .set_route(crate::atomic_url::Routes::Collections) + .to_string() }; collection_resource.set_string(urls::PARENT.into(), &parent, store)?; @@ -524,7 +526,7 @@ mod test { println!("{:?}", subjects); let collections_collection = store .get_resource_extended( - &format!("{}/collections", store.get_server_url()), + &format!("{}collections", store.get_server_url()), false, &ForAgent::Public, ) diff --git a/lib/src/commit.rs b/lib/src/commit.rs index a9a7b4d79..4cc313e7e 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -452,12 +452,21 @@ impl Commit { #[tracing::instrument(skip(store))] pub fn into_resource(&self, store: &impl Storelike) -> AtomicResult { let commit_subject = match self.signature.as_ref() { - Some(sig) => format!("{}/commits/{}", store.get_server_url(), sig), + Some(sig) => store + .get_server_url() + .set_route(Routes::Commits) + .append(sig) + .to_string(), None => { let now = crate::utils::now(); - format!("{}/commitsUnsigned/{}", store.get_server_url(), now) + store + .get_server_url() + .set_route(Routes::CommitsUnsigned) + .append(&now.to_string()) + .to_string() } }; + println!("commit subject: {}", commit_subject); let mut resource = Resource::new_instance(urls::COMMIT, store)?; resource.set_subject(commit_subject); resource.set_unsafe( @@ -780,10 +789,10 @@ mod test { let private_key = "CapMWIhFUT+w7ANv9oCPqrHrwZpkP2JhzF9JnyT6WcI="; let store = crate::Store::init().unwrap(); store.populate().unwrap(); - let agent = Agent::new_from_private_key(None, &store, private_key); + let agent = Agent::new_from_private_key(None, &store, private_key).unwrap(); assert_eq!( &agent.subject, - "local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=" + "http://noresolve.localhost/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=" ); store.add_resource(&agent.to_resource().unwrap()).unwrap(); let subject = "https://localhost/new_thing"; @@ -798,8 +807,8 @@ mod test { let signature = commit.signature.clone().unwrap(); let serialized = commit.serialize_deterministically_json_ad(&store).unwrap(); - assert_eq!(serialized, "{\"https://atomicdata.dev/properties/createdAt\":0,\"https://atomicdata.dev/properties/isA\":[\"https://atomicdata.dev/classes/Commit\"],\"https://atomicdata.dev/properties/set\":{\"https://atomicdata.dev/properties/description\":\"Some value\",\"https://atomicdata.dev/properties/shortname\":\"someval\"},\"https://atomicdata.dev/properties/signer\":\"local:store/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=\",\"https://atomicdata.dev/properties/subject\":\"https://localhost/new_thing\"}"); - assert_eq!(signature, "JOGRyp1NCulc0RNuuNozgIagQPRoZy0Y5+mbSpHY2DKiN3vqUNYLjXbAPYT6Cga6vSG9zztEIa/ZcbQPo7wgBg=="); + assert_eq!(serialized, "{\"https://atomicdata.dev/properties/createdAt\":0,\"https://atomicdata.dev/properties/isA\":[\"https://atomicdata.dev/classes/Commit\"],\"https://atomicdata.dev/properties/set\":{\"https://atomicdata.dev/properties/description\":\"Some value\",\"https://atomicdata.dev/properties/shortname\":\"someval\"},\"https://atomicdata.dev/properties/signer\":\"http://noresolve.localhost/agents/7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=\",\"https://atomicdata.dev/properties/subject\":\"https://localhost/new_thing\"}"); + assert_eq!(signature, "CZbjUJW/tokEKSZTCFjEHWbWqGW+jyhZWYs82K9wt0SArxu9xGg+D3IniAlygQp0F3KcI4Z876th3/X3fJIVAQ=="); } #[test] diff --git a/lib/src/db.rs b/lib/src/db.rs index 656ebe84a..58296d59a 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -19,6 +19,7 @@ use url::Url; use crate::{ agents::ForAgent, + atomic_url::AtomicUrl, atoms::IndexAtom, commit::CommitResponse, db::{ @@ -81,7 +82,7 @@ pub struct Db { /// A list of all the Collections currently being used. Is used to update `query_index`. watched_queries: sled::Tree, /// The address where the db will be hosted, e.g. http://localhost/ - server_url: Url, + server_url: AtomicUrl, /// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it. endpoints: Vec, /// Function called whenever a Commit is applied. @@ -92,7 +93,7 @@ impl Db { /// Creates a new store at the specified path, or opens the store if it already exists. /// The server_url is the domain where the db will be hosted, e.g. http://localhost/ /// It is used for distinguishing locally defined items from externally defined ones. - pub fn init(path: &std::path::Path, server_url: String) -> AtomicResult { + pub fn init(path: &std::path::Path, server_url: &str) -> AtomicResult { let db = sled::open(path).map_err(|e|format!("Failed opening DB at this location: {:?} . Is another instance of Atomic Server running? {}", path, e))?; let resources = db.open_tree("resources_v1").map_err(|e|format!("Failed building resources. Your DB might be corrupt. Go back to a previous version and export your data. {}", e))?; let reference_index = db.open_tree("reference_index_v1")?; @@ -122,10 +123,7 @@ impl Db { pub fn init_temp(id: &str) -> AtomicResult { let tmp_dir_path = format!(".temp/db/{}", id); let _try_remove_existing = std::fs::remove_dir_all(&tmp_dir_path); - let store = Db::init( - std::path::Path::new(&tmp_dir_path), - "https://localhost".into(), - )?; + let store = Db::init(std::path::Path::new(&tmp_dir_path), "https://localhost")?; let agent = store.create_agent(None)?; store.set_default_agent(agent); store.populate()?; @@ -182,7 +180,7 @@ impl Db { Ok(propval) } None => Err(AtomicError::not_found(format!( - "Resource {} not found", + "Resource {} does not exist", subject ))), } @@ -381,6 +379,7 @@ impl Storelike for Db { update_index: bool, overwrite_existing: bool, ) -> AtomicResult<()> { + println!("add_resource_opts {}", resource.get_subject()); // This only works if no external functions rely on using add_resource for atom-like operations! // However, add_atom uses set_propvals, which skips the validation. let existing = self.get_propvals(resource.get_subject()).ok(); @@ -426,11 +425,11 @@ impl Storelike for Db { Ok(()) } - fn get_server_url(&self) -> &Url { + fn get_server_url(&self) -> &AtomicUrl { &self.server_url } - fn get_self_url(&self) -> Option<&Url> { + fn get_self_url(&self) -> Option<&AtomicUrl> { // Since the DB is often also the server, this should make sense. // Some edge cases might appear later on (e.g. a slave DB that only stores copies?) Some(self.get_server_url()) @@ -466,17 +465,13 @@ impl Storelike for Db { let url_span = tracing::span!(tracing::Level::TRACE, "URL parse").entered(); // This might add a trailing slash let url = url::Url::parse(subject)?; - let mut removed_query_params = { + + let removed_query_params = { let mut url_altered = url.clone(); url_altered.set_query(None); url_altered.to_string() }; - // Remove trailing slash - if removed_query_params.ends_with('/') { - removed_query_params.pop(); - } - url_span.exit(); let endpoint_span = tracing::span!(tracing::Level::TRACE, "Endpoint").entered(); diff --git a/lib/src/db/test.rs b/lib/src/db/test.rs index 0c577f246..0d91d4685 100644 --- a/lib/src/db/test.rs +++ b/lib/src/db/test.rs @@ -65,7 +65,7 @@ fn populate_collections() { .map(|r| r.get_subject().into()) .collect(); println!("{:?}", subjects); - let collections_collection_url = format!("{}/collections", store.get_server_url()); + let collections_collection_url = format!("{}collections", store.get_server_url()); let collections_resource = store .get_resource_extended(&collections_collection_url, false, &ForAgent::Public) .unwrap(); @@ -90,8 +90,8 @@ fn populate_collections() { /// Also counts commits. fn destroy_resource_and_check_collection_and_commits() { let store = Db::init_temp("counter").unwrap(); + let agents_url = store.get_server_url().set_route(Routes::Agents).to_string(); let for_agent = &ForAgent::Public; - let agents_url = format!("{}/agents", store.get_server_url()); let agents_collection_1 = store .get_resource_extended(&agents_url, false, for_agent) .unwrap(); @@ -110,7 +110,10 @@ fn destroy_resource_and_check_collection_and_commits() { ); // We will count the commits, and check if they've incremented later on. - let commits_url = format!("{}/commits", store.get_server_url()); + let commits_url = store + .get_server_url() + .set_route(Routes::Commits) + .to_string(); let commits_collection_1 = store .get_resource_extended(&commits_url, false, for_agent) .unwrap(); @@ -189,7 +192,7 @@ fn destroy_resource_and_check_collection_and_commits() { fn get_extended_resource_pagination() { let store = Db::init_temp("get_extended_resource_pagination").unwrap(); let subject = format!( - "{}/commits?current_page=2&page_size=99999", + "{}commits?current_page=2&page_size=99999", store.get_server_url() ); let for_agent = &ForAgent::Public; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 4238fcb8e..f3ab9a290 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -59,6 +59,7 @@ assert!(fetched_new_resource.get_shortname("description", &store).unwrap().to_st */ pub mod agents; +pub mod atomic_url; pub mod atoms; pub mod authentication; pub mod client; @@ -90,6 +91,7 @@ pub mod utils; pub mod validate; pub mod values; +pub use atomic_url::AtomicUrl; pub use atoms::Atom; pub use commit::Commit; #[cfg(feature = "db")] diff --git a/lib/src/parse.rs b/lib/src/parse.rs index 11c3c010a..2257ba775 100644 --- a/lib/src/parse.rs +++ b/lib/src/parse.rs @@ -152,7 +152,7 @@ pub fn parse_json_ad_commit_resource( .get(urls::SUBJECT) .ok_or("No subject field in Commit.")? .to_string(); - let subject = format!("{}/commits/{}", store.get_server_url(), signature); + let subject = format!("{}commits/{}", store.get_server_url(), signature); let mut resource = Resource::new(subject); let propvals = match parse_json_ad_map_to_resource(json, store, &ParseOpts::default())? { SubResource::Resource(r) => r.into_propvals(), diff --git a/lib/src/populate.rs b/lib/src/populate.rs index ad860c2b4..c831c25be 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -161,17 +161,18 @@ pub fn create_drive( for_agent: &str, public_read: bool, ) -> AtomicResult { - let mut self_url = if let Some(url) = store.get_self_url() { + let self_url = if let Some(url) = store.get_self_url() { url.to_owned() } else { return Err("No self URL set. Cannot create drive.".into()); }; let drive_subject: String = if let Some(name) = drive_name { // Let's make a subdomain - let host = self_url.host().expect("No host in server_url"); + let mut url = self_url.url(); + let host = url.host().expect("No host in server_url"); let subdomain_host = format!("{}.{}", name, host); - self_url.set_host(Some(&subdomain_host))?; - self_url.to_string() + url.set_host(Some(&subdomain_host))?; + url.to_string() } else { self_url.to_string() }; @@ -317,13 +318,15 @@ pub fn populate_collections(store: &impl Storelike) -> AtomicResult<()> { /// Adds default Endpoints (versioning) to the Db. /// Makes sure they are fetchable pub fn populate_endpoints(store: &crate::Db) -> AtomicResult<()> { + use crate::atomic_url::Routes; + let endpoints = crate::endpoints::default_endpoints(); - let endpoints_collection = format!("{}/endpoints", store.get_server_url()); + let endpoints_collection = store.get_server_url().set_route(Routes::Endpoints); for endpoint in endpoints { let mut resource = endpoint.to_resource(store)?; resource.set( urls::PARENT.into(), - Value::AtomicUrl(endpoints_collection.clone()), + Value::AtomicUrl(endpoints_collection.to_string()), store, )?; resource.save_locally(store)?; @@ -357,9 +360,9 @@ pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> { let base = store.get_self_url().ok_or("No self_url")?; let mut drive = store.get_resource(base.as_str())?; let arr = vec![ - format!("{}/setup", base), - format!("{}/import", base), - format!("{}/collections", base), + base.set_route(crate::atomic_url::Routes::Setup), + base.set_route(crate::atomic_url::Routes::Import), + base.set_route(crate::atomic_url::Routes::Collections), ]; for item in arr { drive.push(urls::SUBRESOURCES, item.into(), true)?; diff --git a/lib/src/store.rs b/lib/src/store.rs index 92eecdc19..94ad95849 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -7,6 +7,11 @@ use crate::agents::Agent; use crate::storelike::QueryResult; use crate::Value; use crate::{atoms::Atom, storelike::Storelike}; +use crate::{ + atomic_url::AtomicUrl, + atoms::Atom, + storelike::{ResourceCollection, Storelike}, +}; use crate::{errors::AtomicResult, Resource}; use std::{collections::HashMap, sync::Arc, sync::Mutex}; @@ -20,10 +25,11 @@ pub struct Store { } /// The URL used for stores that are not accessible on the web. -pub const LOCAL_STORE_URL_STR: &str = "local:store"; +// I'd prefer this to a non-HTTP URI, but that causes parsing issues when we combine it with some paths (at least with Commits) +pub const LOCAL_STORE_URL_STR: &str = "http://noresolve.localhost"; lazy_static::lazy_static! { - static ref LOCAL_STORE_URL: Url = Url::parse(LOCAL_STORE_URL_STR).unwrap(); + static ref LOCAL_STORE_URL: AtomicUrl = AtomicUrl::try_from(LOCAL_STORE_URL_STR).unwrap(); } impl Store { @@ -167,13 +173,13 @@ impl Storelike for Store { Box::new(self.hashmap.lock().unwrap().clone().into_values()) } - fn get_server_url(&self) -> &Url { + fn get_server_url(&self) -> &AtomicUrl { // TODO Should be implemented later when companion functionality is here // https://github.com/atomicdata-dev/atomic-server/issues/6 &LOCAL_STORE_URL } - fn get_self_url(&self) -> Option<&Url> { + fn get_self_url(&self) -> Option<&AtomicUrl> { Some(self.get_server_url()) } diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index c69079a44..b73b6aeae 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -1,9 +1,8 @@ //! The Storelike Trait contains many useful methods for maniupulting / retrieving data. -use url::Url; - use crate::{ agents::{Agent, ForAgent}, + atomic_url::AtomicUrl, commit::CommitResponse, errors::AtomicError, hierarchy, @@ -86,15 +85,15 @@ pub trait Storelike: Sized { } /// Returns the base URL where the default store is. - /// E.g. `https://example.com` + /// E.g. `https://example.com/` /// This is where deltas should be sent to. /// Also useful for Subject URL generation. - fn get_server_url(&self) -> &Url; + fn get_server_url(&self) -> &AtomicUrl; /// Returns the root URL where this instance of the store is hosted. /// Should return `None` if this is simply a client and not a server. - /// E.g. `https://example.com` - fn get_self_url(&self) -> Option<&Url> { + /// E.g. `https://example.com.` + fn get_self_url(&self) -> Option<&AtomicUrl> { None } @@ -183,11 +182,11 @@ pub trait Storelike: Sized { Property::from_resource(prop) } - /// Get's the resource, parses the Query parameters and calculates dynamic properties. + /// Gets the resource, parses the Query parameters and calculates dynamic properties. /// Defaults to get_resource if store doesn't support extended resources /// If `for_agent` is None, no authorization checks will be done, and all resources will return. - /// If you want public only resurces, pass `Some(crate::authentication::public_agent)` as the agent. - /// - *skip_dynamic* Does not calculte dynamic properties. Adds an `incomplete=true` property if the resource should have been dynamic. + /// If you want public only resources, pass `Some(crate::authentication::public_agent)` as the agent. + /// - *skip_dynamic* Does not calculate dynamic properties. Adds an `incomplete=true` property if the resource should have been dynamic. fn get_resource_extended( &self, subject: &str, @@ -204,14 +203,14 @@ pub trait Storelike: Sized { /// Implement this if you want to have custom handlers for Commits. fn handle_commit(&self, _commit_response: &CommitResponse) {} - fn handle_not_found(&self, subject: &str, error: AtomicError) -> AtomicResult { + fn handle_not_found(&self, subject: &str, _error: AtomicError) -> AtomicResult { // This does not work for subdomains if self.is_external_subject(subject)? { self.fetch_resource(subject, None) } else { Err(AtomicError::not_found(format!( - "Failed to retrieve locally: '{}'. {}", - subject, error + "Subject is not stored on this server: '{}'", + subject ))) } } @@ -232,13 +231,14 @@ pub trait Storelike: Sized { if self_url.as_str() == LOCAL_STORE_URL_STR { return Ok(true); } - if subject.starts_with(&self_url.as_str()) { + if subject.starts_with(self_url.as_str()) { return Ok(false); } else { let subject_url = url::Url::parse(subject)?; let subject_host = subject_url.host().ok_or_else(|| { AtomicError::not_found(format!("Subject URL has no host: {}", subject)) })?; + let self_url = self_url.url(); let self_host = self_url.host().ok_or_else(|| { AtomicError::not_found(format!("Self URL has no host: {}", self_url)) })?; diff --git a/lib/src/urls.rs b/lib/src/urls.rs index 180bc21e8..e791e0f8e 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -1,6 +1,5 @@ //! Contains some of the most important Atomic Data URLs. - -use url::Url; +//! See [crate::atomic_url] for the URL datatype. // Classes pub const CLASS: &str = "https://atomicdata.dev/classes/Class"; @@ -153,9 +152,12 @@ pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent"; pub const SUDO_AGENT: &str = "sudo:agent"; // Paths -pub fn construct_path_import(base: &Url) -> String { - format!("{base}{PATH_IMPORT}") -} +pub const PATH_IMPORT: &str = "import"; +pub const PATH_FETCH_BOOKMARK: &str = "fetch-bookmark"; +pub const PATH_TPF: &str = "tpf"; +pub const PATH_PATH: &str = "path"; +pub const PATH_COMMITS: &str = "commits"; +pub const PATH_ENDPOINTS: &str = "endpoints"; pub const PATH_IMPORT: &str = "/import"; pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark"; diff --git a/lib/src/values.rs b/lib/src/values.rs index 0beb32704..25285f5cc 100644 --- a/lib/src/values.rs +++ b/lib/src/values.rs @@ -2,7 +2,7 @@ use crate::{ datatype::match_datatype, datatype::DataType, errors::AtomicResult, resources::PropVals, - utils::check_valid_url, Resource, + utils::check_valid_url, AtomicUrl, Resource, }; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -353,6 +353,16 @@ impl From> for Value { } } +impl From> for Value { + fn from(val: Vec) -> Self { + let mut vec = Vec::new(); + for i in val { + vec.push(SubResource::Subject(i.to_string())); + } + Value::ResourceArray(vec) + } +} + use std::fmt; impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/server/Cargo.toml b/server/Cargo.toml index 1c86c8436..12e937e88 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -49,6 +49,7 @@ tracing-chrome = "0.7" tracing-log = "0.2" ureq = "2" urlencoding = "2" +url = "2.3.1" [dependencies.instant-acme] optional = true diff --git a/server/src/appstate.rs b/server/src/appstate.rs index e64f166f3..af9a23060 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -4,6 +4,7 @@ use crate::{ }; use atomic_lib::{ agents::{generate_public_key, Agent}, + atomic_url::Routes, commit::CommitResponse, Storelike, }; @@ -119,7 +120,7 @@ fn set_default_agent(config: &Config, store: &impl Storelike) -> AtomicServerRes "server".into(), store, &agent_config.private_key, - ); + )?; store.add_resource(&recreated_agent.to_resource()?)?; agent_config } else { @@ -161,7 +162,7 @@ fn set_default_agent(config: &Config, store: &impl Storelike) -> AtomicServerRes /// Creates the first Invitation that is opened by the user on the Home page. fn set_up_initial_invite(store: &impl Storelike) -> AtomicServerResult<()> { - let subject = format!("{}/setup", store.get_server_url()); + let subject = store.get_server_url().set_route(Routes::Setup).to_string(); tracing::info!("Creating initial Invite at {}", subject); let mut invite = store.get_resource_new(&subject); invite.set_class(atomic_lib::urls::INVITE); diff --git a/server/src/bin.rs b/server/src/bin.rs index a8be8d7e7..c804a33f9 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -68,7 +68,12 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { let importer_subject = if let Some(i) = &import_opts.parent { i.into() } else { - urls::construct_path_import(appstate.store.get_self_url().expect("No self url")) + appstate + .store + .get_self_url() + .expect("No self URL") + .set_route(Routes::Import) + .to_string() }; let parse_opts = atomic_lib::parse::ParseOpts { importer: Some(importer_subject), diff --git a/server/src/errors.rs b/server/src/errors.rs index 6cf565216..b0e144c47 100644 --- a/server/src/errors.rs +++ b/server/src/errors.rs @@ -14,7 +14,7 @@ pub enum AppErrorType { Other, } -// More strict error type, supports HTTP responses +/// Error type that includes a Resource representation of the Error, which can be sent to the client. pub struct AtomicServerError { pub message: String, pub error_type: AppErrorType, @@ -49,8 +49,8 @@ impl ResponseError for AtomicServerError { } fn error_response(&self) -> HttpResponse { // Creates a JSON-AD resource representing the Error. - let r = match &self.error_resource { - Some(r) => r.to_owned(), + let r: Resource = match &self.error_resource { + Some(r) => *r.clone(), None => { let mut r = Resource::new("subject".into()); r.set_class(urls::ERROR); @@ -58,12 +58,12 @@ impl ResponseError for AtomicServerError { urls::DESCRIPTION.into(), Value::String(self.message.clone()), ); - Box::new(r) + r } }; let body = r.to_json_ad().unwrap(); - tracing::info!("Error response: {}", self.message); + // tracing::info!("Error response: {}", self.message); HttpResponse::build(self.status_code()) .content_type(JSON_AD_MIME) .body(body) @@ -179,3 +179,13 @@ impl From for AtomicServerError { } } } + +impl From for AtomicServerError { + fn from(error: url::ParseError) -> Self { + AtomicServerError { + message: error.to_string(), + error_type: AppErrorType::Other, + error_resource: None, + } + } +} diff --git a/server/src/handlers/search.rs b/server/src/handlers/search.rs index cd3a52290..f7d27f02b 100644 --- a/server/src/handlers/search.rs +++ b/server/src/handlers/search.rs @@ -84,11 +84,18 @@ pub async fn search_query( // Create a valid atomic data resource. // You'd think there would be a simpler way of getting the requested URL... - let subject = format!( - "{}{}", - store.get_self_url().ok_or("No base URL set")?, - req.uri().path_and_query().ok_or("Add a query param")? - ); + // See https://github.com/actix/actix-web/issues/2895 + let subject: String = store + .get_self_url() + .ok_or("No base URL set")? + .url() + .join( + req.uri() + .path_and_query() + .ok_or("Add a query param")? + .as_str(), + )? + .to_string(); let mut results_resource = atomic_lib::plugins::search::search_endpoint().to_resource(store)?; results_resource.set_subject(subject.clone()); diff --git a/server/src/handlers/single_page_app.rs b/server/src/handlers/single_page_app.rs index 6140a2c3b..c5bb89a37 100644 --- a/server/src/handlers/single_page_app.rs +++ b/server/src/handlers/single_page_app.rs @@ -35,6 +35,7 @@ pub async fn single_page( "Cache-Control", "no-store, no-cache, must-revalidate, private", )) + .append_header(("Vary", "Accept")) .body(body); Ok(resp) diff --git a/server/src/tests.rs b/server/src/tests.rs index 8c472a27f..fa27ef4af 100644 --- a/server/src/tests.rs +++ b/server/src/tests.rs @@ -91,8 +91,10 @@ async fn server_tests() { let resp = test::call_service(&app, req).await; assert!(resp.status().is_client_error()); - // Edit the main drive, make it hidden to the public agent - let mut drive = store.get_resource(&appstate.config.server_url).unwrap(); + // Edit the properties collection, make it hidden to the public agent + let mut drive = store + .get_resource(appstate.store.get_server_url().as_str()) + .unwrap(); drive .set( urls::READ.into(), @@ -104,7 +106,7 @@ async fn server_tests() { // Should 401 (Unauthorized) let req = - test::TestRequest::with_uri("/properties").insert_header(("Accept", "application/ad+json")); + test::TestRequest::with_uri("properties").insert_header(("Accept", "application/ad+json")); let resp = test::call_service(&app, req.to_request()).await; assert_eq!( resp.status().as_u16(), @@ -113,17 +115,18 @@ async fn server_tests() { ); // Get JSON-AD - let req = build_request_authenticated("/properties", &appstate); + let req = build_request_authenticated("properties", &appstate); let resp = test::call_service(&app, req.to_request()).await; - assert!(resp.status().is_success(), "setup not returning JSON-AD"); let body = get_body(resp); + println!("DEBUG: {:?}", body); + // assert!(resp.status().is_success(), "setup not returning JSON-AD"); assert!( body.as_str().contains("{\n \"@id\""), "response should be json-ad" ); // Get JSON-LD - let req = build_request_authenticated("/properties", &appstate) + let req = build_request_authenticated("properties", &appstate) .insert_header(("Accept", "application/ld+json")); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success(), "setup not returning JSON-LD"); @@ -134,7 +137,7 @@ async fn server_tests() { ); // Get turtle - let req = build_request_authenticated("/properties", &appstate) + let req = build_request_authenticated("properties", &appstate) .insert_header(("Accept", "text/turtle")); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success()); @@ -146,7 +149,7 @@ async fn server_tests() { // Get Search // Does not test the contents of the results - the index isn't built at this point - let req = build_request_authenticated("/search?q=setup", &appstate); + let req = build_request_authenticated("search?q=setup", &appstate); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success()); let body = get_body(resp); From aebadadad1f4e61d343a7cdf7c9ac1b4515e54de Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 12 Oct 2022 22:43:49 +0200 Subject: [PATCH 04/45] #518 Fix cargo in CI --- .cargo/config.toml | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 10dda65b3..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,32 +0,0 @@ -[build] -rustc-wrapper = '/Users/joep/.cargo/bin/sccache' -[target.x86_64-unknown-linux-gnu] -rustflags = [ - '-Clink-arg=-fuse-ld=lld', - '-Zshare-generics=y', -] -linker = '/usr/bin/clang' - -[target.x86_64-pc-windows-msvc] -rustflags = ['-Zshare-generics=y'] -linker = 'rust-lld.exe' - -[target.x86_64-apple-darwin] -rustflags = [ - '-C', - 'link-arg=-fuse-ld=/usr/local/bin/zld', - '-Zshare-generics=y', - '-Csplit-debuginfo=unpacked', -] -[profile.dev] -opt-level = 0 -debug = 2 -incremental = true -codegen-units = 512 - -[profile.release] -opt-level = 3 -debug = 0 -incremental = false -codegen-units = 256 -split-debuginfo = '...' From f406a9950d0fbf248a827111df60c907d50cadb8 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Thu, 13 Oct 2022 09:47:36 +0200 Subject: [PATCH 05/45] Fix tests --- CHANGELOG.md | 1 + lib/src/agents.rs | 1 - lib/src/authentication.rs | 2 +- lib/src/commit.rs | 3 +-- lib/src/db.rs | 1 - server/src/tests.rs | 16 +++++++++------- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da50dda27..7d430591f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - `Store::all_resources` returns `Iterator` instead of `Vec` #522 #487 - Change authentication order #525 - Fix cookie subject check #525 +- Refactored Subject URLs to use `AtomicUrl` ## [v0.33.1] - 2022-09-25 diff --git a/lib/src/agents.rs b/lib/src/agents.rs index e1dafdd01..e2d7c0954 100644 --- a/lib/src/agents.rs +++ b/lib/src/agents.rs @@ -96,7 +96,6 @@ impl Agent { private_key: &str, ) -> AtomicResult { let keypair = generate_public_key(private_key); - println!("server url: {}", store.get_server_url()); let subject = store .get_server_url() .url() diff --git a/lib/src/authentication.rs b/lib/src/authentication.rs index 5c1572f96..135c8dfdd 100644 --- a/lib/src/authentication.rs +++ b/lib/src/authentication.rs @@ -40,7 +40,7 @@ pub fn check_auth_signature(subject: &str, auth_header: &AuthValues) -> AtomicRe .verify(message.as_bytes(), &signature_bytes) .map_err(|_e| { format!( - "Incorrect signature for auth headers. This could be due to an error during signing or serialization of the commit. Compare this to the serialized message in the client: {}", + "Incorrect signature for auth headers. This could be due to an error during signing or serialization of the commit. Compare this to the serialized message in the client: '{}'", message, ) })?; diff --git a/lib/src/commit.rs b/lib/src/commit.rs index 4cc313e7e..98c7e532d 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -119,7 +119,7 @@ impl Commit { .verify(stringified_commit.as_bytes(), &signature_bytes) .map_err(|_e| { format!( - "Incorrect signature for Commit. This could be due to an error during signing or serialization of the commit. Compare this to the serialized commit in the client: {}", + "Incorrect signature for Commit. This could be due to an error during signing or serialization of the commit. Compare this to the serialized commit in the client: '{}'", stringified_commit, ) })?; @@ -466,7 +466,6 @@ impl Commit { .to_string() } }; - println!("commit subject: {}", commit_subject); let mut resource = Resource::new_instance(urls::COMMIT, store)?; resource.set_subject(commit_subject); resource.set_unsafe( diff --git a/lib/src/db.rs b/lib/src/db.rs index 58296d59a..2dcbeb3c2 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -379,7 +379,6 @@ impl Storelike for Db { update_index: bool, overwrite_existing: bool, ) -> AtomicResult<()> { - println!("add_resource_opts {}", resource.get_subject()); // This only works if no external functions rely on using add_resource for atom-like operations! // However, add_atom uses set_propvals, which skips the validation. let existing = self.get_propvals(resource.get_subject()).ok(); diff --git a/server/src/tests.rs b/server/src/tests.rs index fa27ef4af..3c784a7ca 100644 --- a/server/src/tests.rs +++ b/server/src/tests.rs @@ -16,7 +16,9 @@ use atomic_lib::{urls, Storelike}; /// Returns the request with signed headers. Also adds a json-ad accept header - overwrite this if you need something else. fn build_request_authenticated(path: &str, appstate: &AppState) -> TestRequest { - let url = format!("{}{}", appstate.store.get_server_url(), path); + // remove last slash + let base = appstate.store.get_server_url().to_string(); + let url = format!("{}{}", base.trim_end_matches('/'), path); let headers = atomic_lib::client::get_authentication_headers( &url, &appstate.store.get_default_agent().unwrap(), @@ -106,7 +108,7 @@ async fn server_tests() { // Should 401 (Unauthorized) let req = - test::TestRequest::with_uri("properties").insert_header(("Accept", "application/ad+json")); + test::TestRequest::with_uri("/properties").insert_header(("Accept", "application/ad+json")); let resp = test::call_service(&app, req.to_request()).await; assert_eq!( resp.status().as_u16(), @@ -115,7 +117,7 @@ async fn server_tests() { ); // Get JSON-AD - let req = build_request_authenticated("properties", &appstate); + let req = build_request_authenticated("/properties", &appstate); let resp = test::call_service(&app, req.to_request()).await; let body = get_body(resp); println!("DEBUG: {:?}", body); @@ -126,7 +128,7 @@ async fn server_tests() { ); // Get JSON-LD - let req = build_request_authenticated("properties", &appstate) + let req = build_request_authenticated("/properties", &appstate) .insert_header(("Accept", "application/ld+json")); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success(), "setup not returning JSON-LD"); @@ -137,7 +139,7 @@ async fn server_tests() { ); // Get turtle - let req = build_request_authenticated("properties", &appstate) + let req = build_request_authenticated("/properties", &appstate) .insert_header(("Accept", "text/turtle")); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success()); @@ -149,7 +151,7 @@ async fn server_tests() { // Get Search // Does not test the contents of the results - the index isn't built at this point - let req = build_request_authenticated("search?q=setup", &appstate); + let req = build_request_authenticated("/search?q=setup", &appstate); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success()); let body = get_body(resp); @@ -160,7 +162,7 @@ async fn server_tests() { ); } -/// Gets the body from the response as a String. Why doen't actix provide this? +/// Gets the body from the response as a String. Why doesn't actix provide this? fn get_body(resp: ServiceResponse) -> String { let boxbody = resp.into_body(); let bytes = boxbody.try_into_bytes().unwrap(); From 8eedb0994025b90e6343d4198aa533d05d71c300 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sun, 16 Oct 2022 13:30:49 +0200 Subject: [PATCH 06/45] Debug for endpoints --- lib/src/endpoints.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/src/endpoints.rs b/lib/src/endpoints.rs index e97ddc005..869093a44 100644 --- a/lib/src/endpoints.rs +++ b/lib/src/endpoints.rs @@ -58,7 +58,11 @@ pub struct PostEndpoint { impl Endpoint { /// Converts Endpoint to resource. Does not save it. pub fn to_resource(&self, store: &impl Storelike) -> AtomicResult { - let subject = format!("{}{}", store.get_server_url(), self.path); + let subject = store + .get_server_url() + .clone() + .set_path(&self.path) + .to_string(); let mut resource = store.get_resource_new(&subject); resource.set_string(urls::DESCRIPTION.into(), &self.description, store)?; resource.set_string(urls::SHORTNAME.into(), &self.shortname, store)?; @@ -74,6 +78,14 @@ impl Endpoint { } } +impl std::fmt::Debug for Endpoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Endpoint") + .field("path", &self.path) + .finish() + } +} + pub fn default_endpoints() -> Vec { vec![ plugins::versioning::version_endpoint(), From b1bdca69399dc43dfc558b346c61a35e7502b939 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sun, 16 Oct 2022 13:31:09 +0200 Subject: [PATCH 07/45] Fix URL refactor paths endpoints #502 --- lib/src/urls.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/urls.rs b/lib/src/urls.rs index e791e0f8e..fbcb87836 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -152,12 +152,12 @@ pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent"; pub const SUDO_AGENT: &str = "sudo:agent"; // Paths -pub const PATH_IMPORT: &str = "import"; -pub const PATH_FETCH_BOOKMARK: &str = "fetch-bookmark"; -pub const PATH_TPF: &str = "tpf"; -pub const PATH_PATH: &str = "path"; -pub const PATH_COMMITS: &str = "commits"; -pub const PATH_ENDPOINTS: &str = "endpoints"; +pub const PATH_IMPORT: &str = "/import"; +pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark"; +pub const PATH_TPF: &str = "/tpf"; +pub const PATH_PATH: &str = "/path"; +pub const PATH_COMMITS: &str = "/commits"; +pub const PATH_ENDPOINTS: &str = "/endpoints"; pub const PATH_IMPORT: &str = "/import"; pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark"; From 747e5d15d29102b63a46be83d175fcff4ed68a22 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sun, 16 Oct 2022 13:31:47 +0200 Subject: [PATCH 08/45] URL refactor --- lib/src/agents.rs | 6 +++++- lib/src/atomic_url.rs | 1 + lib/src/resources.rs | 2 +- server/src/handlers/download.rs | 9 +++++--- server/src/handlers/upload.rs | 38 ++++++++++++++++++++++----------- server/src/helpers.rs | 29 +++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 18 deletions(-) diff --git a/lib/src/agents.rs b/lib/src/agents.rs index e2d7c0954..927343162 100644 --- a/lib/src/agents.rs +++ b/lib/src/agents.rs @@ -119,7 +119,11 @@ impl Agent { Ok(Agent { private_key: None, public_key: public_key.into(), - subject: format!("{}/agents/{}", store.get_server_url(), public_key), + subject: store + .get_server_url() + .clone() + .set_path(&format!("agents/{}", public_key)) + .to_string(), name: None, created_at: crate::utils::now(), }) diff --git a/lib/src/atomic_url.rs b/lib/src/atomic_url.rs index babdc71a4..f6c7d84da 100644 --- a/lib/src/atomic_url.rs +++ b/lib/src/atomic_url.rs @@ -111,6 +111,7 @@ impl AtomicUrl { Ok(self) } + /// Removes existing path, sets the new one. Escapes special characters pub fn set_path(&mut self, path: &str) -> &Self { self.url.set_path(path); self diff --git a/lib/src/resources.rs b/lib/src/resources.rs index 0ec8ff89b..71d58dd0e 100644 --- a/lib/src/resources.rs +++ b/lib/src/resources.rs @@ -211,7 +211,7 @@ impl Resource { /// Create a new resource with a generated Subject pub fn new_generate_subject(store: &impl Storelike) -> Resource { - let generated = format!("{}/{}", store.get_server_url(), random_string(10)); + let generated = format!("{}{}", store.get_server_url(), random_string(10)); Resource::new(generated) } diff --git a/server/src/handlers/download.rs b/server/src/handlers/download.rs index 810a1e237..93eb8a1c4 100644 --- a/server/src/handlers/download.rs +++ b/server/src/handlers/download.rs @@ -12,13 +12,16 @@ pub async fn handle_download( req: actix_web::HttpRequest, ) -> AtomicServerResult { let headers = req.headers(); - let server_url = &appstate.config.server_url; let store = &appstate.store; // We replace `/download` with `/` to get the subject of the Resource. let subject = if let Some(pth) = path { - let subject = format!("{}/{}", server_url, pth); - subject + appstate + .store + .get_server_url() + .clone() + .set_path(pth.as_str()) + .to_string() } else { // There is no end string, so It's the root of the URL, the base URL! return Err("Put `/download` in front of an File URL to download it.".into()); diff --git a/server/src/handlers/upload.rs b/server/src/handlers/upload.rs index e91915624..d893e9cc1 100644 --- a/server/src/handlers/upload.rs +++ b/server/src/handlers/upload.rs @@ -8,7 +8,11 @@ use atomic_lib::{ use futures::{StreamExt, TryStreamExt}; use serde::Deserialize; -use crate::{appstate::AppState, errors::AtomicServerResult, helpers::get_client_agent}; +use crate::{ + appstate::AppState, + errors::AtomicServerResult, + helpers::{get_client_agent, get_subject}, +}; #[derive(Deserialize, Debug)] pub struct UploadQuery { @@ -27,19 +31,19 @@ pub async fn upload_handler( appstate: web::Data, query: web::Query, req: actix_web::HttpRequest, + conn: actix_web::dev::ConnectionInfo, ) -> AtomicServerResult { let store = &appstate.store; let parent = store.get_resource(&query.parent)?; - let subject = format!( - "{}{}", - store.get_server_url(), - req.head() - .uri - .path_and_query() - .ok_or("Path must be given")? - ); - let agent = get_client_agent(req.headers(), &appstate, subject)?; - check_write(store, &parent, &agent)?; + let subject = get_subject(&req, &conn, &appstate)?; + if let Some(agent) = get_client_agent(req.headers(), &appstate, subject)? { + check_write(store, &parent, &agent)?; + } else { + return Err(AtomicError::unauthorized( + "No authorization headers present. These are required when uploading files.".into(), + ) + .into()); + } let mut created_resources: Vec = Vec::new(); let mut commit_responses: Vec = Vec::new(); @@ -76,8 +80,16 @@ pub async fn upload_handler( .map_err(|_e| "Too large")?; let subject_path = format!("files/{}", urlencoding::encode(&file_id)); - let new_subject = format!("{}/{}", store.get_server_url(), subject_path); - let download_url = format!("{}/download/{}", store.get_server_url(), subject_path); + let new_subject = store + .get_server_url() + .clone() + .set_path(&subject_path) + .to_string(); + let download_url = store + .get_server_url() + .clone() + .set_path(&format!("download/{}", subject_path)) + .to_string(); let mut resource = atomic_lib::Resource::new_instance(urls::FILE, store)?; resource diff --git a/server/src/helpers.rs b/server/src/helpers.rs index e2476328a..a8bc02e38 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -297,3 +297,32 @@ mod test { assert_eq!(out.requested_subject, subject); } } + +/// Extracts the full URL from the request, connection and the store. +// You'd think there would be a simpler way of getting the requested URL... +// See https://github.com/actix/actix-web/issues/2895 +pub fn get_subject( + req: &actix_web::HttpRequest, + conn: &actix_web::dev::ConnectionInfo, + appstate: &AppState, +) -> AtomicServerResult { + let domain = &appstate.config.opts.domain; + let host = conn.host(); + let subdomain = if let Some(index) = host.find(domain) { + if index == 0 { + None + } else { + Some(host[0..index - 1].to_string()) + } + } else { + panic!("Wrong domain! A requested URL did not contain the host for this domain. This should not be able to happen."); + }; + + let mut subject_url = appstate.store.get_server_url().clone(); + if let Some(sd) = subdomain { + subject_url.set_subdomain(Some(&sd))?; + } + let server_without_last_slash = subject_url.to_string().trim_end_matches('/').to_string(); + let subject = format!("{}{}", server_without_last_slash, &req.uri().to_string()); + Ok(subject) +} From 318806ccd82165fb30ba80eee49b78f1b514ce8a Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sun, 16 Oct 2022 13:32:43 +0200 Subject: [PATCH 09/45] Add rust vscode extension suggestions --- .vscode/extensions.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..d41c1cb87 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "vadimcn.vscode-lldb", + "rust-lang.rust-analyzer", + "serayuzgur.crates" + ] +} From 9d6a1cc35a24807319c53439bbbe2f4594f6074b Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sun, 16 Oct 2022 13:32:53 +0200 Subject: [PATCH 10/45] Fix pnpm tasks --- .vscode/tasks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 223a84826..17ea49326 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,9 +26,9 @@ "group": "test" }, { - "label": "test end-to-end / E2E (npm playwright)", + "label": "test end-to-end / E2E (pnpm playwright)", "type": "shell", - "command": "cd server/e2e_tests/ && npm i && npm run test", + "command": "cd server/e2e_tests/ && pnpm i && pnpm run test", "group": "test" }, { From e8409c0b9ce5cbda3794c32968b1af3a8480b611 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 25 Oct 2022 16:24:01 +0200 Subject: [PATCH 11/45] Improve register endpoint, WIP --- lib/src/plugins/register.rs | 43 ++++++++++++++++++++++++++----------- lib/src/populate.rs | 4 ++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index b097cb045..ce781242c 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -1,6 +1,13 @@ //! Creates a new Drive and optionally also an Agent. -use crate::{agents::Agent, endpoints::Endpoint, errors::AtomicResult, urls, Resource, Storelike}; +use crate::{ + agents::Agent, + endpoints::Endpoint, + errors::AtomicResult, + urls::{self, PUBLIC_AGENT}, + values::SubResource, + Resource, Storelike, +}; pub fn register_endpoint() -> Endpoint { Endpoint { @@ -42,13 +49,16 @@ pub fn construct_register_redirect( return Err("No name provided".into()); }; - let mut new_agent = None; - let drive_creator_agent: String = if let Some(key) = pub_key { - let new = Agent::new_from_public_key(store, &key)?.subject; - new_agent = Some(new.clone()); - new + let mut new = Agent::new_from_public_key(store, &key)?; + new.name = Some(name.clone()); + let net_agent_subject = new.subject.to_string(); + new.to_resource()?.save(store)?; + net_agent_subject } else if let Some(agent) = for_agent { + if agent == PUBLIC_AGENT { + return Err("No `public-key` provided.".into()); + } agent.to_string() } else { return Err("No `public-key` provided".into()); @@ -57,16 +67,23 @@ pub fn construct_register_redirect( // Create the new Drive let drive = crate::populate::create_drive(store, Some(&name), &drive_creator_agent, false)?; + // Add the drive to the Agent's list of drives + let mut agent = store.get_resource(&drive_creator_agent)?; + agent.push( + urls::DRIVES, + SubResource::Subject(drive.get_subject().into()), + true, + )?; + agent.save_locally(store)?; + // Construct the Redirect Resource, which might provide the Client with a Subject for his Agent. let mut redirect = Resource::new_instance(urls::REDIRECT, store)?; redirect.set_string(urls::DESTINATION.into(), drive.get_subject(), store)?; - if let Some(agent) = new_agent { - redirect.set( - urls::REDIRECT_AGENT.into(), - crate::Value::AtomicUrl(agent), - store, - )?; - } + redirect.set( + urls::REDIRECT_AGENT.into(), + crate::Value::AtomicUrl(drive_creator_agent), + store, + )?; // The front-end requires the @id to be the same as requested redirect.set_subject(requested_subject); Ok(redirect) diff --git a/lib/src/populate.rs b/lib/src/populate.rs index c831c25be..41a5a94c0 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -177,9 +177,9 @@ pub fn create_drive( self_url.to_string() }; - let mut drive = if drive_name.is_some() { + let mut drive = if let Some(drive_name_some) = drive_name { if store.get_resource(&drive_subject).is_ok() { - return Err("Drive URL is already taken".into()); + return Err(format!("Name '{}' is already taken", drive_name_some).into()); } Resource::new(drive_subject) } else { From 681e344c0c4132cad7e81bbe4ee50fad58220316 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 2 Nov 2022 22:30:50 +0100 Subject: [PATCH 12/45] Rebased --- lib/src/commit.rs | 1 + lib/src/db.rs | 15 ++++++++------- lib/src/db/test.rs | 2 +- lib/src/populate.rs | 1 - lib/src/store.rs | 9 ++------- server/src/helpers.rs | 3 ++- server/src/search.rs | 1 + 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/src/commit.rs b/lib/src/commit.rs index 98c7e532d..52be125c9 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -6,6 +6,7 @@ use urls::{SET, SIGNER}; use crate::{ agents::{decode_base64, encode_base64}, + atomic_url::Routes, datatype::DataType, errors::AtomicResult, hierarchy, diff --git a/lib/src/db.rs b/lib/src/db.rs index 2dcbeb3c2..e6f53461c 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -15,7 +15,6 @@ use std::{ }; use tracing::instrument; -use url::Url; use crate::{ agents::ForAgent, @@ -107,7 +106,7 @@ impl Db { reference_index, query_index, prop_val_sub_index, - server_url, + server_url: AtomicUrl::try_from(server_url)?, watched_queries, endpoints: default_endpoints(), on_commit: None, @@ -196,7 +195,6 @@ impl Db { } fn map_sled_item_to_resource( - &self, item: Result<(sled::IVec, sled::IVec), sled::Error>, self_url: String, include_external: bool, @@ -204,7 +202,10 @@ impl Db { let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG); let subject: String = String::from_utf8_lossy(&subject).to_string(); - if !include_external && self.is_external_subject(&subject).ok()? { + // if !include_external && self.is_external_subject(&subject).ok()? { + // return None; + // } + if !include_external && !subject.starts_with(&self_url) { return None; } @@ -593,10 +594,11 @@ impl Storelike for Db { ) -> Box> { let self_url = self .get_self_url() - .expect("No self URL set, is required in DB"); + .expect("No self URL set, is required in DB") + .to_string(); let result = self.resources.into_iter().filter_map(move |item| { - Db::map_sled_item_to_resource(self, item, self_url.to_string(), include_external) + Db::map_sled_item_to_resource(item, self_url.clone(), include_external) }); Box::new(result) @@ -650,7 +652,6 @@ impl Storelike for Db { fn populate(&self) -> AtomicResult<()> { crate::populate::populate_all(self) - crate::populate::create_drive(self, None, &default_agent.subject, true) } #[instrument(skip(self))] diff --git a/lib/src/db/test.rs b/lib/src/db/test.rs index 0d91d4685..a317829cc 100644 --- a/lib/src/db/test.rs +++ b/lib/src/db/test.rs @@ -1,4 +1,4 @@ -use crate::{agents::ForAgent, urls, Value}; +use crate::{agents::ForAgent, urls, Value, Routes}; use super::*; use ntest::timeout; diff --git a/lib/src/populate.rs b/lib/src/populate.rs index 41a5a94c0..9282dbdd9 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -383,7 +383,6 @@ pub fn populate_all(store: &crate::Db) -> AtomicResult<()> { set_drive_rights(store, true)?; populate_collections(store).map_err(|e| format!("Failed to populate collections. {}", e))?; populate_endpoints(store).map_err(|e| format!("Failed to populate endpoints. {}", e))?; - populate_importer(store).map_err(|e| format!("Failed to populate importer. {}", e))?; populate_sidebar_items(store) .map_err(|e| format!("Failed to populate sidebar items. {}", e))?; Ok(()) diff --git a/lib/src/store.rs b/lib/src/store.rs index 94ad95849..0f74cb1dc 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -5,14 +5,9 @@ use url::Url; use crate::agents::Agent; use crate::storelike::QueryResult; -use crate::Value; -use crate::{atoms::Atom, storelike::Storelike}; -use crate::{ - atomic_url::AtomicUrl, - atoms::Atom, - storelike::{ResourceCollection, Storelike}, -}; +use crate::{atomic_url::AtomicUrl, storelike::Storelike}; use crate::{errors::AtomicResult, Resource}; +use crate::{Atom, Value}; use std::{collections::HashMap, sync::Arc, sync::Mutex}; /// The in-memory store of data, containing the Resources, Properties and Classes diff --git a/server/src/helpers.rs b/server/src/helpers.rs index a8bc02e38..994de8593 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -6,11 +6,12 @@ use actix_web::http::Uri; use atomic_lib::agents::ForAgent; use atomic_lib::authentication::AuthValues; use atomic_lib::AtomicError; +use atomic_lib::Storelike; use percent_encoding::percent_decode_str; use std::str::FromStr; use crate::errors::{AppErrorType, AtomicServerError}; -use crate::{appstate::AppState, content_types::ContentType, errors::AtomicServerResult}; +use crate::{appstate::AppState, errors::AtomicServerResult}; /// Returns the authentication headers from the request #[tracing::instrument(skip_all)] diff --git a/server/src/search.rs b/server/src/search.rs index f7174bc9d..7e8dee2f6 100644 --- a/server/src/search.rs +++ b/server/src/search.rs @@ -106,6 +106,7 @@ pub fn get_schema_fields(appstate: &SearchState) -> AtomicServerResult { } /// Indexes all resources from the store to search. +/// Skips Commits. /// At this moment does not remove existing index. pub fn add_all_resources(search_state: &SearchState, store: &Db) -> AtomicServerResult<()> { tracing::info!("Building search index..."); From f098d78e4e44e4a6871bf673773a255e2713845c Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 9 Nov 2022 16:04:18 +0100 Subject: [PATCH 13/45] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d430591f..0ede093f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,9 +70,12 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Refactor `Endpoint` handlers, uses a Context now #592 - Re-build store + invite when adjusting server url #607 - Use local atomic-server for properties and classes, improves atomic-server #604 +- Add multi-tenancy support. Users can create their own `Drives` on subdomains. #288 +- Add `/register` Endpoint #489 #254 +- Refactor URLs. `store.self_url()` returns an `AtomicUrl`, which provides methods to easily add paths, find subdomains and more. +- Add support for subdomains, use a Wildcard TLS certificate #502 ## [v0.34.1] - 2023-02-11 - - Improve query performance, refactor indexes. The `.tpf` API is deprecated in favor of the more powerful `.query`. #529 - Replace `acme_lib` with `instant-acme`, drop OpenSSL dependency, add DNS verification for TLS option with `--https-dns` #192 - Improved error handling for HTTPS initialization #530 From 0135f4abd1a37718eab21426d69d68f20bda4386 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 29 Nov 2022 12:30:04 +0100 Subject: [PATCH 14/45] Custom .env parsing, smtp server opts --- server/src/config.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/server/src/config.rs b/server/src/config.rs index 6a0f60c96..1e5b89fd4 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -75,6 +75,7 @@ pub struct Opts { pub data_dir: Option, /// CAUTION: Skip authentication checks, making all data publicly readable. Improves performance. + #[clap(long, env = "ATOMIC_PUBLIC_MODE")] pub public_mode: bool, @@ -94,6 +95,18 @@ pub struct Opts { /// Introduces random delays in the server, to simulate a slow connection. Useful for testing. #[clap(long, env = "ATOMIC_SLOW_MODE")] pub slow_mode: bool, + + /// Add this if you want so send e-mails (e.g. on user registration) + #[clap(long, env = "ATOMIC_SMTP_HOST")] + pub smpt_host: Option, + + /// Useful for debugging e-mails during development, or if your SMTP server has an unconventional port number. + #[clap(long, env = "ATOMIC_SMTP_PORT", default_value = "25")] + pub smpt_port: u16, + + /// If you want to parse options from a specific .env file. By default reads the `./.env` file. + #[clap(long)] + pub env_file: Option, } #[derive(clap::ValueEnum, Clone, Debug)] @@ -200,7 +213,14 @@ pub fn read_opts() -> Opts { dotenv().ok(); // Parse CLI options, .env values, set defaults - Opts::parse() + let mut opts = Opts::parse(); + if let Some(env_path) = &opts.env_file { + dotenv::from_path(env_path) + .unwrap_or_else(|_| panic!("Env file not found: {} ", env_path.display())); + // Parse the opts again so the CLI opts override the .env file + opts = Opts::parse(); + } + opts } /// Creates the server config, reads .env values and sets defaults From 63fb5ed4eac2bd5155e979c0d13c4d6cb6b119c0 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 2 Dec 2022 16:20:59 +0100 Subject: [PATCH 15/45] Register endpoint, mail, tokens, #489 #254 #544 --- .vscode/tasks.json | 17 + CHANGELOG.md | 7 +- CONTRIBUTING.md | 1 + Cargo.lock | 2441 +++++++++++++++++++------------ lib/Cargo.toml | 9 +- lib/defaults/default_store.json | 10 + lib/src/client/helpers.rs | 2 +- lib/src/commit.rs | 1 + lib/src/db.rs | 71 +- lib/src/db/migrations.rs | 2 +- lib/src/email.rs | 108 ++ lib/src/endpoints.rs | 2 + lib/src/lib.rs | 26 +- lib/src/plugins/register.rs | 147 +- lib/src/populate.rs | 12 +- lib/src/store.rs | 8 +- lib/src/storelike.rs | 3 + lib/src/token.rs | 79 + lib/src/urls.rs | 5 +- server/src/appstate.rs | 15 +- server/src/bin.rs | 6 +- server/src/handlers/upload.rs | 10 +- server/src/helpers.rs | 1 + server/src/serve.rs | 2 +- server/src/tests.rs | 4 +- 25 files changed, 1958 insertions(+), 1031 deletions(-) create mode 100644 lib/src/email.rs create mode 100644 lib/src/token.rs diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 17ea49326..1e14f6722 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -15,6 +15,23 @@ "group": "build", "problemMatcher": [] }, + { + "label": "watch atomic-server (cargo watch)", + "type": "shell", + "command": "~/.cargo/bin/cargo-watch", + "args": [ + "--", + "cargo", + "run", + "--bin", + "atomic-server", + "--", + "--env-file", + "server/.env", + ], + "group": "build", + "problemMatcher": [] + }, { "label": "test atomic-server (cargo nextest run)", "type": "shell", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ede093f3..1087f3d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,8 +70,8 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Refactor `Endpoint` handlers, uses a Context now #592 - Re-build store + invite when adjusting server url #607 - Use local atomic-server for properties and classes, improves atomic-server #604 +- New sign up / register flow. Add `/register` Endpoint #489 #254 - Add multi-tenancy support. Users can create their own `Drives` on subdomains. #288 -- Add `/register` Endpoint #489 #254 - Refactor URLs. `store.self_url()` returns an `AtomicUrl`, which provides methods to easily add paths, find subdomains and more. - Add support for subdomains, use a Wildcard TLS certificate #502 @@ -80,6 +80,11 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Replace `acme_lib` with `instant-acme`, drop OpenSSL dependency, add DNS verification for TLS option with `--https-dns` #192 - Improved error handling for HTTPS initialization #530 - Add `--force` to `atomic-server import` #536 +- Email support. Connect to external SMTP servers. #276 +- Basic plugin support through Endpoints. For now only works if you use `**Atomic**-Lib` as a library. Add your plugins by calling `Db::register_endpoint`. +- Allow parsing `.env` files from custom locations using the `--env-file` flag. +- Plugins support `tokio`, so you can spawn async tasks from plugins. +- Add JWT token support, used for emails and registration #544 - Fix index issue happening when deleting a single property in a sorted collection #545 - Update JS assets & playwright - Fix initial indexing bug #560 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index baaefd39b..e6a1fb1ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,7 @@ TL;DR Clone the repo and run `cargo run` from each folder (e.g. `cli` or `server - Go to `browser`, run `pnpm install` (if you haven't already), and run `pnpm dev` to start the browser - Visit your `localhost` in your locally running `atomic-data-browser` instance: (e.g. `http://localhost:5173/app/show?subject=http%3A%2F%2Flocalhost`) - use `cargo watch -- cargo run` to automatically recompile `atomic-server` when you update JS assets in `browser` +- use `cargo watch -- cargo run --bin atomic-server -- --env-file server/.env` to automatically recompile `atomic-server` when you update code or JS assets. ### IDE setup (VSCode) diff --git a/Cargo.lock b/Cargo.lock index 3c0cb1c36..396abfc89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,14 @@ version = 3 [[package]] name = "actix" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5" +checksum = "fb72882332b6d6282f428b77ba0358cb2687e61a6f6df6a6d3871e8a177c2d4f" dependencies = [ + "actix-macros", "actix-rt", "actix_derive", - "bitflags 1.3.2", + "bitflags 2.4.2", "bytes", "crossbeam-channel", "futures-core", @@ -28,19 +29,19 @@ dependencies = [ [[package]] name = "actix-codec" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "bytes", "futures-core", "futures-sink", - "log", "memchr", "pin-project-lite", "tokio", "tokio-util", + "tracing", ] [[package]] @@ -60,16 +61,15 @@ dependencies = [ [[package]] name = "actix-files" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689" +checksum = "bf0bdd6ff79de7c9a021f5d9ea79ce23e108d8bfc9b49b5b4a2cf6fad5a35212" dependencies = [ "actix-http", "actix-service", "actix-utils", "actix-web", - "askama_escape", - "bitflags 1.3.2", + "bitflags 2.4.2", "bytes", "derive_more", "futures-core", @@ -79,13 +79,14 @@ dependencies = [ "mime_guess", "percent-encoding", "pin-project-lite", + "v_htmlescape", ] [[package]] name = "actix-http" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129d4c88e98860e1758c5de288d1632b07970a16d59bdf7b8d66053d582bb71f" +checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" dependencies = [ "actix-codec", "actix-rt", @@ -93,8 +94,8 @@ dependencies = [ "actix-tls", "actix-utils", "ahash", - "base64 0.21.0", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.4.2", "brotli", "bytes", "bytestring", @@ -106,7 +107,7 @@ dependencies = [ "http", "httparse", "httpdate", - "itoa 1.0.3", + "itoa 1.0.10", "language-tags", "local-channel", "mime", @@ -123,12 +124,12 @@ dependencies = [ [[package]] name = "actix-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -166,28 +167,27 @@ dependencies = [ "parse-size", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", ] [[package]] name = "actix-router" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" +checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" dependencies = [ "bytestring", - "firestorm", "http", - "log", "regex", "serde", + "tracing", ] [[package]] name = "actix-rt" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" dependencies = [ "actix-macros", "futures-core", @@ -196,9 +196,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" dependencies = [ "actix-rt", "actix-service", @@ -206,8 +206,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "num_cpus", - "socket2 0.4.7", + "socket2", "tokio", "tracing", ] @@ -225,9 +224,9 @@ dependencies = [ [[package]] name = "actix-tls" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929e47cc23865cdb856e59673cfba2d28f00b3bbd060dfc80e33a00a3cea8317" +checksum = "d4cce60a2f2b477bc72e5cde0af1812a6e82d8fd85b5570a5dcf2a5bf2c5be5f" dependencies = [ "actix-rt", "actix-service", @@ -239,14 +238,14 @@ dependencies = [ "tokio-rustls 0.23.4", "tokio-util", "tracing", - "webpki-roots", + "webpki-roots 0.22.6", ] [[package]] name = "actix-utils" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" dependencies = [ "local-waker", "pin-project-lite", @@ -254,9 +253,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.4.1" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43428f3bf11dee6d166b00ec2df4e3aa8cc1606aaa0b7433c146852e2f4e03b" +checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" dependencies = [ "actix-codec", "actix-http", @@ -277,7 +276,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "itoa 1.0.3", + "itoa 1.0.10", "language-tags", "log", "mime", @@ -288,16 +287,16 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.5", - "time 0.3.14", + "socket2", + "time", "url", ] [[package]] name = "actix-web-actors" -version = "4.1.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31efe7896f3933ce03dd4710be560254272334bb321a18fd8ff62b1a557d9d19" +checksum = "420b001bb709d8510c3e2659dae046e54509ff9528018d09c78381e765a1f9fa" dependencies = [ "actix", "actix-codec", @@ -308,6 +307,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -319,7 +319,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", ] [[package]] @@ -336,13 +336,22 @@ dependencies = [ [[package]] name = "actix_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7" +checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", ] [[package]] @@ -353,12 +362,12 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b" dependencies = [ "cfg-if", - "getrandom 0.2.11", + "getrandom 0.2.12", "once_cell", "version_check", "zerocopy", @@ -375,15 +384,15 @@ dependencies = [ [[package]] name = "alloc-no-stdlib" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] @@ -394,6 +403,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -410,23 +425,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "arc-swap" -version = "1.6.0" +name = "anstream" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] [[package]] -name = "askama_escape" -version = "0.10.3" +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "arc-swap" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "assert_cmd" -version = "2.0.4" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ + "anstyle", "bstr", "doc-comment", "predicates", @@ -435,15 +499,24 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -474,7 +547,7 @@ dependencies = [ "actix-web-static-files", "assert_cmd", "atomic_lib", - "base64 0.21.0", + "base64 0.21.7", "chrono", "clap", "colored", @@ -492,7 +565,7 @@ dependencies = [ "regex", "rio_api", "rio_turtle", - "rustls 0.20.6", + "rustls 0.20.9", "rustls-pemfile", "sanitize-filename", "serde", @@ -518,26 +591,30 @@ dependencies = [ name = "atomic_lib" version = "0.38.0" dependencies = [ - "base64 0.21.0", + "async-mutex", + "base64 0.21.7", "bincode", "criterion", "directories", "html2md", "iai", + "jwt-simple", "kuchikiki", "lazy_static", "lol_html", + "mail-send", "ntest", "rand 0.8.5", "regex", - "ring 0.17.6", + "ring 0.17.8", "rio_api", "rio_turtle", "serde", "serde_jcs", "serde_json", "sled", - "toml 0.8.8", + "tokio", + "toml", "tracing", "ureq", "url", @@ -545,33 +622,49 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ - "hermit-abi 0.1.19", + "addr2line", + "cc", + "cfg-if", "libc", - "winapi", + "miniz_oxide", + "object", + "rustc-demangle", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "base16ct" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bincode" @@ -582,6 +675,12 @@ dependencies = [ "serde", ] +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + [[package]] name = "bitflags" version = "1.3.2" @@ -590,9 +689,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bitpacking" @@ -605,18 +704,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "brotli" -version = "3.3.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -625,9 +724,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -635,38 +734,38 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ - "lazy_static", "memchr", - "regex-automata 0.1.10", + "regex-automata 0.4.5", + "serde", ] [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.2.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bytestring" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" dependencies = [ "bytes", ] @@ -679,19 +778,18 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" dependencies = [ - "jobserver", "libc", ] [[package]] name = "census" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fafee10a5dd1cffcb5cc560e0d0df8803d7355a2b12272e3557dee57314cb6e" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" [[package]] name = "cesu8" @@ -717,31 +815,24 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", - "time 0.1.44", "wasm-bindgen", - "winapi", + "windows-targets 0.52.4", ] -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -750,15 +841,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -766,61 +857,80 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.26" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ - "atty", - "bitflags 1.3.2", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "once_cell", - "strsim", - "termcolor", + "strsim 0.11.0", ] [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -dependencies = [ - "os_str_bytes", -] +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "4.4.2" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" dependencies = [ "error-code", "str-buf", "winapi", ] +[[package]] +name = "coarsetime" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "atty", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -835,18 +945,29 @@ dependencies = [ [[package]] name = "console" -version = "0.15.1" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "terminal_size", "unicode-width", - "winapi", + "windows-sys 0.52.0", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" version = "0.4.0" @@ -855,20 +976,20 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.14", + "time", "version_check", ] [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -876,24 +997,24 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -910,7 +1031,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools 0.10.3", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -931,16 +1052,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools 0.10.3", + "itertools 0.10.5", ] [[package]] name = "crossbeam" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ - "cfg-if", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", @@ -950,58 +1070,46 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "once_cell", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" -dependencies = [ - "cfg-if", - "once_cell", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -1009,6 +1117,28 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1038,19 +1168,25 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.52", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "darling" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -1058,27 +1194,70 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.38", + "strsim 0.10.0", + "syn 2.0.52", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.38", + "syn 2.0.52", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid 0.7.1", + "crypto-bigint 0.3.2", + "pem-rfc7468 0.3.1", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468 0.6.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", ] [[package]] @@ -1115,19 +1294,21 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid 0.9.6", "crypto-common", + "subtle", ] [[package]] name = "dircpy" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8466f8d28ca6da4c9dfbbef6ad4bff6f2fdd5e412d821025b0d3f0a9d74a8c1e" +checksum = "29259db751c34980bfc44100875890c507f585323453b91936960ab1104272ca" dependencies = [ "jwalk", "log", @@ -1204,35 +1385,80 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dtoa-short" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" dependencies = [ "dtoa", ] [[package]] -name = "edit" -version = "0.1.4" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c562aa71f7bc691fde4c6bf5f93ae5a5298b617c2eb44c76c87832299a17fbb4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "tempfile", - "which", + "der 0.7.8", + "digest", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] -name = "either" -version = "1.8.0" +name = "ed25519-compact" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom 0.2.12", +] + +[[package]] +name = "edit" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f364860e764787163c8c8f58231003839be31276e821e2ad2092ddf496b1aa09" +dependencies = [ + "tempfile", + "which", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint 0.5.5", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1241,9 +1467,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -1262,33 +1488,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -1301,6 +1506,12 @@ dependencies = [ "str-buf", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fastdivide" version = "0.4.0" @@ -1315,26 +1526,30 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fd-lock" -version = "3.0.6" +version = "3.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix 0.35.9", - "windows-sys 0.36.1", + "rustix", + "windows-sys 0.48.0", ] [[package]] -name = "firestorm" -version = "0.5.1" +name = "ff" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5f6c2c942da57e2aaaa84b8a521489486f14e75e7fa91dab70aba913975f98" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -1348,9 +1563,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1371,7 +1586,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" dependencies = [ - "rustix 0.38.21", + "rustix", "windows-sys 0.48.0", ] @@ -1387,9 +1602,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1402,9 +1617,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1412,15 +1627,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1429,38 +1644,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1485,11 +1700,12 @@ dependencies = [ [[package]] name = "generator" -version = "0.7.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" +checksum = "b5b25e5b3e733153bcab35ee4671b46604b42516163cae442d1601cb716f2ac5" dependencies = [ "cc", + "cfg-if", "libc", "log", "rustversion", @@ -1498,12 +1714,33 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", ] [[package]] @@ -1519,21 +1756,40 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.24" @@ -1546,7 +1802,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.0.2", + "indexmap 2.2.5", "slab", "tokio", "tokio-util", @@ -1555,9 +1811,13 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -1576,9 +1836,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", "allocator-api2", @@ -1586,30 +1846,72 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "libc", + "hmac", ] [[package]] -name = "hermit-abi" -version = "0.3.4" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] [[package]] -name = "hex" -version = "0.4.3" +name = "hmac-sha1-compact" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7" + +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] [[package]] name = "html2md" @@ -1647,20 +1949,20 @@ checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" [[package]] name = "http" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", - "itoa 1.0.3", + "itoa 1.0.10", ] [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -1681,15 +1983,15 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.24" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1700,9 +2002,9 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.3", + "itoa 1.0.10", "pin-project-lite", - "socket2 0.4.7", + "socket2", "tokio", "tower-service", "tracing", @@ -1718,7 +2020,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.9", + "rustls 0.21.10", "rustls-native-certs", "tokio", "tokio-rustls 0.24.1", @@ -1732,16 +2034,25 @@ checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678" [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", + "iana-time-zone-haiku", "js-sys", - "once_cell", "wasm-bindgen", - "winapi", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", ] [[package]] @@ -1752,9 +2063,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1768,9 +2079,9 @@ checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -1779,12 +2090,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.3", "serde", ] @@ -1802,14 +2113,15 @@ dependencies = [ [[package]] name = "instant-acme" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfd67650b79d84a42241813361d52f313271987a48330606653995881fe47ef4" +checksum = "51e78737dbac1bae14cb5556c9cd7c604886095c59cdb5af71f12a4c59be2b05" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", "hyper", "hyper-rustls", - "ring 0.17.6", + "ring 0.17.8", + "rustls-pki-types", "serde", "serde_json", "thiserror", @@ -1821,28 +2133,22 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" -[[package]] -name = "io-lifetimes" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" - [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.4", - "rustix 0.38.21", + "hermit-abi", + "libc", "windows-sys 0.52.0", ] [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1864,9 +2170,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jni" @@ -1888,20 +2194,11 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1916,6 +2213,46 @@ dependencies = [ "rayon", ] +[[package]] +name = "jwt-simple" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357892bb32159d763abdea50733fadcb9a8e1c319a9aa77592db8555d05af83e" +dependencies = [ + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand 0.8.5", + "rsa 0.7.2", + "serde", + "serde_json", + "spki 0.6.0", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature 2.2.0", +] + [[package]] name = "kuchikiki" version = "0.8.2" @@ -1924,7 +2261,7 @@ checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.1", + "indexmap 1.9.3", "matches", "selectors", ] @@ -1940,6 +2277,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "lazycell" @@ -1955,45 +2295,55 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] -name = "linux-raw-sys" -version = "0.0.46" +name = "libm" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall 0.4.1", +] [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "local-channel" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" dependencies = [ "futures-core", "futures-sink", - "futures-util", "local-waker", ] [[package]] name = "local-waker" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -2001,12 +2351,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lol_html" @@ -2014,7 +2361,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10662f7aad081ec900fd735be33076da75e0389400277dc3734e2b0aa02bb115" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "cssparser", "encoding_rs", @@ -2048,14 +2395,14 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" dependencies = [ - "hashbrown 0.14.1", + "hashbrown 0.14.3", ] [[package]] name = "lz4_flex" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" +checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" [[package]] name = "mac" @@ -2063,6 +2410,34 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mail-builder" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8390bb0f68168cabc59999706c2199c9b556d7dfd94e65a26e8840fa1d58b238" +dependencies = [ + "gethostname 0.4.3", +] + +[[package]] +name = "mail-send" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17009bb6737cd4b3e2422a0046dc18a07b8ca8f3fa87e59ea3e8408cb0d2e8f" +dependencies = [ + "base64 0.13.1", + "gethostname 0.2.3", + "mail-builder", + "md5", + "rand 0.8.5", + "rsa 0.6.1", + "rustls 0.20.9", + "sha2", + "tokio", + "tokio-rustls 0.23.4", + "webpki-roots 0.22.6", +] + [[package]] name = "markup5ever" version = "0.11.0" @@ -2100,9 +2475,15 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "md5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "measure_time" @@ -2116,9 +2497,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -2140,9 +2521,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -2162,30 +2543,30 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.48.0", ] [[package]] name = "murmurhash32" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9380db4c04d219ac5c51d14996bbf2c2e9a15229771b53f8671eb6c83cf44df" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" [[package]] name = "mutually_exclusive_features" @@ -2210,9 +2591,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags 1.3.2", "cc", @@ -2274,48 +2655,82 @@ dependencies = [ name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "overload", - "winapi", + "num-traits", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-iter" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", + "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "libc", + "memchr", ] [[package]] @@ -2326,9 +2741,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oneshot" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc22d22931513428ea6cc089e942d38600e3d00976eef8c86de6b8a3aadec6eb" +checksum = "6f6640c6bda7731b1fdbab747981a0f896dd1fedaf9f4a53fa237a04a84431f4" dependencies = [ "loom", ] @@ -2353,7 +2768,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.0.2", + "indexmap 2.2.5", "js-sys", "once_cell", "pin-project-lite", @@ -2423,12 +2838,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - [[package]] name = "overload" version = "0.1.1" @@ -2456,6 +2865,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb175ec8981211357b7b379869c2f8d555881c55ea62311428ec0de46d89bd5c" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -2464,7 +2897,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -2474,14 +2907,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.9", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", @@ -2493,15 +2926,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.4.1", "smallvec", - "windows-sys 0.36.1", + "windows-targets 0.48.5", ] [[package]] @@ -2512,9 +2945,9 @@ checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" [[package]] name = "paste" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "path-matchers" @@ -2533,19 +2966,46 @@ checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696" [[package]] name = "pem" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01de5d978f34aa4b2296576379fcc416034702fd94117c56ffd8a1a767cefb30" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" @@ -2641,29 +3101,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -2671,17 +3131,71 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78f66c04ccc83dd4486fd46c33896f4e17b24a7a3a6400dedc48ed0ddd72320" +dependencies = [ + "der 0.5.1", + "pkcs8 0.8.0", + "zeroize", +] + +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der 0.5.1", + "spki 0.5.4", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.3", +] + [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -2692,24 +3206,30 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" @@ -2719,77 +3239,61 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "2.1.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ + "anstyle", "difflib", - "itertools 0.10.3", "predicates-core", ] [[package]] name = "predicates-core" -version = "1.0.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", ] [[package]] -name = "proc-macro-crate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" -dependencies = [ - "once_cell", - "thiserror", - "toml 0.5.9", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "primeorder" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "elliptic-curve", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-crate" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "once_cell", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2805,9 +3309,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2844,7 +3348,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2864,7 +3368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2878,11 +3382,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", ] [[package]] @@ -2905,9 +3409,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -2930,8 +3434,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" dependencies = [ "pem", - "ring 0.17.6", - "time 0.3.14", + "ring 0.17.8", + "time", "yasna", ] @@ -2955,25 +3459,25 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.11", - "redox_syscall 0.2.16", + "getrandom 0.2.12", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.4" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.7", - "regex-syntax 0.7.5", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", ] [[package]] @@ -2982,31 +3486,41 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax 0.6.27", + "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", ] [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] [[package]] name = "ring" @@ -3025,35 +3539,77 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.6" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if", + "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rio_api" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399f133da00f40a6be2d54b1c57e4a9004e68637530f76350bd02ea8dffef54e" +checksum = "1924fa1f0e6d851f9b73b3c569e607c368a0d92995d99d563ad7bf1414696603" [[package]] name = "rio_turtle" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76f84a733b7b8af1938eec4beb03d3e3cbf2a6a02dced10b82c42706d58e95b" +checksum = "5cec59971eafd99b9c7e3544bfcabafea81a7072ac51c9f46985ca0bd7ba6016" dependencies = [ "oxilangtag", "oxiri", "rio_api", ] +[[package]] +name = "rsa" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf22754c49613d2b3b119f0e5d46e34a2c628a937e3024b8762de4e7d8c710b" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1 0.3.3", + "pkcs8 0.8.0", + "rand_core 0.6.4", + "smallvec", + "subtle", + "zeroize", +] + +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1 0.4.1", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rust-stemmers" version = "1.2.0" @@ -3064,6 +3620,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3081,59 +3643,59 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.9" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 1.3.2", - "errno 0.2.8", - "io-lifetimes", + "bitflags 2.4.2", + "errno", "libc", - "linux-raw-sys 0.0.46", - "windows-sys 0.36.1", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] -name = "rustix" -version = "0.38.21" +name = "rustls" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ - "bitflags 2.4.1", - "errno 0.3.5", - "libc", - "linux-raw-sys 0.4.10", - "windows-sys 0.48.0", + "log", + "ring 0.16.20", + "sct", + "webpki", ] [[package]] name = "rustls" -version = "0.20.6" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ - "log", - "ring 0.16.20", + "ring 0.17.8", + "rustls-webpki 0.101.7", "sct", - "webpki", ] [[package]] name = "rustls" -version = "0.21.9" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ - "ring 0.17.6", - "rustls-webpki", - "sct", + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -3143,28 +3705,45 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.13.0", + "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.6", + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "rustyline" @@ -3192,9 +3771,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "ryu-js" @@ -3229,41 +3808,54 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.52.0", ] [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der 0.7.8", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", ] [[package]] name = "security-framework" -version = "2.7.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3274,9 +3866,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -3304,28 +3896,28 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", ] [[package]] @@ -3341,11 +3933,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.10", "ryu", "serde", ] @@ -3375,38 +3967,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.3", + "itoa 1.0.10", "ryu", "serde", ] [[package]] name = "serde_with" -version = "3.3.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", "chrono", "hex", - "indexmap 1.9.1", - "indexmap 2.0.2", + "indexmap 1.9.3", + "indexmap 2.2.5", "serde", + "serde_derive", "serde_json", "serde_with_macros", - "time 0.3.14", + "time", ] [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", ] [[package]] @@ -3421,9 +4014,20 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.4" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -3432,9 +4036,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -3447,13 +4051,33 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simple-server-timing-header" version = "0.1.1" @@ -3462,24 +4086,24 @@ checksum = "16e78919e05c9b8e123d435a4ad104b488ad1585631830e413830985c214086e" [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "sketches-ddsketch" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a406c1882ed7f29cd5e248c9848a80e7cb6ae0fea82346d2746f2f941c07e1" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" dependencies = [ "serde", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -3502,28 +4126,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.7" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3538,6 +4152,36 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der 0.5.1", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.8", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3563,9 +4207,9 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" [[package]] name = "string_cache" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", @@ -3593,6 +4237,18 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -3606,9 +4262,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -3624,7 +4280,7 @@ dependencies = [ "aho-corasick", "arc-swap", "async-trait", - "base64 0.21.0", + "base64 0.21.7", "bitpacking", "byteorder", "census", @@ -3662,7 +4318,7 @@ dependencies = [ "tantivy-tokenizer-api", "tempfile", "thiserror", - "time 0.3.14", + "time", "uuid", "winapi", ] @@ -3702,7 +4358,7 @@ dependencies = [ "byteorder", "ownedbytes", "serde", - "time 0.3.14", + "time", ] [[package]] @@ -3712,7 +4368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc3c506b1a8443a3a65352df6382a1fb6a7afe1a02e871cee0d25e2c3d5f3944" dependencies = [ "byteorder", - "regex-syntax 0.6.27", + "regex-syntax 0.6.29", "utf8-ranges", ] @@ -3757,15 +4413,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", - "rustix 0.38.21", - "windows-sys 0.48.0", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -3779,30 +4434,11 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "termtree" -version = "0.2.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thin-slice" @@ -3812,30 +4448,31 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ + "cfg-if", "once_cell", ] @@ -3863,33 +4500,34 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "deranged", + "itoa 1.0.10", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", ] [[package]] -name = "time" -version = "0.3.14" +name = "time-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" -dependencies = [ - "itoa 1.0.3", - "libc", - "num_threads", - "serde", - "time-macros", -] +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tinytemplate" @@ -3912,26 +4550,38 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", + "num_cpus", "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.7", - "windows-sys 0.45.0", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", ] [[package]] @@ -3940,7 +4590,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.6", + "rustls 0.20.9", "tokio", "webpki", ] @@ -3951,15 +4601,15 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.9", + "rustls 0.21.10", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -3971,23 +4621,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.6", ] [[package]] @@ -4001,15 +4642,26 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.5", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.2.5", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.5", ] [[package]] @@ -4020,11 +4672,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4046,13 +4697,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -4125,57 +4776,57 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "untrusted" @@ -4191,26 +4842,26 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.5.0" +version = "2.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" dependencies = [ - "base64 0.13.0", - "chunked_transfer", + "base64 0.21.7", "flate2", "log", "once_cell", - "rustls 0.20.6", + "rustls 0.22.2", + "rustls-pki-types", + "rustls-webpki 0.102.2", "url", - "webpki", - "webpki-roots", + "webpki-roots 0.26.1", ] [[package]] name = "url" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -4219,9 +4870,9 @@ dependencies = [ [[package]] name = "urlencoding" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf-8" @@ -4237,20 +4888,26 @@ checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" [[package]] name = "utf8parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.1.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "serde", ] +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "valuable" version = "0.1.0" @@ -4274,22 +4931,20 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -4301,21 +4956,24 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasix" +version = "0.12.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi 0.11.0+wasi-snapshot-preview1", +] [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4323,24 +4981,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4348,28 +5006,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -4387,32 +5045,42 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" -version = "4.3.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -4433,9 +5101,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -4448,37 +5116,40 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.32.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows-core 0.53.0", + "windows-targets 0.52.4", ] [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.52.4", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-result" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -4496,22 +5167,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -4531,25 +5187,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4558,27 +5208,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -4588,27 +5220,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -4618,27 +5232,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -4648,27 +5244,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -4678,15 +5256,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -4696,45 +5268,36 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.5.35" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1931d78a9c73861da0134f453bb1f790ce49b2e30eba8410b4b79bac72b46a2d" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] @@ -4756,27 +5319,27 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.14", + "time", ] [[package]] name = "zerocopy" -version = "0.7.23" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50cbb27c30666a6108abd6bc7577556265b44f243e2be89a8bc4e07a528c107" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.23" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25f293fe55f0a48e7010d65552bb63704f6ceb55a1a385da10d41d8f78e4a3d" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.52", ] [[package]] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index ab3c55d6f..d38f702dc 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -15,6 +15,9 @@ harness = false name = "benchmarks" # path = "benches/benchmarks.rs" +[package.metadata.docs.rs] +all-features = true + [dependencies] base64 = "0.21" bincode = { version = "1", optional = true } @@ -37,6 +40,10 @@ tracing = "0.1" ureq = "2" url = "2" urlencoding = "2" +mail-send = {version= "0.2", optional = true} +async-mutex = {version = "1.4.0", optional = true} +tokio = {version = "1.22.0", optional = true, features = ["full"] } +jwt-simple = {version = "0.11.2", optional = true} [dev-dependencies] criterion = "0.5" @@ -46,6 +53,6 @@ ntest = "0.9" [features] config = ["directories", "toml"] -db = ["sled", "bincode"] html = ["kuchikiki", "lol_html", "html2md"] +db = ["sled", "bincode", "mail-send", "tokio", "async-mutex", "jwt-simple"] rdf = ["rio_api", "rio_turtle"] diff --git a/lib/defaults/default_store.json b/lib/defaults/default_store.json index be560b91b..9c05cd124 100644 --- a/lib/defaults/default_store.json +++ b/lib/defaults/default_store.json @@ -521,6 +521,16 @@ "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", "https://atomicdata.dev/properties/shortname": "overwrite-outside" }, + { + "@id": "https://atomicdata.dev/properties/email", + "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/string", + "https://atomicdata.dev/properties/description": "Primary email address ", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Property" + ], + "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", + "https://atomicdata.dev/properties/shortname": "email" + }, { "@id": "https://atomicdata.dev/properties/incomplete", "https://atomicdata.dev/properties/classtype": "https://atomicdata.dev/classes/Property", diff --git a/lib/src/client/helpers.rs b/lib/src/client/helpers.rs index 5122cf2d3..8e65c0e74 100644 --- a/lib/src/client/helpers.rs +++ b/lib/src/client/helpers.rs @@ -59,7 +59,7 @@ pub fn fetch_body( } let agent = ureq::builder() - .timeout(std::time::Duration::from_secs(2)) + .timeout(std::time::Duration::from_secs(10)) .build(); let resp = agent .get(url) diff --git a/lib/src/commit.rs b/lib/src/commit.rs index 52be125c9..c290bd1cc 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -205,6 +205,7 @@ impl Commit { let _resource_new_classes = resource_new.get_classes(store)?; // BEFORE APPLY COMMIT HANDLERS + // TODO: These should be handled by actual plugins #[cfg(feature = "db")] for class in &_resource_new_classes { match class.subject.as_str() { diff --git a/lib/src/db.rs b/lib/src/db.rs index e6f53461c..6f5963324 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -1,5 +1,6 @@ //! Persistent, ACID compliant, threadsafe to-disk store. //! Powered by Sled - an embedded database. +//! See [Db] mod migrations; mod prop_val_sub_index; @@ -14,17 +15,16 @@ use std::{ vec, }; -use tracing::instrument; +use mail_send::{Connected, Transport}; +use tracing::{info, instrument}; use crate::{ agents::ForAgent, atomic_url::AtomicUrl, atoms::IndexAtom, commit::CommitResponse, - db::{ - query_index::{requires_query_index, NO_VALUE}, - val_prop_sub_index::find_in_val_prop_sub_index, - }, + db::{query_index::requires_query_index, val_prop_sub_index::find_in_val_prop_sub_index}, + email::{self, MailMessage}, endpoints::{default_endpoints, Endpoint, HandleGetContext}, errors::{AtomicError, AtomicResult}, resources::PropVals, @@ -41,7 +41,7 @@ use self::{ }, query_index::{ check_if_atom_matches_watched_query_filters, query_sorted_indexed, should_include_resource, - update_indexed_member, IndexIterator, QueryFilter, + update_indexed_member, IndexIterator, QueryFilter, NO_VALUE, }, val_prop_sub_index::{add_atom_to_reference_index, remove_atom_from_reference_index}, }; @@ -53,21 +53,22 @@ type HandleCommit = Box; /// The String on the left represents a Property URL, and the second one is the set of subjects. pub type PropSubjectMap = HashMap>; -/// The Db is a persistent on-disk Atomic Data store. +/// A persistent on-disk Atomic Data store. /// It's an implementation of [Storelike]. /// It uses [sled::Tree]s as Key Value stores. /// It stores [Resource]s as [PropVals]s by their subject as key. /// It builds a value index for performant [Query]s. /// It keeps track of Queries and updates their index when [crate::Commit]s are applied. -/// You can pass a custom `on_commit` function to run at Commit time. /// `Db` should be easily, cheaply clone-able, as users of this library could have one `Db` per connection. +/// Note that [plugins](crate::plugins) can add their own endpoints to the [Db], +/// and can use [tokio::spawn] to start concurrent tasks. #[derive(Clone)] pub struct Db { /// The Key-Value store that contains all data. /// Resources can be found using their Subject. /// Try not to use this directly, but use the Trees. db: sled::Db, - default_agent: Arc>>, + default_agent: Arc>>, /// Stores all resources. The Key is the Subject as a `string.as_bytes()`, the value a [PropVals]. Propvals must be serialized using [bincode]. resources: sled::Tree, /// Index of all Atoms, sorted by {Value}-{Property}-{Subject}. @@ -85,7 +86,9 @@ pub struct Db { /// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it. endpoints: Vec, /// Function called whenever a Commit is applied. - on_commit: Option>, + handle_commit: Option>, + /// Email SMTP client for sending email. + smtp_client: Option>>>, } impl Db { @@ -101,7 +104,7 @@ impl Db { let watched_queries = db.open_tree("watched_queries")?; let store = Db { db, - default_agent: Arc::new(Mutex::new(None)), + default_agent: Arc::new(std::sync::Mutex::new(None)), resources, reference_index, query_index, @@ -109,7 +112,8 @@ impl Db { server_url: AtomicUrl::try_from(server_url)?, watched_queries, endpoints: default_endpoints(), - on_commit: None, + handle_commit: None, + smtp_client: None, }; migrate_maybe(&store).map(|e| format!("Error during migration of database: {:?}", e))?; crate::populate::populate_base_models(&store) @@ -156,7 +160,7 @@ impl Db { /// Sets a function that is called whenever a [Commit::apply] is called. /// This can be used to listen to events. pub fn set_handle_commit(&mut self, on_commit: HandleCommit) { - self.on_commit = Some(Arc::new(on_commit)); + self.handle_commit = Some(Arc::new(on_commit)); } /// Finds resource by Subject, return PropVals HashMap @@ -262,9 +266,6 @@ impl Db { for (i, atom_res) in atoms.enumerate() { let atom = atom_res?; - if !q.include_external && !atom.subject.starts_with(&self_url) { - continue; - } total_count += 1; @@ -272,6 +273,10 @@ impl Db { continue; } + if !q.include_external && self.is_external_subject(&atom.subject)? { + continue; + } + if q.limit.is_none() || subjects.len() < q.limit.unwrap() { if !should_include_resource(q) { subjects.push(atom.subject.clone()); @@ -317,6 +322,36 @@ impl Db { count: total_count, }) } + + /// Adds an [Endpoint] to the store. This means adding a route with custom behavior. + pub fn register_endpoint(&mut self, endpoint: Endpoint) { + self.endpoints.push(endpoint); + } + + /// Registers an SMTP client to the store, allowing the store to send emails. + pub async fn set_smtp_config( + &mut self, + smtp_config: crate::email::SmtpConfig, + ) -> AtomicResult<()> { + self.smtp_client = Some(Arc::new(Mutex::new( + crate::email::get_smtp_client(smtp_config).await?, + ))); + Ok(()) + } + + pub async fn send_email(&self, message: MailMessage) -> AtomicResult<()> { + let mut client = self + .smtp_client + .as_ref() + .ok_or_else(|| { + AtomicError::other_error( + "No SMTP client configured. Please call set_smtp_config first.".into(), + ) + })? + .lock()?; + email::send_mail(&mut client, message).await?; + Ok(()) + } } impl Drop for Db { @@ -451,7 +486,7 @@ impl Storelike for Db { let resource = crate::resources::Resource::from_propvals(propvals, subject.into()); Ok(resource) } - Err(e) => self.handle_not_found(subject, e, None), + Err(e) => self.handle_not_found(subject, e), } } @@ -570,7 +605,7 @@ impl Storelike for Db { } fn handle_commit(&self, commit_response: &CommitResponse) { - if let Some(fun) = &self.on_commit { + if let Some(fun) = &self.handle_commit { fun(commit_response); } } diff --git a/lib/src/db/migrations.rs b/lib/src/db/migrations.rs index aa4b74939..dadeec924 100644 --- a/lib/src/db/migrations.rs +++ b/lib/src/db/migrations.rs @@ -75,7 +75,7 @@ fn v0_to_v1(store: &Db) -> AtomicResult<()> { /// Add `prop_val_sub` index fn ref_v0_to_v1(store: &Db) -> AtomicResult<()> { - tracing::warn!("Rebuilding indexes..."); + tracing::warn!("Rebuilding indexes due to migrating to new version..."); store.db.drop_tree("reference_index")?; store.build_index(true)?; tracing::warn!("Rebuilding index finished!"); diff --git a/lib/src/email.rs b/lib/src/email.rs new file mode 100644 index 000000000..764fd1b67 --- /dev/null +++ b/lib/src/email.rs @@ -0,0 +1,108 @@ +//! [EmailAddress] with validation, [MailMessage] with sending, and [get_smtp_client] for setting up mail. + +use crate::{errors::AtomicResult, storelike::Query, urls, Storelike}; +use mail_send::{mail_builder::MessageBuilder, Connected, Transport}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EmailAddress { + pub address: String, +} + +impl EmailAddress { + pub fn new(address: String) -> AtomicResult { + // TODO: use decent crate for validation, like Lettre + if !address.contains('@') { + return Err(format!("Invalid email address: {}", address).into()); + } + Ok(Self { address }) + } + + /// Throws error if email address is already taken + pub fn check_used(self, store: &impl Storelike) -> AtomicResult { + let mut query = Query::new(); + // TODO: This hits too many resources, as it will also include non-agent resources + query.property = Some(urls::EMAIL.into()); + query.value = Some(crate::Value::String(self.address.clone())); + if store.query(&query)?.count > 0 { + return Err("Email address already used".into()); + } + Ok(self) + } +} + +impl std::fmt::Display for EmailAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.address) + } +} + +pub struct SmtpConfig { + pub host: String, + pub port: u16, +} + +pub async fn get_smtp_client( + config: SmtpConfig, +) -> AtomicResult> { + let full_address = format!("{}:{}", config.host, config.port); + info!("Connecting to mailserver {full_address}"); + let connection = Transport::new(config.host.clone()) + .port(config.port) + .connect() + .await + .map_err(|e| format!("Error connecting to SMTP mail server: at {full_address}. Is it running? Error message: {e}"))?; + Ok(connection) +} + +#[derive(Debug)] +pub struct MailMessage { + pub to: EmailAddress, + pub subject: String, + pub body: String, + pub action: Option, +} + +#[derive(Debug)] +pub struct MailAction { + pub name: String, + pub url: String, +} + +#[tracing::instrument(skip(connection))] +pub async fn send_mail( + connection: &mut mail_send::Transport<'static, Connected>, + message: MailMessage, +) -> AtomicResult<()> { + let html = if let Some(action) = message.action { + format!( + "{}
{}", + message.body, action.url, action.name + ) + } else { + message.body.clone() + }; + + let builder = MessageBuilder::new() + .from(("Atomic Data", "noreply@atomicdata.dev")) + .to(vec![(message.to.to_string())]) + .subject(message.subject) + .html_body(html) + .text_body(message.body); + info!("Sending mail"); + connection.send(builder).await.map_err(|e| e.to_string())?; + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn create_and_serialize_email() { + EmailAddress::new("invalid email".into()).unwrap_err(); + let valid = EmailAddress::new("valid@email.com".into()).unwrap(); + assert_eq!(valid.to_string(), "valid@email.com"); + } +} diff --git a/lib/src/endpoints.rs b/lib/src/endpoints.rs index 869093a44..29d8940c2 100644 --- a/lib/src/endpoints.rs +++ b/lib/src/endpoints.rs @@ -32,6 +32,7 @@ pub struct HandlePostContext<'a> { pub body: Vec, } /// An API endpoint at some path which accepts requests and returns some Resource. +/// Add them by calling [Db::register_endpoint] #[derive(Clone)] pub struct Endpoint { /// The part behind the server domain, e.g. '/versions' or '/collections'. Include the slash. @@ -94,6 +95,7 @@ pub fn default_endpoints() -> Vec { plugins::search::search_endpoint(), plugins::files::upload_endpoint(), plugins::register::register_endpoint(), + plugins::register::confirm_email_endpoint(), #[cfg(feature = "html")] plugins::bookmark::bookmark_endpoint(), plugins::importer::import_endpoint(), diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f3ab9a290..913c011c6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,25 +1,31 @@ /*! `atomic_lib` helps you to get, store, serialize, parse and validate Atomic Data. +It's primarily used for powering [Atomic-Server](https://github.com/atomicdata-dev/atomic-data-rust). +Many of the features are optional, which helps us keep the default size small. See the [Atomic Data Docs](https://docs.atomicdata.dev) for more information. ## Features - Two stores for Atomic Data: + - **On disk** [Db], powered by Sled. Indexes filtered queries. (requires `db` feature) - **In-memory** [Store] for getting / setting data. Useful for client applications. - - **On disk** [Db], powered by Sled. Useful for applications that persist Atomic Data, such as [`atomic-server`](https://crates.io/crates/atomic-server). -- [serialize] and [parse] tools for [JSON-AD](https://docs.atomicdata.dev/core/json-ad.html), plain JSON, RDF, Turtle, N-Triples and JSON-LD. +- [parse] and import tools for [JSON-AD](https://docs.atomicdata.dev/core/json-ad.html) +- [serialize] tools for JSON-AD, plain JSON, RDF, Turtle, N-Triples and JSON-LD. - [Resource] with getters, setters and a `.save` function that creates Commits. - [Value] converts Atomic Data to Rust native types -- Validate [Atomic Schema](https://docs.atomicdata.dev/schema/intro.html) -- [Commit]s (transactions / delta's / changes / updates / versioning / history). -- [plugins] system (although not very mature) +- [Commit]s (transactions / delta's / changes / updates / versioning / history). Supports many checks, such as Schema, Authorization and more. - [collections] (pagination, sorting, filtering) -- Querying (using triple pattern fragments) (see [storelike::Query]) -- [plugins::invite] for sharing +- Queries (see [storelike::Query]) - [hierarchy] for authorization - [crate::endpoints::Endpoint] for custom API endpoints -- [config::Config] files. +- [config::Config] files. (requires `config` feature) +- [endpoints] which allow easily adding routes with custom features +- [plugins] system basics. Not very mature, as we still need the code in this repo. (all plugins require `db` feature) +- [plugins::invite] for sharing URLs that grant rights +- [plugins::chatroom] for slack-like group chats. +- [plugins::bookmark] for fetching HTML pages, converting them to markdown, and storing them as Atomic Data (requires `html` feature) +- [plugins::versioning] for constructing previous versions of resources, powered by [Commit]s. ## Getting started @@ -71,6 +77,8 @@ pub mod datatype; #[cfg(feature = "db")] pub mod db; #[cfg(feature = "db")] +pub mod email; +#[cfg(feature = "db")] pub mod endpoints; pub mod errors; pub mod hierarchy; @@ -86,6 +94,8 @@ pub mod store; pub mod storelike; #[cfg(test)] mod test_utils; +#[cfg(feature = "db")] +pub mod token; pub mod urls; pub mod utils; pub mod validate; diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index ce781242c..945135f27 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -1,20 +1,23 @@ //! Creates a new Drive and optionally also an Agent. +use serde::{Deserialize, Serialize}; + use crate::{ agents::Agent, - endpoints::Endpoint, + email::{EmailAddress, MailAction, MailMessage}, + endpoints::{Endpoint, HandleGetContext}, errors::AtomicResult, - urls::{self, PUBLIC_AGENT}, + urls::{self}, values::SubResource, - Resource, Storelike, + Resource, Storelike, Value, }; pub fn register_endpoint() -> Endpoint { Endpoint { path: "/register".to_string(), params: [ - urls::INVITE_PUBKEY.to_string(), urls::NAME.to_string(), + urls::EMAIL.to_string(), ].into(), description: "Allows new users to easily, in one request, make both an Agent and a Drive. This drive will be created at the subdomain of `name`.".to_string(), shortname: "register".to_string(), @@ -23,49 +26,125 @@ pub fn register_endpoint() -> Endpoint { } } -#[tracing::instrument(skip(store))] -pub fn construct_register_redirect( - url: url::Url, - store: &impl Storelike, - for_agent: Option<&str>, -) -> AtomicResult { - let requested_subject = url.to_string(); - let mut pub_key = None; +pub fn confirm_email_endpoint() -> Endpoint { + Endpoint { + path: "/confirmEmail".to_string(), + params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), + description: "Confirm email address and set a key for your Agent.".to_string(), + shortname: "confirm-email".to_string(), + handle: Some(construct_confirm_email_redirect), + handle_post: None, + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct MailConfirmation { + pub email: EmailAddress, + pub name: String, +} + +#[tracing::instrument()] +pub fn construct_register_redirect(context: HandleGetContext) -> AtomicResult { let mut name_option = None; - for (k, v) in url.query_pairs() { + let mut email_option: Option = None; + let store = context.store; + for (k, v) in context.subject.query_pairs() { match k.as_ref() { - "public-key" | urls::INVITE_PUBKEY => pub_key = Some(v.to_string()), "name" | urls::NAME => name_option = Some(v.to_string()), + "email" => email_option = Some(EmailAddress::new(v.to_string())?), _ => {} } } - if pub_key.is_none() && name_option.is_none() { + // by default just return the Endpoint + if name_option.is_none() && email_option.is_none() { return register_endpoint().to_resource(store); - } + }; + + let name = name_option.ok_or("No name provided")?; + let email = email_option.ok_or("No email provided")?.check_used(store)?; + + // send the user an e-mail to confirm sign up + let store_clone = store.clone(); + let confirmation_token_struct = MailConfirmation { + email: email.clone(), + name: name.clone(), + }; + let token = crate::token::sign_claim(store, confirmation_token_struct)?; + let mut confirm_url = store + .get_server_url() + .clone() + .set_path("confirmEmail") + .url(); + confirm_url.set_query(Some(&format!("token={}", token))); + let message = MailMessage { + to: email, + subject: "Confirm your e-mail address".to_string(), + body: format!("Welcome to Atomic Data, {}. Please confirm your e-mail address by clicking the link below", name), + action: Some(MailAction { + name: "Confirm e-mail address".to_string(), + url: confirm_url.into() + }) + }; + // async, because mails are slow + tokio::spawn(async move { + store_clone + .send_email(message) + .await + .unwrap_or_else(|e| tracing::error!("Error sending email: {}", e)); + }); + + // Here we probably want to return some sort of SuccesMessage page. + // Not sure what that should be. + let mut resource = Resource::new_generate_subject(store); + resource.set_string(urls::DESCRIPTION.into(), "success", store)?; + + // resource.set_propval(urls::, value, store) - let name = if let Some(n) = name_option { - n + Ok(resource) +} + +#[tracing::instrument()] +pub fn construct_confirm_email_redirect(context: HandleGetContext) -> AtomicResult { + let url = context.subject; + let store = context.store; + let mut token_opt: Option = None; + let mut pubkey_option = None; + + for (k, v) in url.query_pairs() { + match k.as_ref() { + "token" | urls::TOKEN => token_opt = Some(v.to_string()), + "public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()), + _ => {} + } + } + let token = if let Some(t) = token_opt { + t } else { - return Err("No name provided".into()); + return confirm_email_endpoint().to_resource(store); }; + let pubkey = pubkey_option.ok_or("No public-key provided")?; - let drive_creator_agent: String = if let Some(key) = pub_key { - let mut new = Agent::new_from_public_key(store, &key)?; - new.name = Some(name.clone()); + // Parse and verify the JWT token + let confirmation = crate::token::verify_claim::(store, &token)?.custom; + + // Create the Agent if it doesn't exist yet. + // Note: this happens before the drive is saved, which checks if the name is available. + // We get new agents that just do nothing, but perhaps that's not a problem. + let drive_creator_agent: String = { + let mut new = Agent::new_from_public_key(store, &pubkey)?; + new.name = Some(confirmation.name.clone()); let net_agent_subject = new.subject.to_string(); new.to_resource()?.save(store)?; net_agent_subject - } else if let Some(agent) = for_agent { - if agent == PUBLIC_AGENT { - return Err("No `public-key` provided.".into()); - } - agent.to_string() - } else { - return Err("No `public-key` provided".into()); }; // Create the new Drive - let drive = crate::populate::create_drive(store, Some(&name), &drive_creator_agent, false)?; + let drive = crate::populate::create_drive( + store, + Some(&confirmation.name), + &drive_creator_agent, + false, + )?; // Add the drive to the Agent's list of drives let mut agent = store.get_resource(&drive_creator_agent)?; @@ -74,6 +153,12 @@ pub fn construct_register_redirect( SubResource::Subject(drive.get_subject().into()), true, )?; + // TODO: Make sure this only works if the server sets the email address. + agent.set( + urls::EMAIL.into(), + Value::String(confirmation.email.to_string()), + store, + )?; agent.save_locally(store)?; // Construct the Redirect Resource, which might provide the Client with a Subject for his Agent. @@ -84,7 +169,5 @@ pub fn construct_register_redirect( crate::Value::AtomicUrl(drive_creator_agent), store, )?; - // The front-end requires the @id to be the same as requested - redirect.set_subject(requested_subject); Ok(redirect) } diff --git a/lib/src/populate.rs b/lib/src/populate.rs index 9282dbdd9..7c570b767 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -154,7 +154,7 @@ pub fn populate_base_models(store: &impl Storelike) -> AtomicResult<()> { } /// Creates a Drive resource at the base URL if no name is passed. -#[tracing::instrument(skip(store), level = "info")] +// #[tracing::instrument(skip(store), level = "info")] pub fn create_drive( store: &impl Storelike, drive_name: Option<&str>, @@ -338,10 +338,12 @@ pub fn populate_endpoints(store: &crate::Db) -> AtomicResult<()> { /// Adds default Endpoints (versioning) to the Db. /// Makes sure they are fetchable pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> { + use crate::urls::IMPORTER; + let base = store .get_self_url() .ok_or("No self URL in this Store - required for populating importer")?; - let mut importer = crate::Resource::new(urls::construct_path_import(&base)); + let mut importer = crate::Resource::new(base.set_path(IMPORTER).to_string()); importer.set_class(urls::IMPORTER); importer.set( urls::PARENT.into(), @@ -359,13 +361,13 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> { pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> { let base = store.get_self_url().ok_or("No self_url")?; let mut drive = store.get_resource(base.as_str())?; - let arr = vec![ + let sidebar_items = vec![ base.set_route(crate::atomic_url::Routes::Setup), base.set_route(crate::atomic_url::Routes::Import), base.set_route(crate::atomic_url::Routes::Collections), ]; - for item in arr { - drive.push(urls::SUBRESOURCES, item.into(), true)?; + for item in sidebar_items { + drive.push(urls::SUBRESOURCES, item.to_string().into(), true)?; } drive.save_locally(store)?; Ok(()) diff --git a/lib/src/store.rs b/lib/src/store.rs index 0f74cb1dc..143c5c7c2 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -1,8 +1,6 @@ //! In-memory store of Atomic data. //! This provides many methods for finding, changing, serializing and parsing Atomic Data. -use url::Url; - use crate::agents::Agent; use crate::storelike::QueryResult; use crate::{atomic_url::AtomicUrl, storelike::Storelike}; @@ -189,11 +187,7 @@ impl Storelike for Store { if let Some(resource) = self.hashmap.lock().unwrap().get(subject) { return Ok(resource.clone()); } - self.handle_not_found( - subject, - "Not found in HashMap.".into(), - self.get_default_agent().ok().as_ref(), - ) + self.handle_not_found(subject, "Not found in HashMap.".into()) } fn remove_resource(&self, subject: &str) -> AtomicResult<()> { diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index b73b6aeae..39d98f937 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -1,5 +1,7 @@ //! The Storelike Trait contains many useful methods for maniupulting / retrieving data. +use tracing::info; + use crate::{ agents::{Agent, ForAgent}, atomic_url::AtomicUrl, @@ -242,6 +244,7 @@ pub trait Storelike: Sized { let self_host = self_url.host().ok_or_else(|| { AtomicError::not_found(format!("Self URL has no host: {}", self_url)) })?; + info!("Comparing hosts: {} and {}", subject_host, self_host); if subject_host == self_host { return Ok(false); } diff --git a/lib/src/token.rs b/lib/src/token.rs new file mode 100644 index 000000000..73f4b0e78 --- /dev/null +++ b/lib/src/token.rs @@ -0,0 +1,79 @@ +//! JWT tokens +//! https://github.com/atomicdata-dev/atomic-data-rust/issues/544 + +use jwt_simple::prelude::*; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::errors::AtomicResult; +use crate::Storelike; + +/// Signs a claim as the Default Agent and creates a JWT token. +pub fn sign_claim( + store: &impl Storelike, + custom_claim: CustomClaims, +) -> AtomicResult { + let key = HS256Key::from_bytes( + store + .get_default_agent()? + .private_key + .ok_or("No private key in default agent, can't sign claims")? + .as_bytes(), + ); + let time = Duration::from_hours(1u64); + let claim = Claims::with_custom_claims(custom_claim, time); + let token = key + .authenticate(claim) + .map_err(|e| format!("fail to create token: {}", e))?; + Ok(token) +} + +/// Parses a JWT token, verifies its hash with the Current Agent and returns the Custom Claims. +pub fn verify_claim( + store: &impl Storelike, + token: &str, +) -> AtomicResult> { + let key = HS256Key::from_bytes( + store + .get_default_agent()? + .private_key + .ok_or("No private key in default agent, can't sign claims")? + .as_bytes(), + ); + let verify_opts = VerificationOptions::default(); + let claims = key + .verify_token(token, Some(verify_opts)) + .map_err(|e| format!("fail to verify token: {}", e))?; + Ok(claims) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug)] + struct CustomClaims { + pub name: String, + pub email: String, + } + + #[test] + fn test_sign_claim_store() { + let store = crate::test_utils::init_store(); + let customclaim = CustomClaims { + name: "John Doe".to_string(), + email: "awdaw@adiow.com".to_string(), + }; + let token = sign_claim(&store, customclaim).unwrap(); + assert!(token.starts_with("ey")); + let claim = verify_claim::(&store, &token).unwrap(); + assert!(claim.expires_at.is_some()); + assert_eq!(claim.custom.email, "awdaw@adiow.com".to_string()); + + let malicous_agent = store.create_agent(None).unwrap(); + store.set_default_agent(malicous_agent); + let wrong_claim = verify_claim::(&store, &token); + assert!(wrong_claim.is_err()); + } +} diff --git a/lib/src/urls.rs b/lib/src/urls.rs index fbcb87836..bbe884ea2 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -56,6 +56,7 @@ pub const LAST_COMMIT: &str = "https://atomicdata.dev/properties/lastCommit"; pub const PUBLIC_KEY: &str = "https://atomicdata.dev/properties/publicKey"; pub const NAME: &str = "https://atomicdata.dev/properties/name"; pub const DRIVES: &str = "https://atomicdata.dev/properties/drives"; +pub const EMAIL: &str = "https://atomicdata.dev/properties/email"; // ... for Collections pub const COLLECTION_PROPERTY: &str = "https://atomicdata.dev/properties/collection/property"; pub const COLLECTION_VALUE: &str = "https://atomicdata.dev/properties/collection/value"; @@ -81,6 +82,7 @@ pub const SEARCH_LIMIT: &str = "https://atomicdata.dev/properties/search/limit"; pub const SEARCH_PROPERTY: &str = "https://atomicdata.dev/properties/search/property"; pub const URL: &str = "https://atomicdata.dev/property/url"; pub const PREVIEW: &str = "https://atomicdata.dev/property/preview"; +pub const TOKEN: &str = "https://atomicdata.dev/property/token"; // ... for Bookmarks pub const IMAGE_URL: &str = "https://atomicdata.dev/properties/imageUrl"; // ... for Hierarchy / Drive @@ -158,8 +160,5 @@ pub const PATH_TPF: &str = "/tpf"; pub const PATH_PATH: &str = "/path"; pub const PATH_COMMITS: &str = "/commits"; pub const PATH_ENDPOINTS: &str = "/endpoints"; - -pub const PATH_IMPORT: &str = "/import"; -pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark"; pub const PATH_QUERY: &str = "/query"; pub const PATH_PRUNE_TESTS: &str = "/prunetests"; diff --git a/server/src/appstate.rs b/server/src/appstate.rs index af9a23060..bf0e24ee0 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -6,6 +6,7 @@ use atomic_lib::{ agents::{generate_public_key, Agent}, atomic_url::Routes, commit::CommitResponse, + email::SmtpConfig, Storelike, }; @@ -29,7 +30,7 @@ pub struct AppState { /// Creates the AppState (the server's context available in Handlers). /// Initializes or opens a store on disk. /// Creates a new agent, if necessary. -pub fn init(config: Config) -> AtomicServerResult { +pub async fn init(config: Config) -> AtomicServerResult { tracing::info!("Initializing AppState"); // We warn over here because tracing needs to be initialized first. @@ -41,8 +42,18 @@ pub fn init(config: Config) -> AtomicServerResult { } tracing::info!("Opening database at {:?}", &config.store_path); + let mut store = atomic_lib::Db::init(&config.store_path, &config.server_url)?; + + if let Some(host) = &config.opts.smpt_host { + store + .set_smtp_config(SmtpConfig { + host: host.clone(), + port: config.opts.smpt_port, + }) + .await?; + }; + let should_init = !&config.store_path.exists() || config.initialize; - let mut store = atomic_lib::Db::init(&config.store_path, config.server_url.clone())?; if should_init { tracing::info!("Initialize: creating and populating new Database..."); atomic_lib::populate::populate_default_store(&store) diff --git a/server/src/bin.rs b/server/src/bin.rs index c804a33f9..d6d040dc3 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -1,4 +1,4 @@ -use atomic_lib::{agents::ForAgent, urls, Storelike}; +use atomic_lib::{agents::ForAgent, atomic_url::Routes, Storelike}; use atomic_server_lib::config::Opts; use std::{fs::File, io::Write}; @@ -48,7 +48,7 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { pt } }; - let appstate = appstate::init(config.clone())?; + let appstate = appstate::init(config.clone()).await?; let outstr = appstate.store.export(!e.only_internal)?; std::fs::create_dir_all(path.parent().unwrap()) .map_err(|e| format!("Failed to create directory {:?}. {}", path, e))?; @@ -64,7 +64,7 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { std::fs::read_to_string(path)? }; - let appstate = appstate::init(config.clone())?; + let appstate = appstate::init(config.clone()).await?; let importer_subject = if let Some(i) = &import_opts.parent { i.into() } else { diff --git a/server/src/handlers/upload.rs b/server/src/handlers/upload.rs index d893e9cc1..e2ebb885a 100644 --- a/server/src/handlers/upload.rs +++ b/server/src/handlers/upload.rs @@ -36,14 +36,8 @@ pub async fn upload_handler( let store = &appstate.store; let parent = store.get_resource(&query.parent)?; let subject = get_subject(&req, &conn, &appstate)?; - if let Some(agent) = get_client_agent(req.headers(), &appstate, subject)? { - check_write(store, &parent, &agent)?; - } else { - return Err(AtomicError::unauthorized( - "No authorization headers present. These are required when uploading files.".into(), - ) - .into()); - } + let for_agent = get_client_agent(req.headers(), &appstate, subject)?; + check_write(store, &parent, &for_agent)?; let mut created_resources: Vec = Vec::new(); let mut commit_responses: Vec = Vec::new(); diff --git a/server/src/helpers.rs b/server/src/helpers.rs index 994de8593..ef3de0a70 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -10,6 +10,7 @@ use atomic_lib::Storelike; use percent_encoding::percent_decode_str; use std::str::FromStr; +use crate::content_types::ContentType; use crate::errors::{AppErrorType, AtomicServerError}; use crate::{appstate::AppState, errors::AtomicServerResult}; diff --git a/server/src/serve.rs b/server/src/serve.rs index 253be727c..ea8a38c5b 100644 --- a/server/src/serve.rs +++ b/server/src/serve.rs @@ -37,7 +37,7 @@ pub async fn serve(config: crate::config::Config) -> AtomicServerResult<()> { let tracing_chrome_flush_guard = crate::trace::init_tracing(&config); // Setup the database and more - let appstate = crate::appstate::init(config.clone())?; + let appstate = crate::appstate::init(config.clone()).await?; // Start async processes if config.opts.rebuild_indexes { diff --git a/server/src/tests.rs b/server/src/tests.rs index 3c784a7ca..31a35b1dd 100644 --- a/server/src/tests.rs +++ b/server/src/tests.rs @@ -51,7 +51,9 @@ async fn server_tests() { // This prevents folder access issues when running concurrent tests config.search_index_path = format!("./.temp/{}/search_index", unique_string).into(); - let appstate = crate::appstate::init(config.clone()).expect("failed init appstate"); + let appstate = crate::appstate::init(config.clone()) + .await + .expect("failed to init appstate"); let data = Data::new(appstate.clone()); let app = test::init_service( App::new() From e82201f862f2f485e00cc19f8792e000a897feb9 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 9 Dec 2022 09:22:53 +0100 Subject: [PATCH 16/45] WIP --- lib/src/atomic_url.rs | 2 +- lib/src/plugins/mod.rs | 1 + lib/src/plugins/register.rs | 19 +++++---- lib/src/plugins/reset_pubkey.rs | 70 +++++++++++++++++++++++++++++++++ lib/src/storelike.rs | 17 +++++++- lib/src/urls.rs | 4 ++ server/src/handlers/commit.rs | 2 +- 7 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 lib/src/plugins/reset_pubkey.rs diff --git a/lib/src/atomic_url.rs b/lib/src/atomic_url.rs index f6c7d84da..5afc0b32e 100644 --- a/lib/src/atomic_url.rs +++ b/lib/src/atomic_url.rs @@ -112,7 +112,7 @@ impl AtomicUrl { } /// Removes existing path, sets the new one. Escapes special characters - pub fn set_path(&mut self, path: &str) -> &Self { + pub fn set_path(mut self, path: &str) -> Self { self.url.set_path(path); self } diff --git a/lib/src/plugins/mod.rs b/lib/src/plugins/mod.rs index 569b4989b..8703d1448 100644 --- a/lib/src/plugins/mod.rs +++ b/lib/src/plugins/mod.rs @@ -46,5 +46,6 @@ pub mod path; pub mod prunetests; pub mod query; pub mod register; +pub mod reset_pubkey; pub mod search; pub mod versioning; diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index 945135f27..57a940a2c 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -14,7 +14,7 @@ use crate::{ pub fn register_endpoint() -> Endpoint { Endpoint { - path: "/register".to_string(), + path: urls::PATH_REGISTER.to_string(), params: [ urls::NAME.to_string(), urls::EMAIL.to_string(), @@ -28,7 +28,7 @@ pub fn register_endpoint() -> Endpoint { pub fn confirm_email_endpoint() -> Endpoint { Endpoint { - path: "/confirmEmail".to_string(), + path: urls::PATH_CONFIRM_EMAIL.to_string(), params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), description: "Confirm email address and set a key for your Agent.".to_string(), shortname: "confirm-email".to_string(), @@ -110,6 +110,7 @@ pub fn construct_confirm_email_redirect(context: HandleGetContext) -> AtomicResu let mut token_opt: Option = None; let mut pubkey_option = None; + println!("url: {:?}", url); for (k, v) in url.query_pairs() { match k.as_ref() { "token" | urls::TOKEN => token_opt = Some(v.to_string()), @@ -131,11 +132,15 @@ pub fn construct_confirm_email_redirect(context: HandleGetContext) -> AtomicResu // Note: this happens before the drive is saved, which checks if the name is available. // We get new agents that just do nothing, but perhaps that's not a problem. let drive_creator_agent: String = { - let mut new = Agent::new_from_public_key(store, &pubkey)?; - new.name = Some(confirmation.name.clone()); - let net_agent_subject = new.subject.to_string(); - new.to_resource()?.save(store)?; - net_agent_subject + let mut new_agent = Agent::new_from_public_key(store, &pubkey)?; + new_agent.name = Some(confirmation.name.clone()); + let net_agent_subject = store + .get_server_url() + .clone() + .set_path(&format!("agents/{}", confirmation.name)); + new_agent.subject = net_agent_subject.to_string(); + new_agent.to_resource()?.save(store)?; + net_agent_subject.to_string() }; // Create the new Drive diff --git a/lib/src/plugins/reset_pubkey.rs b/lib/src/plugins/reset_pubkey.rs new file mode 100644 index 000000000..55ae3fad6 --- /dev/null +++ b/lib/src/plugins/reset_pubkey.rs @@ -0,0 +1,70 @@ +/*! +Reset email +*/ + +use crate::{endpoints::Endpoint, errors::AtomicResult, urls, Db, Resource}; + +pub fn request_email_pubkey_reset() -> Endpoint { + Endpoint { + path: urls::PATH_RESET_PUBKEY.to_string(), + params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), + description: "Requests an email to set a new PublicKey to an Agent.".to_string(), + shortname: "request-pubkey-reset".to_string(), + handle: Some(construct_reset_pubkey), + } +} + +pub fn confirm_pubkey_reset() -> Endpoint { + Endpoint { + path: urls::PATH_CONFIRM_RESET.to_string(), + params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), + description: "Requests an email to set a new PublicKey to an Agent.".to_string(), + shortname: "request-pubkey-reset".to_string(), + handle: Some(construct_confirm_reset_pubkey), + } +} + +#[tracing::instrument(skip(store))] +pub fn construct_confirm_reset_pubkey( + url: url::Url, + store: &Db, + for_agent: Option<&str>, +) -> AtomicResult { + let mut token_opt: Option = None; + let mut pubkey_option = None; + + println!("url: {:?}", url); + for (k, v) in url.query_pairs() { + match k.as_ref() { + "token" | urls::TOKEN => token_opt = Some(v.to_string()), + "public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()), + _ => {} + } + } + let Some(token) = token_opt else { + return confirm_pubkey_reset().to_resource(store); + }; + let pubkey = pubkey_option.ok_or("No public-key provided")?; + + // Parse and verify the JWT token + let confirmation = crate::token::verify_claim::(store, &token)?.custom; + + // Add the drive to the Agent's list of drives + let mut agent = store.get_resource(&drive_creator_agent)?; + agent.push_propval( + urls::USED_PUBKEYS.into(), + SubResource::Subject(drive.get_subject().into()), + true, + )?; + agent.save_locally(store)?; + + // Construct the Redirect Resource, which might provide the Client with a Subject for his Agent. + let mut redirect = Resource::new_instance(urls::REDIRECT, store)?; + redirect.set_propval_string(urls::DESTINATION.into(), drive.get_subject(), store)?; + redirect.set_propval( + urls::REDIRECT_AGENT.into(), + crate::Value::AtomicUrl(drive_creator_agent), + store, + )?; + Ok(redirect) +} diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index 39d98f937..8db37248c 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -236,6 +236,7 @@ pub trait Storelike: Sized { if subject.starts_with(self_url.as_str()) { return Ok(false); } else { + // Is it a subdomain of the self_url? let subject_url = url::Url::parse(subject)?; let subject_host = subject_url.host().ok_or_else(|| { AtomicError::not_found(format!("Subject URL has no host: {}", subject)) @@ -244,8 +245,20 @@ pub trait Storelike: Sized { let self_host = self_url.host().ok_or_else(|| { AtomicError::not_found(format!("Self URL has no host: {}", self_url)) })?; - info!("Comparing hosts: {} and {}", subject_host, self_host); - if subject_host == self_host { + // remove the subdomain from subject, if any. + // The server can have multiple subdomains + let subject_host_string = subject_host.to_string(); + let subject_host_parts = subject_host_string.split('.').collect::>(); + + // Check if the last part of the host is equal + let Some(subject_host_stripped) = subject_host_parts.last() else { + return Ok(false) + }; + info!( + "Comparing hosts: {} and {}", + subject_host_stripped, self_host + ); + if subject_host_stripped == &self_host.to_string() { return Ok(false); } } diff --git a/lib/src/urls.rs b/lib/src/urls.rs index bbe884ea2..19ebb7a68 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -160,5 +160,9 @@ pub const PATH_TPF: &str = "/tpf"; pub const PATH_PATH: &str = "/path"; pub const PATH_COMMITS: &str = "/commits"; pub const PATH_ENDPOINTS: &str = "/endpoints"; +pub const PATH_REGISTER: &str = "/register"; +pub const PATH_CONFIRM_EMAIL: &str = "/confirm-email"; +pub const PATH_RESET_PUBKEY: &str = "/reset-public-key"; +pub const PATH_CONFIRM_RESET: &str = "/confirm-reset-public-key"; pub const PATH_QUERY: &str = "/query"; pub const PATH_PRUNE_TESTS: &str = "/prunetests"; diff --git a/server/src/handlers/commit.rs b/server/src/handlers/commit.rs index 61c094ecf..201bf141e 100644 --- a/server/src/handlers/commit.rs +++ b/server/src/handlers/commit.rs @@ -19,7 +19,7 @@ pub async fn post_commit( let mut builder = HttpResponse::Ok(); let incoming_commit_resource = parse_json_ad_commit_resource(&body, store)?; let incoming_commit = Commit::from_resource(incoming_commit_resource)?; - if store.is_external_subject(&incoming_commit.subject)? { +if store.is_external_subject(&incoming_commit.subject)? { return Err("Subject of commit is external, and should be sent to its origin domain. This store can not own this resource. See https://github.com/atomicdata-dev/atomic-data-rust/issues/509".into()); } let opts = CommitOpts { From 79782dd5207845c45a0e1f5f2b705318f9aa7efd Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Thu, 15 Dec 2022 21:09:08 +0100 Subject: [PATCH 17/45] Public key reset endpoint WIP --- lib/src/agents.rs | 37 ++++++++- lib/src/collections.rs | 6 +- lib/src/db.rs | 9 +-- lib/src/db/query_index.rs | 8 +- lib/src/email.rs | 2 +- lib/src/hierarchy.rs | 2 +- lib/src/lib.rs | 4 +- lib/src/plugins/chatroom.rs | 3 +- lib/src/plugins/mod.rs | 3 + lib/src/plugins/prunetests.rs | 3 +- lib/src/plugins/register.rs | 24 +++--- lib/src/plugins/reset_pubkey.rs | 138 +++++++++++++++++++++++--------- lib/src/plugins/utils.rs | 10 +++ lib/src/plugins/versioning.rs | 3 +- lib/src/populate.rs | 5 +- lib/src/query.rs | 75 +++++++++++++++++ lib/src/resources.rs | 3 +- lib/src/store.rs | 12 +-- lib/src/storelike.rs | 86 ++------------------ lib/src/urls.rs | 7 ++ 20 files changed, 271 insertions(+), 169 deletions(-) create mode 100644 lib/src/plugins/utils.rs create mode 100644 lib/src/query.rs diff --git a/lib/src/agents.rs b/lib/src/agents.rs index 927343162..a353e4b9d 100644 --- a/lib/src/agents.rs +++ b/lib/src/agents.rs @@ -5,7 +5,7 @@ use base64::{engine::general_purpose, Engine}; use serde_json::from_slice; -use crate::{errors::AtomicResult, urls, Resource, Storelike, Value}; +use crate::{errors::AtomicResult, urls, Query, Resource, Storelike, Value}; /// None represents no right checks will be performed, effectively SUDO mode. #[derive(Clone, Debug, PartialEq)] @@ -81,6 +81,41 @@ impl Agent { Ok(resource) } + pub fn from_email(email: &str, store: &impl Storelike) -> AtomicResult { + let mut query = Query::new(); + query.property = Some(urls::EMAIL.into()); + query.value = Some(Value::String(email.to_string())); + let response = store.query(&query)?; + if response.resources.is_empty() { + return Err(format!("Agent with Email {} not found", email).into()); + } + if response.resources.len() > 1 { + return Err(format!( + "Email {} is not unique, {} agents have this email", + email, response.count + ) + .into()); + } + let resource = response.resources.first().unwrap(); + Agent::from_resource(resource.clone()) + } + + pub fn from_resource(resource: Resource) -> AtomicResult { + let name = if let Ok(name) = resource.get(urls::NAME) { + Some(name.to_string()) + } else { + None + }; + + return Ok(Self { + created_at: resource.get(urls::CREATED_AT)?.to_int()?, + name, + public_key: resource.get(urls::PUBLIC_KEY)?.to_string(), + private_key: None, + subject: resource.get_subject().into(), + }); + } + /// Creates a new Agent, generates a new Keypair. pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult { let keypair = generate_keypair()?; diff --git a/lib/src/collections.rs b/lib/src/collections.rs index 0e767748a..74783b6a1 100644 --- a/lib/src/collections.rs +++ b/lib/src/collections.rs @@ -1,10 +1,8 @@ //! Collections are dynamic resources that refer to multiple resources. //! They are constructed using a [Query] use crate::{ - agents::ForAgent, - errors::AtomicResult, - storelike::{Query, ResourceCollection}, - urls, Resource, Storelike, Value, + agents::ForAgent, errors::AtomicResult, storelike::ResourceCollection, urls, Query, Resource, + Storelike, Value, }; const DEFAULT_PAGE_SIZE: usize = 30; diff --git a/lib/src/db.rs b/lib/src/db.rs index 6f5963324..0c4e1692a 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -27,10 +27,11 @@ use crate::{ email::{self, MailMessage}, endpoints::{default_endpoints, Endpoint, HandleGetContext}, errors::{AtomicError, AtomicResult}, + query::QueryResult, resources::PropVals, - storelike::{Query, QueryResult, Storelike}, + storelike::Storelike, values::SortableValue, - Atom, Resource, + Atom, Query, Resource, }; use self::{ @@ -254,10 +255,6 @@ impl Db { } fn query_basic(&self, q: &Query) -> AtomicResult { - let self_url = self - .get_self_url() - .ok_or("No self_url set, required for Queries")?; - let mut subjects: Vec = vec![]; let mut resources: Vec = vec![]; let mut total_count = 0; diff --git a/lib/src/db/query_index.rs b/lib/src/db/query_index.rs index 0e5af9e8f..fd931b02d 100644 --- a/lib/src/db/query_index.rs +++ b/lib/src/db/query_index.rs @@ -2,8 +2,8 @@ //! It relies on lexicographic ordering of keys, which Sled utilizes using `scan_prefix` queries. use crate::{ - agents::ForAgent, atoms::IndexAtom, errors::AtomicResult, storelike::Query, - values::SortableValue, Atom, Db, Resource, Storelike, Value, + agents::ForAgent, atoms::IndexAtom, errors::AtomicResult, values::SortableValue, Atom, Db, + Query, Resource, Storelike, Value, }; use serde::{Deserialize, Serialize}; @@ -99,10 +99,6 @@ pub fn query_sorted_indexed( let mut resources: Vec = vec![]; let mut count = 0; - let self_url = store - .get_self_url() - .ok_or("No self_url set, required for Queries")?; - let limit = q.limit.unwrap_or(std::usize::MAX); for (i, kv) in iter.enumerate() { diff --git a/lib/src/email.rs b/lib/src/email.rs index 764fd1b67..aa2d03149 100644 --- a/lib/src/email.rs +++ b/lib/src/email.rs @@ -1,6 +1,6 @@ //! [EmailAddress] with validation, [MailMessage] with sending, and [get_smtp_client] for setting up mail. -use crate::{errors::AtomicResult, storelike::Query, urls, Storelike}; +use crate::{errors::AtomicResult, urls, Query, Storelike}; use mail_send::{mail_builder::MessageBuilder, Connected, Transport}; use serde::{Deserialize, Serialize}; use tracing::info; diff --git a/lib/src/hierarchy.rs b/lib/src/hierarchy.rs index 0eb1f92d6..42a3b733c 100644 --- a/lib/src/hierarchy.rs +++ b/lib/src/hierarchy.rs @@ -4,7 +4,7 @@ use core::fmt; -use crate::{agents::ForAgent, errors::AtomicResult, storelike::Query, urls, Resource, Storelike}; +use crate::{agents::ForAgent, errors::AtomicResult, urls, Query, Resource, Storelike}; #[derive(Debug)] pub enum Right { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 913c011c6..d6b3c38f3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -16,7 +16,7 @@ See the [Atomic Data Docs](https://docs.atomicdata.dev) for more information. - [Value] converts Atomic Data to Rust native types - [Commit]s (transactions / delta's / changes / updates / versioning / history). Supports many checks, such as Schema, Authorization and more. - [collections] (pagination, sorting, filtering) -- Queries (see [storelike::Query]) +- Queries (see [query::Query]) - [hierarchy] for authorization - [crate::endpoints::Endpoint] for custom API endpoints - [config::Config] files. (requires `config` feature) @@ -87,6 +87,7 @@ pub mod parse; #[cfg(feature = "db")] pub mod plugins; pub mod populate; +pub mod query; pub mod resources; pub mod schema; pub mod serialize; @@ -108,6 +109,7 @@ pub use commit::Commit; pub use db::Db; pub use errors::AtomicError; pub use errors::AtomicErrorType; +pub use query::Query; pub use resources::Resource; pub use store::Store; pub use storelike::Storelike; diff --git a/lib/src/plugins/chatroom.rs b/lib/src/plugins/chatroom.rs index 962fbede4..1e4470da2 100644 --- a/lib/src/plugins/chatroom.rs +++ b/lib/src/plugins/chatroom.rs @@ -8,9 +8,8 @@ use crate::{ agents::ForAgent, commit::{CommitBuilder, CommitResponse}, errors::AtomicResult, - storelike::Query, urls::{self, PARENT}, - utils, Resource, Storelike, Value, + utils, Query, Resource, Storelike, Value, }; // Find the messages for the ChatRoom diff --git a/lib/src/plugins/mod.rs b/lib/src/plugins/mod.rs index 8703d1448..680f11c08 100644 --- a/lib/src/plugins/mod.rs +++ b/lib/src/plugins/mod.rs @@ -49,3 +49,6 @@ pub mod register; pub mod reset_pubkey; pub mod search; pub mod versioning; + +// Utilities / helpers +mod utils; diff --git a/lib/src/plugins/prunetests.rs b/lib/src/plugins/prunetests.rs index c167d854c..7a4a4f906 100644 --- a/lib/src/plugins/prunetests.rs +++ b/lib/src/plugins/prunetests.rs @@ -1,8 +1,7 @@ use crate::{ endpoints::{Endpoint, HandleGetContext, HandlePostContext}, errors::AtomicResult, - storelike::Query, - urls, Resource, Storelike, Value, + urls, Query, Resource, Storelike, Value, }; pub fn prune_tests_endpoint() -> Endpoint { diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index 57a940a2c..403f4ae47 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -12,6 +12,8 @@ use crate::{ Resource, Storelike, Value, }; +use super::utils::return_success; + pub fn register_endpoint() -> Endpoint { Endpoint { path: urls::PATH_REGISTER.to_string(), @@ -21,8 +23,8 @@ pub fn register_endpoint() -> Endpoint { ].into(), description: "Allows new users to easily, in one request, make both an Agent and a Drive. This drive will be created at the subdomain of `name`.".to_string(), shortname: "register".to_string(), - handle: Some(construct_register_redirect), handle_post: None, + handle: Some(handle_register_name_and_email), } } @@ -43,12 +45,17 @@ struct MailConfirmation { pub name: String, } -#[tracing::instrument()] -pub fn construct_register_redirect(context: HandleGetContext) -> AtomicResult { +#[tracing::instrument] +pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult { + let HandleGetContext { + subject, + store: _, + for_agent: _, + } = context; let mut name_option = None; let mut email_option: Option = None; let store = context.store; - for (k, v) in context.subject.query_pairs() { + for (k, v) in subject.query_pairs() { match k.as_ref() { "name" | urls::NAME => name_option = Some(v.to_string()), "email" => email_option = Some(EmailAddress::new(v.to_string())?), @@ -93,14 +100,7 @@ pub fn construct_register_redirect(context: HandleGetContext) -> AtomicResult Endpoint { +use crate::{ + agents::Agent, + email::{EmailAddress, MailAction, MailMessage}, + endpoints::{Endpoint, HandleGetContext}, + errors::AtomicResult, + plugins::utils::return_success, + urls, Resource, Storelike, +}; + +pub fn request_email_add_pubkey() -> Endpoint { Endpoint { - path: urls::PATH_RESET_PUBKEY.to_string(), + path: urls::PATH_ADD_PUBKEY.to_string(), params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), - description: "Requests an email to set a new PublicKey to an Agent.".to_string(), - shortname: "request-pubkey-reset".to_string(), - handle: Some(construct_reset_pubkey), + description: "Requests an email to add a new PublicKey to an Agent.".to_string(), + shortname: "pubkey-add".to_string(), + handle: Some(handle_request_email_pubkey), + handle_post: None, } } -pub fn confirm_pubkey_reset() -> Endpoint { +pub fn confirm_add_pubkey() -> Endpoint { Endpoint { - path: urls::PATH_CONFIRM_RESET.to_string(), + path: urls::PATH_CONFIRM_PUBKEY.to_string(), params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), - description: "Requests an email to set a new PublicKey to an Agent.".to_string(), + description: "Confirms a token to add a new Public Key.".to_string(), shortname: "request-pubkey-reset".to_string(), - handle: Some(construct_confirm_reset_pubkey), + handle: Some(handle_confirm_add_pubkey), + handle_post: None, + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct AddPubkeyToken { + agent: String, +} + +pub fn handle_request_email_pubkey(context: HandleGetContext) -> AtomicResult { + let HandleGetContext { + subject, + store, + for_agent: _, + } = context; + let mut email_option: Option = None; + for (k, v) in subject.query_pairs() { + match k.as_ref() { + "email" => email_option = Some(EmailAddress::new(v.to_string())?), + _ => {} + } } + // by default just return the Endpoint + let Some(email) = email_option else { + return request_email_add_pubkey().to_resource(store); + }; + + // Find the agent by their email + let agent = Agent::from_email(&email.to_string(), store)?; + + // send the user an e-mail to confirm sign up + let store_clone = store.clone(); + let confirmation_token_struct = AddPubkeyToken { + agent: agent.subject, + }; + let token = crate::token::sign_claim(store, confirmation_token_struct)?; + let mut confirm_url = store + .get_server_url() + .clone() + .set_path(urls::PATH_CONFIRM_PUBKEY) + .url(); + confirm_url.set_query(Some(&format!("token={}", token))); + let message = MailMessage { + to: email, + subject: "Add a new Passphrase to your account".to_string(), + body: "You've requested adding a new Passphrase. Click the link below to do so!" + .to_string(), + action: Some(MailAction { + name: "Add new Passphrase to account".to_string(), + url: confirm_url.into(), + }), + }; + // async, because mails are slow + tokio::spawn(async move { + store_clone + .send_email(message) + .await + .unwrap_or_else(|e| tracing::error!("Error sending email: {}", e)); + }); + + return_success() } -#[tracing::instrument(skip(store))] -pub fn construct_confirm_reset_pubkey( - url: url::Url, - store: &Db, - for_agent: Option<&str>, -) -> AtomicResult { +#[tracing::instrument] +pub fn handle_confirm_add_pubkey(context: HandleGetContext) -> AtomicResult { + let HandleGetContext { + subject, + store, + for_agent: _, + } = context; let mut token_opt: Option = None; let mut pubkey_option = None; - println!("url: {:?}", url); - for (k, v) in url.query_pairs() { + for (k, v) in subject.query_pairs() { match k.as_ref() { "token" | urls::TOKEN => token_opt = Some(v.to_string()), "public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()), _ => {} } } + let pubkey = pubkey_option.ok_or("No public-key provided")?; + let Some(token) = token_opt else { - return confirm_pubkey_reset().to_resource(store); + return confirm_add_pubkey().to_resource(store); }; - let pubkey = pubkey_option.ok_or("No public-key provided")?; // Parse and verify the JWT token - let confirmation = crate::token::verify_claim::(store, &token)?.custom; + let confirmation = crate::token::verify_claim::(store, &token)?.custom; - // Add the drive to the Agent's list of drives - let mut agent = store.get_resource(&drive_creator_agent)?; - agent.push_propval( - urls::USED_PUBKEYS.into(), - SubResource::Subject(drive.get_subject().into()), - true, - )?; - agent.save_locally(store)?; + // Add the key to the agent + let mut agent = store.get_resource(&confirmation.agent)?; + agent.push(urls::ACTIVE_KEYS, pubkey.into(), true)?; + agent.save(store)?; - // Construct the Redirect Resource, which might provide the Client with a Subject for his Agent. - let mut redirect = Resource::new_instance(urls::REDIRECT, store)?; - redirect.set_propval_string(urls::DESTINATION.into(), drive.get_subject(), store)?; - redirect.set_propval( - urls::REDIRECT_AGENT.into(), - crate::Value::AtomicUrl(drive_creator_agent), - store, - )?; - Ok(redirect) + return_success() } diff --git a/lib/src/plugins/utils.rs b/lib/src/plugins/utils.rs new file mode 100644 index 000000000..642bbb9af --- /dev/null +++ b/lib/src/plugins/utils.rs @@ -0,0 +1,10 @@ +//! Functions that can be valuable in multiple plugins + +use crate::{errors::AtomicResult, urls, Resource, Value}; + +/// Returns a Resource with a description of "success" +pub fn return_success() -> AtomicResult { + let mut resource = Resource::new("unknown".into()); + resource.set_unsafe(urls::DESCRIPTION.into(), Value::String("success".into())); + Ok(resource) +} diff --git a/lib/src/plugins/versioning.rs b/lib/src/plugins/versioning.rs index a8eedd558..d74848d33 100644 --- a/lib/src/plugins/versioning.rs +++ b/lib/src/plugins/versioning.rs @@ -5,8 +5,7 @@ use crate::{ collections::CollectionBuilder, endpoints::{Endpoint, HandleGetContext}, errors::AtomicResult, - storelike::Query, - urls, AtomicError, Commit, Resource, Storelike, + urls, AtomicError, Commit, Query, Resource, Storelike, }; pub fn version_endpoint() -> Endpoint { diff --git a/lib/src/populate.rs b/lib/src/populate.rs index 7c570b767..a38f3033a 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -8,8 +8,7 @@ use crate::{ errors::AtomicResult, parse::ParseOpts, schema::{Class, Property}, - storelike::Query, - urls, Resource, Storelike, Value, + urls, Query, Resource, Storelike, Value, }; const DEFAULT_ONTOLOGY_PATH: &str = "defaultOntology"; @@ -307,7 +306,7 @@ pub fn populate_collections(store: &impl Storelike) -> AtomicResult<()> { for subject in result.subjects { let mut collection = - crate::collections::create_collection_resource_for_class(store, &subject)?; + crate::collections::create_collection_resource_for_class(store, &subject.to_string())?; collection.save_locally(store)?; } diff --git a/lib/src/query.rs b/lib/src/query.rs new file mode 100644 index 000000000..4607f2b83 --- /dev/null +++ b/lib/src/query.rs @@ -0,0 +1,75 @@ +use crate::{agents::ForAgent, urls, Resource, Value}; + +/// Use this to construct a list of Resources +#[derive(Debug)] +pub struct Query { + /// Filter by Property + pub property: Option, + /// Filter by Value + pub value: Option, + /// Maximum of items to return + pub limit: Option, + /// Value at which to begin lexicographically sorting things. + pub start_val: Option, + /// Value at which to stop lexicographically sorting things. + pub end_val: Option, + /// How many items to skip from the first one + pub offset: usize, + /// The Property URL that is used to sort the results + pub sort_by: Option, + /// Sort descending instead of ascending. + pub sort_desc: bool, + /// Whether to include non-server resources + pub include_external: bool, + /// Whether to include full Resources in the result, if not, will add empty vector here. + pub include_nested: bool, + /// For which Agent the query is executed. Pass `None` if you want to skip permission checks. + pub for_agent: ForAgent, +} + +impl Query { + pub fn new() -> Self { + Query { + property: None, + value: None, + limit: None, + start_val: None, + end_val: None, + offset: 0, + sort_by: None, + sort_desc: false, + include_external: false, + include_nested: true, + for_agent: ForAgent::Sudo, + } + } + + /// Search for a property-value combination + pub fn new_prop_val(prop: &str, val: &str) -> Self { + let mut q = Self::new(); + q.property = Some(prop.to_string()); + q.value = Some(Value::String(val.to_string())); + q + } + + /// Search for instances of some Class + pub fn new_class(class: &str) -> Self { + let mut q = Self::new(); + q.property = Some(urls::IS_A.into()); + q.value = Some(Value::AtomicUrl(class.to_string())); + q + } +} + +impl Default for Query { + fn default() -> Self { + Self::new() + } +} + +pub struct QueryResult { + pub subjects: Vec, + pub resources: Vec, + /// The amount of hits that were found, including the ones that were out of bounds or not authorized. + pub count: usize, +} diff --git a/lib/src/resources.rs b/lib/src/resources.rs index 71d58dd0e..ba6ea4ba5 100644 --- a/lib/src/resources.rs +++ b/lib/src/resources.rs @@ -2,8 +2,6 @@ //! Has methods for saving resources and getting properties inside them. use crate::commit::{CommitOpts, CommitResponse}; -use crate::storelike::Query; -use crate::urls; use crate::utils::random_string; use crate::values::{SubResource, Value}; use crate::{commit::CommitBuilder, errors::AtomicResult}; @@ -12,6 +10,7 @@ use crate::{ schema::{Class, Property}, Atom, Storelike, }; +use crate::{urls, Query}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tracing::instrument; diff --git a/lib/src/store.rs b/lib/src/store.rs index 143c5c7c2..89310e3b0 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -2,10 +2,10 @@ //! This provides many methods for finding, changing, serializing and parsing Atomic Data. use crate::agents::Agent; -use crate::storelike::QueryResult; +use crate::query::QueryResult; use crate::{atomic_url::AtomicUrl, storelike::Storelike}; use crate::{errors::AtomicResult, Resource}; -use crate::{Atom, Value}; +use crate::{Atom, Query, Value}; use std::{collections::HashMap, sync::Arc, sync::Mutex}; /// The in-memory store of data, containing the Resources, Properties and Classes @@ -17,12 +17,8 @@ pub struct Store { default_agent: Arc>>, } -/// The URL used for stores that are not accessible on the web. -// I'd prefer this to a non-HTTP URI, but that causes parsing issues when we combine it with some paths (at least with Commits) -pub const LOCAL_STORE_URL_STR: &str = "http://noresolve.localhost"; - lazy_static::lazy_static! { - static ref LOCAL_STORE_URL: AtomicUrl = AtomicUrl::try_from(LOCAL_STORE_URL_STR).unwrap(); + static ref LOCAL_STORE_URL: AtomicUrl = AtomicUrl::try_from(crate::urls::LOCAL_STORE).unwrap(); } impl Store { @@ -206,7 +202,7 @@ impl Storelike for Store { self.default_agent.lock().unwrap().replace(agent); } - fn query(&self, q: &crate::storelike::Query) -> AtomicResult { + fn query(&self, q: &Query) -> AtomicResult { let atoms = self.tpf( None, q.property.as_deref(), diff --git a/lib/src/storelike.rs b/lib/src/storelike.rs index 8db37248c..d2ff18c29 100644 --- a/lib/src/storelike.rs +++ b/lib/src/storelike.rs @@ -1,4 +1,5 @@ -//! The Storelike Trait contains many useful methods for maniupulting / retrieving data. +//! The Storelike Trait contains many useful methods for manipulating / retrieving data. +//! It is the basis for both the in-memory [Store] and the on-disk [Db] use tracing::info; @@ -8,9 +9,10 @@ use crate::{ commit::CommitResponse, errors::AtomicError, hierarchy, + query::QueryResult, schema::{Class, Property}, - store::LOCAL_STORE_URL_STR, - urls, + urls::LOCAL_STORE, + Query, }; use crate::{errors::AtomicResult, parse::parse_json_ad_string}; use crate::{mapping::Mapping, values::Value, Atom, Resource}; @@ -230,7 +232,7 @@ pub trait Storelike: Sized { /// the answer should always be `true`. fn is_external_subject(&self, subject: &str) -> AtomicResult { if let Some(self_url) = self.get_self_url() { - if self_url.as_str() == LOCAL_STORE_URL_STR { + if self_url.as_str() == LOCAL_STORE { return Ok(true); } if subject.starts_with(self_url.as_str()) { @@ -252,7 +254,7 @@ pub trait Storelike: Sized { // Check if the last part of the host is equal let Some(subject_host_stripped) = subject_host_parts.last() else { - return Ok(false) + return Ok(false); }; info!( "Comparing hosts: {} and {}", @@ -394,77 +396,3 @@ pub trait Storelike: Sized { crate::validate::validate_store(self, false) } } - -/// Use this to construct a list of Resources -#[derive(Debug)] -pub struct Query { - /// Filter by Property - pub property: Option, - /// Filter by Value - pub value: Option, - /// Maximum of items to return, if none returns all items. - pub limit: Option, - /// Value at which to begin lexicographically sorting things. - pub start_val: Option, - /// Value at which to stop lexicographically sorting things. - pub end_val: Option, - /// How many items to skip from the first one - pub offset: usize, - /// The Property URL that is used to sort the results - pub sort_by: Option, - /// Sort descending instead of ascending. - pub sort_desc: bool, - /// Whether to include non-server resources - pub include_external: bool, - /// Whether to include full Resources in the result, if not, will add empty vector here. - pub include_nested: bool, - /// For which Agent the query is executed. Pass `None` if you want to skip permission checks. - pub for_agent: ForAgent, -} - -impl Query { - pub fn new() -> Self { - Query { - property: None, - value: None, - limit: None, - start_val: None, - end_val: None, - offset: 0, - sort_by: None, - sort_desc: false, - include_external: false, - include_nested: true, - for_agent: ForAgent::Sudo, - } - } - - /// Search for a property-value combination - pub fn new_prop_val(prop: &str, val: &str) -> Self { - let mut q = Self::new(); - q.property = Some(prop.to_string()); - q.value = Some(Value::String(val.to_string())); - q - } - - /// Search for instances of some Class - pub fn new_class(class: &str) -> Self { - let mut q = Self::new(); - q.property = Some(urls::IS_A.into()); - q.value = Some(Value::AtomicUrl(class.to_string())); - q - } -} - -impl Default for Query { - fn default() -> Self { - Self::new() - } -} - -pub struct QueryResult { - pub subjects: Vec, - pub resources: Vec, - /// The amount of hits that were found, including the ones that were out of bounds or not authorized. - pub count: usize, -} diff --git a/lib/src/urls.rs b/lib/src/urls.rs index 19ebb7a68..8671e5057 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -54,6 +54,7 @@ pub const PREVIOUS_COMMIT: &str = "https://atomicdata.dev/properties/previousCom pub const LAST_COMMIT: &str = "https://atomicdata.dev/properties/lastCommit"; // ... for Agents pub const PUBLIC_KEY: &str = "https://atomicdata.dev/properties/publicKey"; +pub const ACTIVE_KEYS: &str = "https://atomicdata.dev/properties/activeKeys"; pub const NAME: &str = "https://atomicdata.dev/properties/name"; pub const DRIVES: &str = "https://atomicdata.dev/properties/drives"; pub const EMAIL: &str = "https://atomicdata.dev/properties/email"; @@ -153,6 +154,10 @@ pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent"; // We don't want a user to actually control this URL. pub const SUDO_AGENT: &str = "sudo:agent"; +/// The URL used for stores that are not accessible on the web. +// I'd prefer this to a non-HTTP URI, but that causes parsing issues when we combine it with some paths (at least with Commits) +pub const LOCAL_STORE: &str = "http://noresolve.localhost"; + // Paths pub const PATH_IMPORT: &str = "/import"; pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark"; @@ -163,6 +168,8 @@ pub const PATH_ENDPOINTS: &str = "/endpoints"; pub const PATH_REGISTER: &str = "/register"; pub const PATH_CONFIRM_EMAIL: &str = "/confirm-email"; pub const PATH_RESET_PUBKEY: &str = "/reset-public-key"; +pub const PATH_CONFIRM_PUBKEY: &str = "/confirm-public-key"; +pub const PATH_ADD_PUBKEY: &str = "/add-public-key"; pub const PATH_CONFIRM_RESET: &str = "/confirm-reset-public-key"; pub const PATH_QUERY: &str = "/query"; pub const PATH_PRUNE_TESTS: &str = "/prunetests"; From b6c650f2ab9db5c12d1c70a7943f3da869e9f0cf Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 27 Dec 2022 13:19:57 +0100 Subject: [PATCH 18/45] #502 fix path confirm email --- lib/src/plugins/register.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index 403f4ae47..bbff901ef 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -80,7 +80,7 @@ pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult let mut confirm_url = store .get_server_url() .clone() - .set_path("confirmEmail") + .set_path(urls::PATH_CONFIRM_EMAIL) .url(); confirm_url.set_query(Some(&format!("token={}", token))); let message = MailMessage { From 9e4694d441f98c58ec40d7dc479213ab4aa10145 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sat, 31 Dec 2022 14:10:35 +0100 Subject: [PATCH 19/45] Check uniqueness username earlier #489 --- lib/src/plugins/register.rs | 32 ++++++++++++++++++++++++++++++-- server/src/helpers.rs | 1 + 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index bbff901ef..846fbfedb 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -45,6 +45,33 @@ struct MailConfirmation { pub name: String, } +#[derive(Debug, Clone)] +struct UserName { + pub name: String, +} + +impl UserName { + /// Throws error if email address is already taken + pub fn check_used(name: &str, store: &impl Storelike) -> AtomicResult { + let mut drive_url = store + .get_self_url() + .ok_or("No self url, cant check name")? + .clone(); + drive_url.set_subdomain(Some(name))?; + + match store.get_resource(&drive_url.to_string()) { + Ok(_) => Err("Name already used".into()), + Err(_) => Ok(Self { name: name.into() }), + } + } +} + +impl std::fmt::Display for UserName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + #[tracing::instrument] pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult { let HandleGetContext { @@ -57,7 +84,7 @@ pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult let store = context.store; for (k, v) in subject.query_pairs() { match k.as_ref() { - "name" | urls::NAME => name_option = Some(v.to_string()), + "name" | urls::NAME => name_option = Some(UserName::check_used(&v, store)?), "email" => email_option = Some(EmailAddress::new(v.to_string())?), _ => {} } @@ -68,13 +95,14 @@ pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult }; let name = name_option.ok_or("No name provided")?; + let _validate_name = Value::new(&name.to_string(), &crate::datatype::DataType::Slug)?; let email = email_option.ok_or("No email provided")?.check_used(store)?; // send the user an e-mail to confirm sign up let store_clone = store.clone(); let confirmation_token_struct = MailConfirmation { email: email.clone(), - name: name.clone(), + name: name.to_string(), }; let token = crate::token::sign_claim(store, confirmation_token_struct)?; let mut confirm_url = store diff --git a/server/src/helpers.rs b/server/src/helpers.rs index ef3de0a70..e6e642f49 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -69,6 +69,7 @@ fn origin(url: &str) -> String { ) } +/// Checks if the origin in the Cookie matches the requested subject. pub fn get_auth_from_cookie( headers: &HeaderMap, requested_subject: &str, From 461255583efcf684b0ba4f3eae1fcd5f35581747 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 3 Jan 2023 12:48:47 +0100 Subject: [PATCH 20/45] Refactor endpoints, update JSON --- CHANGELOG.md | 1 + lib/defaults/default_base_models.json | 18 ++-- lib/defaults/default_store.json | 12 ++- lib/src/plugins/add_pubkey.rs | 135 ++++++++++++++++++++++++++ lib/src/plugins/mod.rs | 29 +++++- lib/src/plugins/register.rs | 1 + lib/src/populate.rs | 2 +- 7 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 lib/src/plugins/add_pubkey.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1087f3d68..3a6af3086 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Re-build store + invite when adjusting server url #607 - Use local atomic-server for properties and classes, improves atomic-server #604 - New sign up / register flow. Add `/register` Endpoint #489 #254 +- New sign up / register flow. Add `/register`, `/confirm-email`, `/add-public-key` endpoints #489 #254 - Add multi-tenancy support. Users can create their own `Drives` on subdomains. #288 - Refactor URLs. `store.self_url()` returns an `AtomicUrl`, which provides methods to easily add paths, find subdomains and more. - Add support for subdomains, use a Wildcard TLS certificate #502 diff --git a/lib/defaults/default_base_models.json b/lib/defaults/default_base_models.json index 4731f33d4..f5262cbef 100644 --- a/lib/defaults/default_base_models.json +++ b/lib/defaults/default_base_models.json @@ -78,17 +78,23 @@ }, { "@id": "https://atomicdata.dev/classes/Property", - "https://atomicdata.dev/properties/description": "A Resource that should redirect the browser to a new location. It can also set a `redirectAgent`, which is used in Invites to create an Agent Resource on the Server from a Public Key that the user posesses. See the [Invite docs](https://docs.atomicdata.dev/invitations.html).", + "https://atomicdata.dev/properties/description": "A Property is a single field in a Class. It's the thing that a property field in an Atom points to. An example is `birthdate`. An instance of Property requires various Properties, most notably a `datatype` (e.g. `string` or `integer`), a human readable `description` (such as the thing you're reading), and a `shortname`.", "https://atomicdata.dev/properties/isA": [ "https://atomicdata.dev/classes/Class" ], - "https://atomicdata.dev/properties/requires": [ - "https://atomicdata.dev/properties/destination" - ], + "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/classes", "https://atomicdata.dev/properties/recommends": [ - "https://atomicdata.dev/properties/invite/redirectAgent" + "https://atomicdata.dev/properties/classtype", + "https://atomicdata.dev/properties/isDynamic", + "https://atomicdata.dev/properties/isLocked", + "https://atomicdata.dev/properties/allowsOnly" + ], + "https://atomicdata.dev/properties/requires": [ + "https://atomicdata.dev/properties/shortname", + "https://atomicdata.dev/properties/datatype", + "https://atomicdata.dev/properties/description" ], - "https://atomicdata.dev/properties/shortname": "redirect" + "https://atomicdata.dev/properties/shortname": "property" }, { "@id": "https://atomicdata.dev/classes/Class", diff --git a/lib/defaults/default_store.json b/lib/defaults/default_store.json index 9c05cd124..a4743317f 100644 --- a/lib/defaults/default_store.json +++ b/lib/defaults/default_store.json @@ -808,7 +808,6 @@ "https://atomicdata.dev/properties/isA": [ "https://atomicdata.dev/classes/Property" ], - "https://atomicdata.dev/properties/lastCommit": "https://atomicdata.dev/commits/VB3gtWMkysTX5hKjbYjIM1hfVGPywT3pEPL8c7NwaUAJID6RzptGRPzmix8aKKDeb8Pj1WFv0UPV0YVPxcduBg==", "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", "https://atomicdata.dev/properties/shortname": "sub-resources" }, @@ -820,10 +819,19 @@ "https://atomicdata.dev/properties/isA": [ "https://atomicdata.dev/classes/Property" ], - "https://atomicdata.dev/properties/lastCommit": "https://atomicdata.dev/commits/fS0krtm1wDk0lodH0psnUKmBHBMKLuxnjkd7E7QbkzDk/irQ43gNW3lWxkwQj58ZNg6rUAUMDGJrLy1X3cHwBQ==", "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", "https://atomicdata.dev/properties/shortname": "tags" }, + { + "@id": "https://atomicdata.dev/properties/token", + "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/string", + "https://atomicdata.dev/properties/description": "A server-generated string that should not mean anything to the client. It could be a JWT token, or something else.", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Property" + ], + "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", + "https://atomicdata.dev/properties/shortname": "token" + }, { "@id": "https://atomicdata.dev/properties/write", "https://atomicdata.dev/properties/classtype": "https://atomicdata.dev/classes/Agent", diff --git a/lib/src/plugins/add_pubkey.rs b/lib/src/plugins/add_pubkey.rs new file mode 100644 index 000000000..33c8f769c --- /dev/null +++ b/lib/src/plugins/add_pubkey.rs @@ -0,0 +1,135 @@ +/*! +Sends users a link to add a new public key to their account. +Useful when a users loses their private key. +*/ + +use serde::{Deserialize, Serialize}; + +use crate::{ + agents::Agent, + email::{EmailAddress, MailAction, MailMessage}, + endpoints::{Endpoint, HandleGetContext}, + errors::AtomicResult, + plugins::utils::return_success, + urls, Resource, Storelike, +}; + +pub fn request_email_add_pubkey() -> Endpoint { + Endpoint { + path: urls::PATH_ADD_PUBKEY.to_string(), + params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), + description: "Requests an email to add a new PublicKey to an Agent.".to_string(), + shortname: "request-pubkey-reset".to_string(), + handle: Some(handle_request_email_pubkey), + handle_post: None, + } +} + +pub fn confirm_add_pubkey() -> Endpoint { + Endpoint { + path: urls::PATH_CONFIRM_PUBKEY.to_string(), + params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), + description: "Confirms a token to add a new Public Key.".to_string(), + shortname: "request-pubkey-reset".to_string(), + handle: Some(handle_confirm_add_pubkey), + handle_post: None, + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct AddPubkeyToken { + agent: String, +} + +pub fn handle_request_email_pubkey(context: HandleGetContext) -> AtomicResult { + let HandleGetContext { + subject, + store, + for_agent: _, + } = context; + let mut email_option: Option = None; + for (k, v) in subject.query_pairs() { + if let "email" = k.as_ref() { + email_option = Some(EmailAddress::new(v.to_string())?) + } + } + // by default just return the Endpoint + let Some(email) = email_option else { + return request_email_add_pubkey().to_resource(store); + }; + + // Find the agent by their email + let agent = match Agent::from_email(&email.to_string(), store) { + Ok(a) => a, + // If we can't find the agent, we should still return a `success` response, + // in order to prevent users to know that the email exists. + Err(_) => return return_success(), + }; + + // send the user an e-mail to confirm sign up + let store_clone = store.clone(); + let confirmation_token_struct = AddPubkeyToken { + agent: agent.subject, + }; + let token = crate::token::sign_claim(store, confirmation_token_struct)?; + let mut confirm_url = store + .get_server_url() + .clone() + .set_path(urls::PATH_CONFIRM_PUBKEY) + .url(); + confirm_url.set_query(Some(&format!("token={}", token))); + let message = MailMessage { + to: email, + subject: "Add a new Passphrase to your account".to_string(), + body: "You've requested adding a new Passphrase. Click the link below to do so!" + .to_string(), + action: Some(MailAction { + name: "Add new Passphrase to account".to_string(), + url: confirm_url.into(), + }), + }; + // async, because mails are slow + tokio::spawn(async move { + store_clone + .send_email(message) + .await + .unwrap_or_else(|e| tracing::error!("Error sending email: {}", e)); + }); + + return_success() +} + +pub fn handle_confirm_add_pubkey(context: HandleGetContext) -> AtomicResult { + let HandleGetContext { + subject, + store, + for_agent: _, + } = context; + + let mut token_opt: Option = None; + let mut pubkey_option = None; + + for (k, v) in subject.query_pairs() { + match k.as_ref() { + "token" | urls::TOKEN => token_opt = Some(v.to_string()), + "public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()), + _ => {} + } + } + + let Some(token) = token_opt else { + return confirm_add_pubkey().to_resource(store); + }; + + let pubkey = pubkey_option.ok_or("No public-key provided")?; + + // Parse and verify the JWT token + let confirmation = crate::token::verify_claim::(store, &token)?.custom; + + // Add the key to the agent + let mut agent = store.get_resource(&confirmation.agent)?; + agent.push_propval(urls::ACTIVE_KEYS, pubkey.into(), true)?; + agent.save(store)?; + + return_success() +} diff --git a/lib/src/plugins/mod.rs b/lib/src/plugins/mod.rs index 680f11c08..87c0f6ecd 100644 --- a/lib/src/plugins/mod.rs +++ b/lib/src/plugins/mod.rs @@ -6,7 +6,7 @@ Plugins can have functions that are called at specific moments by Atomic-Server. For example: -- Before returning a Resource. These are either Endpoints or Class Extenders. +- Before returning a Resource. These are either [Endpoint]s or Class Extenders. - Before applying a Commit. In the long term, these plugins will probably be powered by WASM and can be extended at runtime. @@ -16,13 +16,13 @@ However, they are designed in such a way that they have a limited scope and a cl ## Extending resources There are two ways of extending / modifying a Resource. -Endpoints are great for APIs that have a fixed route, and Class Extenders are great for APIs that don't have a fixed route. +[Endpoint]s are great for APIs that have a fixed route, and Class Extenders are great for APIs that don't have a fixed route. Endpoints are easier to generate from Rust, and will be available the second a server is Running. -### Endpoints +### [Endpoint]s Resources that typically parse query parameters and return a dynamic resource. -When adding an endpoint, add it to the list of endpoints in [lib/src/endpoints.rs] +When adding an endpoint, add it to the list of [default_endpoints] in this file. Endpoints are all instances of the [crate] class. They are presented in the UI as a form. @@ -31,14 +31,18 @@ They are presented in the UI as a form. Similar to Endpoints, Class Extenders can modify their contents before creating a response. Contrary to Endpoints, these can be any type of Class. They are used for performing custom queries, or calculating dynamic attributes. +Add these by registering the handler at [crate::db::Db::get_resource_extended]. */ +use crate::endpoints::Endpoint; + // Class Extenders pub mod chatroom; pub mod importer; pub mod invite; // Endpoints +pub mod add_pubkey; #[cfg(feature = "html")] pub mod bookmark; pub mod files; @@ -46,9 +50,24 @@ pub mod path; pub mod prunetests; pub mod query; pub mod register; -pub mod reset_pubkey; pub mod search; pub mod versioning; // Utilities / helpers mod utils; + +pub fn default_endpoints() -> Vec { + vec![ + versioning::version_endpoint(), + versioning::all_versions_endpoint(), + path::path_endpoint(), + search::search_endpoint(), + files::upload_endpoint(), + register::register_endpoint(), + register::confirm_email_endpoint(), + add_pubkey::request_email_add_pubkey(), + add_pubkey::confirm_add_pubkey(), + #[cfg(feature = "html")] + bookmark::bookmark_endpoint(), + ] +} diff --git a/lib/src/plugins/register.rs b/lib/src/plugins/register.rs index 846fbfedb..2a9aca99e 100644 --- a/lib/src/plugins/register.rs +++ b/lib/src/plugins/register.rs @@ -109,6 +109,7 @@ pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult .get_server_url() .clone() .set_path(urls::PATH_CONFIRM_EMAIL) + // .set_subdomain(Some(&name.to_string()))? .url(); confirm_url.set_query(Some(&format!("token={}", token))); let message = MailMessage { diff --git a/lib/src/populate.rs b/lib/src/populate.rs index a38f3033a..f0f43c4e2 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -319,7 +319,7 @@ pub fn populate_collections(store: &impl Storelike) -> AtomicResult<()> { pub fn populate_endpoints(store: &crate::Db) -> AtomicResult<()> { use crate::atomic_url::Routes; - let endpoints = crate::endpoints::default_endpoints(); + let endpoints = crate::plugins::default_endpoints(); let endpoints_collection = store.get_server_url().set_route(Routes::Endpoints); for endpoint in endpoints { let mut resource = endpoint.to_resource(store)?; From 6eaba9dfa5da5df8f28c36adb95d361c4cb8fa4f Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Thu, 12 Jan 2023 10:41:05 +0100 Subject: [PATCH 21/45] Improve check_append error #558 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6af3086..3b6712e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,7 +89,6 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Fix index issue happening when deleting a single property in a sorted collection #545 - Update JS assets & playwright - Fix initial indexing bug #560 -- Fix errors on succesful export / import #565 - Fix envs for store path, change `ATOMIC_STORE_DIR` to `ATOMIC_DATA_DIR` #567 - Refactor static file asset hosting #578 - Meta tags server side #577 @@ -97,6 +96,8 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Remove feature to index external RDF files and search them #579 - Add staging environment #588 - Add systemd instructions to readme #271 +- Improve check_append error #558 +- Fix errors on successful export / import #565 ## [v0.34.0] - 2022-10-31 From 0d3b943a5a142b450523a33a7479e5dc7982f99d Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 23 Jan 2023 14:00:30 +0100 Subject: [PATCH 22/45] #556 constrain URLs --- CHANGELOG.md | 2 ++ lib/src/atomic_url.rs | 4 ++-- lib/src/collections.rs | 14 ++++++++++++-- lib/src/commit.rs | 18 ++++++++++++++++++ lib/src/db/test.rs | 4 ++-- lib/src/parse.rs | 7 ++++--- lib/src/resources.rs | 4 ++++ lib/test_files/local_id.json | 10 +++++----- server/src/handlers/commit.rs | 3 ++- server/src/tests.rs | 14 +++++++------- 10 files changed, 58 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6712e74..0e9c0db2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,8 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain - Add systemd instructions to readme #271 - Improve check_append error #558 - Fix errors on successful export / import #565 +- Most Collection routes now live under `/collections`, e.g. `/collections/agents` instead of `/agents`. #556 +- Constrain new URLs. Commits for new Resources are now only valid if their parent is part of the current URL. So it's no longer possible to create `/some-path/new-resource` if `new-resource` is its parent is not in its URL. #556 ## [v0.34.0] - 2022-10-31 diff --git a/lib/src/atomic_url.rs b/lib/src/atomic_url.rs index 5afc0b32e..778c89590 100644 --- a/lib/src/atomic_url.rs +++ b/lib/src/atomic_url.rs @@ -36,9 +36,9 @@ impl AtomicUrl { pub fn set_route(&self, route: Routes) -> Self { let path = match route { Routes::AllVersions => "/all-versions".to_string(), - Routes::Agents => "/agents".to_string(), + Routes::Agents => "/collections/agents".to_string(), Routes::Collections => "/collections".to_string(), - Routes::Commits => "/commits".to_string(), + Routes::Commits => "/collections/commits".to_string(), Routes::CommitsUnsigned => "/commits-unsigned".to_string(), Routes::Endpoints => "/endpoints".to_string(), Routes::Import => "/import".to_string(), diff --git a/lib/src/collections.rs b/lib/src/collections.rs index 74783b6a1..64e654cd0 100644 --- a/lib/src/collections.rs +++ b/lib/src/collections.rs @@ -385,6 +385,10 @@ pub fn create_collection_resource_for_class( ) -> AtomicResult { let class = store.get_class(class_subject)?; + // We use the `Collections` collection as the parent for all collections. + // This also influences their URLs. + let is_collections_collection = class.subject == urls::COLLECTION; + // Pluralize the shortname let pluralized = match class.shortname.as_ref() { "class" => "classes".to_string(), @@ -392,7 +396,13 @@ pub fn create_collection_resource_for_class( other => format!("{}s", other), }; - let mut collection = CollectionBuilder::class_collection(&class.subject, &pluralized, store); + let path = if is_collections_collection { + "/collections".to_string() + } else { + format!("/collections/{}", pluralized) + }; + + let mut collection = CollectionBuilder::class_collection(&class.subject, &path, store); collection.sort_by = match class_subject { urls::COMMIT => Some(urls::CREATED_AT.to_string()), @@ -413,7 +423,7 @@ pub fn create_collection_resource_for_class( .ok_or("No self_url present in store, can't populate collections")?; // Let the Collections collection be the top level item - let parent = if class.subject == urls::COLLECTION { + let parent = if is_collections_collection { drive.to_string() } else { drive diff --git a/lib/src/commit.rs b/lib/src/commit.rs index c290bd1cc..b6917ffe7 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -46,6 +46,8 @@ pub struct CommitOpts { pub update_index: bool, /// For who the right checks will be perormed. If empty, the signer of the Commit will be used. pub validate_for_agent: Option, + /// Checks if the URL of the parent is present in its Parent URL. + pub validate_subject_url_parent: bool, } /// A Commit is a set of changes to a Resource. @@ -170,6 +172,20 @@ impl Commit { .apply_changes(resource_old.clone(), store, false) .map_err(|e| format!("Error applying changes to Resource {}. {}", self.subject, e))?; + // For new subjects, make sure that the parent of the resource is part of the URL of the new subject. + if is_new && opts.validate_subject_url_parent { + if let Ok(parent) = resource_new.get(urls::PARENT) { + let parent_str = parent.to_string(); + if !self.subject.starts_with(&parent_str) { + return Err(format!( + "The parent '{}' is not part of the URL of the new subject '{}'.", + parent_str, self.subject + ) + .into()); + } + } + } + if opts.validate_rights { let validate_for = opts.validate_for_agent.as_ref().unwrap_or(&self.signer); if is_new { @@ -398,6 +414,7 @@ impl Commit { validate_signature: false, validate_timestamp: false, validate_rights: false, + validate_subject_url_parent: false, validate_previous_commit: false, validate_for_agent: None, update_index: false, @@ -718,6 +735,7 @@ mod test { validate_previous_commit: true, validate_rights: false, validate_for_agent: None, + validate_subject_url_parent: true, update_index: true, }; } diff --git a/lib/src/db/test.rs b/lib/src/db/test.rs index a317829cc..0a1b09fce 100644 --- a/lib/src/db/test.rs +++ b/lib/src/db/test.rs @@ -192,7 +192,7 @@ fn destroy_resource_and_check_collection_and_commits() { fn get_extended_resource_pagination() { let store = Db::init_temp("get_extended_resource_pagination").unwrap(); let subject = format!( - "{}commits?current_page=2&page_size=99999", + "{}collections/commits?current_page=2&page_size=99999", store.get_server_url() ); let for_agent = &ForAgent::Public; @@ -212,7 +212,7 @@ fn get_extended_resource_pagination() { .unwrap() .to_int() .unwrap(); - assert_eq!(cur_page, 2); + assert_eq!(cur_page, num); assert_eq!(resource.get_subject(), &subject_with_page_size); } diff --git a/lib/src/parse.rs b/lib/src/parse.rs index 2257ba775..39eab63f0 100644 --- a/lib/src/parse.rs +++ b/lib/src/parse.rs @@ -347,7 +347,8 @@ fn parse_json_ad_map_to_resource( validate_timestamp: false, validate_rights: parse_opts.for_agent != ForAgent::Sudo, validate_previous_commit: false, - validate_for_agent: Some(parse_opts.for_agent.to_string()), + validate_for_agent: parse_opts.for_agent.clone(), + validate_subject_url_parent: true, update_index: true, }; @@ -573,8 +574,8 @@ mod test { .import(include_str!("../test_files/local_id.json"), &parse_opts) .unwrap(); - let reference_subject = generate_id_from_local_id(&importer, "reference"); - let my_subject = generate_id_from_local_id(&importer, "my-local-id"); + let reference_subject = generate_id_from_local_id(&importer, "parent"); + let my_subject = generate_id_from_local_id(&importer, "parent/my-local-id"); let found = store.get_resource(&my_subject).unwrap(); let found_ref = store.get_resource(&reference_subject).unwrap(); diff --git a/lib/src/resources.rs b/lib/src/resources.rs index ba6ea4ba5..857920a75 100644 --- a/lib/src/resources.rs +++ b/lib/src/resources.rs @@ -352,6 +352,7 @@ impl Resource { validate_for_agent: agent.subject.into(), // TODO: auto-merge should work before we enable this https://github.com/atomicdata-dev/atomic-server/issues/412 validate_previous_commit: false, + validate_subject_url_parent: true, update_index: true, }; let commit_response = commit.apply_opts(store, &opts)?; @@ -380,6 +381,8 @@ impl Resource { validate_for_agent: agent.subject.into(), // https://github.com/atomicdata-dev/atomic-server/issues/412 validate_previous_commit: false, + // This is contentious: https://github.com/atomicdata-dev/atomic-data-rust/issues/556 + validate_subject_url_parent: false, update_index: true, }; let commit_response = commit.apply_opts(store, &opts)?; @@ -690,6 +693,7 @@ mod test { validate_signature: true, validate_timestamp: true, validate_rights: false, + validate_subject_url_parent: true, validate_previous_commit: true, validate_for_agent: None, update_index: true, diff --git a/lib/test_files/local_id.json b/lib/test_files/local_id.json index 8773e399b..d9610a5f5 100644 --- a/lib/test_files/local_id.json +++ b/lib/test_files/local_id.json @@ -1,17 +1,17 @@ [ { - "https://atomicdata.dev/properties/localId": "reference", + "https://atomicdata.dev/properties/localId": "parent", "https://atomicdata.dev/properties/write": [ - "my-local-id" + "parent/my-local-id" ], "https://atomicdata.dev/properties/name": "My referenced resource" }, { - "https://atomicdata.dev/properties/localId": "my-local-id", + "https://atomicdata.dev/properties/localId": "parent/my-local-id", "https://atomicdata.dev/properties/name": "My resource that refers", - "https://atomicdata.dev/properties/parent": "reference", + "https://atomicdata.dev/properties/parent": "parent", "https://atomicdata.dev/properties/write": [ - "reference" + "parent" ] } ] diff --git a/server/src/handlers/commit.rs b/server/src/handlers/commit.rs index 201bf141e..7d4472d42 100644 --- a/server/src/handlers/commit.rs +++ b/server/src/handlers/commit.rs @@ -19,7 +19,7 @@ pub async fn post_commit( let mut builder = HttpResponse::Ok(); let incoming_commit_resource = parse_json_ad_commit_resource(&body, store)?; let incoming_commit = Commit::from_resource(incoming_commit_resource)?; -if store.is_external_subject(&incoming_commit.subject)? { + if store.is_external_subject(&incoming_commit.subject)? { return Err("Subject of commit is external, and should be sent to its origin domain. This store can not own this resource. See https://github.com/atomicdata-dev/atomic-data-rust/issues/509".into()); } let opts = CommitOpts { @@ -30,6 +30,7 @@ if store.is_external_subject(&incoming_commit.subject)? { // https://github.com/atomicdata-dev/atomic-server/issues/412 validate_previous_commit: false, validate_for_agent: Some(incoming_commit.signer.to_string()), + validate_subject_url_parent: true, update_index: true, }; let commit_response = incoming_commit.apply_opts(store, &opts)?; diff --git a/server/src/tests.rs b/server/src/tests.rs index 31a35b1dd..6d11ff01f 100644 --- a/server/src/tests.rs +++ b/server/src/tests.rs @@ -79,8 +79,8 @@ async fn server_tests() { assert!(body.as_str().contains("html")); // Should 200 (public) - let req = - test::TestRequest::with_uri("/properties").insert_header(("Accept", "application/ad+json")); + let req = test::TestRequest::with_uri("/collections/properties") + .insert_header(("Accept", "application/ad+json")); let resp = test::call_service(&app, req.to_request()).await; assert_eq!( resp.status().as_u16(), @@ -109,8 +109,8 @@ async fn server_tests() { drive.save(store).unwrap(); // Should 401 (Unauthorized) - let req = - test::TestRequest::with_uri("/properties").insert_header(("Accept", "application/ad+json")); + let req = test::TestRequest::with_uri("/collections/properties") + .insert_header(("Accept", "application/ad+json")); let resp = test::call_service(&app, req.to_request()).await; assert_eq!( resp.status().as_u16(), @@ -119,7 +119,7 @@ async fn server_tests() { ); // Get JSON-AD - let req = build_request_authenticated("/properties", &appstate); + let req = build_request_authenticated("/collections/properties", &appstate); let resp = test::call_service(&app, req.to_request()).await; let body = get_body(resp); println!("DEBUG: {:?}", body); @@ -130,7 +130,7 @@ async fn server_tests() { ); // Get JSON-LD - let req = build_request_authenticated("/properties", &appstate) + let req = build_request_authenticated("/collections/properties", &appstate) .insert_header(("Accept", "application/ld+json")); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success(), "setup not returning JSON-LD"); @@ -141,7 +141,7 @@ async fn server_tests() { ); // Get turtle - let req = build_request_authenticated("/properties", &appstate) + let req = build_request_authenticated("/collections/properties", &appstate) .insert_header(("Accept", "text/turtle")); let resp = test::call_service(&app, req.to_request()).await; assert!(resp.status().is_success()); From 609a2fd77a4111786d6fa655116b7b30f84399b2 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 23 Jan 2023 14:20:35 +0100 Subject: [PATCH 23/45] Store.tpf public for tests --- lib/src/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/store.rs b/lib/src/store.rs index 89310e3b0..4d32b86ca 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -38,7 +38,7 @@ impl Store { /// Returns an empty array if nothing is found. // Very costly, slow implementation. // Does not assume any indexing. - fn tpf( + pub fn tpf( &self, q_subject: Option<&str>, q_property: Option<&str>, From f697ec0431a3458aabae49e4f78c64582541dcdc Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 24 Jan 2023 13:40:28 +0100 Subject: [PATCH 24/45] import_file test failing on macos #564 --- server/src/appstate.rs | 16 +++++++++++----- server/src/bin.rs | 13 ++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/server/src/appstate.rs b/server/src/appstate.rs index bf0e24ee0..57bec6646 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -7,7 +7,7 @@ use atomic_lib::{ atomic_url::Routes, commit::CommitResponse, email::SmtpConfig, - Storelike, + Db, Storelike, }; /// The AppState contains all the relevant Context for the server. @@ -27,6 +27,15 @@ pub struct AppState { pub search_state: SearchState, } +/// Initializes the Store and sets the default agent. +pub fn init_store(config: &Config) -> AtomicServerResult { + let store = atomic_lib::Db::init(&config.store_path, &config.server_url)?; + + tracing::info!("Setting default agent"); + set_default_agent(config, &store)?; + Ok(store) +} + /// Creates the AppState (the server's context available in Handlers). /// Initializes or opens a store on disk. /// Creates a new agent, if necessary. @@ -42,7 +51,7 @@ pub async fn init(config: Config) -> AtomicServerResult { } tracing::info!("Opening database at {:?}", &config.store_path); - let mut store = atomic_lib::Db::init(&config.store_path, &config.server_url)?; + let mut store = init_store(&config)?; if let Some(host) = &config.opts.smpt_host { store @@ -60,9 +69,6 @@ pub async fn init(config: Config) -> AtomicServerResult { .map_err(|e| format!("Failed to populate default store. {}", e))?; } - tracing::info!("Setting default agent"); - set_default_agent(&config, &store)?; - // Initialize search constructs tracing::info!("Starting search service"); let search_state = diff --git a/server/src/bin.rs b/server/src/bin.rs index d6d040dc3..1b8d836a0 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -48,8 +48,8 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { pt } }; - let appstate = appstate::init(config.clone()).await?; - let outstr = appstate.store.export(!e.only_internal)?; + let store = appstate::init_store(&config)?; + let outstr = store.export(!e.only_internal)?; std::fs::create_dir_all(path.parent().unwrap()) .map_err(|e| format!("Failed to create directory {:?}. {}", path, e))?; let mut file = File::create(&path) @@ -64,12 +64,11 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { std::fs::read_to_string(path)? }; - let appstate = appstate::init(config.clone()).await?; + let store = appstate::init_store(&config)?; let importer_subject = if let Some(i) = &import_opts.parent { i.into() } else { - appstate - .store + store .get_self_url() .expect("No self URL") .set_route(Routes::Import) @@ -84,10 +83,10 @@ async fn main_wrapped() -> errors::AtomicServerResult<()> { } else { atomic_lib::parse::SaveOpts::Commit }, - signer: Some(appstate.store.get_default_agent()?), + signer: Some(store.get_default_agent()?), }; println!("Importing..."); - appstate.store.import(&readstring, &parse_opts)?; + store.import(&readstring, &parse_opts)?; println!("Sucesfully imported {:?} to store.", import_opts.file); Ok(()) From c265fde02fa022a30c3136399e69ddcd04df6154 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 24 Jan 2023 13:51:36 +0100 Subject: [PATCH 25/45] Throw error on trailing slash server URL #566 --- server/src/config.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/config.rs b/server/src/config.rs index 1e5b89fd4..d29e9ea9f 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -280,7 +280,11 @@ pub fn build_config(opts: Opts) -> AtomicServerResult { // This logic could be a bit too complicated, but I'm not sure on how to make this simpler. let server_url = if let Some(addr) = opts.server_url.clone() { - addr + if addr.ends_with('/') { + return Err("The Server URL should not end with a trailing slash.".into()); + } else { + addr + } } else if opts.https && opts.port_https == 443 || !opts.https && opts.port == 80 { format!("{}://{}", schema, opts.domain) } else { From 77f79c30a233820ec93acc34387a0dde348b61b9 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sun, 5 Feb 2023 11:42:55 +0100 Subject: [PATCH 26/45] Fix cookies subject in search --- server/src/handlers/search.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/server/src/handlers/search.rs b/server/src/handlers/search.rs index f7d27f02b..e405ee6fa 100644 --- a/server/src/handlers/search.rs +++ b/server/src/handlers/search.rs @@ -5,6 +5,7 @@ use crate::{ appstate::AppState, errors::{AtomicServerError, AtomicServerResult}, + helpers::get_subject, search::{resource_to_facet, Fields}, }; use actix_web::{web, HttpResponse}; @@ -55,6 +56,7 @@ pub async fn search_query( appstate: web::Data, params: web::Query, req: actix_web::HttpRequest, + conn: actix_web::dev::ConnectionInfo, ) -> AtomicServerResult { let mut timer = Timer::new(); let store = &appstate.store; @@ -83,19 +85,7 @@ pub async fn search_query( let subjects = docs_to_subjects(top_docs, &fields, &searcher)?; // Create a valid atomic data resource. - // You'd think there would be a simpler way of getting the requested URL... - // See https://github.com/actix/actix-web/issues/2895 - let subject: String = store - .get_self_url() - .ok_or("No base URL set")? - .url() - .join( - req.uri() - .path_and_query() - .ok_or("Add a query param")? - .as_str(), - )? - .to_string(); + let subject: String = get_subject(&req, &conn, &appstate)?; let mut results_resource = atomic_lib::plugins::search::search_endpoint().to_resource(store)?; results_resource.set_subject(subject.clone()); From bf6ddab964f9bbec451d435d5d3df45629d078db Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 10 Feb 2023 14:19:42 +0100 Subject: [PATCH 27/45] Set max chars for meta tags --- server/src/handlers/single_page_app.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/handlers/single_page_app.rs b/server/src/handlers/single_page_app.rs index c5bb89a37..543d18741 100644 --- a/server/src/handlers/single_page_app.rs +++ b/server/src/handlers/single_page_app.rs @@ -100,7 +100,10 @@ impl Default for MetaTags { impl Display for MetaTags { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let description = escape_html(&self.description); + let description = escape_html(&self.description) + .chars() + .take(250) + .collect::(); let image = &self.image; let title = escape_html(&self.title); From 4e31d0bb11b4c99904ced0271666edffe9ad9513 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 10 Feb 2023 14:31:26 +0100 Subject: [PATCH 28/45] Improve commit error --- lib/src/commit.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/commit.rs b/lib/src/commit.rs index b6917ffe7..152b9d1c9 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -168,6 +168,7 @@ impl Commit { } }; + // We apply the changes and create a new resource, but don't index it yet. let mut resource_new = self .apply_changes(resource_old.clone(), store, false) .map_err(|e| format!("Error applying changes to Resource {}. {}", self.subject, e))?; @@ -178,7 +179,7 @@ impl Commit { let parent_str = parent.to_string(); if !self.subject.starts_with(&parent_str) { return Err(format!( - "The parent '{}' is not part of the URL of the new subject '{}'.", + "You cannot create a new Resource with this subject, because the parent '{}' is not part of the URL of the new subject '{}'.", parent_str, self.subject ) .into()); From 0fb74c299ded1cc5bbeb30be283d88811a016d7e Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sat, 25 Feb 2023 14:31:31 +0100 Subject: [PATCH 29/45] Cleanup, imports --- server/src/config.rs | 4 ++-- server/src/helpers.rs | 18 ------------------ 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/server/src/config.rs b/server/src/config.rs index d29e9ea9f..e0dfb2729 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -96,11 +96,11 @@ pub struct Opts { #[clap(long, env = "ATOMIC_SLOW_MODE")] pub slow_mode: bool, - /// Add this if you want so send e-mails (e.g. on user registration) + /// Host address of an SMTP server. Add if you want so send e-mails (e.g. for user registration). Also set the port. #[clap(long, env = "ATOMIC_SMTP_HOST")] pub smpt_host: Option, - /// Useful for debugging e-mails during development, or if your SMTP server has an unconventional port number. + /// Port of your SMTP server. #[clap(long, env = "ATOMIC_SMTP_PORT", default_value = "25")] pub smpt_port: u16, diff --git a/server/src/helpers.rs b/server/src/helpers.rs index e6e642f49..d4056c2c5 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -187,24 +187,6 @@ pub fn get_client_agent( Ok(for_agent) } -/// Finds the extension -pub fn try_extension(path: &str) -> Option<(ContentType, &str)> { - let items: Vec<&str> = path.split('.').collect(); - if items.len() == 2 { - let path = items[0]; - let content_type = match items[1] { - "json" => ContentType::Json, - "jsonld" => ContentType::JsonLd, - "jsonad" => ContentType::JsonAd, - "html" => ContentType::Html, - "ttl" => ContentType::Turtle, - _ => return None, - }; - return Some((content_type, path)); - } - None -} - fn session_cookies_from_header(header: &HeaderValue) -> AtomicServerResult> { let cookies: Vec<&str> = header .to_str() From 39766e8bff557a1ae07012e34b60e4069a920f95 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 27 Feb 2023 15:20:46 +0100 Subject: [PATCH 30/45] Fix rebase issues --- lib/src/plugins/reset_pubkey.rs | 130 -------------------------------- server/src/helpers.rs | 18 +++++ 2 files changed, 18 insertions(+), 130 deletions(-) delete mode 100644 lib/src/plugins/reset_pubkey.rs diff --git a/lib/src/plugins/reset_pubkey.rs b/lib/src/plugins/reset_pubkey.rs deleted file mode 100644 index b0ccc1c1b..000000000 --- a/lib/src/plugins/reset_pubkey.rs +++ /dev/null @@ -1,130 +0,0 @@ -/*! -Sends users a link to add a new public key to their account. -Useful when a users loses their private key. -*/ - -use serde::{Deserialize, Serialize}; - -use crate::{ - agents::Agent, - email::{EmailAddress, MailAction, MailMessage}, - endpoints::{Endpoint, HandleGetContext}, - errors::AtomicResult, - plugins::utils::return_success, - urls, Resource, Storelike, -}; - -pub fn request_email_add_pubkey() -> Endpoint { - Endpoint { - path: urls::PATH_ADD_PUBKEY.to_string(), - params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), - description: "Requests an email to add a new PublicKey to an Agent.".to_string(), - shortname: "pubkey-add".to_string(), - handle: Some(handle_request_email_pubkey), - handle_post: None, - } -} - -pub fn confirm_add_pubkey() -> Endpoint { - Endpoint { - path: urls::PATH_CONFIRM_PUBKEY.to_string(), - params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(), - description: "Confirms a token to add a new Public Key.".to_string(), - shortname: "request-pubkey-reset".to_string(), - handle: Some(handle_confirm_add_pubkey), - handle_post: None, - } -} - -#[derive(Debug, Serialize, Deserialize)] -struct AddPubkeyToken { - agent: String, -} - -pub fn handle_request_email_pubkey(context: HandleGetContext) -> AtomicResult { - let HandleGetContext { - subject, - store, - for_agent: _, - } = context; - let mut email_option: Option = None; - for (k, v) in subject.query_pairs() { - match k.as_ref() { - "email" => email_option = Some(EmailAddress::new(v.to_string())?), - _ => {} - } - } - // by default just return the Endpoint - let Some(email) = email_option else { - return request_email_add_pubkey().to_resource(store); - }; - - // Find the agent by their email - let agent = Agent::from_email(&email.to_string(), store)?; - - // send the user an e-mail to confirm sign up - let store_clone = store.clone(); - let confirmation_token_struct = AddPubkeyToken { - agent: agent.subject, - }; - let token = crate::token::sign_claim(store, confirmation_token_struct)?; - let mut confirm_url = store - .get_server_url() - .clone() - .set_path(urls::PATH_CONFIRM_PUBKEY) - .url(); - confirm_url.set_query(Some(&format!("token={}", token))); - let message = MailMessage { - to: email, - subject: "Add a new Passphrase to your account".to_string(), - body: "You've requested adding a new Passphrase. Click the link below to do so!" - .to_string(), - action: Some(MailAction { - name: "Add new Passphrase to account".to_string(), - url: confirm_url.into(), - }), - }; - // async, because mails are slow - tokio::spawn(async move { - store_clone - .send_email(message) - .await - .unwrap_or_else(|e| tracing::error!("Error sending email: {}", e)); - }); - - return_success() -} - -#[tracing::instrument] -pub fn handle_confirm_add_pubkey(context: HandleGetContext) -> AtomicResult { - let HandleGetContext { - subject, - store, - for_agent: _, - } = context; - let mut token_opt: Option = None; - let mut pubkey_option = None; - - for (k, v) in subject.query_pairs() { - match k.as_ref() { - "token" | urls::TOKEN => token_opt = Some(v.to_string()), - "public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()), - _ => {} - } - } - let pubkey = pubkey_option.ok_or("No public-key provided")?; - - let Some(token) = token_opt else { - return confirm_add_pubkey().to_resource(store); - }; - - // Parse and verify the JWT token - let confirmation = crate::token::verify_claim::(store, &token)?.custom; - - // Add the key to the agent - let mut agent = store.get_resource(&confirmation.agent)?; - agent.push(urls::ACTIVE_KEYS, pubkey.into(), true)?; - agent.save(store)?; - - return_success() -} diff --git a/server/src/helpers.rs b/server/src/helpers.rs index d4056c2c5..d7e745dbf 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -311,3 +311,21 @@ pub fn get_subject( let subject = format!("{}{}", server_without_last_slash, &req.uri().to_string()); Ok(subject) } + +/// Finds the extension +pub fn try_extension(path: &str) -> Option<(ContentType, &str)> { + let items: Vec<&str> = path.split('.').collect(); + if items.len() == 2 { + let path = items[0]; + let content_type = match items[1] { + "json" => ContentType::Json, + "jsonld" => ContentType::JsonLd, + "jsonad" => ContentType::JsonAd, + "html" => ContentType::Html, + "ttl" => ContentType::Turtle, + _ => return None, + }; + return Some((content_type, path)); + } + None +} From bff73c3ba2152bbda898eabd17d8cae45dfcc255 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 27 Feb 2023 16:58:21 +0100 Subject: [PATCH 31/45] Use get_subject --- lib/src/parse.rs | 2 +- server/src/handlers/get_resource.rs | 33 ++------------ server/src/handlers/post_resource.rs | 33 ++------------ server/src/handlers/search.rs | 2 +- server/src/handlers/single_page_app.rs | 5 +- server/src/handlers/upload.rs | 2 +- server/src/helpers.rs | 63 ++++++++++++++++++-------- 7 files changed, 58 insertions(+), 82 deletions(-) diff --git a/lib/src/parse.rs b/lib/src/parse.rs index 39eab63f0..0a82bec60 100644 --- a/lib/src/parse.rs +++ b/lib/src/parse.rs @@ -347,7 +347,7 @@ fn parse_json_ad_map_to_resource( validate_timestamp: false, validate_rights: parse_opts.for_agent != ForAgent::Sudo, validate_previous_commit: false, - validate_for_agent: parse_opts.for_agent.clone(), + validate_for_agent: Some(parse_opts.for_agent.to_string()), validate_subject_url_parent: true, update_index: true, }; diff --git a/server/src/handlers/get_resource.rs b/server/src/handlers/get_resource.rs index 1ef8f199c..184543f88 100644 --- a/server/src/handlers/get_resource.rs +++ b/server/src/handlers/get_resource.rs @@ -1,9 +1,8 @@ use crate::{ appstate::AppState, - content_types::get_accept, content_types::ContentType, errors::AtomicServerResult, - helpers::{get_client_agent, try_extension}, + helpers::{get_client_agent, get_subject}, }; use actix_web::{web, HttpResponse}; use atomic_lib::Storelike; @@ -16,39 +15,13 @@ pub async fn handle_get_resource( path: Option>, appstate: web::Data, req: actix_web::HttpRequest, + conn: actix_web::dev::ConnectionInfo, ) -> AtomicServerResult { let mut timer = Timer::new(); let headers = req.headers(); - let mut content_type = get_accept(headers); - let server_url = &appstate.config.server_url; // Get the subject from the path, or return the home URL - let subject = if let Some(subj_end) = path { - let mut subj_end_string = subj_end.as_str(); - // If the request is for the root, return the home URL - if subj_end_string.is_empty() { - server_url.to_string() - } else { - if content_type == ContentType::Html { - if let Some((ext, path)) = try_extension(subj_end_string) { - content_type = ext; - subj_end_string = path; - } - } - // Check extensions and set datatype. Harder than it looks to get right... - // This might not be the best way of creating the subject. But I can't access the full URL from any actix stuff! - let querystring = if req.query_string().is_empty() { - "".to_string() - } else { - format!("?{}", req.query_string()) - }; - let subject = format!("{}/{}{}", server_url, subj_end_string, querystring); - subject - } - } else { - // There is no end string, so It's the root of the URL, the base URL! - String::from(server_url) - }; + let (subject, content_type) = get_subject(&req, &conn, &appstate)?; let store = &appstate.store; timer.add("parse_headers"); diff --git a/server/src/handlers/post_resource.rs b/server/src/handlers/post_resource.rs index 881467751..5c676e612 100644 --- a/server/src/handlers/post_resource.rs +++ b/server/src/handlers/post_resource.rs @@ -1,9 +1,8 @@ use crate::{ appstate::AppState, - content_types::get_accept, content_types::ContentType, errors::AtomicServerResult, - helpers::{get_client_agent, try_extension}, + helpers::{get_client_agent, get_subject}, }; use actix_web::{web, HttpResponse}; use atomic_lib::Storelike; @@ -16,39 +15,13 @@ pub async fn handle_post_resource( appstate: web::Data, req: actix_web::HttpRequest, body: web::Bytes, + conn: actix_web::dev::ConnectionInfo, ) -> AtomicServerResult { let mut timer = Timer::new(); let headers = req.headers(); - let mut content_type = get_accept(headers); - let server_url = &appstate.config.server_url; // Get the subject from the path, or return the home URL - let subject = if let Some(subj_end) = path { - let mut subj_end_string = subj_end.as_str(); - // If the request is for the root, return the home URL - if subj_end_string.is_empty() { - server_url.to_string() - } else { - if content_type == ContentType::Html { - if let Some((ext, path)) = try_extension(subj_end_string) { - content_type = ext; - subj_end_string = path; - } - } - // Check extensions and set datatype. Harder than it looks to get right... - // This might not be the best way of creating the subject. But I can't access the full URL from any actix stuff! - let querystring = if req.query_string().is_empty() { - "".to_string() - } else { - format!("?{}", req.query_string()) - }; - let subject = format!("{}/{}{}", server_url, subj_end_string, querystring); - subject - } - } else { - // There is no end string, so It's the root of the URL, the base URL! - String::from(server_url) - }; + let (subject, content_type) = get_subject(&req, &conn, &appstate)?; let store = &appstate.store; timer.add("parse_headers"); diff --git a/server/src/handlers/search.rs b/server/src/handlers/search.rs index e405ee6fa..bc603f20c 100644 --- a/server/src/handlers/search.rs +++ b/server/src/handlers/search.rs @@ -85,7 +85,7 @@ pub async fn search_query( let subjects = docs_to_subjects(top_docs, &fields, &searcher)?; // Create a valid atomic data resource. - let subject: String = get_subject(&req, &conn, &appstate)?; + let (subject, _) = get_subject(&req, &conn, &appstate)?; let mut results_resource = atomic_lib::plugins::search::search_endpoint().to_resource(store)?; results_resource.set_subject(subject.clone()); diff --git a/server/src/handlers/single_page_app.rs b/server/src/handlers/single_page_app.rs index 543d18741..766f1495c 100644 --- a/server/src/handlers/single_page_app.rs +++ b/server/src/handlers/single_page_app.rs @@ -1,6 +1,7 @@ use std::fmt::Display; use std::fmt::Formatter; +use crate::helpers::get_subject; use crate::{appstate::AppState, errors::AtomicServerResult}; use actix_web::HttpResponse; @@ -9,9 +10,11 @@ use actix_web::HttpResponse; pub async fn single_page( appstate: actix_web::web::Data, path: actix_web::web::Path, + req: actix_web::HttpRequest, + conn: actix_web::dev::ConnectionInfo, ) -> AtomicServerResult { let template = include_str!("../../assets_tmp/index.html"); - let subject = format!("{}/{}", appstate.store.get_server_url(), path); + let (subject, _content_type) = get_subject(&req, &conn, &appstate)?; let meta_tags: MetaTags = if let Ok(resource) = appstate .store diff --git a/server/src/handlers/upload.rs b/server/src/handlers/upload.rs index e2ebb885a..d2d1262a9 100644 --- a/server/src/handlers/upload.rs +++ b/server/src/handlers/upload.rs @@ -35,7 +35,7 @@ pub async fn upload_handler( ) -> AtomicServerResult { let store = &appstate.store; let parent = store.get_resource(&query.parent)?; - let subject = get_subject(&req, &conn, &appstate)?; + let (subject, _) = get_subject(&req, &conn, &appstate)?; let for_agent = get_client_agent(req.headers(), &appstate, subject)?; check_write(store, &parent, &for_agent)?; diff --git a/server/src/helpers.rs b/server/src/helpers.rs index d7e745dbf..db255d67b 100644 --- a/server/src/helpers.rs +++ b/server/src/helpers.rs @@ -10,7 +10,7 @@ use atomic_lib::Storelike; use percent_encoding::percent_decode_str; use std::str::FromStr; -use crate::content_types::ContentType; +use crate::content_types::{get_accept, ContentType}; use crate::errors::{AppErrorType, AtomicServerError}; use crate::{appstate::AppState, errors::AtomicServerResult}; @@ -290,7 +290,9 @@ pub fn get_subject( req: &actix_web::HttpRequest, conn: &actix_web::dev::ConnectionInfo, appstate: &AppState, -) -> AtomicServerResult { +) -> AtomicServerResult<(String, ContentType)> { + let content_type = get_accept(req.headers()); + let domain = &appstate.config.opts.domain; let host = conn.host(); let subdomain = if let Some(index) = host.find(domain) { @@ -309,23 +311,48 @@ pub fn get_subject( } let server_without_last_slash = subject_url.to_string().trim_end_matches('/').to_string(); let subject = format!("{}{}", server_without_last_slash, &req.uri().to_string()); - Ok(subject) + // if let Some((ct, path)) = try_extension(req.path()) { + // content_type = ct; + // return Ok((path.to_string(), content_type)); + // } + Ok((subject, content_type)) } -/// Finds the extension -pub fn try_extension(path: &str) -> Option<(ContentType, &str)> { - let items: Vec<&str> = path.split('.').collect(); - if items.len() == 2 { - let path = items[0]; - let content_type = match items[1] { - "json" => ContentType::Json, - "jsonld" => ContentType::JsonLd, - "jsonad" => ContentType::JsonAd, - "html" => ContentType::Html, - "ttl" => ContentType::Turtle, - _ => return None, - }; - return Some((content_type, path)); +/// Finds the extension of a supported serialization format. +/// Not used right now, see: https://github.com/atomicdata-dev/atomic-data-rust/issues/601 +#[allow(dead_code)] +fn try_extension(path: &str) -> Option<(ContentType, &str)> { + // Check if path ends with one of the folliwing extensions + let extensions = [ + ".json", + ".jsonld", + ".jsonad", + ".html", + ".ttl", + ".nt", + ".nq", + ".ntriples", + ".nt", + ]; + let mut found = None; + for ext in extensions.iter() { + if path.ends_with(ext) { + println!("Found extension: {}", ext); + let path = &path[0..path.len() - ext.len()]; + let content_type = match *ext { + ".json" => Some(ContentType::Json), + ".jsonld" => Some(ContentType::JsonLd), + ".jsonad" => Some(ContentType::JsonAd), + ".html" => Some(ContentType::Html), + ".ttl" => Some(ContentType::Turtle), + ".nt" => Some(ContentType::NTriples), + ".ntriples" => Some(ContentType::NTriples), + _ => None, + }; + if let Some(ct) = content_type { + found = Some((ct, path)); + } + } } - None + found } From c74156659c16d60fb6185bfdaf5a8ff980e95786 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Thu, 30 Mar 2023 12:38:17 +0200 Subject: [PATCH 32/45] Do not add `register` endpoint if there is no SMTP server --- lib/src/db.rs | 43 +++++++++++++++++++++++++++++++++++++----- lib/src/endpoints.rs | 4 +--- lib/src/plugins/mod.rs | 18 ------------------ lib/src/populate.rs | 1 - server/src/appstate.rs | 4 +++- 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/src/db.rs b/lib/src/db.rs index 0c4e1692a..d78c88e8f 100644 --- a/lib/src/db.rs +++ b/lib/src/db.rs @@ -20,18 +20,20 @@ use tracing::{info, instrument}; use crate::{ agents::ForAgent, - atomic_url::AtomicUrl, + atomic_url::{AtomicUrl, Routes}, atoms::IndexAtom, commit::CommitResponse, db::{query_index::requires_query_index, val_prop_sub_index::find_in_val_prop_sub_index}, email::{self, MailMessage}, - endpoints::{default_endpoints, Endpoint, HandleGetContext}, + endpoints::{build_default_endpoints, Endpoint, HandleGetContext}, errors::{AtomicError, AtomicResult}, + plugins, query::QueryResult, resources::PropVals, storelike::Storelike, + urls, values::SortableValue, - Atom, Query, Resource, + Atom, Query, Resource, Value, }; use self::{ @@ -112,7 +114,7 @@ impl Db { prop_val_sub_index, server_url: AtomicUrl::try_from(server_url)?, watched_queries, - endpoints: default_endpoints(), + endpoints: Vec::new(), handle_commit: None, smtp_client: None, }; @@ -220,6 +222,28 @@ impl Db { Some(Resource::from_propvals(propvals, subject)) } + pub fn register_default_endpoints(&mut self) -> AtomicResult<()> { + // First we delete all existing endpoint resources, as they might not be there in this new run + let found_endpoints = self.query(&Query::new_class(urls::ENDPOINT))?.resources; + + for mut found in found_endpoints { + found.destroy(self)?; + } + + let mut endpoints = build_default_endpoints(); + + if self.smtp_client.is_some() { + endpoints.push(plugins::register::register_endpoint()); + endpoints.push(plugins::register::confirm_email_endpoint()); + } + + for endpoint in endpoints { + self.register_endpoint(endpoint)?; + } + + Ok(()) + } + fn build_index_for_atom( &self, atom: &IndexAtom, @@ -321,8 +345,17 @@ impl Db { } /// Adds an [Endpoint] to the store. This means adding a route with custom behavior. - pub fn register_endpoint(&mut self, endpoint: Endpoint) { + pub fn register_endpoint(&mut self, endpoint: Endpoint) -> AtomicResult<()> { + let mut resource = endpoint.to_resource(self)?; + let endpoints_collection = self.get_server_url().set_route(Routes::Endpoints); + resource.set_propval( + urls::PARENT.into(), + Value::AtomicUrl(endpoints_collection.to_string()), + self, + )?; + resource.save_locally(self)?; self.endpoints.push(endpoint); + Ok(()) } /// Registers an SMTP client to the store, allowing the store to send emails. diff --git a/lib/src/endpoints.rs b/lib/src/endpoints.rs index 29d8940c2..fc4e4d3b8 100644 --- a/lib/src/endpoints.rs +++ b/lib/src/endpoints.rs @@ -87,15 +87,13 @@ impl std::fmt::Debug for Endpoint { } } -pub fn default_endpoints() -> Vec { +pub fn build_default_endpoints() -> Vec { vec![ plugins::versioning::version_endpoint(), plugins::versioning::all_versions_endpoint(), plugins::path::path_endpoint(), plugins::search::search_endpoint(), plugins::files::upload_endpoint(), - plugins::register::register_endpoint(), - plugins::register::confirm_email_endpoint(), #[cfg(feature = "html")] plugins::bookmark::bookmark_endpoint(), plugins::importer::import_endpoint(), diff --git a/lib/src/plugins/mod.rs b/lib/src/plugins/mod.rs index 87c0f6ecd..d240f5907 100644 --- a/lib/src/plugins/mod.rs +++ b/lib/src/plugins/mod.rs @@ -34,8 +34,6 @@ They are used for performing custom queries, or calculating dynamic attributes. Add these by registering the handler at [crate::db::Db::get_resource_extended]. */ -use crate::endpoints::Endpoint; - // Class Extenders pub mod chatroom; pub mod importer; @@ -55,19 +53,3 @@ pub mod versioning; // Utilities / helpers mod utils; - -pub fn default_endpoints() -> Vec { - vec![ - versioning::version_endpoint(), - versioning::all_versions_endpoint(), - path::path_endpoint(), - search::search_endpoint(), - files::upload_endpoint(), - register::register_endpoint(), - register::confirm_email_endpoint(), - add_pubkey::request_email_add_pubkey(), - add_pubkey::confirm_add_pubkey(), - #[cfg(feature = "html")] - bookmark::bookmark_endpoint(), - ] -} diff --git a/lib/src/populate.rs b/lib/src/populate.rs index f0f43c4e2..94db99aad 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -383,7 +383,6 @@ pub fn populate_all(store: &crate::Db) -> AtomicResult<()> { .map_err(|e| format!("Failed to create default ontology. {}", e))?; set_drive_rights(store, true)?; populate_collections(store).map_err(|e| format!("Failed to populate collections. {}", e))?; - populate_endpoints(store).map_err(|e| format!("Failed to populate endpoints. {}", e))?; populate_sidebar_items(store) .map_err(|e| format!("Failed to populate sidebar items. {}", e))?; Ok(()) diff --git a/server/src/appstate.rs b/server/src/appstate.rs index 57bec6646..9818d4c48 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -29,10 +29,12 @@ pub struct AppState { /// Initializes the Store and sets the default agent. pub fn init_store(config: &Config) -> AtomicServerResult { - let store = atomic_lib::Db::init(&config.store_path, &config.server_url)?; + let mut store = atomic_lib::Db::init(&config.store_path, &config.server_url)?; tracing::info!("Setting default agent"); set_default_agent(config, &store)?; + store.register_default_endpoints()?; + Ok(store) } From 286d3dfc9e9615573e91bb1d29b69647b6ed1a23 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 26 Jul 2023 17:25:16 +0200 Subject: [PATCH 33/45] Fix issues? --- lib/src/populate.rs | 3 +-- lib/src/resources.rs | 2 ++ lib/src/urls.rs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/populate.rs b/lib/src/populate.rs index 94db99aad..aca2076ce 100644 --- a/lib/src/populate.rs +++ b/lib/src/populate.rs @@ -319,9 +319,8 @@ pub fn populate_collections(store: &impl Storelike) -> AtomicResult<()> { pub fn populate_endpoints(store: &crate::Db) -> AtomicResult<()> { use crate::atomic_url::Routes; - let endpoints = crate::plugins::default_endpoints(); let endpoints_collection = store.get_server_url().set_route(Routes::Endpoints); - for endpoint in endpoints { + for endpoint in crate::endpoints::build_default_endpoints() { let mut resource = endpoint.to_resource(store)?; resource.set( urls::PARENT.into(), diff --git a/lib/src/resources.rs b/lib/src/resources.rs index 857920a75..086c11f4b 100644 --- a/lib/src/resources.rs +++ b/lib/src/resources.rs @@ -2,8 +2,10 @@ //! Has methods for saving resources and getting properties inside them. use crate::commit::{CommitOpts, CommitResponse}; +use crate::urls; use crate::utils::random_string; use crate::values::{SubResource, Value}; +use crate::Query; use crate::{commit::CommitBuilder, errors::AtomicResult}; use crate::{ mapping::is_url, diff --git a/lib/src/urls.rs b/lib/src/urls.rs index 8671e5057..79fb69432 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -170,6 +170,7 @@ pub const PATH_CONFIRM_EMAIL: &str = "/confirm-email"; pub const PATH_RESET_PUBKEY: &str = "/reset-public-key"; pub const PATH_CONFIRM_PUBKEY: &str = "/confirm-public-key"; pub const PATH_ADD_PUBKEY: &str = "/add-public-key"; +pub const PATH_CONFIRM_PUBKEY: &str = "/confirm-add-public-key"; pub const PATH_CONFIRM_RESET: &str = "/confirm-reset-public-key"; pub const PATH_QUERY: &str = "/query"; pub const PATH_PRUNE_TESTS: &str = "/prunetests"; From 5feb834946741c1ed43076ea9ca2bfcff405916d Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Wed, 26 Jul 2023 19:24:58 +0200 Subject: [PATCH 34/45] Add register form + email confirm #640 #544 #489 #276 --- CONTRIBUTING.md | 1 + browser/CHANGELOG.md | 13 + .../data-browser/src/components/CodeBlock.tsx | 15 +- .../src/components/Dialog/useDialog.tsx | 10 +- .../data-browser/src/components/ErrorLook.tsx | 20 +- browser/data-browser/src/components/Guard.tsx | 17 + .../src/components/RegisterSignIn.tsx | 299 +++++++ .../src/components/SettingsAgent.tsx | 198 +++++ .../src/components/SideBar/DriveSwitcher.tsx | 12 +- .../forms/FileDropzone/FileDropzoneInput.tsx | 2 +- .../CustomForms/NewBookmarkDialog.tsx | 58 +- .../src/components/forms/ResourceSelector.tsx | 206 +++++ .../data-browser/src/helpers/AppSettings.tsx | 3 +- .../data-browser/src/hooks/useSavedDrives.ts | 6 +- browser/data-browser/src/hooks/useUpload.ts | 3 +- .../data-browser/src/routes/ConfirmEmail.tsx | 108 +++ .../src/routes/NewResource/NewRoute.tsx | 10 +- browser/data-browser/src/routes/Routes.tsx | 9 +- .../data-browser/src/routes/SearchRoute.tsx | 2 +- .../data-browser/src/routes/SettingsAgent.tsx | 272 +----- browser/data-browser/src/routes/paths.tsx | 1 + .../data-browser/src/views/ChatRoomPage.tsx | 42 +- .../data-browser/src/views/CollectionPage.tsx | 4 +- browser/data-browser/src/views/CrashPage.tsx | 29 +- browser/data-browser/src/views/DrivePage.tsx | 34 +- browser/data-browser/src/views/ErrorPage.tsx | 17 +- browser/data-browser/tests/e2e.spec.ts | 841 ++++++++++++++++++ browser/lib/src/authentication.ts | 166 +++- browser/lib/src/store.ts | 12 + browser/lib/src/urls.ts | 4 + browser/react/src/index.ts | 1 + browser/react/src/useServerSupports.ts | 20 + lib/src/db/test.rs | 3 +- lib/src/populate.rs | 6 +- lib/src/resources.rs | 1 - lib/src/urls.rs | 1 - 36 files changed, 2088 insertions(+), 358 deletions(-) create mode 100644 browser/data-browser/src/components/Guard.tsx create mode 100644 browser/data-browser/src/components/RegisterSignIn.tsx create mode 100644 browser/data-browser/src/components/SettingsAgent.tsx create mode 100644 browser/data-browser/src/components/forms/ResourceSelector.tsx create mode 100644 browser/data-browser/src/routes/ConfirmEmail.tsx create mode 100644 browser/data-browser/tests/e2e.spec.ts create mode 100644 browser/react/src/useServerSupports.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6a1fb1ff..bcce7effd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,6 +50,7 @@ TL;DR Clone the repo and run `cargo run` from each folder (e.g. `cli` or `server - Visit your `localhost` in your locally running `atomic-data-browser` instance: (e.g. `http://localhost:5173/app/show?subject=http%3A%2F%2Flocalhost`) - use `cargo watch -- cargo run` to automatically recompile `atomic-server` when you update JS assets in `browser` - use `cargo watch -- cargo run --bin atomic-server -- --env-file server/.env` to automatically recompile `atomic-server` when you update code or JS assets. +- If you want to debug emails: `brew install mailhog` => `mailhog` => `http://localhost:8025` and add `ATOMIC_SMTP_HOST=localhost` `ATOMIC_SMTP_PORT=1025` to your `.env`. ### IDE setup (VSCode) diff --git a/browser/CHANGELOG.md b/browser/CHANGELOG.md index d8f99656a..de35ad6f1 100644 --- a/browser/CHANGELOG.md +++ b/browser/CHANGELOG.md @@ -174,11 +174,18 @@ This changelog covers all five packages, as they are (for now) updated as a whol - Add `store.getResourceAncestry` method, which returns the ancestry of a resource, including the resource itself. - Add `resource.title` property, which returns the name of a resource, or the first property that is can be used to name the resource. - `store.createSubject` now accepts a `parent` argument, which allows creating nested subjects. +- Add `store.getServerSupports` to know which features a Server supports + +### @tomic/react + +- Add `useServerSupports` hook to see supported features of the server ## v0.35.0 ### @tomic/browser +- Let users register using e-mail address, improve sign-up UX. +- Add `Store.parseMetaTags` to load JSON-AD objects stored in the DOM. Speeds up initial page load by allowing server to set JSON-AD objects in the initial HTML response. - Move static assets around, align build with server and fix PWA #292 - Add `useChildren` hook and `Store.getChildren` method - Add new file preview UI for images, audio, text and PDF files. @@ -186,8 +193,14 @@ This changelog covers all five packages, as they are (for now) updated as a whol - Fix Dialogue form #308 - Refactor search, escape query strings for Tantivy - Add `import` context menu, allows importing anywhere +- Let users register using e-mail address, improve sign-up UX. ### @tomic/react +- `store.createSubject` allows creating nested paths +- `store.createSubject` allows creating nested paths +- Add `useChildren` hook and `Store.getChildren` method +- Add `Store.postToServer` method, add `endpoints`, `import_json_ad_string` +- Add `store.preloadClassesAndProperties` and remove `urls.properties.getAll` and `urls.classes.getAll`. This enables using `atomic-data-browser` without relying on `atomicdata.dev` being available. - Add more options to `useSearch` diff --git a/browser/data-browser/src/components/CodeBlock.tsx b/browser/data-browser/src/components/CodeBlock.tsx index aafe0fa56..fc6b85e68 100644 --- a/browser/data-browser/src/components/CodeBlock.tsx +++ b/browser/data-browser/src/components/CodeBlock.tsx @@ -7,9 +7,11 @@ import { Button } from './Button'; interface CodeBlockProps { content?: string; loading?: boolean; + wrapContent?: boolean; } -export function CodeBlock({ content, loading }: CodeBlockProps) { +/** Codeblock with copy feature */ +export function CodeBlock({ content, loading, wrapContent }: CodeBlockProps) { const [isCopied, setIsCopied] = useState(undefined); function copyToClipboard() { @@ -19,7 +21,7 @@ export function CodeBlock({ content, loading }: CodeBlockProps) { } return ( - + {loading ? ( 'loading...' ) : ( @@ -46,7 +48,12 @@ export function CodeBlock({ content, loading }: CodeBlockProps) { ); } -export const CodeBlockStyled = styled.pre` +interface Props { + /** Renders all in a single line */ + wrapContent?: boolean; +} + +export const CodeBlockStyled = styled.pre` position: relative; background-color: ${p => p.theme.colors.bg1}; border-radius: ${p => p.theme.radius}; @@ -55,4 +62,6 @@ export const CodeBlockStyled = styled.pre` font-family: monospace; width: 100%; overflow-x: auto; + word-wrap: ${p => (p.wrapContent ? 'break-word' : 'initial')}; + white-space: ${p => (p.wrapContent ? 'pre-wrap' : 'pre')}; `; diff --git a/browser/data-browser/src/components/Dialog/useDialog.tsx b/browser/data-browser/src/components/Dialog/useDialog.tsx index 7ef7d5740..ffbd9510a 100644 --- a/browser/data-browser/src/components/Dialog/useDialog.tsx +++ b/browser/data-browser/src/components/Dialog/useDialog.tsx @@ -1,16 +1,16 @@ import { useCallback, useMemo, useState } from 'react'; import { InternalDialogProps } from './index'; -export type UseDialogReturnType = [ +export type UseDialogReturnType = { /** Props meant to pass to a {@link Dialog} component */ - dialogProps: InternalDialogProps, + dialogProps: InternalDialogProps; /** Function to show the dialog */ - show: () => void, + show: () => void; /** Function to close the dialog */ close: (success?: boolean) => void, /** Boolean indicating wether the dialog is currently open */ - isOpen: boolean, -]; + isOpen: boolean; +}; export type UseDialogOptions = { bindShow?: React.Dispatch; diff --git a/browser/data-browser/src/components/ErrorLook.tsx b/browser/data-browser/src/components/ErrorLook.tsx index 71c1469dd..d6ba24275 100644 --- a/browser/data-browser/src/components/ErrorLook.tsx +++ b/browser/data-browser/src/components/ErrorLook.tsx @@ -2,6 +2,7 @@ import { lighten } from 'polished'; import { styled, css } from 'styled-components'; import { FaExclamationTriangle } from 'react-icons/fa'; +import { Column } from './Row'; export const errorLookStyle = css` color: ${props => props.theme.colors.alert}; @@ -25,18 +26,21 @@ export function ErrorBlock({ error, showTrace }: ErrorBlockProps): JSX.Element { Something went wrong -
-        {error.message}
+      
+        {error.message}
         {showTrace && (
           <>
-            
-
- Stack trace: -
- {error.stack} + Stack trace: + + {error.stack} + )} -
+ ); } diff --git a/browser/data-browser/src/components/Guard.tsx b/browser/data-browser/src/components/Guard.tsx new file mode 100644 index 000000000..60554e845 --- /dev/null +++ b/browser/data-browser/src/components/Guard.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useSettings } from '../helpers/AppSettings'; +import { RegisterSignIn } from './RegisterSignIn'; + +/** + * The Guard can be wrapped around a Component that depends on a user being logged in. + * If the user is not logged in, it will show a button to sign up / sign in. + * Show to users after a new Agent has been created. + * Instructs them to save their secret somewhere safe + */ +export function Guard({ children }: React.PropsWithChildren): JSX.Element { + const { agent } = useSettings(); + + if (agent) { + return <>{children}; + } else return ; +} diff --git a/browser/data-browser/src/components/RegisterSignIn.tsx b/browser/data-browser/src/components/RegisterSignIn.tsx new file mode 100644 index 000000000..e893114cc --- /dev/null +++ b/browser/data-browser/src/components/RegisterSignIn.tsx @@ -0,0 +1,299 @@ +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + useDialog, +} from './Dialog'; +import React, { FormEvent, useCallback, useEffect, useState } from 'react'; +import { useSettings } from '../helpers/AppSettings'; +import { Button } from './Button'; +import { + addPublicKey, + nameRegex, + register as createRegistration, + useServerSupports, + useServerURL, + useStore, +} from '@tomic/react'; +import Field from './forms/Field'; +import { InputWrapper, InputStyled } from './forms/InputStyles'; +import { Row } from './Row'; +import { ErrorLook } from './ErrorLook'; +import { SettingsAgent } from './SettingsAgent'; + +interface RegisterSignInProps { + // URL where to send the user to after successful registration + redirect?: string; +} + +/** What is currently showing */ +enum PageStateOpts { + none, + signIn, + register, + reset, + mailSentRegistration, + mailSentAddPubkey, +} + +/** + * Two buttons: Register / Sign in. + * Opens a Dialog / Modal with the appropriate form. + */ +export function RegisterSignIn({ + children, +}: React.PropsWithChildren): JSX.Element { + const { dialogProps, show, close } = useDialog(); + const { agent } = useSettings(); + const [pageState, setPageState] = useState(PageStateOpts.none); + const [email, setEmail] = useState(''); + const { emailRegister } = useServerSupports(); + + if (agent) { + return <>{children}; + } else if (!emailRegister) { + return ; + } + + return ( + <> + + + + + + {pageState === PageStateOpts.register && ( + + )} + {pageState === PageStateOpts.signIn && ( + + )} + {pageState === PageStateOpts.reset && ( + + )} + {pageState === PageStateOpts.mailSentRegistration && ( + + )} + {pageState === PageStateOpts.mailSentAddPubkey && ( + + )} + + + ); +} + +function Reset({ email, setEmail, setPageState }) { + const store = useStore(); + const [err, setErr] = useState(undefined); + + const handleRequestReset = useCallback(async () => { + try { + await addPublicKey(store, email); + setPageState(PageStateOpts.mailSentAddPubkey); + } catch (e) { + setErr(e); + } + }, [email]); + + return ( + <> + +

Reset your PassKey

+
+ +

+ { + "Lost it? No worries, we'll send a link that let's you create a new one." + } +

+ { + setErr(undefined); + setEmail(e); + }} + /> + {err && {err.message}} +
+ + + + + ); +} + +function MailSentConfirm({ email, close, message }) { + return ( + <> + +

Go to your email inbox

+
+ +

+ {"We've sent a confirmation link to "} + {email} + {'.'} +

+

{message}

+
+ + + + + ); +} + +function Register({ setPageState, email, setEmail }) { + const [name, setName] = useState(''); + const [serverUrlStr] = useServerURL(); + const [nameErr, setErr] = useState(undefined); + const store = useStore(); + + const serverUrl = new URL(serverUrlStr); + serverUrl.host = `${name}.${serverUrl.host}`; + + useEffect(() => { + // check regex of name, set error + if (!name.match(nameRegex)) { + setErr(new Error('Name must be lowercase and only contain numbers')); + } else { + setErr(undefined); + } + }, [name, email]); + + const handleSubmit = useCallback( + async (event: FormEvent) => { + event.preventDefault(); + + if (!name) { + setErr(new Error('Name is required')); + + return; + } + + try { + await createRegistration(store, name, email); + setPageState(PageStateOpts.mailSentRegistration); + } catch (er) { + setErr(er); + } + }, + [name, email], + ); + + return ( + <> + +

Register

+
+ +
+ + + { + setName(e.target.value); + }} + /> + + + + {name && nameErr && {nameErr.message}} + +
+ + + + + + ); +} + +function SignIn({ setPageState }) { + return ( + <> + +

Sign in

+
+ + + + + + + + + ); +} + +function EmailField({ setEmail, email }) { + return ( + + + { + setEmail(e.target.value); + }} + /> + + + ); +} diff --git a/browser/data-browser/src/components/SettingsAgent.tsx b/browser/data-browser/src/components/SettingsAgent.tsx new file mode 100644 index 000000000..edda04afa --- /dev/null +++ b/browser/data-browser/src/components/SettingsAgent.tsx @@ -0,0 +1,198 @@ +import { Agent } from '@tomic/react'; +import React, { useState } from 'react'; +import { FaCog, FaEye, FaEyeSlash } from 'react-icons/fa'; +import { useSettings } from '../helpers/AppSettings'; +import { ButtonInput } from './Button'; +import Field from './forms/Field'; +import { InputStyled, InputWrapper } from './forms/InputStyles'; + +/** Form where users can post their Private Key, or edit their Agent */ +export const SettingsAgent: React.FunctionComponent = () => { + const { agent, setAgent } = useSettings(); + const [subject, setSubject] = useState(undefined); + const [privateKey, setPrivateKey] = useState(undefined); + const [error, setError] = useState(undefined); + const [showPrivateKey, setShowPrivateKey] = useState(false); + const [advanced, setAdvanced] = useState(false); + const [secret, setSecret] = useState(undefined); + + // When there is an agent, set the advanced values + // Otherwise, reset the secret value + React.useEffect(() => { + if (agent !== undefined) { + fillAdvanced(); + } else { + setSecret(''); + } + }, [agent]); + + // When the key or subject changes, update the secret + React.useEffect(() => { + renewSecret(); + }, [subject, privateKey]); + + function renewSecret() { + if (agent) { + setSecret(agent.buildSecret()); + } + } + + function fillAdvanced() { + try { + if (!agent) { + throw new Error('No agent set'); + } + + setSubject(agent.subject); + setPrivateKey(agent.privateKey); + } catch (e) { + const err = new Error('Cannot fill subject and privatekey fields.' + e); + setError(err); + setSubject(''); + } + } + + function setAgentIfChanged(oldAgent: Agent | undefined, newAgent: Agent) { + if (JSON.stringify(oldAgent) !== JSON.stringify(newAgent)) { + setAgent(newAgent); + } + } + + /** Called when the secret or the subject is updated manually */ + async function handleUpdateSubjectAndKey() { + renewSecret(); + setError(undefined); + + try { + const newAgent = new Agent(privateKey!, subject); + await newAgent.getPublicKey(); + await newAgent.verifyPublicKeyWithServer(); + + setAgentIfChanged(agent, newAgent); + } catch (e) { + const err = new Error('Invalid Agent' + e); + setError(err); + } + } + + function handleCopy() { + secret && navigator.clipboard.writeText(secret); + } + + /** When the Secret updates, parse it and try if the */ + async function handleUpdateSecret(updateSecret: string) { + setSecret(updateSecret); + + if (updateSecret === '') { + setSecret(''); + setError(undefined); + + return; + } + + setError(undefined); + + try { + const newAgent = Agent.fromSecret(updateSecret); + setAgentIfChanged(agent, newAgent); + setPrivateKey(newAgent.privateKey); + setSubject(newAgent.subject); + // This will fail and throw if the agent is not public, which is by default + // await newAgent.checkPublicKey(); + } catch (e) { + const err = new Error('Invalid secret. ' + e); + setError(err); + } + } + + return ( +
+ + + handleUpdateSecret(e.target.value)} + type={showPrivateKey ? 'text' : 'password'} + disabled={agent !== undefined} + name='secret' + id='current-password' + autoComplete='current-password' + spellCheck='false' + placeholder='Paste your Passphrase' + /> + setShowPrivateKey(!showPrivateKey)} + > + {showPrivateKey ? : } + + setAdvanced(!advanced)} + > + + + {agent && ( + + copy + + )} + + + {advanced ? ( + + + + { + setSubject(e.target.value); + handleUpdateSubjectAndKey(); + }} + /> + + + + + { + setPrivateKey(e.target.value); + handleUpdateSubjectAndKey(); + }} + /> + setShowPrivateKey(!showPrivateKey)} + > + {showPrivateKey ? : } + + + + + ) : null} +
+ ); +}; diff --git a/browser/data-browser/src/components/SideBar/DriveSwitcher.tsx b/browser/data-browser/src/components/SideBar/DriveSwitcher.tsx index 6d4ba73c7..27089ecae 100644 --- a/browser/data-browser/src/components/SideBar/DriveSwitcher.tsx +++ b/browser/data-browser/src/components/SideBar/DriveSwitcher.tsx @@ -1,4 +1,10 @@ -import { Resource, core, server, useResources } from '@tomic/react'; +import { + Resource, + server, + truncateUrl, + urls, + useResources, +} from '@tomic/react'; import { useMemo } from 'react'; import { FaCog, @@ -21,7 +27,8 @@ const Trigger = buildDefaultTrigger(, 'Open Drive Settings'); function getTitle(resource: Resource): string { return ( - (resource.get(core.properties.name) as string) ?? resource.getSubject() + (resource.get(urls.properties.name) as string) ?? + truncateUrl(resource.getSubject(), 20) ); } @@ -45,6 +52,7 @@ export function DriveSwitcher() { }; const createNewResource = useNewResourceUI(); + // const createNewDrive = useDefaultNewInstanceHandler(classes.drive); const items = useMemo( () => [ diff --git a/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx b/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx index 61f51ecec..5d74e34b6 100644 --- a/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx +++ b/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx @@ -8,11 +8,11 @@ import { useUpload } from '../../../hooks/useUpload'; export interface FileDropzoneInputProps { parentResource: Resource; - onFilesUploaded?: (files: string[]) => void; text?: string; maxFiles?: number; className?: string; accept?: string[]; + onFilesUploaded?: (fileSubjects: string[]) => void; } /** diff --git a/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewBookmarkDialog.tsx b/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewBookmarkDialog.tsx index f2b7b4811..482f56bbf 100644 --- a/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewBookmarkDialog.tsx +++ b/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewBookmarkDialog.tsx @@ -27,7 +27,7 @@ export const NewBookmarkDialog: FC = ({ }) => { const [url, setUrl] = useState(''); - const [dialogProps, show, hide] = useDialog({ onCancel: onClose }); + const { dialogProps, show, close } = useDialog({ onCancel: onClose }); const createResourceAndNavigate = useCreateAndNavigate(); @@ -58,32 +58,34 @@ export const NewBookmarkDialog: FC = ({ }, []); return ( - - -

New Bookmark

-
- -
- - - setUrl(e.target.value)} - /> - - -
-
- - - - -
+ <> + + +

New Bookmark

+
+ +
+ + + setUrl(e.target.value)} + /> + + +
+
+ + + + +
+ ); }; diff --git a/browser/data-browser/src/components/forms/ResourceSelector.tsx b/browser/data-browser/src/components/forms/ResourceSelector.tsx new file mode 100644 index 000000000..558b76299 --- /dev/null +++ b/browser/data-browser/src/components/forms/ResourceSelector.tsx @@ -0,0 +1,206 @@ +import { + ArrayError, + urls, + useArray, + useResource, + useStore, + useTitle, +} from '@tomic/react'; +import React, { + Dispatch, + SetStateAction, + useContext, + useState, + useCallback, +} from 'react'; +import { ErrMessage, InputWrapper } from './InputStyles'; +import { DropdownInput } from './DropdownInput'; +import { Dialog, useDialog } from '../Dialog'; +import { DialogTreeContext } from '../Dialog/dialogContext'; +import { useSettings } from '../../helpers/AppSettings'; +import styled from 'styled-components'; +import { NewFormDialog } from './NewForm/NewFormDialog'; + +interface ResourceSelectorProps { + /** + * Whether a certain type of Class is required here. Pass the URL of the + * class. Is used for constructing a list of options. + */ + classType?: string; + /** If true, the form will show an error if it is left empty. */ + required?: boolean; + /** + * This callback is called when the Subject Changes. You can pass an Error + * Handler as the second argument to set an error message. Take the second + * argument of a `useString` hook and pass the setString part to this property + */ + setSubject: ( + subject: string | undefined, + errHandler?: Dispatch>, + ) => void; + /** The value (URL of the Resource that is selected) */ + value?: string; + /** A function to remove this item. Only relevant in arrays. */ + handleRemove?: () => void; + /** Only pass an error if it is applicable to this specific field */ + error?: Error; + /** + * Set an ArrayError. A special type, because the parent needs to know where + * in the Array the error occurred + */ + setError?: Dispatch>; + disabled?: boolean; + autoFocus?: boolean; + /** Is used when a new item is created using the ResourceSelector */ + parent?: string; +} + +/** + * Form field for selecting a single resource. Needs external subject & + * setSubject properties + */ +export const ResourceSelector = React.memo(function ResourceSelector({ + required, + setSubject, + value, + handleRemove, + error, + setError, + classType, + disabled, + parent, + ...props +}: ResourceSelectorProps): JSX.Element { + // TODO: This list should use the user's Pod instead of a hardcoded collection; + const classesCollection = useResource(getCollectionURL(classType)); + let [options] = useArray( + classesCollection, + urls.properties.collection.members, + ); + const requiredClass = useResource(classType); + const [classTypeTitle] = useTitle(requiredClass); + const store = useStore(); + const { + dialogProps, + show: showDialog, + close: closeDialog, + isOpen: isDialogOpen, + } = useDialog(); + const { drive } = useSettings(); + + const [ + /** The value of the input underneath, updated through a callback */ + inputValue, + setInputValue, + ] = useState(value || ''); + + const handleUpdate = useCallback( + (newval?: string) => { + // Pass the error setter for validation purposes + // Pass the Error handler to its parent, so validation errors appear locally + setSubject(newval, setError); + // Reset the error every time anything changes + // setError(null); + }, + [setSubject], + ); + + const handleBlur = useCallback(() => { + value === undefined && handleUpdate(inputValue); + }, [inputValue, value]); + + const isInDialogTree = useContext(DialogTreeContext); + + if (options.length === 0) { + options = store.getAllSubjects(); + } + + let placeholder = 'Enter an Atomic URL...'; + + if (classType && classTypeTitle?.length > 0) { + placeholder = `Select a ${classTypeTitle} or enter a ${classTypeTitle} URL...`; + } + + if (classType && !requiredClass.isReady()) { + placeholder = 'Loading Class...'; + } + + return ( + + + {value && value !== '' && error && ( + {error?.message} + )} + {!isInDialogTree && ( + + {isDialogOpen && ( + + )} + + )} + {required && value === '' && Required} + + ); +}); + +/** For a given class URL, this tries to return a URL of a Collection containing these. */ +// TODO: Scope to current store / make adjustable https://github.com/atomicdata-dev/atomic-data-browser/issues/295 +export function getCollectionURL(classtypeUrl?: string): string | undefined { + switch (classtypeUrl) { + case urls.classes.property: + return 'https://atomicdata.dev/properties/?page_size=999'; + case urls.classes.class: + return 'https://atomicdata.dev/classes/?page_size=999'; + case urls.classes.agent: + return 'https://atomicdata.dev/agents/'; + case urls.classes.commit: + return 'https://atomicdata.dev/commits'; + case urls.classes.datatype: + return 'https://atomicdata.dev/datatypes'; + default: + return undefined; + } +} + +const Wrapper = styled.div` + flex: 1; + --radius: ${props => props.theme.radius}; + ${InputWrapper} { + border-radius: 0; + } + + &:first-of-type ${InputWrapper} { + border-top-left-radius: var(--radius); + border-top-right-radius: var(--radius); + } + + &:last-of-type ${InputWrapper} { + border-bottom-left-radius: var(--radius); + border-bottom-right-radius: var(--radius); + } + + &:not(:last-of-type) ${InputWrapper} { + border-bottom: none; + } +`; diff --git a/browser/data-browser/src/helpers/AppSettings.tsx b/browser/data-browser/src/helpers/AppSettings.tsx index 0f001258e..ae629a73a 100644 --- a/browser/data-browser/src/helpers/AppSettings.tsx +++ b/browser/data-browser/src/helpers/AppSettings.tsx @@ -42,7 +42,8 @@ export const AppSettingsContextProvider = ( const [agent, setAgent] = useCurrentAgent(); const [baseURL, setBaseURL] = useServerURL(); - const [drive, innerSetDrive] = useLocalStorage('drive', baseURL); + // By default, we want to use the current URL's origin with a trailing slash. + const [drive, innerSetDrive] = useLocalStorage('drive', baseURL + '/'); const setDrive = useCallback( (newDrive: string) => { diff --git a/browser/data-browser/src/hooks/useSavedDrives.ts b/browser/data-browser/src/hooks/useSavedDrives.ts index a58e1b2eb..44c25ab88 100644 --- a/browser/data-browser/src/hooks/useSavedDrives.ts +++ b/browser/data-browser/src/hooks/useSavedDrives.ts @@ -4,9 +4,9 @@ import { isDev } from '../config'; import { useSettings } from '../helpers/AppSettings'; const rootDrives = [ - window.location.origin, - 'https://atomicdata.dev', - ...(isDev() ? ['http://localhost:9883'] : []), + window.location.origin + '/', + 'https://atomicdata.dev/', + ...(isDev() ? ['http://localhost:9883/'] : []), ]; const arrayOpts = { diff --git a/browser/data-browser/src/hooks/useUpload.ts b/browser/data-browser/src/hooks/useUpload.ts index 062c2b146..1a58fde0d 100644 --- a/browser/data-browser/src/hooks/useUpload.ts +++ b/browser/data-browser/src/hooks/useUpload.ts @@ -39,7 +39,8 @@ export function useUpload(parentResource: Resource): UseUploadResult { ); const allUploaded = [...netUploaded]; setIsUploading(false); - setSubResources([...subResources, ...allUploaded]); + await setSubResources([...subResources, ...allUploaded]); + await parentResource.save(store); return allUploaded; } catch (e) { diff --git a/browser/data-browser/src/routes/ConfirmEmail.tsx b/browser/data-browser/src/routes/ConfirmEmail.tsx new file mode 100644 index 000000000..23ca32e7f --- /dev/null +++ b/browser/data-browser/src/routes/ConfirmEmail.tsx @@ -0,0 +1,108 @@ +import { confirmEmail, useStore } from '@tomic/react'; +import * as React from 'react'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { Button } from '../components/Button'; +import { CodeBlockStyled } from '../components/CodeBlock'; +import { ContainerNarrow } from '../components/Containers'; +import { isDev } from '../config'; +import { useSettings } from '../helpers/AppSettings'; +import { + useCurrentSubject, + useSubjectParam, +} from '../helpers/useCurrentSubject'; +import { paths } from './paths'; + +/** Route that connects to `/confirm-email`, which confirms an email and creates a secret key. */ +const ConfirmEmail: React.FunctionComponent = () => { + // Value shown in navbar, after Submitting + const [subject] = useCurrentSubject(); + const [secret, setSecret] = useState(''); + const store = useStore(); + const [token] = useSubjectParam('token'); + const { setAgent } = useSettings(); + const [destinationToGo, setDestination] = useState(); + const [err, setErr] = useState(undefined); + const [triedConfirm, setTriedConfirm] = useState(false); + + const handleConfirm = React.useCallback(async () => { + setTriedConfirm(true); + let tokenUrl = subject as string; + + if (isDev()) { + const url = new URL(store.getServerUrl()); + url.pathname = paths.confirmEmail; + url.searchParams.set('token', token as string); + tokenUrl = url.href; + } + + try { + const { agent: newAgent, destination } = await confirmEmail( + store, + tokenUrl, + ); + setSecret(newAgent.buildSecret()); + setDestination(destination); + setAgent(newAgent); + toast.success('Email confirmed!'); + } catch (e) { + setErr(e); + } + }, [subject]); + + if (!triedConfirm && subject) { + handleConfirm(); + } + + if (err) { + if (err.message.includes('expired')) { + return ( + + The link has expired. Request a new one by Registering again. + + ); + } + + return {err?.message}; + } + + if (secret) { + return ; + } + + return Verifying token...; +}; + +function SavePassphrase({ secret, destination }) { + const [copied, setCopied] = useState(false); + + function copyToClipboard() { + setCopied(secret); + navigator.clipboard.writeText(secret || ''); + toast.success('Copied to clipboard'); + } + + return ( + +

Mail confirmed, please save your passphrase

+

+ Your Passphrase is like your password. Never share it with anyone. Use a + password manager like{' '} + + BitWarden + {' '} + to store it securely. +

+ {secret} + {copied ? ( + + {"I've saved my PassPhrase, open my new Drive!"} + + ) : ( + + )} +
+ ); +} + +export default ConfirmEmail; diff --git a/browser/data-browser/src/routes/NewResource/NewRoute.tsx b/browser/data-browser/src/routes/NewResource/NewRoute.tsx index c6e353b07..22bf97ce2 100644 --- a/browser/data-browser/src/routes/NewResource/NewRoute.tsx +++ b/browser/data-browser/src/routes/NewResource/NewRoute.tsx @@ -49,11 +49,13 @@ function NewResourceSelector() { } const onUploadComplete = useCallback( - (files: string[]) => { - toast.success(`Uploaded ${files.length} files.`); + (fileSubjects: string[]) => { + toast.success(`Uploaded ${fileSubjects.length} files.`); - if (calculatedParent) { - navigate(constructOpenURL(calculatedParent)); + if (fileSubjects.length > 1 && parentSubject) { + navigate(constructOpenURL(parentSubject)); + } else { + navigate(constructOpenURL(fileSubjects[0])); } }, [parentSubject, navigate], diff --git a/browser/data-browser/src/routes/Routes.tsx b/browser/data-browser/src/routes/Routes.tsx index 3c4cc7d40..c1e11f927 100644 --- a/browser/data-browser/src/routes/Routes.tsx +++ b/browser/data-browser/src/routes/Routes.tsx @@ -11,7 +11,7 @@ import Data from './DataRoute'; import { Shortcuts } from './ShortcutsRoute'; import { About as About } from './AboutRoute'; import Local from './LocalRoute'; -import SettingsAgent from './SettingsAgent'; +import { SettingsAgentRoute } from './SettingsAgent'; import { SettingsServer } from './SettingsServer'; import { paths } from './paths'; import ResourcePage from '../views/ResourcePage'; @@ -21,8 +21,10 @@ import { TokenRoute } from './TokenRoute'; import { ImporterPage } from '../views/ImporterPage'; import { History } from './History'; import { PruneTestsRoute } from './PruneTestsRoute'; +import ConfirmEmail from './ConfirmEmail'; -const homeURL = window.location.origin; +/** Server URLs should have a `/` at the end */ +const homeURL = window.location.origin + '/'; const isDev = import.meta.env.MODE === 'development'; @@ -37,7 +39,7 @@ export function AppRoutes(): JSX.Element { } /> } /> - } /> + } /> } /> } /> } /> @@ -51,6 +53,7 @@ export function AppRoutes(): JSX.Element { } /> {isDev && } />} {isDev && } />} + } /> } /> } /> diff --git a/browser/data-browser/src/routes/SearchRoute.tsx b/browser/data-browser/src/routes/SearchRoute.tsx index d979baa2a..21b5e8de3 100644 --- a/browser/data-browser/src/routes/SearchRoute.tsx +++ b/browser/data-browser/src/routes/SearchRoute.tsx @@ -94,7 +94,7 @@ export function Search(): JSX.Element { } if (loading) { - message = 'Loading results...'; + message = 'Loading results for'; } if (results.length > 0) { diff --git a/browser/data-browser/src/routes/SettingsAgent.tsx b/browser/data-browser/src/routes/SettingsAgent.tsx index 44dc5ed41..c646027d5 100644 --- a/browser/data-browser/src/routes/SettingsAgent.tsx +++ b/browser/data-browser/src/routes/SettingsAgent.tsx @@ -1,72 +1,19 @@ import * as React from 'react'; -import { useState } from 'react'; -import { Agent } from '@tomic/react'; -import { FaCog, FaEye, FaEyeSlash, FaUser } from 'react-icons/fa'; import { useSettings } from '../helpers/AppSettings'; -import { - InputStyled, - InputWrapper, - LabelStyled, -} from '../components/forms/InputStyles'; -import { ButtonInput, Button } from '../components/Button'; +import { Button } from '../components/Button'; import { Margin } from '../components/Card'; -import Field from '../components/forms/Field'; import { ResourceInline } from '../views/ResourceInline'; import { ContainerNarrow } from '../components/Containers'; -import { AtomicLink } from '../components/AtomicLink'; import { editURL } from '../helpers/navigation'; +import { Guard } from '../components/Guard'; import { useNavigate } from 'react-router'; -import { Main } from '../components/Main'; -import { Column } from '../components/Row'; -import { WarningBlock } from '../components/WarningBlock'; +import { SettingsAgent } from '../components/SettingsAgent'; -const SettingsAgent: React.FunctionComponent = () => { +export function SettingsAgentRoute() { const { agent, setAgent } = useSettings(); - const [subject, setSubject] = useState(undefined); - const [privateKey, setPrivateKey] = useState(undefined); - const [error, setError] = useState(undefined); - const [showPrivateKey, setShowPrivateKey] = useState(false); - const [advanced, setAdvanced] = useState(false); - const [secret, setSecret] = useState(undefined); const navigate = useNavigate(); - // When there is an agent, set the advanced values - // Otherwise, reset the secret value - React.useEffect(() => { - if (agent !== undefined) { - fillAdvanced(); - } else { - setSecret(''); - } - }, [agent]); - - // When the key or subject changes, update the secret - React.useEffect(() => { - renewSecret(); - }, [subject, privateKey]); - - function renewSecret() { - if (agent) { - setSecret(agent.buildSecret()); - } - } - - function fillAdvanced() { - try { - if (!agent) { - throw new Error('No agent set'); - } - - setSubject(agent.subject); - setPrivateKey(agent.privateKey); - } catch (e) { - const err = new Error('Cannot fill subject and privatekey fields.' + e); - setError(err); - setSubject(''); - } - } - function handleSignOut() { if ( window.confirm( @@ -74,196 +21,27 @@ const SettingsAgent: React.FunctionComponent = () => { ) ) { setAgent(undefined); - setError(undefined); - setSubject(''); - setPrivateKey(''); - } - } - - function setAgentIfChanged(oldAgent: Agent | undefined, newAgent: Agent) { - if (JSON.stringify(oldAgent) !== JSON.stringify(newAgent)) { - setAgent(newAgent); - } - } - - /** Called when the secret or the subject is updated manually */ - async function handleUpdateSubjectAndKey() { - renewSecret(); - setError(undefined); - - try { - const newAgent = new Agent(privateKey!, subject); - await newAgent.getPublicKey(); - await newAgent.verifyPublicKeyWithServer(); - - setAgentIfChanged(agent, newAgent); - } catch (e) { - const err = new Error('Invalid Agent' + e); - setError(err); - } - } - - function handleCopy() { - secret && navigator.clipboard.writeText(secret); - } - - /** When the Secret updates, parse it and try if the */ - async function handleUpdateSecret(updateSecret: string) { - setSecret(updateSecret); - - if (updateSecret === '') { - setSecret(''); - setError(undefined); - - return; - } - - setError(undefined); - - try { - const newAgent = Agent.fromSecret(updateSecret); - setAgentIfChanged(agent, newAgent); - setPrivateKey(newAgent.privateKey); - setSubject(newAgent.subject); - // This will fail and throw if the agent is not public, which is by default - // await newAgent.checkPublicKey(); - } catch (e) { - const err = new Error('Invalid secret. ' + e); - setError(err); } } return ( -
- -
-

User Settings

-

- An Agent is a user, consisting of a Subject (its URL) and Private - Key. Together, these can be used to edit data and sign Commits. -

- {agent ? ( - - {agent.subject?.startsWith('http://localhost') && ( - - Warning: - { - "You're using a local Agent, which cannot authenticate on other domains, because its URL does not resolve." - } - - )} -
- - You{"'"}re signed in as - - -
- - -
- ) : ( + +

User Settings

+

+ An Agent is a user, consisting of a Subject (its URL) and Private Key. + Together, these can be used to edit data and sign Commits. +

+ + {agent && ( + <>

- You can create your own Agent by hosting an{' '} - - atomic-server - - . Alternatively, you can use{' '} - - an Invite - {' '} - to get a guest Agent on someone else{"'s"} Atomic Server. +

- )} - - - handleUpdateSecret(e.target.value)} - type={showPrivateKey ? 'text' : 'password'} - disabled={agent !== undefined} - name='secret' - id='current-password' - autoComplete='current-password' - spellCheck='false' - /> - setShowPrivateKey(!showPrivateKey)} - > - {showPrivateKey ? : } - - setAdvanced(!advanced)} - > - - - {agent && ( - - copy - - )} - - - {advanced ? ( - <> - - - { - setSubject(e.target.value); - handleUpdateSubjectAndKey(); - }} - /> - - - - - { - setPrivateKey(e.target.value); - handleUpdateSubjectAndKey(); - }} - /> - setShowPrivateKey(!showPrivateKey)} - > - {showPrivateKey ? : } - - - - - ) : null} - {agent && ( + + + - )} - -
-
+ + )} + + ); -}; - -export default SettingsAgent; +} diff --git a/browser/data-browser/src/routes/paths.tsx b/browser/data-browser/src/routes/paths.tsx index 66531d2a7..bd6b27b3e 100644 --- a/browser/data-browser/src/routes/paths.tsx +++ b/browser/data-browser/src/routes/paths.tsx @@ -15,6 +15,7 @@ export const paths = { history: '/app/history', allVersions: '/all-versions', sandbox: '/sandbox', + confirmEmail: '/confirm-email', fetchBookmark: '/fetch-bookmark', pruneTests: '/prunetests', }; diff --git a/browser/data-browser/src/views/ChatRoomPage.tsx b/browser/data-browser/src/views/ChatRoomPage.tsx index ebd8eb281..562e30011 100644 --- a/browser/data-browser/src/views/ChatRoomPage.tsx +++ b/browser/data-browser/src/views/ChatRoomPage.tsx @@ -22,6 +22,7 @@ import { CommitDetail } from '../components/CommitDetail'; import Markdown from '../components/datatypes/Markdown'; import { Detail } from '../components/Detail'; import { EditableTitle } from '../components/EditableTitle'; +import { Guard } from '../components/Guard'; import { NavBarSpacer } from '../components/NavBarSpacer'; import { editURL } from '../helpers/navigation'; import { ResourceInline } from './ResourceInline'; @@ -147,25 +148,28 @@ export function ChatRoomPage({ resource }: ResourcePageProps) { )} - - - - Send - - + + + + + Send + + + + ); diff --git a/browser/data-browser/src/views/CollectionPage.tsx b/browser/data-browser/src/views/CollectionPage.tsx index e467a39c6..a83d7a85c 100644 --- a/browser/data-browser/src/views/CollectionPage.tsx +++ b/browser/data-browser/src/views/CollectionPage.tsx @@ -13,6 +13,7 @@ import { FaArrowLeft, FaArrowRight, FaInfo, + FaPlus, FaTable, FaThLarge, } from 'react-icons/fa'; @@ -171,8 +172,9 @@ function Collection({ resource }: ResourcePageProps): JSX.Element { {isClass && ( diff --git a/browser/data-browser/src/views/CrashPage.tsx b/browser/data-browser/src/views/CrashPage.tsx index 520f82586..f5d559913 100644 --- a/browser/data-browser/src/views/CrashPage.tsx +++ b/browser/data-browser/src/views/CrashPage.tsx @@ -14,6 +14,32 @@ type ErrorPageProps = { clearError: () => void; }; +const githubIssueTemplate = ( + message, + stack, +) => `**Describe what you did to produce the bug** + +## Error message +\`\`\` +${message} +\`\`\` + +## Stack trace +\`\`\` +${stack} +\`\`\` +`; + +function createGithubIssueLink(error: Error): string { + const url = new URL( + 'https://github.com/atomicdata-dev/atomic-data-browser/issues/new', + ); + url.searchParams.set('body', githubIssueTemplate(error.message, error.stack)); + url.searchParams.set('labels', 'bug'); + + return url.href; +} + /** If the entire app crashes, show this page */ function CrashPage({ resource, @@ -26,6 +52,7 @@ function CrashPage({ {children ? children : } + Create Github issue {clearError && } diff --git a/browser/data-browser/src/views/DrivePage.tsx b/browser/data-browser/src/views/DrivePage.tsx index 8c725b7b0..3cf8b69d3 100644 --- a/browser/data-browser/src/views/DrivePage.tsx +++ b/browser/data-browser/src/views/DrivePage.tsx @@ -69,23 +69,23 @@ function DrivePage({ resource }: ResourcePageProps): JSX.Element { - ))} - - - Create new resource - - - - - {baseURL.startsWith('http://localhost') && ( -

- You are running Atomic-Server on `localhost`, which means that it - will not be available from any other machine than your current local - device. If you want your Atomic-Server to be available from the web, - you should set this up at a Domain on a server. -

- )} - + ); + })} + + + Create new resource + + + + + {baseURL.includes('localhost') && ( +

+ You are running Atomic-Server on `localhost`, which means that it will + not be available from any other machine than your current local + device. If you want your Atomic-Server to be available from the web, + you should set this up at a Domain on a server. +

+ )} ); } diff --git a/browser/data-browser/src/views/ErrorPage.tsx b/browser/data-browser/src/views/ErrorPage.tsx index 4be11ec79..95a708ace 100644 --- a/browser/data-browser/src/views/ErrorPage.tsx +++ b/browser/data-browser/src/views/ErrorPage.tsx @@ -3,12 +3,12 @@ import { isUnauthorized, useStore } from '@tomic/react'; import { ContainerWide } from '../components/Containers'; import { ErrorBlock } from '../components/ErrorLook'; import { Button } from '../components/Button'; -import { SignInButton } from '../components/SignInButton'; import { useSettings } from '../helpers/AppSettings'; import { ResourcePageProps } from './ResourcePage'; import { Column, Row } from '../components/Row'; import CrashPage from './CrashPage'; import { clearAllLocalData } from '../helpers/clearData'; +import { Guard } from '../components/Guard'; /** * A View for Resource Errors. Not to be confused with the CrashPage, which is @@ -19,13 +19,26 @@ function ErrorPage({ resource }: ResourcePageProps): JSX.Element { const store = useStore(); const subject = resource.getSubject(); + React.useEffect(() => { + // Try again when agent changes + store.fetchResourceFromServer(subject); + }, [agent]); + if (isUnauthorized(resource.error)) { + // This might be a bit too aggressive, but it fixes 'Unauthorized' messages after signing in to a new drive. + store.fetchResourceFromServer(subject); + return (

Unauthorized

{agent ? ( <> +

+ { + "You don't have access to this. Try asking for access, or sign in with a different account." + } +

- + {/* {pageState === PageStateOpts.register && ( )} - + */} ); } diff --git a/browser/data-browser/src/views/ErrorPage.tsx b/browser/data-browser/src/views/ErrorPage.tsx index 95a708ace..43e26f358 100644 --- a/browser/data-browser/src/views/ErrorPage.tsx +++ b/browser/data-browser/src/views/ErrorPage.tsx @@ -17,7 +17,7 @@ import { Guard } from '../components/Guard'; function ErrorPage({ resource }: ResourcePageProps): JSX.Element { const { agent } = useSettings(); const store = useStore(); - const subject = resource.getSubject(); + const subject = resource.subject; React.useEffect(() => { // Try again when agent changes @@ -60,8 +60,8 @@ function ErrorPage({ resource }: ResourcePageProps): JSX.Element { return ( -

Could not open {resource.getSubject()}

- +

Could not open {resource.subject}

+ + ); +} + export function ErrorBlock({ error, showTrace }: ErrorBlockProps): JSX.Element { return ( @@ -48,14 +86,6 @@ const ErrorLookBig = styled.div` background-color: ${p => p.theme.colors.bg}; `; -const Pre = styled.pre` - white-space: pre-wrap; - border-radius: ${p => p.theme.radius}; - padding: ${p => p.theme.margin}rem; - background-color: ${p => p.theme.colors.bg}; - font-size: 0.9rem; -`; - const BiggerText = styled.p` color: ${p => p.theme.colors.alert}; font-size: 1.3rem; diff --git a/browser/data-browser/src/routes/DataRoute.tsx b/browser/data-browser/src/routes/DataRoute.tsx index 1aa41f022..3134e6fba 100644 --- a/browser/data-browser/src/routes/DataRoute.tsx +++ b/browser/data-browser/src/routes/DataRoute.tsx @@ -59,7 +59,7 @@ function Data(): JSX.Element { setTextResponseLoading(true); try { - const resp = await window.fetch(subject!, { headers: headers }); + const resp = await fetch(subject!, { headers: headers }); const body = await resp.text(); setTextResponseLoading(false); setTextResponse(body); diff --git a/browser/data-browser/src/views/CrashPage.tsx b/browser/data-browser/src/views/CrashPage.tsx index f5d559913..e8e88fa04 100644 --- a/browser/data-browser/src/views/CrashPage.tsx +++ b/browser/data-browser/src/views/CrashPage.tsx @@ -2,7 +2,11 @@ import * as React from 'react'; import { Resource } from '@tomic/react'; import { ContainerWide } from '../components/Containers'; -import { ErrorBlock } from '../components/ErrorLook'; +import { + createGithubIssueLink, + ErrorBlock, + GitHubIssueButton, +} from '../components/ErrorLook'; import { Button } from '../components/Button'; import { Column, Row } from '../components/Row'; @@ -14,32 +18,6 @@ type ErrorPageProps = { clearError: () => void; }; -const githubIssueTemplate = ( - message, - stack, -) => `**Describe what you did to produce the bug** - -## Error message -\`\`\` -${message} -\`\`\` - -## Stack trace -\`\`\` -${stack} -\`\`\` -`; - -function createGithubIssueLink(error: Error): string { - const url = new URL( - 'https://github.com/atomicdata-dev/atomic-data-browser/issues/new', - ); - url.searchParams.set('body', githubIssueTemplate(error.message, error.stack)); - url.searchParams.set('labels', 'bug'); - - return url.href; -} - /** If the entire app crashes, show this page */ function CrashPage({ resource, @@ -53,6 +31,7 @@ function CrashPage({ {children ? children : } Create Github issue + {clearError && } - {/* + {pageState === PageStateOpts.register && ( )} - */} + ); } @@ -135,7 +128,7 @@ function Reset({ email, setEmail, setPageState }) { return ( <> -

Reset your PassKey

+

Reset your Secret

@@ -145,7 +138,7 @@ function Reset({ email, setEmail, setPageState }) {

{ + setEmail={(e: unknown) => { setErr(undefined); setEmail(e); }} @@ -277,7 +270,7 @@ function SignIn({ setPageState }) { Register diff --git a/browser/data-browser/src/components/SettingsAgent.tsx b/browser/data-browser/src/components/SettingsAgent.tsx index edda04afa..90b2b9671 100644 --- a/browser/data-browser/src/components/SettingsAgent.tsx +++ b/browser/data-browser/src/components/SettingsAgent.tsx @@ -108,9 +108,9 @@ export const SettingsAgent: React.FunctionComponent = () => { return (
@@ -124,7 +124,7 @@ export const SettingsAgent: React.FunctionComponent = () => { id='current-password' autoComplete='current-password' spellCheck='false' - placeholder='Paste your Passphrase' + placeholder='Paste your Secret' /> = ({ onClose(); }, [name, createResourceAndNavigate, onClose, parent]); - const [dialogProps, show, hide] = useDialog({ onSuccess, onCancel: onClose }); + const { dialogProps, show, close } = useDialog({ + onSuccess, + onCancel: onClose, + }); const onNameChange = (e: React.ChangeEvent) => { setName(e.target.value); @@ -69,7 +72,7 @@ export const NewArticleDialog: FC = ({ { e.preventDefault(); - hide(true); + close(true); }} > @@ -89,10 +92,10 @@ export const NewArticleDialog: FC = ({
- - diff --git a/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewCollectionDialog.tsx b/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewCollectionDialog.tsx index 349866c84..09f2ccd54 100644 --- a/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewCollectionDialog.tsx +++ b/browser/data-browser/src/components/forms/NewForm/CustomCreateActions/CustomForms/NewCollectionDialog.tsx @@ -22,7 +22,7 @@ export const NewCollectionDialog: FC = ({ const [valueFilter, setValue] = useState(); const [propertyFilter, setProperty] = useState(); - const [dialogProps, show, hide] = useDialog({ onCancel: onClose }); + const { dialogProps, show, close } = useDialog({ onCancel: onClose }); const createResourceAndNavigate = useCreateAndNavigate(); @@ -91,7 +91,7 @@ export const NewCollectionDialog: FC = ({ - + )} ); diff --git a/browser/data-browser/src/routes/History/HistoryMobileView.tsx b/browser/data-browser/src/routes/History/HistoryMobileView.tsx index b69ac8f6b..df0b57466 100644 --- a/browser/data-browser/src/routes/History/HistoryMobileView.tsx +++ b/browser/data-browser/src/routes/History/HistoryMobileView.tsx @@ -23,7 +23,7 @@ export function HistoryMobileView({ onSelectVersion, onVersionAccept, }: HistoryViewProps) { - const [dialogProps, showDialog, closeDialog] = useDialog(); + const { dialogProps, show: showDialog, close: closeDialog } = useDialog(); const handleVersionSelect = useCallback((version: Version) => { onSelectVersion(version); diff --git a/browser/data-browser/src/routes/Sandbox.tsx b/browser/data-browser/src/routes/Sandbox.tsx index 8685fc42f..f7ffb6e45 100644 --- a/browser/data-browser/src/routes/Sandbox.tsx +++ b/browser/data-browser/src/routes/Sandbox.tsx @@ -1,6 +1,15 @@ +import { Button } from '../components/Button'; import { ContainerFull } from '../components/Containers'; +import { + Dialog, + DialogContent, + DialogTitle, + useDialog, +} from '../components/Dialog'; export function Sandbox(): JSX.Element { + const { dialogProps, show, isOpen } = useDialog(); + return (
@@ -9,6 +18,12 @@ export function Sandbox(): JSX.Element { Welcome to the sandbox. This is a place to test components in isolation.

+

{isOpen ? 'TRUE' : 'FALSE'}

+ + + Title + Content +
); diff --git a/browser/data-browser/src/routes/SettingsAgent.tsx b/browser/data-browser/src/routes/SettingsAgent.tsx index c646027d5..61d7f7e0c 100644 --- a/browser/data-browser/src/routes/SettingsAgent.tsx +++ b/browser/data-browser/src/routes/SettingsAgent.tsx @@ -1,5 +1,3 @@ -import * as React from 'react'; - import { useSettings } from '../helpers/AppSettings'; import { Button } from '../components/Button'; import { Margin } from '../components/Card'; diff --git a/browser/data-browser/src/views/CodeUsage/ResourceCodeUsageDialog.tsx b/browser/data-browser/src/views/CodeUsage/ResourceCodeUsageDialog.tsx index a00b091ce..f903aba5c 100644 --- a/browser/data-browser/src/views/CodeUsage/ResourceCodeUsageDialog.tsx +++ b/browser/data-browser/src/views/CodeUsage/ResourceCodeUsageDialog.tsx @@ -21,7 +21,7 @@ export function ResourceCodeUsageDialog({ bindShow, }: ResourceCodeUsageDialogProps): React.JSX.Element { const resource = useResource(subject); - const [dialogProps, show, hide, isOpen] = useDialog({ bindShow }); + const { dialogProps, show, close: hide, isOpen } = useDialog({ bindShow }); useEffect(() => { if (open) { diff --git a/browser/data-browser/src/views/OntologyPage/CreateInstanceButton.tsx b/browser/data-browser/src/views/OntologyPage/CreateInstanceButton.tsx index 7c14ff9e0..d6f52eb97 100644 --- a/browser/data-browser/src/views/OntologyPage/CreateInstanceButton.tsx +++ b/browser/data-browser/src/views/OntologyPage/CreateInstanceButton.tsx @@ -14,7 +14,7 @@ interface CreateInstanceButtonProps { export function CreateInstanceButton({ ontology }: CreateInstanceButtonProps) { const [active, setActive] = useState(false); const [classSubject, setClassSubject] = useState(); - const [dialogProps, show, close, isOpen] = useDialog({ + const { dialogProps, show, close, isOpen } = useDialog({ onSuccess: () => { setClassSubject(undefined); setActive(false); diff --git a/browser/data-browser/src/views/OntologyPage/NewClassButton.tsx b/browser/data-browser/src/views/OntologyPage/NewClassButton.tsx index feb0a8da9..7e06b883d 100644 --- a/browser/data-browser/src/views/OntologyPage/NewClassButton.tsx +++ b/browser/data-browser/src/views/OntologyPage/NewClassButton.tsx @@ -28,7 +28,12 @@ export function NewClassButton({ resource }: NewClassButtonProps): JSX.Element { const subject = subjectForClass(resource, inputValue); - const [dialogProps, show, hide, isOpen] = useDialog({ + const { + dialogProps, + show, + close: hide, + isOpen, + } = useDialog({ onSuccess: () => { newClass(inputValue, resource, store); }, diff --git a/browser/data-browser/src/views/OntologyPage/Property/PropertyLineWrite.tsx b/browser/data-browser/src/views/OntologyPage/Property/PropertyLineWrite.tsx index 82f75fedd..b845a0b1a 100644 --- a/browser/data-browser/src/views/OntologyPage/Property/PropertyLineWrite.tsx +++ b/browser/data-browser/src/views/OntologyPage/Property/PropertyLineWrite.tsx @@ -25,7 +25,7 @@ export function PropertyLineWrite({ const resource = useResource(subject); const shortnameProp = useProperty(core.properties.shortname); const descriptionProp = useProperty(core.properties.description); - const [dialogProps, show, hide] = useDialog(); + const { dialogProps, show, close: hide } = useDialog(); const [canEdit] = useCanWrite(resource); const { hasProperty } = useOntologyContext(); diff --git a/browser/data-browser/src/views/OntologyPage/Property/PropertyWriteDialog.tsx b/browser/data-browser/src/views/OntologyPage/Property/PropertyWriteDialog.tsx index 0d143c6cf..396983f45 100644 --- a/browser/data-browser/src/views/OntologyPage/Property/PropertyWriteDialog.tsx +++ b/browser/data-browser/src/views/OntologyPage/Property/PropertyWriteDialog.tsx @@ -25,7 +25,7 @@ export function PropertyWriteDialog({ return ( - {dialogProps.show && ( + {dialogProps.isVisible && ( <> { if (showDialog) { diff --git a/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx b/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx index ab1e62480..52f8bb4a3 100644 --- a/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx +++ b/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx @@ -30,7 +30,7 @@ export function ExternalPropertyDialog({ urls.properties.recommends, { commit: true }, ); - const [dialogProps, show, hide] = useDialog({ bindShow }); + const { dialogProps, show, close: hide } = useDialog({ bindShow }); const onAddClick = () => { if (subject) { diff --git a/browser/data-browser/src/views/TablePage/PropertyForm/NewPropertyDialog.tsx b/browser/data-browser/src/views/TablePage/PropertyForm/NewPropertyDialog.tsx index b00bbe3f2..d7f1c3faf 100644 --- a/browser/data-browser/src/views/TablePage/PropertyForm/NewPropertyDialog.tsx +++ b/browser/data-browser/src/views/TablePage/PropertyForm/NewPropertyDialog.tsx @@ -133,7 +133,11 @@ export function NewPropertyDialog({ setResource(null); }, [resource, store, tableClassResource, pushProp]); - const [dialogProps, show, hide] = useDialog({ + const { + dialogProps, + show, + close: hide, + } = useDialog({ bindShow, onCancel: handleUserCancelAction, onSuccess: handleUserSuccessAction, diff --git a/browser/e2e/tests/e2e.spec.ts b/browser/e2e/tests/e2e.spec.ts index 86d1575d9..21808d1f0 100644 --- a/browser/e2e/tests/e2e.spec.ts +++ b/browser/e2e/tests/e2e.spec.ts @@ -65,9 +65,9 @@ test.describe('data-browser', async () => { // Sign out await openAgentPage(page); await page.click('[data-test="sign-out"]'); - await expect(page.locator('text=Enter your Agent secret')).toBeVisible(); + await expect(page.locator('text=Enter your Secret')).toBeVisible(); await page.reload(); - await expect(page.locator('text=Enter your Agent secret')).toBeVisible(); + await expect(page.locator('text=Enter your Secret')).toBeVisible(); }); test('sign up and edit document atomicdata.dev', async ({ page }) => { diff --git a/browser/lib/src/authentication.ts b/browser/lib/src/authentication.ts index 39d077c82..a5482fb4e 100644 --- a/browser/lib/src/authentication.ts +++ b/browser/lib/src/authentication.ts @@ -1,6 +1,7 @@ import { Agent } from './agent.js'; import type { HeadersObject } from './client.js'; import { generateKeyPair, getTimestampNow, signToBase64 } from './commit.js'; +import type { Resource } from './resource.js'; import { Store } from './store.js'; import { properties } from './urls.js'; @@ -122,7 +123,7 @@ export const nameRegex = '^[a-z0-9_-]+'; export async function serverSupportsRegister(store: Store) { const url = new URL('/register', store.getServerUrl()); - const resource = await store.getResourceAsync(url.toString()); + const resource = await store.getResource(url.toString()); if (!resource) { return false; @@ -135,6 +136,23 @@ export async function serverSupportsRegister(store: Store) { return true; } +/** Run this after making a call to an endpoint. Throws if something went wrong. */ +function checkResourceSuccess(resource?: Resource) { + if (!resource) { + throw new Error('No resource received'); + } + + if (resource.error) { + throw resource.error; + } + + const respName = resource.get(properties.name) as string; + + if (!respName.includes('Success')) { + throw new Error('Expected a `success` message, did not receive one'); + } +} + /** Asks the server to create an Agent + a Drive. * Sends the confirmation email to the user. * Throws if the name is not available or the email is invalid. @@ -148,20 +166,7 @@ export async function register( url.searchParams.set('name', name); url.searchParams.set('email', email); const resource = await store.getResourceAsync(url.toString()); - - if (!resource) { - throw new Error('No resource received'); - } - - if (resource.error) { - throw resource.error; - } - - const description = resource.get(properties.description) as string; - - if (!description.includes('success')) { - throw new Error('Expected a `success` message, did not receive one'); - } + checkResourceSuccess(resource); return; } @@ -175,20 +180,7 @@ export async function addPublicKey(store: Store, email: string): Promise { const url = new URL('/add-public-key', store.getServerUrl()); url.searchParams.set('email', email); const resource = await store.getResourceAsync(url.toString()); - - if (!resource) { - throw new Error('No resource received'); - } - - if (resource.error) { - throw resource.error; - } - - const description = resource.get(properties.description) as string; - - if (!description.includes('success')) { - throw new Error('Expected a `success` message, did not receive one'); - } + checkResourceSuccess(resource); return; } @@ -252,7 +244,7 @@ export async function confirmEmail( return { agent, destination }; } -function parseJwt(token) { +function parseJwt(token: string) { try { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); diff --git a/browser/package.json b/browser/package.json index cac894ce7..55910501a 100644 --- a/browser/package.json +++ b/browser/package.json @@ -23,7 +23,7 @@ "typedoc": "^0.25.3", "typedoc-plugin-missing-exports": "^2.1.0", "typescript": "^5.4.5", - "vite": "^5.0.12", + "vite": "^5.3.4", "eslint-plugin-import": "^2.26.0" }, "name": "@tomic/root", diff --git a/browser/pnpm-lock.yaml b/browser/pnpm-lock.yaml index 867972a95..3ba2fc772 100644 --- a/browser/pnpm-lock.yaml +++ b/browser/pnpm-lock.yaml @@ -81,8 +81,8 @@ importers: specifier: ^5.4.5 version: 5.5.3 vite: - specifier: ^5.0.12 - version: 5.3.3(@types/node@20.14.10) + specifier: ^5.3.4 + version: 5.3.4(@types/node@20.14.10) cli: dependencies: @@ -346,7 +346,7 @@ importers: version: 3.9.1(@types/node@20.14.10)(typescript@5.5.3)(vite@5.3.3) vitest: specifier: ^0.34.6 - version: 0.34.6(playwright@1.43.1)(terser@5.31.0) + version: 0.34.6 react: dependencies: @@ -448,7 +448,7 @@ packages: '@babel/traverse': 7.24.8 '@babel/types': 7.24.9 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -534,7 +534,7 @@ packages: '@babel/core': 7.24.9 '@babel/helper-compilation-targets': 7.24.8 '@babel/helper-plugin-utils': 7.24.8 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -1669,7 +1669,7 @@ packages: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.8 '@babel/types': 7.24.9 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -2495,7 +2495,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -2642,7 +2642,7 @@ packages: deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2973,7 +2973,7 @@ packages: engines: {node: '>=8'} dev: true - /@mapbox/node-pre-gyp@1.0.11: + /@mapbox/node-pre-gyp@1.0.11(supports-color@9.4.0): resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true dependencies: @@ -2990,24 +2990,6 @@ packages: - encoding - supports-color - /@mapbox/node-pre-gyp@1.0.11(supports-color@9.4.0): - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} - hasBin: true - dependencies: - detect-libc: 2.0.3 - https-proxy-agent: 5.0.1(supports-color@9.4.0) - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.6.2 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - /@microsoft/api-extractor-model@7.28.13(@types/node@20.14.10): resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==} dependencies: @@ -3415,48 +3397,6 @@ packages: urlpattern-polyfill: 8.0.2 dev: true - /@netlify/zip-it-and-ship-it@9.17.0: - resolution: {integrity: sha512-7wnrWxtczXzBMYh9QXmvG9WkCJSyK+abQGdhwSoZcFPQ0u0HZzY/9rU8jLcRnTodEK20lZPil60FRU/Nta5spg==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - dependencies: - '@babel/parser': 7.24.8 - '@netlify/binary-info': 1.0.0 - '@netlify/serverless-functions-api': 1.7.3 - '@vercel/nft': 0.23.1 - archiver: 5.3.2 - common-path-prefix: 3.0.0 - cp-file: 10.0.0 - es-module-lexer: 1.5.4 - esbuild: 0.19.2 - execa: 6.1.0 - filter-obj: 5.1.0 - find-up: 6.3.0 - get-tsconfig: 4.7.5 - glob: 8.1.0 - is-builtin-module: 3.2.1 - is-path-inside: 4.0.0 - junk: 4.0.1 - locate-path: 7.2.0 - merge-options: 3.0.4 - minimatch: 9.0.5 - normalize-path: 3.0.0 - p-map: 5.5.0 - path-exists: 5.0.0 - precinct: 11.0.5 - require-package-name: 2.0.1 - resolve: 2.0.0-next.5 - semver: 7.5.4 - tmp-promise: 3.0.3 - toml: 3.0.0 - unixify: 1.0.0 - urlpattern-polyfill: 8.0.2 - yargs: 17.7.2 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - /@netlify/zip-it-and-ship-it@9.17.0(supports-color@9.4.0): resolution: {integrity: sha512-7wnrWxtczXzBMYh9QXmvG9WkCJSyK+abQGdhwSoZcFPQ0u0HZzY/9rU8jLcRnTodEK20lZPil60FRU/Nta5spg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -6073,8 +6013,8 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) - debug: 4.3.5 + '@typescript-eslint/typescript-estree': 5.62.0(supports-color@9.4.0)(typescript@5.5.3) + debug: 4.3.5(supports-color@9.4.0) eslint: 8.57.0 typescript: 5.5.3 transitivePeerDependencies: @@ -6095,7 +6035,7 @@ packages: '@typescript-eslint/types': 7.16.0 '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) '@typescript-eslint/visitor-keys': 7.16.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) eslint: 8.57.0 typescript: 5.5.3 transitivePeerDependencies: @@ -6130,7 +6070,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.5.3) typescript: 5.5.3 @@ -6159,28 +6099,7 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.4(supports-color@9.4.0) - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - tsutils: 3.21.0(typescript@5.5.3) - typescript: 5.5.3 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3): - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -6201,7 +6120,7 @@ packages: dependencies: '@typescript-eslint/types': 7.16.0 '@typescript-eslint/visitor-keys': 7.16.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -6248,27 +6167,6 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vercel/nft@0.23.1: - resolution: {integrity: sha512-NE0xSmGWVhgHF1OIoir71XAd0W0C1UE3nzFyhpFiMr3rVhetww7NvM1kc41trBsPG37Bh+dE5FYCTMzM/gBu0w==} - engines: {node: '>=14'} - hasBin: true - dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - '@rollup/pluginutils': 4.2.1 - acorn: 8.12.1 - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - micromatch: 4.0.7 - node-gyp-build: 4.8.1 - resolve-from: 5.0.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - /@vercel/nft@0.23.1(supports-color@9.4.0): resolution: {integrity: sha512-NE0xSmGWVhgHF1OIoir71XAd0W0C1UE3nzFyhpFiMr3rVhetww7NvM1kc41trBsPG37Bh+dE5FYCTMzM/gBu0w==} engines: {node: '>=14'} @@ -7626,7 +7524,7 @@ packages: engines: {node: '>=6'} requiresBuild: true dependencies: - '@mapbox/node-pre-gyp': 1.0.11 + '@mapbox/node-pre-gyp': 1.0.11(supports-color@9.4.0) nan: 2.20.0 simple-get: 3.1.1 transitivePeerDependencies: @@ -8313,7 +8211,7 @@ packages: luxon: 3.4.4 dev: true - cross-spawn@7.0.3: + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} dependencies: @@ -8504,7 +8402,7 @@ packages: ms: 2.1.2 supports-color: 9.4.0 - /debug@4.3.5: + /debug@4.3.5(supports-color@9.4.0): resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} engines: {node: '>=6.0'} peerDependencies: @@ -8514,6 +8412,7 @@ packages: optional: true dependencies: ms: 2.1.2 + supports-color: 9.4.0 /decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} @@ -8791,18 +8690,6 @@ packages: engines: {node: '>=14'} dev: true - /detective-typescript@11.2.0: - resolution: {integrity: sha512-ARFxjzizOhPqs1fYC/2NMC3N4jrQ6HvVflnXBTRqNEqJuXwyKLRr9CrJwkRcV/SnZt1sNXgsF6FPm0x57Tq0rw==} - engines: {node: ^14.14.0 || >=16.0.0} - dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) - ast-module-types: 5.0.0 - node-source-walk: 6.0.2 - typescript: 5.5.3 - transitivePeerDependencies: - - supports-color - dev: true - /detective-typescript@11.2.0(supports-color@9.4.0): resolution: {integrity: sha512-ARFxjzizOhPqs1fYC/2NMC3N4jrQ6HvVflnXBTRqNEqJuXwyKLRr9CrJwkRcV/SnZt1sNXgsF6FPm0x57Tq0rw==} engines: {node: ^14.14.0 || >=16.0.0} @@ -9485,7 +9372,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11633,7 +11520,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -12441,7 +12328,7 @@ packages: cli-truncate: 2.1.0 commander: 6.2.1 cosmiconfig: 7.1.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) dedent: 0.7.0 enquirer: 2.4.1 execa: 4.1.0 @@ -13492,7 +13379,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -13516,7 +13403,7 @@ packages: resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -13845,7 +13732,7 @@ packages: '@netlify/edge-bundler': 8.19.0 '@netlify/local-functions-proxy': 1.1.1 '@netlify/serverless-functions-api': 1.7.3 - '@netlify/zip-it-and-ship-it': 9.17.0 + '@netlify/zip-it-and-ship-it': 9.17.0(supports-color@9.4.0) '@octokit/rest': 19.0.13 ansi-escapes: 6.2.0 ansi-styles: 6.2.1 @@ -14012,17 +13899,6 @@ packages: dependencies: whatwg-url: 5.0.0 - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - dependencies: - whatwg-url: 5.0.0 - /node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -14858,27 +14734,6 @@ packages: source-map-js: 1.2.0 dev: true - /precinct@11.0.5: - resolution: {integrity: sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w==} - engines: {node: ^14.14.0 || >=16.0.0} - hasBin: true - dependencies: - '@dependents/detective-less': 4.1.0 - commander: 10.0.1 - detective-amd: 5.0.2 - detective-cjs: 5.0.1 - detective-es6: 4.0.1 - detective-postcss: 6.1.3 - detective-sass: 5.0.3 - detective-scss: 4.0.3 - detective-stylus: 4.0.0 - detective-typescript: 11.2.0 - module-definition: 5.0.1 - node-source-walk: 6.0.2 - transitivePeerDependencies: - - supports-color - dev: true - /precinct@11.0.5(supports-color@9.4.0): resolution: {integrity: sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w==} engines: {node: ^14.14.0 || >=16.0.0} @@ -18055,11 +17910,11 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) mlly: 1.7.1 pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.10) + vite: 5.3.4(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - less @@ -18084,7 +17939,7 @@ packages: '@microsoft/api-extractor': 7.43.0(@types/node@20.14.10) '@rollup/pluginutils': 5.1.0(rollup@2.79.1) '@vue/language-core': 1.8.27(typescript@5.5.3) - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) kolorist: 1.8.0 magic-string: 0.30.10 typescript: 5.5.3 @@ -18115,7 +17970,7 @@ packages: workbox-build: ^7.0.0 workbox-window: ^7.0.0 dependencies: - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) fast-glob: 3.3.2 pretty-bytes: 6.1.1 vite: 5.3.3(@types/node@20.14.10) @@ -18175,6 +18030,42 @@ packages: fsevents: 2.3.3 dev: true + /vite@5.3.4(@types/node@20.14.10): + resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.14.10 + esbuild: 0.21.5 + postcss: 8.4.39 + rollup: 4.18.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vitest@0.34.6: resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} @@ -18218,7 +18109,7 @@ packages: acorn-walk: 8.3.3 cac: 6.7.14 chai: 4.4.1 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) local-pkg: 0.4.3 magic-string: 0.30.10 pathe: 1.1.2 @@ -18318,7 +18209,7 @@ packages: engines: {node: '>=6'} dev: true - whatwg-url@5.0.0: + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 @@ -26942,7 +26833,6 @@ snapshots: dependencies: luxon: 3.4.4 dev: true - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -35328,7 +35218,6 @@ snapshots: resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} engines: {node: '>=6'} dev: true - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/browser/react/src/useServerSupports.ts b/browser/react/src/useServerSupports.ts index 15301ea7f..ad2695e13 100644 --- a/browser/react/src/useServerSupports.ts +++ b/browser/react/src/useServerSupports.ts @@ -5,16 +5,16 @@ export function useServerSupports(): ServerSupports { const store = useStore(); const serverURL = useServerURL(); const [supports, setSupports] = useState({ - emailRegister: false, + emailRegister: true, }); useEffect(() => { - async function check() { - const res = await store.getServerSupports(); - setSupports(res); - } - - check(); + console.log('useEffect'); + // async function check() { + // const res = await store.getServerSupports(); + // setSupports(res); + // } + // check(); }, [store, serverURL]); return supports; diff --git a/cli/src/commit.rs b/cli/src/commit.rs index 1251a0721..88bf8ee13 100644 --- a/cli/src/commit.rs +++ b/cli/src/commit.rs @@ -8,7 +8,7 @@ pub fn set(context: &Context, subject: &str, property: &str, value: &str) -> Ato Ok(r) => r, Err(_) => atomic_lib::Resource::new(subject.into()), }; - resource.set_shortname(&property, &value, &context.store)?; + resource.set_shortname(property, value, &context.store)?; resource.save(&context.store)?; Ok(()) } @@ -17,19 +17,19 @@ pub fn set(context: &Context, subject: &str, property: &str, value: &str) -> Ato #[cfg(feature = "native")] pub fn edit(context: &Context, subject: &str, prop: &str) -> AtomicResult<()> { // If the resource is not found, create it - let mut resource = match context.store.get_resource(&subject) { + let mut resource = match context.store.get_resource(subject) { Ok(r) => r, Err(_) => atomic_lib::Resource::new(subject.into()), }; // If the prop is not found, create it - let current_val = match resource.get_shortname(&prop, &context.store) { + let current_val = match resource.get_shortname(prop, &context.store) { Ok(val) => val.to_string(), Err(_) => "".to_string(), }; let edited = edit::edit(current_val)?; // Remove newline - or else I can's save shortnames or numbers using vim; let trimmed = edited.trim_end_matches('\n'); - resource.set_shortname(&prop, trimmed, &context.store)?; + resource.set_shortname(prop, trimmed, &context.store)?; resource.save(&context.store)?; Ok(()) } @@ -37,7 +37,7 @@ pub fn edit(context: &Context, subject: &str, prop: &str) -> AtomicResult<()> { /// Apply a Commit using the Remove method - removes a property from a resource pub fn remove(context: &Context, subject: &str, prop: &str) -> AtomicResult<()> { let mut resource = context.store.get_resource(subject)?; - resource.remove_propval_shortname(&prop, &context.store)?; + resource.remove_propval_shortname(prop, &context.store)?; resource.save(&context.store)?; Ok(()) } diff --git a/cli/src/main.rs b/cli/src/main.rs index f10ad2acb..7e2b15982 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,5 +1,4 @@ use atomic_lib::serialize::Format; -use atomic_lib::atomic_url::Routes; use atomic_lib::{agents::generate_public_key, mapping::Mapping}; use atomic_lib::{agents::Agent, config::Config}; use atomic_lib::{errors::AtomicResult, Storelike}; @@ -114,9 +113,9 @@ pub enum SerializeOptions { NTriples, } -impl Into for SerializeOptions { - fn into(self) -> Format { - match self { +impl From<&SerializeOptions> for Format { + fn from(val: &SerializeOptions) -> Self { + match val { SerializeOptions::Pretty => Format::Pretty, SerializeOptions::Json => Format::Json, SerializeOptions::NTriples => Format::NTriples, diff --git a/cli/src/path.rs b/cli/src/path.rs index 9d7a5fdce..145677a7b 100644 --- a/cli/src/path.rs +++ b/cli/src/path.rs @@ -4,7 +4,7 @@ use atomic_lib::{agents::ForAgent, errors::AtomicResult, serialize, storelike, A /// Resolves an Atomic Path query pub fn get_path( context: &mut Context, - path_vec: &Vec, + path_vec: &[String], serialize: &SerializeOptions, ) -> AtomicResult<()> { // let subcommand_matches = context.matches.subcommand_matches("get").unwrap(); diff --git a/cli/src/print.rs b/cli/src/print.rs index a05f55276..83a568f13 100644 --- a/cli/src/print.rs +++ b/cli/src/print.rs @@ -7,7 +7,7 @@ use colored::*; use crate::{Context, SerializeOptions}; -/// Prints a resource for the terminal with readble formatting and colors +/// Prints a resource for the terminal with readable formatting and colors pub fn pretty_print_resource(resource: &Resource, store: &impl Storelike) -> AtomicResult { let mut output = String::new(); output.push_str(&format!( @@ -32,7 +32,7 @@ pub fn print_resource( resource: &Resource, serialize: &SerializeOptions, ) -> AtomicResult<()> { - let format: Format = serialize.clone().into(); + let format: Format = serialize.into(); let out = match format { Format::Json => resource.to_json(&context.store)?, Format::JsonLd => resource.to_json_ld(&context.store)?, diff --git a/docs/src/react/useStore.md b/docs/src/react/useStore.md index f14ce5150..9db6812ad 100644 --- a/docs/src/react/useStore.md +++ b/docs/src/react/useStore.md @@ -23,10 +23,10 @@ export const Login = () => { return (
); } @@ -148,8 +150,7 @@ export function ResourceCardDefault({ export default ResourceCard; const DescriptionWrapper = styled.div` - max-height: 10rem; - overflow: hidden; + overflow: auto; `; const ClassName = styled.span` From 0ef208d09e31e3f5354038ca291dc5803e8d3848 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 23 Jul 2024 16:44:34 +0200 Subject: [PATCH 45/45] Fix history page --- browser/lib/src/resource.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/browser/lib/src/resource.ts b/browser/lib/src/resource.ts index 4c8f1dd3e..4da0824ca 100644 --- a/browser/lib/src/resource.ts +++ b/browser/lib/src/resource.ts @@ -1,3 +1,4 @@ +import { commits } from './ontologies/commits.js'; import { EventManager } from './EventManager.js'; import type { Agent } from './agent.js'; import { Client } from './client.js'; @@ -371,19 +372,25 @@ export class Resource { public async getHistory( progressCallback?: (percentage: number) => void, ): Promise { - const commitsCollection = await this.store.fetchResourceFromServer( - this.getCommitsCollectionSubject(), - ); - const commits = commitsCollection.get( - properties.collection.members, - ) as string[]; + const commitsCollection = await new CollectionBuilder(this.store) + .setPageSize(9999) + .setProperty(commits.properties.subject) + .setValue(this.subject) + .setSortBy(commits.properties.createdAt) + .buildAndFetch(); + + const commitSubjects = await commitsCollection.getAllMembers(); const builtVersions: Version[] = []; + if (!commitSubjects) { + return builtVersions; + } + let previousResource = new Resource(this.subject); - for (let i = 0; i < commits.length; i++) { - const commitResource = await this.store.getResource(commits[i]); + for (let i = 0; i < commitSubjects.length; i++) { + const commitResource = await this.store.getResource(commitSubjects[i]); const parsedCommit = parseCommitResource(commitResource); const builtResource = applyCommitToResource( previousResource.clone(), @@ -397,7 +404,7 @@ export class Resource { // Every 30 cycles we report the progress if (progressCallback && i % 30 === 0) { - progressCallback(Math.round((i / commits.length) * 100)); + progressCallback(Math.round((i / commitSubjects.length) * 100)); await WaitForImmediate(); } }