diff --git a/.sqlx/query-18caaf9ec8c752b1ed530f8fb2991c51a3af0d260bf8e726c1c54966e11a9711.json b/.sqlx/query-18caaf9ec8c752b1ed530f8fb2991c51a3af0d260bf8e726c1c54966e11a9711.json deleted file mode 100644 index f39f37c8f..000000000 --- a/.sqlx/query-18caaf9ec8c752b1ed530f8fb2991c51a3af0d260bf8e726c1c54966e11a9711.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT totp_enabled OR email_mfa_enabled OR coalesce(bool_or(wallet.use_for_mfa), FALSE) OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" LEFT JOIN wallet ON wallet.user_id = \"user\".id LEFT JOIN webauthn ON webauthn.user_id = \"user\".id WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "bool!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - null - ] - }, - "hash": "18caaf9ec8c752b1ed530f8fb2991c51a3af0d260bf8e726c1c54966e11a9711" -} diff --git a/.sqlx/query-2e73d767e5c98702de3d5340e9f0818f346d9d25547aedd4dc388347b7620cfd.json b/.sqlx/query-1c220b1ba44ec49ad6423e97635995ecb9c0fcb8762c25aa960e7b5a2e49963b.json similarity index 59% rename from .sqlx/query-2e73d767e5c98702de3d5340e9f0818f346d9d25547aedd4dc388347b7620cfd.json rename to .sqlx/query-1c220b1ba44ec49ad6423e97635995ecb9c0fcb8762c25aa960e7b5a2e49963b.json index 827af9731..69f000106 100644 --- a/.sqlx/query-2e73d767e5c98702de3d5340e9f0818f346d9d25547aedd4dc388347b7620cfd.json +++ b/.sqlx/query-1c220b1ba44ec49ad6423e97635995ecb9c0fcb8762c25aa960e7b5a2e49963b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT address \"address!\", name, chain_id, use_for_mfa FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", + "query": "SELECT address \"address!\", name, chain_id FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", "describe": { "columns": [ { @@ -17,11 +17,6 @@ "ordinal": 2, "name": "chain_id", "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "use_for_mfa", - "type_info": "Bool" } ], "parameters": { @@ -30,11 +25,10 @@ ] }, "nullable": [ - false, false, false, false ] }, - "hash": "2e73d767e5c98702de3d5340e9f0818f346d9d25547aedd4dc388347b7620cfd" + "hash": "1c220b1ba44ec49ad6423e97635995ecb9c0fcb8762c25aa960e7b5a2e49963b" } diff --git a/.sqlx/query-1feeccc3729029c40d9cbecd21919fc184af94f52cb135c4c8bd2830c92a47db.json b/.sqlx/query-1feeccc3729029c40d9cbecd21919fc184af94f52cb135c4c8bd2830c92a47db.json deleted file mode 100644 index 20ef03c47..000000000 --- a/.sqlx/query-1feeccc3729029c40d9cbecd21919fc184af94f52cb135c4c8bd2830c92a47db.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE wallet SET use_for_mfa = FALSE WHERE user_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "1feeccc3729029c40d9cbecd21919fc184af94f52cb135c4c8bd2830c92a47db" -} diff --git a/.sqlx/query-7faf2df7356d7887e9780894568b76674eef0d3848f3805778291df5d4fa3537.json b/.sqlx/query-5aaac5fbf3656a825ab33f07dd9bf8698c283d3158c7b6806a24d777819c7e64.json similarity index 72% rename from .sqlx/query-7faf2df7356d7887e9780894568b76674eef0d3848f3805778291df5d4fa3537.json rename to .sqlx/query-5aaac5fbf3656a825ab33f07dd9bf8698c283d3158c7b6806a24d777819c7e64.json index b5d5b92a2..2ec55bf90 100644 --- a/.sqlx/query-7faf2df7356d7887e9780894568b76674eef0d3848f3805778291df5d4fa3537.json +++ b/.sqlx/query-5aaac5fbf3656a825ab33f07dd9bf8698c283d3158c7b6806a24d777819c7e64.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, (SELECT count(*) > 0 FROM wallet WHERE user_id = $1 AND wallet.use_for_mfa) \"web3_available!\", (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" FROM \"user\" WHERE \"user\".id = $1", + "query": "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" FROM \"user\" WHERE \"user\".id = $1", "describe": { "columns": [ { @@ -33,11 +33,6 @@ }, { "ordinal": 3, - "name": "web3_available!", - "type_info": "Bool" - }, - { - "ordinal": 4, "name": "webauthn_available!", "type_info": "Bool" } @@ -51,9 +46,8 @@ false, false, false, - null, null ] }, - "hash": "7faf2df7356d7887e9780894568b76674eef0d3848f3805778291df5d4fa3537" + "hash": "5aaac5fbf3656a825ab33f07dd9bf8698c283d3158c7b6806a24d777819c7e64" } diff --git a/.sqlx/query-560de8bc16c1bcc80d31c99dc4b788bf5eeef60689d104448989726a5999eb42.json b/.sqlx/query-94d004eec19959acba27847ff30d6304eab840ddfce68b9c3950683b3951ba7f.json similarity index 72% rename from .sqlx/query-560de8bc16c1bcc80d31c99dc4b788bf5eeef60689d104448989726a5999eb42.json rename to .sqlx/query-94d004eec19959acba27847ff30d6304eab840ddfce68b9c3950683b3951ba7f.json index bdf9168f9..18d6e656a 100644 --- a/.sqlx/query-560de8bc16c1bcc80d31c99dc4b788bf5eeef60689d104448989726a5999eb42.json +++ b/.sqlx/query-94d004eec19959acba27847ff30d6304eab840ddfce68b9c3950683b3951ba7f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"wallet\" SET \"user_id\" = $2,\"address\" = $3,\"name\" = $4,\"chain_id\" = $5,\"challenge_message\" = $6,\"challenge_signature\" = $7,\"creation_timestamp\" = $8,\"validation_timestamp\" = $9,\"use_for_mfa\" = $10 WHERE id = $1", + "query": "UPDATE \"wallet\" SET \"user_id\" = $2,\"address\" = $3,\"name\" = $4,\"chain_id\" = $5,\"challenge_message\" = $6,\"challenge_signature\" = $7,\"creation_timestamp\" = $8,\"validation_timestamp\" = $9 WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -13,11 +13,10 @@ "Text", "Text", "Timestamp", - "Timestamp", - "Bool" + "Timestamp" ] }, "nullable": [] }, - "hash": "560de8bc16c1bcc80d31c99dc4b788bf5eeef60689d104448989726a5999eb42" + "hash": "94d004eec19959acba27847ff30d6304eab840ddfce68b9c3950683b3951ba7f" } diff --git a/.sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json b/.sqlx/query-b120af63a9a1faaa8f64db27ca2989525bfaedcf7f3e025ec9fc80ae734681e0.json similarity index 82% rename from .sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json rename to .sqlx/query-b120af63a9a1faaa8f64db27ca2989525bfaedcf7f3e025ec9fc80ae734681e0.json index d72c99d11..fa090ee21 100644 --- a/.sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json +++ b/.sqlx/query-b120af63a9a1faaa8f64db27ca2989525bfaedcf7f3e025ec9fc80ae734681e0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, creation_timestamp, validation_timestamp, use_for_mfa FROM wallet WHERE user_id = $1 AND address = $2", + "query": "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, creation_timestamp, validation_timestamp FROM wallet WHERE user_id = $1 AND address = $2", "describe": { "columns": [ { @@ -47,11 +47,6 @@ "ordinal": 8, "name": "validation_timestamp", "type_info": "Timestamp" - }, - { - "ordinal": 9, - "name": "use_for_mfa", - "type_info": "Bool" } ], "parameters": { @@ -69,9 +64,8 @@ false, true, false, - true, - false + true ] }, - "hash": "e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed" + "hash": "b120af63a9a1faaa8f64db27ca2989525bfaedcf7f3e025ec9fc80ae734681e0" } diff --git a/.sqlx/query-c4e2279ae22667242ddab424cb35d81d565b02d86af3e2a8eabc08f9ca2cbfbb.json b/.sqlx/query-c4e2279ae22667242ddab424cb35d81d565b02d86af3e2a8eabc08f9ca2cbfbb.json new file mode 100644 index 000000000..ebfe58956 --- /dev/null +++ b/.sqlx/query-c4e2279ae22667242ddab424cb35d81d565b02d86af3e2a8eabc08f9ca2cbfbb.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT totp_enabled OR email_mfa_enabled OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" LEFT JOIN webauthn ON webauthn.user_id = \"user\".id WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "bool!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + null + ] + }, + "hash": "c4e2279ae22667242ddab424cb35d81d565b02d86af3e2a8eabc08f9ca2cbfbb" +} diff --git a/.sqlx/query-7d202e1f66e134a78fbd808e578e295afcdc4daf5f3be7cca0be247e47133794.json b/.sqlx/query-ca812bc6c7fc8d13f0c40031408f2cc2b97916326f416adc5ae0de1e73f0fa60.json similarity index 68% rename from .sqlx/query-7d202e1f66e134a78fbd808e578e295afcdc4daf5f3be7cca0be247e47133794.json rename to .sqlx/query-ca812bc6c7fc8d13f0c40031408f2cc2b97916326f416adc5ae0de1e73f0fa60.json index 6376a5fac..27dfbff51 100644 --- a/.sqlx/query-7d202e1f66e134a78fbd808e578e295afcdc4daf5f3be7cca0be247e47133794.json +++ b/.sqlx/query-ca812bc6c7fc8d13f0c40031408f2cc2b97916326f416adc5ae0de1e73f0fa60.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"wallet\" (\"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id", + "query": "INSERT INTO \"wallet\" (\"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id", "describe": { "columns": [ { @@ -18,13 +18,12 @@ "Text", "Text", "Timestamp", - "Timestamp", - "Bool" + "Timestamp" ] }, "nullable": [ false ] }, - "hash": "7d202e1f66e134a78fbd808e578e295afcdc4daf5f3be7cca0be247e47133794" + "hash": "ca812bc6c7fc8d13f0c40031408f2cc2b97916326f416adc5ae0de1e73f0fa60" } diff --git a/.sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json b/.sqlx/query-e20ee95915d744a046e0f6aaf9610a4a495830071f9b2d68f5e0ff14b186a182.json similarity index 82% rename from .sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json rename to .sqlx/query-e20ee95915d744a046e0f6aaf9610a4a495830071f9b2d68f5e0ff14b186a182.json index 4002b0276..619d6c2b2 100644 --- a/.sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json +++ b/.sqlx/query-e20ee95915d744a046e0f6aaf9610a4a495830071f9b2d68f5e0ff14b186a182.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\"", + "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\" FROM \"wallet\"", "describe": { "columns": [ { @@ -47,11 +47,6 @@ "ordinal": 8, "name": "validation_timestamp", "type_info": "Timestamp" - }, - { - "ordinal": 9, - "name": "use_for_mfa", - "type_info": "Bool" } ], "parameters": { @@ -66,9 +61,8 @@ false, true, false, - true, - false + true ] }, - "hash": "054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347" + "hash": "e20ee95915d744a046e0f6aaf9610a4a495830071f9b2d68f5e0ff14b186a182" } diff --git a/.sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json b/.sqlx/query-eda201202a33aeda6c2d5589d21985dfde04b4c28fa8901a040d9286c020cc93.json similarity index 82% rename from .sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json rename to .sqlx/query-eda201202a33aeda6c2d5589d21985dfde04b4c28fa8901a040d9286c020cc93.json index 9a93f3484..117365f60 100644 --- a/.sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json +++ b/.sqlx/query-eda201202a33aeda6c2d5589d21985dfde04b4c28fa8901a040d9286c020cc93.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\" FROM \"wallet\" WHERE id = $1", "describe": { "columns": [ { @@ -47,11 +47,6 @@ "ordinal": 8, "name": "validation_timestamp", "type_info": "Timestamp" - }, - { - "ordinal": 9, - "name": "use_for_mfa", - "type_info": "Bool" } ], "parameters": { @@ -68,9 +63,8 @@ false, true, false, - true, - false + true ] }, - "hash": "c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d" + "hash": "eda201202a33aeda6c2d5589d21985dfde04b4c28fa8901a040d9286c020cc93" } diff --git a/migrations/20241119105926_disable_wallet_mfa.down.sql b/migrations/20241119105926_disable_wallet_mfa.down.sql index e69de29bb..7341d953f 100644 --- a/migrations/20241119105926_disable_wallet_mfa.down.sql +++ b/migrations/20241119105926_disable_wallet_mfa.down.sql @@ -0,0 +1 @@ +ALTER TABLE wallet ADD COLUMN use_for_mfa boolean NOT NULL DEFAULT true; diff --git a/migrations/20241119105926_disable_wallet_mfa.up.sql b/migrations/20241119105926_disable_wallet_mfa.up.sql index 9e5266fd7..110466238 100644 --- a/migrations/20241119105926_disable_wallet_mfa.up.sql +++ b/migrations/20241119105926_disable_wallet_mfa.up.sql @@ -1 +1 @@ -UPDATE wallet SET use_for_mfa = false; +ALTER TABLE wallet DROP use_for_mfa; diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index d13db81b3..77e289c08 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -40,12 +40,11 @@ pub struct NewOpenIDClient { pub enabled: bool, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct WalletInfo { pub address: String, pub name: String, pub chain_id: Id, - pub use_for_mfa: bool, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -217,7 +216,6 @@ impl UserDetails { pub struct MFAInfo { mfa_method: MFAMethod, totp_available: bool, - web3_available: bool, webauthn_available: bool, email_available: bool, } @@ -227,7 +225,6 @@ impl MFAInfo { query_as!( Self, "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, \ - (SELECT count(*) > 0 FROM wallet WHERE user_id = $1 AND wallet.use_for_mfa) \"web3_available!\", \ (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" \ FROM \"user\" WHERE \"user\".id = $1", user.id @@ -236,10 +233,7 @@ impl MFAInfo { #[must_use] pub fn mfa_available(&self) -> bool { - self.webauthn_available - || self.totp_available - || self.web3_available - || self.email_available + self.webauthn_available || self.totp_available || self.email_available } #[must_use] @@ -257,9 +251,6 @@ impl MFAInfo { if self.webauthn_available { methods.push(MFAMethod::Webauthn); } - if self.web3_available { - methods.push(MFAMethod::Web3); - } if self.totp_available { methods.push(MFAMethod::OneTimePassword); } diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 77c76dbb1..6315d5d81 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -15,7 +15,6 @@ use totp_lite::{totp_custom, Sha1}; use super::{ device::{Device, UserDevice}, group::Group, - wallet::Wallet, webauthn::WebAuthn, MFAInfo, OAuth2AuthorizedAppInfo, SecurityKey, WalletInfo, }; @@ -234,7 +233,6 @@ impl User { /// Check if any of the multi-factor authentication methods is on. /// - TOTP is enabled - /// - a [`Wallet`] flagged `use_for_mfa` /// - a security key for Webauthn async fn check_mfa_enabled<'e, E>(&self, executor: E) -> Result where @@ -246,9 +244,8 @@ impl User { } query_scalar!( - "SELECT totp_enabled OR email_mfa_enabled OR coalesce(bool_or(wallet.use_for_mfa), FALSE) \ + "SELECT totp_enabled OR email_mfa_enabled \ OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" \ - LEFT JOIN wallet ON wallet.user_id = \"user\".id \ LEFT JOIN webauthn ON webauthn.user_id = \"user\".id \ WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", self.id @@ -360,7 +357,6 @@ impl User { ) .execute(pool) .await?; - Wallet::disable_mfa_for_user(pool, self.id).await?; WebAuthn::delete_all_for_user(pool, self.id).await?; self.totp_secret = None; @@ -727,7 +723,7 @@ impl User { { query_as!( WalletInfo, - "SELECT address \"address!\", name, chain_id, use_for_mfa \ + "SELECT address \"address!\", name, chain_id \ FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", self.id ) diff --git a/src/db/models/wallet.rs b/src/db/models/wallet.rs index 557395ccd..8537c66b2 100644 --- a/src/db/models/wallet.rs +++ b/src/db/models/wallet.rs @@ -71,7 +71,6 @@ pub struct Wallet { pub challenge_signature: Option, pub creation_timestamp: NaiveDateTime, pub validation_timestamp: Option, - pub use_for_mfa: bool, } impl Wallet { @@ -93,7 +92,6 @@ impl Wallet { challenge_signature: None, creation_timestamp: Utc::now().naive_utc(), validation_timestamp: None, - use_for_mfa: false, } } @@ -199,7 +197,7 @@ impl Wallet { query_as!( Self, "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, \ - creation_timestamp, validation_timestamp, use_for_mfa FROM wallet \ + creation_timestamp, validation_timestamp FROM wallet \ WHERE user_id = $1 AND address = $2", user_id, address @@ -207,18 +205,4 @@ impl Wallet { .fetch_optional(executor) .await } - - pub async fn disable_mfa_for_user<'e, E>(executor: E, user_id: Id) -> Result<(), SqlxError> - where - E: PgExecutor<'e>, - { - query!( - "UPDATE wallet SET use_for_mfa = FALSE WHERE user_id = $1", - user_id - ) - .execute(executor) - .await?; - - Ok(()) - } } diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index d08f0dc88..0f26c316f 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -754,70 +754,12 @@ pub async fn web3auth_start( /// Finish Web3 authentication pub async fn web3auth_end( - private_cookies: PrivateCookieJar, - mut session: Session, - State(appstate): State, Json(signature): Json, ) -> Result<(PrivateCookieJar, ApiResponse), WebError> { debug!( "Finishing web3 authentication for wallet {}", signature.address ); - if let Some(ref challenge) = session.web3_challenge { - if let Some(wallet) = - Wallet::find_by_user_and_address(&appstate.pool, session.user_id, &signature.address) - .await? - { - if wallet.use_for_mfa { - return match wallet.verify_address(challenge, &signature.signature) { - Ok(true) => { - session - .set_state(&appstate.pool, SessionState::MultiFactorVerified) - .await?; - if let Some(user) = - User::find_by_id(&appstate.pool, session.user_id).await? - { - let username = user.username.clone(); - let user_info = UserInfo::from_user(&appstate.pool, &user).await?; - info!( - "User {username} authenticated with wallet {}", - signature.address - ); - if let Some(openid_cookie) = private_cookies.get(SIGN_IN_COOKIE_NAME) { - debug!("Found openid session cookie."); - let redirect_url = openid_cookie.value().to_string(); - let private_cookies = private_cookies.remove(openid_cookie); - Ok(( - private_cookies, - ApiResponse { - json: json!(AuthResponse { - user: user_info, - url: Some(redirect_url), - }), - status: StatusCode::OK, - }, - )) - } else { - Ok(( - private_cookies, - ApiResponse { - json: json!(AuthResponse { - user: user_info, - url: None, - }), - status: StatusCode::OK, - }, - )) - } - } else { - Ok((private_cookies, ApiResponse::default())) - } - } - _ => Err(WebError::Authorization("Signature not verified".into())), - }; - } - } - } Err(WebError::Http(StatusCode::BAD_REQUEST)) } diff --git a/src/handlers/user.rs b/src/handlers/user.rs index 4bb32f35e..8aff5cd94 100644 --- a/src/handlers/user.rs +++ b/src/handlers/user.rs @@ -3,13 +3,11 @@ use axum::{ http::StatusCode, }; use serde_json::json; -use utoipa::ToSchema; use super::{ - mail::{send_mfa_configured_email, EMAIL_PASSOWRD_RESET_START_SUBJECT}, - user_for_admin_or_self, AddUserData, ApiResponse, ApiResult, PasswordChange, - PasswordChangeSelf, RecoveryCodes, StartEnrollmentRequest, Username, WalletChallenge, - WalletChange, WalletSignature, + mail::EMAIL_PASSOWRD_RESET_START_SUBJECT, user_for_admin_or_self, AddUserData, ApiResponse, + ApiResult, PasswordChange, PasswordChangeSelf, StartEnrollmentRequest, Username, + WalletChallenge, WalletSignature, }; use crate::{ appstate::AppState, @@ -18,9 +16,10 @@ use crate::{ models::{ device::DeviceInfo, enrollment::{Token, PASSWORD_RESET_TOKEN_TYPE}, + WalletInfo, }, - AppEvent, GatewayEvent, MFAMethod, OAuth2AuthorizedApp, Settings, User, UserDetails, - UserInfo, Wallet, WebAuthn, WireguardNetwork, + AppEvent, GatewayEvent, OAuth2AuthorizedApp, Settings, User, UserDetails, UserInfo, Wallet, + WebAuthn, WireguardNetwork, }, enterprise::limits::update_counts, error::WebError, @@ -984,14 +983,6 @@ pub async fn reset_password( } } -/// Similar to [`models::WalletInfo`] but without `use_for_mfa`. -#[derive(Deserialize, ToSchema)] -pub struct WalletInfoShort { - pub address: String, - pub name: String, - pub chain_id: i64, -} - /// Wallet challenge /// /// Endpoint allows to generate a wallet challenge for ownership verification. @@ -1020,7 +1011,7 @@ pub async fn wallet_challenge( session: SessionInfo, State(appstate): State, Path(username): Path, - Query(wallet_info): Query, + Query(wallet_info): Query, ) -> ApiResult { debug!( "User {} generating wallet challenge for user {username}", @@ -1157,68 +1148,12 @@ pub async fn set_wallet( pub async fn update_wallet( session: SessionInfo, Path((username, address)): Path<(String, String)>, - State(appstate): State, - Json(data): Json, ) -> ApiResult { debug!( "User {} updating wallet {address} for user {username}", session.user.username, ); - let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - if let Some(mut wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id, &address).await? - { - if wallet.user_id == user.id { - let mfa_change = wallet.use_for_mfa != data.use_for_mfa; - wallet.use_for_mfa = data.use_for_mfa; - wallet.save(&appstate.pool).await?; - if mfa_change { - if data.use_for_mfa { - debug!("Wallet {} MFA flag enabled", wallet.address); - if !user.mfa_enabled { - // send notification email about enabled MFA - send_mfa_configured_email( - Some(&session.session), - &user, - &MFAMethod::Web3, - &appstate.mail_tx, - )?; - user.set_mfa_method(&appstate.pool, MFAMethod::Web3).await?; - let recovery_codes = user.get_recovery_codes(&appstate.pool).await?; - info!("User {} MFA enabled", username); - info!( - "User {} updated wallet {address} for user {username}", - session.user.username, - ); - return Ok(ApiResponse { - json: json!(RecoveryCodes::new(recovery_codes)), - status: StatusCode::OK, - }); - } - } else { - debug!("Wallet {} MFA flag removed", wallet.address); - user.verify_mfa_state(&appstate.pool).await?; - } - } - info!( - "User {} updated wallet {address} for user {username}", - session.user.username, - ); - Ok(ApiResponse::default()) - } else { - error!( - "User {} failed to update wallet {address} for user {username} (id: {}), the owner id is {}", - session.user.username, user.id, wallet.user_id - ); - Err(WebError::ObjectNotFound("wrong wallet".into())) - } - } else { - error!( - "User {} failed to update wallet {address} for user {username}, wallet not found", - session.user.username - ); - Err(WebError::ObjectNotFound("wallet not found".into())) - } + Err(WebError::ObjectNotFound("deprecated wallet update".into())) } /// Delete wallet. diff --git a/src/lib.rs b/src/lib.rs index aecf9e6ff..c40fc74ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,8 +160,7 @@ mod openapi { use error::WebError; use handlers::{ group::{self, BulkAssignToGroupsRequest, Groups}, - user::{self, WalletInfoShort}, - wireguard as device, + user, wireguard as device, wireguard::AddDeviceResult, ApiResponse, EditGroupInfo, GroupInfo, PasswordChange, PasswordChangeSelf, StartEnrollmentRequest, Username, WalletChange, WalletSignature, @@ -213,7 +212,7 @@ mod openapi { ), components( schemas( - ApiResponse, UserInfo, WebError, UserDetails, UserDevice, Groups, Username, StartEnrollmentRequest, PasswordChangeSelf, PasswordChange, WalletInfoShort, WalletSignature, WalletChange, AddDevice, AddDeviceResult, Device, ModifyDevice, BulkAssignToGroupsRequest, GroupInfo, EditGroupInfo + ApiResponse, UserInfo, WebError, UserDetails, UserDevice, Groups, Username, StartEnrollmentRequest, PasswordChangeSelf, PasswordChange, WalletSignature, WalletChange, AddDevice, AddDeviceResult, Device, ModifyDevice, BulkAssignToGroupsRequest, GroupInfo, EditGroupInfo ), ), tags( diff --git a/tests/auth.rs b/tests/auth.rs index 0270c45d9..4e9281cb2 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -7,14 +7,11 @@ use claims::{assert_err, assert_ok}; use common::fetch_user_details; use defguard::{ auth::{TOTP_CODE_DIGITS, TOTP_CODE_VALIDITY_PERIOD}, - db::{models::wallet::keccak256, MFAInfo, MFAMethod, Settings, User, UserDetails, Wallet}, - handlers::{Auth, AuthCode, AuthResponse, AuthTotp, WalletChallenge}, - hex::to_lower_hex, + db::{MFAInfo, MFAMethod, Settings, User, UserDetails}, + handlers::{Auth, AuthCode, AuthResponse, AuthTotp}, secret::SecretString, }; -use ethers_core::types::transaction::eip712::{Eip712, TypedData}; use reqwest::{header::USER_AGENT, StatusCode}; -use secp256k1::{rand::rngs::OsRng, All, Message, Secp256k1, SecretKey}; use serde::Deserialize; use serde_json::json; use sqlx::{query, PgPool}; @@ -32,67 +29,20 @@ pub struct RecoveryCodes { } async fn make_client() -> TestClient { - let (client, client_state) = make_test_client().await; - - Wallet::new_for_user( - client_state.test_user.id, - "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", - "test", - 5, - "", - ) - .save(&client_state.pool) - .await - .unwrap(); - + let (client, _) = make_test_client().await; client } async fn make_client_with_db() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; - - Wallet::new_for_user( - client_state.test_user.id, - "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", - "test", - 5, - "", - ) - .save(&client_state.pool) - .await - .unwrap(); - (client, client_state.pool) } async fn make_client_with_state() -> (TestClient, ClientState) { let (client, client_state) = make_test_client().await; - - Wallet::new_for_user( - client_state.test_user.id, - "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", - "test", - 5, - "", - ) - .save(&client_state.pool) - .await - .unwrap(); - (client, client_state) } -async fn make_client_with_wallet(address: &str) -> TestClient { - let (client, client_state) = make_test_client().await; - - Wallet::new_for_user(client_state.test_user.id, address, "test", 5, "") - .save(&client_state.pool) - .await - .unwrap(); - - client -} - #[tokio::test] async fn test_logout() { let mut client = make_client().await; @@ -717,293 +667,6 @@ async fn test_mfa_method_is_updated_when_removing_last_webauthn_passkey() { assert_eq!(mfa_info.current_mfa_method(), &MFAMethod::OneTimePassword); } -#[derive(Deserialize)] -struct Challenge { - challenge: String, -} - -// helper to perform login using a wallet -async fn wallet_login( - client: &TestClient, - wallet_address: String, - secp: &Secp256k1, - secret_key: SecretKey, -) { - let wallet_address_request = json!({ - "address": wallet_address.clone(), - }); - - // obtain challenge message - let response = client - .post("/api/v1/auth/web3/start") - .json(&wallet_address_request) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - let data: Challenge = response.json().await; - - let parsed_data: TypedData = serde_json::from_str(&data.challenge).unwrap(); - let parsed_message = parsed_data.message; - - let challenge_message = "Please read this carefully: - -Click to sign to prove you are in possesion of your private key to the account. -This request will not trigger a blockchain transaction or cost any gas fees."; - let message: String = format!( - r#"{{ - "domain": {{ "name": "Defguard", "version": "1" }}, - "types": {{ - "EIP712Domain": [ - {{ "name": "name", "type": "string" }}, - {{ "name": "version", "type": "string" }} - ], - "ProofOfOwnership": [ - {{ "name": "wallet", "type": "address" }}, - {{ "name": "content", "type": "string" }}, - {{ "name": "nonce", "type": "string" }} - ] - }}, - "primaryType": "ProofOfOwnership", - "message": {{ - "wallet": "{wallet_address}", - "content": "{challenge_message}", - "nonce": {} - }}}} - "#, - parsed_message.get("nonce").unwrap(), - ) - .chars() - .filter(|c| *c != ' ' && *c != '\r' && *c != '\n' && *c != '\t') - .collect::(); - let challenge = data - .challenge - .chars() - .filter(|c| *c != ' ' && *c != '\r' && *c != '\n' && *c != '\t') - .collect::(); - assert_eq!(challenge, message); - - // Sign message - let signature = sign_message(&data.challenge, secp, secret_key); - - // Check if invalid signature results into 401 - let invalid_request_response = client - .post("/api/v1/auth/web3") - .json(&json!({ - "address": wallet_address.clone(), - "signature": "0x00" - })) - .send() - .await; - - assert_eq!(invalid_request_response.status(), StatusCode::UNAUTHORIZED); - - // Web3 authentication - let response = client - .post("/api/v1/auth/web3") - .json(&json!({ - "address": wallet_address.clone(), - "signature": signature, - })) - .send() - .await; - - assert_eq!(response.status(), StatusCode::OK); -} - -fn sign_message(message: &str, secp: &Secp256k1, secret_key: SecretKey) -> String { - let typed_data: TypedData = serde_json::from_str(message).unwrap(); - let hash_msg = typed_data.encode_eip712().unwrap(); - let message = Message::from_digest_slice(&hash_msg).unwrap(); - let sig_r = secp.sign_ecdsa_recoverable(&message, &secret_key); - let (rec_id, sig) = sig_r.serialize_compact(); - - // Create recoverable_signature array - let mut sig_arr = [0; 65]; - sig_arr[0..64].copy_from_slice(&sig[0..64]); - sig_arr[64] = rec_id.to_i32() as u8; - - to_lower_hex(&sig_arr) -} - -#[tokio::test] -async fn test_web3() { - let secp = Secp256k1::new(); - let (secret_key, public_key) = secp.generate_keypair(&mut OsRng); - - // create eth wallet address - let public_key = public_key.serialize_uncompressed(); - let hash = keccak256(&public_key[1..]); - let addr = &hash[hash.len() - 20..]; - let wallet_address = to_lower_hex(addr); - - // create client - let client = make_client_with_wallet(&wallet_address).await; - - // login - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::OK); - - // set wallet for MFA - let response = client - .put(format!("/api/v1/user/hpotter/wallet/{wallet_address}")) - .json(&json!({ - "use_for_mfa": true - })) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - - // check recovery codes - let recovery_codes: RecoveryCodes = response.json().await; - assert_eq!(recovery_codes.codes.unwrap().len(), 8); // RECOVERY_CODES_COUNT - - // enable MFA - let response = client.put("/api/v1/auth/mfa").send().await; - assert_eq!(response.status(), StatusCode::OK); - - // login with wallet - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::CREATED); - wallet_login(&client, wallet_address, &secp, secret_key).await; - - // disable MFA - let response = client.delete("/api/v1/auth/mfa").send().await; - assert_eq!(response.status(), StatusCode::OK); - - // login again - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::OK); -} - -#[tokio::test] -async fn test_re_adding_wallet() { - let secp = Secp256k1::new(); - let (secret_key, public_key) = secp.generate_keypair(&mut OsRng); - - // create eth wallet address - let public_key = public_key.serialize_uncompressed(); - let hash = keccak256(&public_key[1..]); - let addr = &hash[hash.len() - 20..]; - let wallet_address = to_lower_hex(addr); - - // create client - let client = make_client().await; - - // login - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::OK); - - // add wallet - let response = client - .get(format!( - "/api/v1/user/hpotter/challenge?address={}&name=TestWallet&chain_id=1", - &wallet_address - )) - .send() - .await; - let challenge: WalletChallenge = response.json().await; - let signature = sign_message(&challenge.message, &secp, secret_key); - let response = client - .put("/api/v1/user/hpotter/wallet") - .json(&json!({ - "address": wallet_address, - "chain_id": 1, - "name": "TestWallet", - "signature": signature - })) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - - // enable wallet for MFA - let response = client - .put(format!("/api/v1/user/hpotter/wallet/{}", &wallet_address)) - .json(&json!({ - "use_for_mfa": true - })) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - - // check recovery codes - let recovery_codes: RecoveryCodes = response.json().await; - assert_eq!(recovery_codes.codes.unwrap().len(), 8); // RECOVERY_CODES_COUNT - - // enable MFA - let response = client.put("/api/v1/auth/mfa").send().await; - assert_eq!(response.status(), StatusCode::OK); - - // login with wallet - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::CREATED); - wallet_login(&client, wallet_address.clone(), &secp, secret_key).await; - - // remove wallet - let response = client - .delete(format!("/api/v1/user/hpotter/wallet/{}", &wallet_address)) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - - // logout - let response = client.post("/api/v1/auth/logout").send().await; - assert_eq!(response.status(), StatusCode::OK); - - // login without MFA - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::OK); - - // add the same wallet and enable MFA - let response = client - .get(format!( - "/api/v1/user/hpotter/challenge?address={}&name=TestWallet&chain_id=1", - &wallet_address - )) - .send() - .await; - let challenge: WalletChallenge = response.json().await; - let signature = sign_message(&challenge.message, &secp, secret_key); - let response = client - .put("/api/v1/user/hpotter/wallet") - .json(&json!({ - "address": wallet_address, - "chain_id": 1, - "name": "TestWallet", - "signature": signature - })) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - let response = client - .put(format!("/api/v1/user/hpotter/wallet/{}", &wallet_address)) - .json(&json!({ - "use_for_mfa": true - })) - .send() - .await; - assert_eq!(response.status(), StatusCode::OK); - - // check recovery codes - let recovery_codes: RecoveryCodes = response.json().await; - assert_eq!(recovery_codes.codes.unwrap().len(), 8); // RECOVERY_CODES_COUNT - - // enable MFA - let response = client.put("/api/v1/auth/mfa").send().await; - assert_eq!(response.status(), StatusCode::OK); - - // login with wallet - let auth = Auth::new("hpotter", "pass123"); - let response = client.post("/api/v1/auth").json(&auth).send().await; - assert_eq!(response.status(), StatusCode::CREATED); - wallet_login(&client, wallet_address.clone(), &secp, secret_key).await; -} - #[tokio::test] async fn test_mfa_method_totp_enabled_mail() { let (client, state) = make_test_client().await; diff --git a/tests/openid_login.rs b/tests/openid_login.rs index aee2b4baa..4b09725dd 100644 --- a/tests/openid_login.rs +++ b/tests/openid_login.rs @@ -1,9 +1,7 @@ use chrono::{Duration, Utc}; use common::{exceed_enterprise_limits, make_test_client}; -use defguard::db::{models::oauth2client::OAuth2Client, Id}; use defguard::enterprise::license::get_cached_license; use defguard::{ - db::models::NewOpenIDClient, enterprise::{ handlers::openid_providers::AddProviderData, license::{set_cached_license, License}, @@ -11,7 +9,7 @@ use defguard::{ handlers::Auth, }; use reqwest::{StatusCode, Url}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; mod common; use self::common::client::TestClient; diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts index b536b3494..f8d23147d 100644 --- a/web/src/shared/types.ts +++ b/web/src/shared/types.ts @@ -79,7 +79,6 @@ export interface WalletInfo { address: string; chain_id: number; name: string; - use_for_mfa: boolean; } export type AddDeviceResponseDevice = Omit; @@ -295,7 +294,6 @@ export interface UserEditRequest { export interface EditWalletMFARequest { username: string; address: string; - use_for_mfa: boolean; } export interface MFALoginResponse {