diff --git a/.idea/trifid.iml b/.idea/trifid.iml
index bd7461a..7ec81cf 100644
--- a/.idea/trifid.iml
+++ b/.idea/trifid.iml
@@ -6,10 +6,12 @@
-
-
+
+
+
+
diff --git a/trifid-api/config.toml b/trifid-api/config.toml
index 0843af7..d78483d 100644
--- a/trifid-api/config.toml
+++ b/trifid-api/config.toml
@@ -39,4 +39,7 @@ starttls = false
# [tokens] contains options for token expiry
[tokens]
# (Required) How long should magic links be valid for, in seconds?
-magic_link_expiry_seconds = 3600 # 1 hour
\ No newline at end of file
+magic_link_expiry_seconds = 3600 # 1 hour
+# (Required) How long should session tokens be valid for, in seconds? This controls how long users can remain "identified"
+# before they must re-identify via magic link.
+session_token_expiry_seconds = 31536000 # ~1 year
\ No newline at end of file
diff --git a/trifid-api/migrations/2023-11-19-033954_create_users/up.sql b/trifid-api/migrations/2023-11-19-033954_create_users/up.sql
index 0741c9b..4a487b7 100644
--- a/trifid-api/migrations/2023-11-19-033954_create_users/up.sql
+++ b/trifid-api/migrations/2023-11-19-033954_create_users/up.sql
@@ -1,4 +1,5 @@
-CREATE TABLE users (
- id VARCHAR NOT NULL PRIMARY KEY,
+CREATE TABLE users
+(
+ id VARCHAR NOT NULL PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE
);
\ No newline at end of file
diff --git a/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql b/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql
index 6146e82..760ace7 100644
--- a/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql
+++ b/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql
@@ -1,5 +1,6 @@
-CREATE TABLE magic_links (
- id VARCHAR NOT NULL PRIMARY KEY,
- user_id VARCHAR NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+CREATE TABLE magic_links
+(
+ id VARCHAR NOT NULL PRIMARY KEY,
+ user_id VARCHAR NOT NULL REFERENCES users (id) ON DELETE CASCADE,
expires TIMESTAMP NOT NULL
);
\ No newline at end of file
diff --git a/trifid-api/migrations/2023-11-20-012402_create_session_tokens/down.sql b/trifid-api/migrations/2023-11-20-012402_create_session_tokens/down.sql
new file mode 100644
index 0000000..36a2921
--- /dev/null
+++ b/trifid-api/migrations/2023-11-20-012402_create_session_tokens/down.sql
@@ -0,0 +1 @@
+DROP TABLE session_tokens;
\ No newline at end of file
diff --git a/trifid-api/migrations/2023-11-20-012402_create_session_tokens/up.sql b/trifid-api/migrations/2023-11-20-012402_create_session_tokens/up.sql
new file mode 100644
index 0000000..78e2703
--- /dev/null
+++ b/trifid-api/migrations/2023-11-20-012402_create_session_tokens/up.sql
@@ -0,0 +1,6 @@
+CREATE TABLE session_tokens
+(
+ id VARCHAR NOT NULL PRIMARY KEY,
+ user_id VARCHAR NOT NULL REFERENCES users (id) ON DELETE CASCADE,
+ expires TIMESTAMP NOT NULL
+);
\ No newline at end of file
diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs
index ef0c9e6..e9ed439 100644
--- a/trifid-api/src/config.rs
+++ b/trifid-api/src/config.rs
@@ -40,5 +40,6 @@ pub struct ConfigEmail {
#[derive(Deserialize, Clone)]
pub struct ConfigTokens {
- pub magic_link_expiry_seconds: u64
+ pub magic_link_expiry_seconds: u64,
+ pub session_token_expiry_seconds: u64
}
\ No newline at end of file
diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs
index f13e340..893db62 100644
--- a/trifid-api/src/main.rs
+++ b/trifid-api/src/main.rs
@@ -123,6 +123,7 @@ async fn main() {
})
}))
.service(routes::v1::signup::signup_req)
+ .service(routes::v1::auth::verify_magic_link::verify_link_req)
.wrap(Logger::default())
.wrap(actix_cors::Cors::permissive())
.app_data(app_state.clone())
diff --git a/trifid-api/src/models.rs b/trifid-api/src/models.rs
index 8250c60..3bced51 100644
--- a/trifid-api/src/models.rs
+++ b/trifid-api/src/models.rs
@@ -17,4 +17,14 @@ pub struct MagicLink {
pub id: String,
pub user_id: String,
pub expires: SystemTime
+}
+
+#[derive(Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq)]
+#[diesel(belongs_to(User))]
+#[diesel(table_name = crate::schema::session_tokens)]
+#[diesel(check_for_backend(diesel::pg::Pg))]
+pub struct SessionToken {
+ pub id: String,
+ pub user_id: String,
+ pub expires: SystemTime
}
\ No newline at end of file
diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs
new file mode 100644
index 0000000..06675be
--- /dev/null
+++ b/trifid-api/src/routes/v1/auth/mod.rs
@@ -0,0 +1 @@
+pub mod verify_magic_link;
\ No newline at end of file
diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs
new file mode 100644
index 0000000..881e456
--- /dev/null
+++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs
@@ -0,0 +1,67 @@
+use std::time::{Duration, SystemTime};
+use actix_web::http::StatusCode;
+use actix_web::post;
+use actix_web::web::{Data, Json};
+use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
+use serde::{Deserialize, Serialize};
+use crate::{AppState, randid};
+use crate::models::{MagicLink, SessionToken};
+use crate::response::JsonAPIResponse;
+use diesel_async::RunQueryDsl;
+use crate::schema::session_tokens;
+
+#[derive(Deserialize)]
+pub struct VerifyLinkReq {
+ #[serde(rename = "magicLinkToken")]
+ pub magic_link_token: String,
+}
+
+#[derive(Serialize, Debug)]
+pub struct VerifyLinkResp {
+ pub data: VerifyLinkRespData,
+ pub metadata: VerifyLinkRespMetadata
+}
+#[derive(Serialize, Debug)]
+pub struct VerifyLinkRespData {
+ #[serde(rename = "sessionToken")]
+ pub session_token: String
+}
+#[derive(Serialize, Debug)]
+pub struct VerifyLinkRespMetadata {}
+
+#[post("/v1/auth/verify-magic-link")]
+pub async fn verify_link_req(req: Json, state: Data) -> JsonAPIResponse {
+ use crate::schema::magic_links::dsl::*;
+
+ let mut conn = handle_error!(state.pool.get().await);
+
+ let tokens = handle_error!(magic_links.filter(id.eq(&req.magic_link_token)).select(MagicLink::as_select()).load(&mut conn).await);
+
+ let token = match tokens.get(0) {
+ Some(token) => token,
+ None => {
+ err!(StatusCode::BAD_REQUEST, make_err!("ERR_INVALID_MAGIC_LINK_TOKEN", "does not exist (maybe it expired?)", "magicLinkToken"))
+ }
+ };
+
+ if token.expires < SystemTime::now() {
+ err!(StatusCode::BAD_REQUEST, make_err!("ERR_INVALID_MAGIC_LINK_TOKEN", "does not exist (maybe it expired?)", "magicLinkToken"))
+ }
+
+ handle_error!(diesel::delete(token).execute(&mut conn).await);
+
+ let new_token = SessionToken {
+ id: randid!(token "sess"),
+ user_id: token.user_id.clone(),
+ expires: SystemTime::now() + Duration::from_secs(state.config.tokens.session_token_expiry_seconds),
+ };
+
+ handle_error!(diesel::insert_into(session_tokens::table).values(&new_token).execute(&mut conn).await);
+
+ ok!(VerifyLinkResp {
+ data: VerifyLinkRespData {
+ session_token: new_token.id.clone()
+ },
+ metadata: VerifyLinkRespMetadata {}
+ })
+}
\ No newline at end of file
diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs
index a4f0d8d..824934f 100644
--- a/trifid-api/src/routes/v1/mod.rs
+++ b/trifid-api/src/routes/v1/mod.rs
@@ -1 +1,2 @@
-pub mod signup;
\ No newline at end of file
+pub mod signup;
+pub mod auth;
\ No newline at end of file
diff --git a/trifid-api/src/schema.rs b/trifid-api/src/schema.rs
index 6e38b6a..6436c1b 100644
--- a/trifid-api/src/schema.rs
+++ b/trifid-api/src/schema.rs
@@ -8,6 +8,14 @@ diesel::table! {
}
}
+diesel::table! {
+ session_tokens (id) {
+ id -> Varchar,
+ user_id -> Varchar,
+ expires -> Timestamp,
+ }
+}
+
diesel::table! {
users (id) {
id -> Varchar,
@@ -16,8 +24,10 @@ diesel::table! {
}
diesel::joinable!(magic_links -> users (user_id));
+diesel::joinable!(session_tokens -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!(
magic_links,
+ session_tokens,
users,
);