verify magic link
/ build (push) Successful in 46s Details
/ build_x64 (push) Successful in 2m7s Details
/ build_arm64 (push) Successful in 2m37s Details
/ build_win64 (push) Successful in 2m34s Details

This commit is contained in:
core 2023-11-19 20:58:46 -05:00
parent cd07ff5310
commit 51b6d3a85a
Signed by: core
GPG Key ID: FDBF740DADDCEECF
13 changed files with 115 additions and 10 deletions

View File

@ -6,10 +6,12 @@
<sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-pki/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/trifid-pki/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dnapi-rs/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/dnapi-rs/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api/trifid_api_entities/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api/trifid_api_migration/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tfcli/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/tfcli/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/nebula-ffi/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/nebula-ffi/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/sagittariusdb/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api-old/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api-old/trifid_api_entities/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api-old/trifid_api_migration/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

View File

@ -39,4 +39,7 @@ starttls = false
# [tokens] contains options for token expiry # [tokens] contains options for token expiry
[tokens] [tokens]
# (Required) How long should magic links be valid for, in seconds? # (Required) How long should magic links be valid for, in seconds?
magic_link_expiry_seconds = 3600 # 1 hour 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

View File

@ -1,4 +1,5 @@
CREATE TABLE users ( CREATE TABLE users
id VARCHAR NOT NULL PRIMARY KEY, (
id VARCHAR NOT NULL PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE email VARCHAR NOT NULL UNIQUE
); );

View File

@ -1,5 +1,6 @@
CREATE TABLE magic_links ( CREATE TABLE magic_links
id VARCHAR NOT NULL PRIMARY KEY, (
user_id VARCHAR NOT NULL REFERENCES users(id) ON DELETE CASCADE, id VARCHAR NOT NULL PRIMARY KEY,
user_id VARCHAR NOT NULL REFERENCES users (id) ON DELETE CASCADE,
expires TIMESTAMP NOT NULL expires TIMESTAMP NOT NULL
); );

View File

@ -0,0 +1 @@
DROP TABLE session_tokens;

View File

@ -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
);

View File

@ -40,5 +40,6 @@ pub struct ConfigEmail {
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
pub struct ConfigTokens { pub struct ConfigTokens {
pub magic_link_expiry_seconds: u64 pub magic_link_expiry_seconds: u64,
pub session_token_expiry_seconds: u64
} }

View File

@ -123,6 +123,7 @@ async fn main() {
}) })
})) }))
.service(routes::v1::signup::signup_req) .service(routes::v1::signup::signup_req)
.service(routes::v1::auth::verify_magic_link::verify_link_req)
.wrap(Logger::default()) .wrap(Logger::default())
.wrap(actix_cors::Cors::permissive()) .wrap(actix_cors::Cors::permissive())
.app_data(app_state.clone()) .app_data(app_state.clone())

View File

@ -17,4 +17,14 @@ pub struct MagicLink {
pub id: String, pub id: String,
pub user_id: String, pub user_id: String,
pub expires: SystemTime 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
} }

View File

@ -0,0 +1 @@
pub mod verify_magic_link;

View File

@ -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<VerifyLinkReq>, state: Data<AppState>) -> JsonAPIResponse<VerifyLinkResp> {
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 {}
})
}

View File

@ -1 +1,2 @@
pub mod signup; pub mod signup;
pub mod auth;

View File

@ -8,6 +8,14 @@ diesel::table! {
} }
} }
diesel::table! {
session_tokens (id) {
id -> Varchar,
user_id -> Varchar,
expires -> Timestamp,
}
}
diesel::table! { diesel::table! {
users (id) { users (id) {
id -> Varchar, id -> Varchar,
@ -16,8 +24,10 @@ diesel::table! {
} }
diesel::joinable!(magic_links -> users (user_id)); diesel::joinable!(magic_links -> users (user_id));
diesel::joinable!(session_tokens -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
magic_links, magic_links,
session_tokens,
users, users,
); );