Skip to content

Commit c07fb58

Browse files
committed
Refactor endpoints, update JSON
1 parent 69361a5 commit c07fb58

File tree

7 files changed

+184
-14
lines changed

7 files changed

+184
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain
3838
- Re-build store + invite when adjusting server url #607
3939
- Use local atomic-server for properties and classes, improves atomic-server #604
4040
- New sign up / register flow. Add `/register` Endpoint #489 #254
41+
- New sign up / register flow. Add `/register`, `/confirm-email`, `/add-public-key` endpoints #489 #254
4142
- Add multi-tenancy support. Users can create their own `Drives` on subdomains. #288
4243
- Refactor URLs. `store.self_url()` returns an `AtomicUrl`, which provides methods to easily add paths, find subdomains and more.
4344
- Add support for subdomains, use a Wildcard TLS certificate #502

lib/defaults/default_base_models.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,23 @@
7878
},
7979
{
8080
"@id": "https://atomicdata.dev/classes/Property",
81-
"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).",
81+
"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`.",
8282
"https://atomicdata.dev/properties/isA": [
8383
"https://atomicdata.dev/classes/Class"
8484
],
85-
"https://atomicdata.dev/properties/requires": [
86-
"https://atomicdata.dev/properties/destination"
87-
],
85+
"https://atomicdata.dev/properties/parent": "https://atomicdata.dev/classes",
8886
"https://atomicdata.dev/properties/recommends": [
89-
"https://atomicdata.dev/properties/invite/redirectAgent"
87+
"https://atomicdata.dev/properties/classtype",
88+
"https://atomicdata.dev/properties/isDynamic",
89+
"https://atomicdata.dev/properties/isLocked",
90+
"https://atomicdata.dev/properties/allowsOnly"
91+
],
92+
"https://atomicdata.dev/properties/requires": [
93+
"https://atomicdata.dev/properties/shortname",
94+
"https://atomicdata.dev/properties/datatype",
95+
"https://atomicdata.dev/properties/description"
9096
],
91-
"https://atomicdata.dev/properties/shortname": "redirect"
97+
"https://atomicdata.dev/properties/shortname": "property"
9298
},
9399
{
94100
"@id": "https://atomicdata.dev/classes/Class",

lib/defaults/default_store.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,6 @@
789789
"https://atomicdata.dev/properties/isA": [
790790
"https://atomicdata.dev/classes/Property"
791791
],
792-
"https://atomicdata.dev/properties/lastCommit": "https://atomicdata.dev/commits/VB3gtWMkysTX5hKjbYjIM1hfVGPywT3pEPL8c7NwaUAJID6RzptGRPzmix8aKKDeb8Pj1WFv0UPV0YVPxcduBg==",
793792
"https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties",
794793
"https://atomicdata.dev/properties/shortname": "sub-resources"
795794
},
@@ -801,10 +800,19 @@
801800
"https://atomicdata.dev/properties/isA": [
802801
"https://atomicdata.dev/classes/Property"
803802
],
804-
"https://atomicdata.dev/properties/lastCommit": "https://atomicdata.dev/commits/fS0krtm1wDk0lodH0psnUKmBHBMKLuxnjkd7E7QbkzDk/irQ43gNW3lWxkwQj58ZNg6rUAUMDGJrLy1X3cHwBQ==",
805803
"https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties",
806804
"https://atomicdata.dev/properties/shortname": "tags"
807805
},
806+
{
807+
"@id": "https://atomicdata.dev/properties/token",
808+
"https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/string",
809+
"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.",
810+
"https://atomicdata.dev/properties/isA": [
811+
"https://atomicdata.dev/classes/Property"
812+
],
813+
"https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties",
814+
"https://atomicdata.dev/properties/shortname": "token"
815+
},
808816
{
809817
"@id": "https://atomicdata.dev/properties/write",
810818
"https://atomicdata.dev/properties/classtype": "https://atomicdata.dev/classes/Agent",

lib/src/plugins/add_pubkey.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*!
2+
Sends users a link to add a new public key to their account.
3+
Useful when a users loses their private key.
4+
*/
5+
6+
use serde::{Deserialize, Serialize};
7+
8+
use crate::{
9+
agents::Agent,
10+
email::{EmailAddress, MailAction, MailMessage},
11+
endpoints::{Endpoint, HandleGetContext},
12+
errors::AtomicResult,
13+
plugins::utils::return_success,
14+
urls, Resource, Storelike,
15+
};
16+
17+
pub fn request_email_add_pubkey() -> Endpoint {
18+
Endpoint {
19+
path: urls::PATH_ADD_PUBKEY.to_string(),
20+
params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(),
21+
description: "Requests an email to add a new PublicKey to an Agent.".to_string(),
22+
shortname: "request-pubkey-reset".to_string(),
23+
handle: Some(handle_request_email_pubkey),
24+
handle_post: None,
25+
}
26+
}
27+
28+
pub fn confirm_add_pubkey() -> Endpoint {
29+
Endpoint {
30+
path: urls::PATH_CONFIRM_PUBKEY.to_string(),
31+
params: [urls::TOKEN.to_string(), urls::INVITE_PUBKEY.to_string()].into(),
32+
description: "Confirms a token to add a new Public Key.".to_string(),
33+
shortname: "request-pubkey-reset".to_string(),
34+
handle: Some(handle_confirm_add_pubkey),
35+
handle_post: None,
36+
}
37+
}
38+
39+
#[derive(Debug, Serialize, Deserialize)]
40+
struct AddPubkeyToken {
41+
agent: String,
42+
}
43+
44+
pub fn handle_request_email_pubkey(context: HandleGetContext) -> AtomicResult<Resource> {
45+
let HandleGetContext {
46+
subject,
47+
store,
48+
for_agent: _,
49+
} = context;
50+
let mut email_option: Option<EmailAddress> = None;
51+
for (k, v) in subject.query_pairs() {
52+
if let "email" = k.as_ref() {
53+
email_option = Some(EmailAddress::new(v.to_string())?)
54+
}
55+
}
56+
// by default just return the Endpoint
57+
let Some(email) = email_option else {
58+
return request_email_add_pubkey().to_resource(store);
59+
};
60+
61+
// Find the agent by their email
62+
let agent = match Agent::from_email(&email.to_string(), store) {
63+
Ok(a) => a,
64+
// If we can't find the agent, we should still return a `success` response,
65+
// in order to prevent users to know that the email exists.
66+
Err(_) => return return_success(),
67+
};
68+
69+
// send the user an e-mail to confirm sign up
70+
let store_clone = store.clone();
71+
let confirmation_token_struct = AddPubkeyToken {
72+
agent: agent.subject,
73+
};
74+
let token = crate::token::sign_claim(store, confirmation_token_struct)?;
75+
let mut confirm_url = store
76+
.get_server_url()
77+
.clone()
78+
.set_path(urls::PATH_CONFIRM_PUBKEY)
79+
.url();
80+
confirm_url.set_query(Some(&format!("token={}", token)));
81+
let message = MailMessage {
82+
to: email,
83+
subject: "Add a new Passphrase to your account".to_string(),
84+
body: "You've requested adding a new Passphrase. Click the link below to do so!"
85+
.to_string(),
86+
action: Some(MailAction {
87+
name: "Add new Passphrase to account".to_string(),
88+
url: confirm_url.into(),
89+
}),
90+
};
91+
// async, because mails are slow
92+
tokio::spawn(async move {
93+
store_clone
94+
.send_email(message)
95+
.await
96+
.unwrap_or_else(|e| tracing::error!("Error sending email: {}", e));
97+
});
98+
99+
return_success()
100+
}
101+
102+
pub fn handle_confirm_add_pubkey(context: HandleGetContext) -> AtomicResult<Resource> {
103+
let HandleGetContext {
104+
subject,
105+
store,
106+
for_agent: _,
107+
} = context;
108+
109+
let mut token_opt: Option<String> = None;
110+
let mut pubkey_option = None;
111+
112+
for (k, v) in subject.query_pairs() {
113+
match k.as_ref() {
114+
"token" | urls::TOKEN => token_opt = Some(v.to_string()),
115+
"public-key" | urls::INVITE_PUBKEY => pubkey_option = Some(v.to_string()),
116+
_ => {}
117+
}
118+
}
119+
120+
let Some(token) = token_opt else {
121+
return confirm_add_pubkey().to_resource(store);
122+
};
123+
124+
let pubkey = pubkey_option.ok_or("No public-key provided")?;
125+
126+
// Parse and verify the JWT token
127+
let confirmation = crate::token::verify_claim::<AddPubkeyToken>(store, &token)?.custom;
128+
129+
// Add the key to the agent
130+
let mut agent = store.get_resource(&confirmation.agent)?;
131+
agent.push_propval(urls::ACTIVE_KEYS, pubkey.into(), true)?;
132+
agent.save(store)?;
133+
134+
return_success()
135+
}

lib/src/plugins/mod.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Plugins can have functions that are called at specific moments by Atomic-Server.
66
77
For example:
88
9-
- Before returning a Resource. These are either Endpoints or Class Extenders.
9+
- Before returning a Resource. These are either [Endpoint]s or Class Extenders.
1010
- Before applying a Commit.
1111
1212
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
1616
## Extending resources
1717
1818
There are two ways of extending / modifying a Resource.
19-
Endpoints are great for APIs that have a fixed route, and Class Extenders are great for APIs that don't have a fixed route.
19+
[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.
2020
Endpoints are easier to generate from Rust, and will be available the second a server is Running.
2121
22-
### Endpoints
22+
### [Endpoint]s
2323
2424
Resources that typically parse query parameters and return a dynamic resource.
25-
When adding an endpoint, add it to the list of endpoints in [lib/src/endpoints.rs]
25+
When adding an endpoint, add it to the list of [default_endpoints] in this file.
2626
Endpoints are all instances of the [crate] class.
2727
They are presented in the UI as a form.
2828
@@ -31,23 +31,42 @@ They are presented in the UI as a form.
3131
Similar to Endpoints, Class Extenders can modify their contents before creating a response.
3232
Contrary to Endpoints, these can be any type of Class.
3333
They are used for performing custom queries, or calculating dynamic attributes.
34+
Add these by registering the handler at [crate::db::Db::get_resource_extended].
3435
*/
3536

37+
use crate::endpoints::Endpoint;
38+
3639
// Class Extenders
3740
pub mod chatroom;
3841
pub mod importer;
3942
pub mod invite;
4043

4144
// Endpoints
45+
pub mod add_pubkey;
4246
#[cfg(feature = "html")]
4347
pub mod bookmark;
4448
pub mod files;
4549
pub mod path;
4650
pub mod query;
4751
pub mod register;
48-
pub mod reset_pubkey;
4952
pub mod search;
5053
pub mod versioning;
5154

5255
// Utilities / helpers
5356
mod utils;
57+
58+
pub fn default_endpoints() -> Vec<Endpoint> {
59+
vec![
60+
versioning::version_endpoint(),
61+
versioning::all_versions_endpoint(),
62+
path::path_endpoint(),
63+
search::search_endpoint(),
64+
files::upload_endpoint(),
65+
register::register_endpoint(),
66+
register::confirm_email_endpoint(),
67+
add_pubkey::request_email_add_pubkey(),
68+
add_pubkey::confirm_add_pubkey(),
69+
#[cfg(feature = "html")]
70+
bookmark::bookmark_endpoint(),
71+
]
72+
}

lib/src/plugins/register.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ pub fn handle_register_name_and_email(context: HandleGetContext) -> AtomicResult
109109
.get_server_url()
110110
.clone()
111111
.set_path(urls::PATH_CONFIRM_EMAIL)
112+
// .set_subdomain(Some(&name.to_string()))?
112113
.url();
113114
confirm_url.set_query(Some(&format!("token={}", token)));
114115
let message = MailMessage {

lib/src/populate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ pub fn populate_collections(store: &impl Storelike) -> AtomicResult<()> {
253253
pub fn populate_endpoints(store: &crate::Db) -> AtomicResult<()> {
254254
use crate::atomic_url::Routes;
255255

256-
let endpoints = crate::endpoints::default_endpoints();
256+
let endpoints = crate::plugins::default_endpoints();
257257
let endpoints_collection = store.get_server_url().set_route(Routes::Endpoints);
258258
for endpoint in endpoints {
259259
let mut resource = endpoint.to_resource(store)?;

0 commit comments

Comments
 (0)