From 99dae538d4b1e96677abd8ea91ca305b339641d3 Mon Sep 17 00:00:00 2001 From: 0x0fbc <10455804+0x0fbc@users.noreply.github.com> Date: Thu, 6 Jun 2024 22:50:29 -0400 Subject: [PATCH] Add db schema and models for Duo 2FA state storage --- .../down.sql | 1 + .../up.sql | 8 ++ .../down.sql | 1 + .../up.sql | 8 ++ .../down.sql | 1 + .../up.sql | 8 ++ src/db/models/mod.rs | 2 + src/db/models/two_factor_duo_context.rs | 92 +++++++++++++++++++ src/db/schemas/mysql/schema.rs | 9 ++ src/db/schemas/postgresql/schema.rs | 9 ++ src/db/schemas/sqlite/schema.rs | 9 ++ 11 files changed, 148 insertions(+) create mode 100644 migrations/mysql/2024-06-05-131359_add_2fa_duo_store/down.sql create mode 100644 migrations/mysql/2024-06-05-131359_add_2fa_duo_store/up.sql create mode 100644 migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/down.sql create mode 100644 migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/up.sql create mode 100644 migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/down.sql create mode 100644 migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/up.sql create mode 100644 src/db/models/two_factor_duo_context.rs diff --git a/migrations/mysql/2024-06-05-131359_add_2fa_duo_store/down.sql b/migrations/mysql/2024-06-05-131359_add_2fa_duo_store/down.sql new file mode 100644 index 00000000..7af867a2 --- /dev/null +++ b/migrations/mysql/2024-06-05-131359_add_2fa_duo_store/down.sql @@ -0,0 +1 @@ +DROP TABLE twofactor_duo_ctx; \ No newline at end of file diff --git a/migrations/mysql/2024-06-05-131359_add_2fa_duo_store/up.sql b/migrations/mysql/2024-06-05-131359_add_2fa_duo_store/up.sql new file mode 100644 index 00000000..6cd1cdc5 --- /dev/null +++ b/migrations/mysql/2024-06-05-131359_add_2fa_duo_store/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE twofactor_duo_ctx ( + state VARCHAR(1024) NOT NULL, + user_email VARCHAR(255) NOT NULL, + nonce VARCHAR(1024) NOT NULL, + exp BIGINT NOT NULL, + + PRIMARY KEY (state) +); \ No newline at end of file diff --git a/migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/down.sql b/migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/down.sql new file mode 100644 index 00000000..0b5d4cd8 --- /dev/null +++ b/migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/down.sql @@ -0,0 +1 @@ +DROP TABLE twofactor_duo_ctx; diff --git a/migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/up.sql b/migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/up.sql new file mode 100644 index 00000000..6cd1cdc5 --- /dev/null +++ b/migrations/postgresql/2024-06-05-131359_add_2fa_duo_store/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE twofactor_duo_ctx ( + state VARCHAR(1024) NOT NULL, + user_email VARCHAR(255) NOT NULL, + nonce VARCHAR(1024) NOT NULL, + exp BIGINT NOT NULL, + + PRIMARY KEY (state) +); \ No newline at end of file diff --git a/migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/down.sql b/migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/down.sql new file mode 100644 index 00000000..7af867a2 --- /dev/null +++ b/migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/down.sql @@ -0,0 +1 @@ +DROP TABLE twofactor_duo_ctx; \ No newline at end of file diff --git a/migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/up.sql b/migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/up.sql new file mode 100644 index 00000000..40d8e52f --- /dev/null +++ b/migrations/sqlite/2024-06-05-131359_add_2fa_duo_store/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE twofactor_duo_ctx ( + state TEXT NOT NULL, + user_email TEXT NOT NULL, + nonce TEXT NOT NULL, + exp INTEGER NOT NULL, + + PRIMARY KEY (state) +); diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 0379141a..3516c1b7 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -13,6 +13,7 @@ mod organization; mod send; mod two_factor; mod two_factor_incomplete; +mod two_factor_duo_context; mod user; pub use self::attachment::Attachment; @@ -29,5 +30,6 @@ pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType}; pub use self::organization::{Organization, OrganizationApiKey, UserOrgStatus, UserOrgType, UserOrganization}; pub use self::send::{Send, SendType}; pub use self::two_factor::{TwoFactor, TwoFactorType}; +pub use self::two_factor_duo_context::TwoFactorDuoContext; pub use self::two_factor_incomplete::TwoFactorIncomplete; pub use self::user::{Invitation, User, UserKdfType, UserStampException}; diff --git a/src/db/models/two_factor_duo_context.rs b/src/db/models/two_factor_duo_context.rs new file mode 100644 index 00000000..669fd961 --- /dev/null +++ b/src/db/models/two_factor_duo_context.rs @@ -0,0 +1,92 @@ +use chrono::Utc; + +use crate::{api::EmptyResult, db::DbConn, error::MapResult}; + +db_object! { + #[derive(Identifiable, Queryable, Insertable, AsChangeset)] + #[diesel(table_name = twofactor_duo_ctx)] + #[diesel(primary_key(state))] + pub struct TwoFactorDuoContext { + pub state: String, + pub user_email: String, + pub nonce: String, + pub exp: i64, + } +} + +impl TwoFactorDuoContext { + pub async fn find_by_state(state: &str, conn: &mut DbConn) -> Option { + db_run! { + conn: { + twofactor_duo_ctx::table + .filter(twofactor_duo_ctx::state.eq(state)) + .first::(conn) + .ok() + .from_db() + } + } + } + + pub async fn save( + state: &str, + user_email: &str, + nonce: &str, + ttl: i64, + conn: &mut DbConn, + ) -> EmptyResult { + // A saved context should never be changed, only created or deleted. + let exists = Self::find_by_state(state, conn).await; + if exists.is_some() { + return Ok(()) + }; + + let exp = Utc::now().timestamp() + ttl; + + db_run! { + conn: { + diesel::insert_into(twofactor_duo_ctx::table) + .values(( + twofactor_duo_ctx::state.eq(state), + twofactor_duo_ctx::user_email.eq(user_email), + twofactor_duo_ctx::nonce.eq(nonce), + twofactor_duo_ctx::exp.eq(exp) + )) + .execute(conn) + .map_res("Error saving context to twofactor_duo_ctx") + } + } + } + + pub async fn find_expired(conn: &mut DbConn) -> Vec { + let now = Utc::now().timestamp(); + db_run! { + conn: { + twofactor_duo_ctx::table + .filter(twofactor_duo_ctx::exp.lt(now)) + .load::(conn) + .expect("Error finding expired contexts in twofactor_duo_ctx") + .from_db() + } + } + } + + pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { + db_run! { + conn: { + diesel::delete( + twofactor_duo_ctx::table + .filter(twofactor_duo_ctx::state.eq(&self.state))) + .execute(conn) + .map_res("Error deleting from twofactor_duo_ctx") + } + } + } + + pub async fn purge_expired_duo_contexts(conn: &mut DbConn) { + for context in Self::find_expired(conn).await { + if context.exp < Utc::now().timestamp() { + context.delete(conn).await.ok(); + } + } + } +} \ No newline at end of file diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs index 0fb286a4..58ec55a2 100644 --- a/src/db/schemas/mysql/schema.rs +++ b/src/db/schemas/mysql/schema.rs @@ -174,6 +174,15 @@ table! { } } +table! { + twofactor_duo_ctx (state) { + state -> Text, + user_email -> Text, + nonce -> Text, + exp -> BigInt, + } +} + table! { users (uuid) { uuid -> Text, diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs index 26bf4b68..10b5313e 100644 --- a/src/db/schemas/postgresql/schema.rs +++ b/src/db/schemas/postgresql/schema.rs @@ -174,6 +174,15 @@ table! { } } +table! { + twofactor_duo_ctx (state) { + state -> Text, + user_email -> Text, + nonce -> Text, + exp -> BigInt, + } +} + table! { users (uuid) { uuid -> Text, diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs index 26bf4b68..10b5313e 100644 --- a/src/db/schemas/sqlite/schema.rs +++ b/src/db/schemas/sqlite/schema.rs @@ -174,6 +174,15 @@ table! { } } +table! { + twofactor_duo_ctx (state) { + state -> Text, + user_email -> Text, + nonce -> Text, + exp -> BigInt, + } +} + table! { users (uuid) { uuid -> Text,