Skip to content

Commit 3951526

Browse files
committed
Public key reset endpoint WIP
1 parent d9f8bcc commit 3951526

20 files changed

+243
-146
lines changed

lib/src/agents.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! Agents are actors (such as users) that can edit content.
33
//! https://docs.atomicdata.dev/commits/concepts.html
44
5-
use crate::{errors::AtomicResult, urls, Resource, Storelike, Value};
5+
use crate::{errors::AtomicResult, urls, Query, Resource, Storelike, Value};
66

77
#[derive(Clone, Debug)]
88
pub struct Agent {
@@ -39,6 +39,41 @@ impl Agent {
3939
Ok(resource)
4040
}
4141

42+
pub fn from_email(email: &str, store: &impl Storelike) -> AtomicResult<Self> {
43+
let mut query = Query::new();
44+
query.property = Some(urls::EMAIL.into());
45+
query.value = Some(Value::String(email.to_string()));
46+
let response = store.query(&query)?;
47+
if response.resources.is_empty() {
48+
return Err(format!("Agent with Email {} not found", email).into());
49+
}
50+
if response.resources.len() > 1 {
51+
return Err(format!(
52+
"Email {} is not unique, {} agents have this email",
53+
email, response.count
54+
)
55+
.into());
56+
}
57+
let resource = response.resources.first().unwrap();
58+
Agent::from_resource(resource.clone())
59+
}
60+
61+
pub fn from_resource(resource: Resource) -> AtomicResult<Self> {
62+
let name = if let Ok(name) = resource.get(urls::NAME) {
63+
Some(name.to_string())
64+
} else {
65+
None
66+
};
67+
68+
return Ok(Self {
69+
created_at: resource.get(urls::CREATED_AT)?.to_int()?,
70+
name,
71+
public_key: resource.get(urls::PUBLIC_KEY)?.to_string(),
72+
private_key: None,
73+
subject: resource.get_subject().into(),
74+
});
75+
}
76+
4277
/// Creates a new Agent, generates a new Keypair.
4378
pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult<Agent> {
4479
let keypair = generate_keypair()?;

lib/src/collections.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
//! Collections are dynamic resources that refer to multiple resources.
22
//! They are constructed using a TPF query
33
use crate::{
4-
errors::AtomicResult,
5-
storelike::{Query, ResourceCollection},
6-
urls, Resource, Storelike, Value,
4+
errors::AtomicResult, storelike::ResourceCollection, urls, Query, Resource, Storelike, Value,
75
};
86

97
#[derive(Debug)]

lib/src/db.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ use crate::{
2626
email::{self, MailMessage},
2727
endpoints::{default_endpoints, Endpoint},
2828
errors::{AtomicError, AtomicResult},
29+
query::QueryResult,
2930
resources::PropVals,
30-
storelike::{Query, QueryResult, Storelike},
31+
storelike::Storelike,
3132
values::SortableValue,
32-
Atom, Resource,
33+
Atom, Query, Resource,
3334
};
3435

3536
use self::{

lib/src/db/query_index.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22
//! It relies on lexicographic ordering of keys, which Sled utilizes using `scan_prefix` queries.
33
44
use crate::{
5-
atoms::IndexAtom,
6-
errors::AtomicResult,
7-
storelike::{Query, QueryResult},
8-
values::SortableValue,
9-
Atom, Db, Resource, Storelike, Value,
5+
atoms::IndexAtom, errors::AtomicResult, query::QueryResult, values::SortableValue, Atom, Db,
6+
Query, Resource, Storelike, Value,
107
};
118
use serde::{Deserialize, Serialize};
129

lib/src/email.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! [EmailAddress] with validation, [MailMessage] with sending, and [get_smtp_client] for setting up mail.
22
3-
use crate::{errors::AtomicResult, storelike::Query, urls, Storelike};
3+
use crate::{errors::AtomicResult, urls, Query, Storelike};
44
use mail_send::{mail_builder::MessageBuilder, Connected, Transport};
55
use serde::{Deserialize, Serialize};
66
use tracing::info;

lib/src/hierarchy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use core::fmt;
66

7-
use crate::{errors::AtomicResult, storelike::Query, urls, AtomicError, Resource, Storelike};
7+
use crate::{errors::AtomicResult, urls, AtomicError, Query, Resource, Storelike};
88

99
#[derive(Debug)]
1010
pub enum Right {

lib/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub mod parse;
8787
#[cfg(feature = "db")]
8888
pub mod plugins;
8989
pub mod populate;
90+
pub mod query;
9091
pub mod resources;
9192
pub mod schema;
9293
pub mod serialize;
@@ -108,6 +109,7 @@ pub use commit::Commit;
108109
pub use db::Db;
109110
pub use errors::AtomicError;
110111
pub use errors::AtomicErrorType;
112+
pub use query::Query;
111113
pub use resources::Resource;
112114
pub use store::Store;
113115
pub use storelike::Storelike;

lib/src/plugins/chatroom.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ They list a bunch of Messages.
77
use crate::{
88
commit::{CommitBuilder, CommitResponse},
99
errors::AtomicResult,
10-
storelike::Query,
1110
urls::{self, PARENT},
12-
utils, Resource, Storelike, Value,
11+
utils, Query, Resource, Storelike, Value,
1312
};
1413

1514
// Find the messages for the ChatRoom

lib/src/plugins/importer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Importers allow users to (periodically) import JSON-AD files from a remote source.
33
*/
44

5-
use crate::{errors::AtomicResult, storelike::Query, urls, Resource, Storelike};
5+
use crate::{errors::AtomicResult, urls, Query, Resource, Storelike};
66

77
/// When an importer is shown, we list a bunch of Paramaters and a list of previously imported items.
88
#[tracing::instrument(skip(store, query_params))]

lib/src/plugins/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,6 @@ pub mod register;
4747
pub mod reset_pubkey;
4848
pub mod search;
4949
pub mod versioning;
50+
51+
// Utilities / helpers
52+
mod utils;

lib/src/plugins/register.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::{
1212
Db, Resource, Storelike, Value,
1313
};
1414

15+
use super::utils::return_success;
16+
1517
pub fn register_endpoint() -> Endpoint {
1618
Endpoint {
1719
path: urls::PATH_REGISTER.to_string(),
@@ -21,7 +23,7 @@ pub fn register_endpoint() -> Endpoint {
2123
].into(),
2224
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(),
2325
shortname: "register".to_string(),
24-
handle: Some(construct_register_redirect),
26+
handle: Some(handle_register_name_and_email),
2527
}
2628
}
2729

@@ -42,7 +44,7 @@ struct MailConfirmation {
4244
}
4345

4446
#[tracing::instrument(skip(store))]
45-
pub fn construct_register_redirect(
47+
pub fn handle_register_name_and_email(
4648
url: url::Url,
4749
store: &Db,
4850
for_agent: Option<&str>,
@@ -94,14 +96,7 @@ pub fn construct_register_redirect(
9496
.unwrap_or_else(|e| tracing::error!("Error sending email: {}", e));
9597
});
9698

97-
// Here we probably want to return some sort of SuccesMessage page.
98-
// Not sure what that should be.
99-
let mut resource = Resource::new_generate_subject(store);
100-
resource.set_propval_string(urls::DESCRIPTION.into(), "success", store)?;
101-
102-
// resource.set_propval(urls::, value, store)
103-
104-
Ok(resource)
99+
return_success()
105100
}
106101

107102
#[tracing::instrument(skip(store))]

lib/src/plugins/reset_pubkey.rs

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,125 @@
11
/*!
2-
Reset email
2+
Sends users a link to add a new public key to their account.
3+
Useful when a users loses their private key.
34
*/
45

5-
use crate::{endpoints::Endpoint, errors::AtomicResult, urls, Db, Resource};
6+
use serde::{Deserialize, Serialize};
67

7-
pub fn request_email_pubkey_reset() -> Endpoint {
8+
use crate::{
9+
agents::Agent,
10+
email::{EmailAddress, MailAction, MailMessage},
11+
endpoints::Endpoint,
12+
errors::AtomicResult,
13+
plugins::utils::return_success,
14+
urls, Db, Resource, Storelike,
15+
};
16+
17+
pub fn request_email_add_pubkey() -> Endpoint {
818
Endpoint {
9-
path: urls::PATH_RESET_PUBKEY.to_string(),
19+
path: urls::PATH_ADD_PUBKEY.to_string(),
1020
params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(),
11-
description: "Requests an email to set a new PublicKey to an Agent.".to_string(),
21+
description: "Requests an email to add a new PublicKey to an Agent.".to_string(),
1222
shortname: "request-pubkey-reset".to_string(),
13-
handle: Some(construct_reset_pubkey),
23+
handle: Some(handle_request_email_pubkey),
1424
}
1525
}
1626

17-
pub fn confirm_pubkey_reset() -> Endpoint {
27+
pub fn confirm_add_pubkey() -> Endpoint {
1828
Endpoint {
19-
path: urls::PATH_CONFIRM_RESET.to_string(),
29+
path: urls::PATH_CONFIRM_PUBKEY.to_string(),
2030
params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(),
21-
description: "Requests an email to set a new PublicKey to an Agent.".to_string(),
31+
description: "Confirms a token to add a new Public Key.".to_string(),
2232
shortname: "request-pubkey-reset".to_string(),
23-
handle: Some(construct_confirm_reset_pubkey),
33+
handle: Some(handle_confirm_add_pubkey),
34+
}
35+
}
36+
37+
#[derive(Debug, Serialize, Deserialize)]
38+
struct AddPubkeyToken {
39+
agent: String,
40+
}
41+
42+
pub fn handle_request_email_pubkey(
43+
url: url::Url,
44+
store: &Db,
45+
_for_agent: Option<&str>,
46+
) -> AtomicResult<Resource> {
47+
let mut email_option: Option<EmailAddress> = None;
48+
for (k, v) in url.query_pairs() {
49+
match k.as_ref() {
50+
"email" => email_option = Some(EmailAddress::new(v.to_string())?),
51+
_ => {}
52+
}
2453
}
54+
// by default just return the Endpoint
55+
let Some(email) = email_option else {
56+
return request_email_add_pubkey().to_resource(store);
57+
};
58+
59+
// Find the agent by their email
60+
let agent = Agent::from_email(&email.to_string(), store)?;
61+
62+
// send the user an e-mail to confirm sign up
63+
let store_clone = store.clone();
64+
let confirmation_token_struct = AddPubkeyToken {
65+
agent: agent.subject.clone(),
66+
};
67+
let token = crate::token::sign_claim(store, confirmation_token_struct)?;
68+
let mut confirm_url = store
69+
.get_server_url()
70+
.clone()
71+
.set_path(urls::PATH_CONFIRM_PUBKEY)
72+
.url();
73+
confirm_url.set_query(Some(&format!("token={}", token)));
74+
let message = MailMessage {
75+
to: email,
76+
subject: "Add a new Passphrase to your account".to_string(),
77+
body: format!("You've requested adding a new Passphrase. Click the link below to do so!"),
78+
action: Some(MailAction {
79+
name: "Add new Passphrase to account".to_string(),
80+
url: confirm_url.into(),
81+
}),
82+
};
83+
// async, because mails are slow
84+
tokio::spawn(async move {
85+
store_clone
86+
.send_email(message)
87+
.await
88+
.unwrap_or_else(|e| tracing::error!("Error sending email: {}", e));
89+
});
90+
91+
return_success()
2592
}
2693

2794
#[tracing::instrument(skip(store))]
28-
pub fn construct_confirm_reset_pubkey(
95+
pub fn handle_confirm_add_pubkey(
2996
url: url::Url,
3097
store: &Db,
3198
for_agent: Option<&str>,
3299
) -> AtomicResult<Resource> {
33100
let mut token_opt: Option<String> = None;
34101
let mut pubkey_option = None;
35102

36-
println!("url: {:?}", url);
37103
for (k, v) in url.query_pairs() {
38104
match k.as_ref() {
39105
"token" | urls::TOKEN => token_opt = Some(v.to_string()),
40106
"public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()),
41107
_ => {}
42108
}
43109
}
110+
let pubkey = pubkey_option.ok_or("No public-key provided")?;
111+
44112
let Some(token) = token_opt else {
45-
return confirm_pubkey_reset().to_resource(store);
113+
return confirm_add_pubkey().to_resource(store);
46114
};
47-
let pubkey = pubkey_option.ok_or("No public-key provided")?;
48115

49116
// Parse and verify the JWT token
50-
let confirmation = crate::token::verify_claim::<MailConfirmation>(store, &token)?.custom;
117+
let confirmation = crate::token::verify_claim::<AddPubkeyToken>(store, &token)?.custom;
51118

52-
// Add the drive to the Agent's list of drives
53-
let mut agent = store.get_resource(&drive_creator_agent)?;
54-
agent.push_propval(
55-
urls::USED_PUBKEYS.into(),
56-
SubResource::Subject(drive.get_subject().into()),
57-
true,
58-
)?;
59-
agent.save_locally(store)?;
119+
// Add the key to the agent
120+
let mut agent = store.get_resource(&confirmation.agent)?;
121+
agent.push_propval(urls::ACTIVE_KEYS, pubkey.into(), true)?;
122+
agent.save(store)?;
60123

61-
// Construct the Redirect Resource, which might provide the Client with a Subject for his Agent.
62-
let mut redirect = Resource::new_instance(urls::REDIRECT, store)?;
63-
redirect.set_propval_string(urls::DESTINATION.into(), drive.get_subject(), store)?;
64-
redirect.set_propval(
65-
urls::REDIRECT_AGENT.into(),
66-
crate::Value::AtomicUrl(drive_creator_agent),
67-
store,
68-
)?;
69-
Ok(redirect)
124+
return_success()
70125
}

lib/src/plugins/utils.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Functions that can be valuable in multiple plugins
2+
3+
use crate::{errors::AtomicResult, urls, Resource, Value};
4+
5+
/// Returns a Resource with a description of "success"
6+
pub fn return_success() -> AtomicResult<Resource> {
7+
let mut resource = Resource::new("unknown".into());
8+
resource.set_propval_unsafe(urls::DESCRIPTION.into(), Value::String("success".into()));
9+
Ok(resource)
10+
}

lib/src/plugins/versioning.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use tracing::warn;
22

33
use crate::{
4-
collections::CollectionBuilder, endpoints::Endpoint, errors::AtomicResult, storelike::Query,
5-
urls, AtomicError, Commit, Resource, Storelike,
4+
collections::CollectionBuilder, endpoints::Endpoint, errors::AtomicResult, urls, AtomicError,
5+
Commit, Query, Resource, Storelike,
66
};
77

88
pub fn version_endpoint() -> Endpoint {

lib/src/populate.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ use crate::{
88
errors::AtomicResult,
99
parse::ParseOpts,
1010
schema::{Class, Property},
11-
storelike::Query,
12-
urls, Resource, Storelike, Value,
11+
urls, Query, Resource, Storelike, Value,
1312
};
1413

1514
/// Populates a store with some of the most fundamental Properties and Classes needed to bootstrap the whole.

0 commit comments

Comments
 (0)