From 0eb81f04b3e8f41483d85bc68dae3088245d16be Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 14:49:16 +0000 Subject: [PATCH 1/2] chore(version): v1.76.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 412b42afc2eb..c5926cfe86ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.76.0 (2023-11-12) + +### Features + +- **analytics:** Analytics APIs ([#2792](https://github.com/juspay/hyperswitch/pull/2792)) ([`f847802`](https://github.com/juspay/hyperswitch/commit/f847802339bfedb24cbaa47ad55e31d80cefddca)) +- **router:** Added Payment link new design ([#2731](https://github.com/juspay/hyperswitch/pull/2731)) ([`2a4f5d1`](https://github.com/juspay/hyperswitch/commit/2a4f5d13717a78dc2e2e4fc9a492a45b92151dbe)) +- **user:** Setup user tables ([#2803](https://github.com/juspay/hyperswitch/pull/2803)) ([`20c4226`](https://github.com/juspay/hyperswitch/commit/20c4226a36e4650a3ba8811b758ac5f7969bcfb3)) + +### Refactors + +- **connector:** [Zen] change error message from NotSupported to NotImplemented ([#2831](https://github.com/juspay/hyperswitch/pull/2831)) ([`b5ea8db`](https://github.com/juspay/hyperswitch/commit/b5ea8db2d2b7e7544931704a7191b42d3a8299be)) +- **core:** Remove connector response table and use payment_attempt instead ([#2644](https://github.com/juspay/hyperswitch/pull/2644)) ([`966369b`](https://github.com/juspay/hyperswitch/commit/966369b6f2c205b59524c23ad3b21ebab547631f)) +- **events:** Update api events to follow snake case naming ([#2828](https://github.com/juspay/hyperswitch/pull/2828)) ([`b3d5062`](https://github.com/juspay/hyperswitch/commit/b3d5062dc07676ec12e903b1999fdd9138c0891d)) + +### Documentation + +- **README:** Add bootstrap button for cloudformation deployment ([#2827](https://github.com/juspay/hyperswitch/pull/2827)) ([`e67e808`](https://github.com/juspay/hyperswitch/commit/e67e808d70d41c371fff168824e5a4dbb8b3a040)) + +**Full Changelog:** [`v1.75.0...v1.76.0`](https://github.com/juspay/hyperswitch/compare/v1.75.0...v1.76.0) + +- - - + + ## 1.75.0 (2023-11-09) ### Features From f88eee7362be2cc3e8e8dc2bb7bfd263892ff01e Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:17:35 +0530 Subject: [PATCH 2/2] feat(router): Add new JWT authentication variants and use them (#2835) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .typos.toml | 1 + Cargo.lock | 56 + crates/api_models/src/events.rs | 1 + crates/api_models/src/events/user.rs | 14 + crates/api_models/src/lib.rs | 1 + crates/api_models/src/user.rs | 21 + crates/data_models/Cargo.toml | 2 +- crates/router/Cargo.toml | 3 + crates/router/src/consts.rs | 6 + crates/router/src/consts/user.rs | 8 + crates/router/src/core.rs | 2 + crates/router/src/core/errors.rs | 4 + crates/router/src/core/errors/user.rs | 78 + crates/router/src/core/user.rs | 81 + crates/router/src/lib.rs | 1 + crates/router/src/routes.rs | 4 +- crates/router/src/routes/admin.rs | 91 +- crates/router/src/routes/api_keys.rs | 32 +- crates/router/src/routes/app.rs | 16 +- crates/router/src/routes/lock_utils.rs | 3 + crates/router/src/routes/payments.rs | 19 +- crates/router/src/routes/refunds.rs | 8 +- crates/router/src/routes/routing.rs | 44 +- crates/router/src/routes/user.rs | 31 + crates/router/src/services.rs | 2 + crates/router/src/services/authentication.rs | 101 +- crates/router/src/services/jwt.rs | 42 + crates/router/src/types/domain.rs | 4 + crates/router/src/types/domain/user.rs | 483 ++++ crates/router/src/utils.rs | 2 + crates/router/src/utils/user.rs | 1 + .../router/src/utils/user/blocker_emails.txt | 2349 +++++++++++++++++ crates/router/src/utils/user/password.rs | 43 + crates/router_env/src/logger/types.rs | 2 + 34 files changed, 3489 insertions(+), 67 deletions(-) create mode 100644 crates/api_models/src/events/user.rs create mode 100644 crates/api_models/src/user.rs create mode 100644 crates/router/src/consts/user.rs create mode 100644 crates/router/src/core/errors/user.rs create mode 100644 crates/router/src/core/user.rs create mode 100644 crates/router/src/routes/user.rs create mode 100644 crates/router/src/services/jwt.rs create mode 100644 crates/router/src/types/domain/user.rs create mode 100644 crates/router/src/utils/user.rs create mode 100644 crates/router/src/utils/user/blocker_emails.txt create mode 100644 crates/router/src/utils/user/password.rs diff --git a/.typos.toml b/.typos.toml index 0d6e6fd8e38c..1ac38a005c9e 100644 --- a/.typos.toml +++ b/.typos.toml @@ -40,4 +40,5 @@ afe = "afe" # Commit id extend-exclude = [ "config/redis.conf", # `typos` also checked "AKE" in the file, which is present as a quoted string "openapi/open_api_spec.yaml", # no longer updated + "crates/router/src/utils/user/blocker_emails.txt", # this file contains various email domains ] diff --git a/Cargo.lock b/Cargo.lock index c96ce2c18258..ae7afa85d7d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,6 +436,18 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f907281554a3d0312bb7aab855a8e0ef6cbf1614d06de54105039ca8b34460e" +[[package]] +name = "argon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -1145,6 +1157,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bb8" version = "0.8.1" @@ -1205,6 +1223,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake3" version = "1.4.0" @@ -3854,6 +3881,17 @@ dependencies = [ "regex", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -4562,6 +4600,7 @@ dependencies = [ "actix-rt", "actix-web", "api_models", + "argon2", "async-bb8-diesel", "async-trait", "awc", @@ -4637,10 +4676,12 @@ dependencies = [ "time", "tokio", "toml 0.7.4", + "unicode-segmentation", "url", "utoipa", "utoipa-swagger-ui", "uuid", + "validator", "wiremock", "x509-parser", ] @@ -6376,6 +6417,21 @@ dependencies = [ "serde", ] +[[package]] +name = "validator" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 23e7c9dc706a..ad07340615b4 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -5,6 +5,7 @@ pub mod payment; pub mod payouts; pub mod refund; pub mod routing; +pub mod user; use common_utils::{ events::{ApiEventMetric, ApiEventsType}, diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs new file mode 100644 index 000000000000..2a896cc38776 --- /dev/null +++ b/crates/api_models/src/events/user.rs @@ -0,0 +1,14 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::user::{ConnectAccountRequest, ConnectAccountResponse}; + +impl ApiEventMetric for ConnectAccountResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::User { + merchant_id: self.merchant_id.clone(), + user_id: self.user_id.clone(), + }) + } +} + +impl ApiEventMetric for ConnectAccountRequest {} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 75509ed7386d..bcc3913ea824 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -21,5 +21,6 @@ pub mod payments; pub mod payouts; pub mod refunds; pub mod routing; +pub mod user; pub mod verifications; pub mod webhooks; diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs new file mode 100644 index 000000000000..91f7702c654e --- /dev/null +++ b/crates/api_models/src/user.rs @@ -0,0 +1,21 @@ +use common_utils::pii; +use masking::Secret; + +#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)] +pub struct ConnectAccountRequest { + pub email: pii::Email, + pub password: Secret, +} + +#[derive(serde::Serialize, Debug, Clone)] +pub struct ConnectAccountResponse { + pub token: Secret, + pub merchant_id: String, + pub name: Secret, + pub email: pii::Email, + pub verification_days_left: Option, + pub user_role: String, + //this field is added for audit/debug reasons + #[serde(skip_serializing)] + pub user_id: String, +} diff --git a/crates/data_models/Cargo.toml b/crates/data_models/Cargo.toml index 254c194182f3..c7c872771689 100644 --- a/crates/data_models/Cargo.toml +++ b/crates/data_models/Cargo.toml @@ -27,4 +27,4 @@ serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" strum = { version = "0.25", features = [ "derive" ] } thiserror = "1.0.40" -time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } \ No newline at end of file +time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 7456944a8e4e..d765a5b5c5ed 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -38,6 +38,7 @@ actix-cors = "0.6.4" actix-multipart = "0.6.0" actix-rt = "2.8.0" actix-web = "4.3.1" +argon2 = { version = "0.5.0", features = ["std"] } async-bb8-diesel = "0.1.0" async-trait = "0.1.68" aws-config = { version = "0.55.3", optional = true } @@ -89,10 +90,12 @@ thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } tera = "1.19.1" +unicode-segmentation = "1.10.1" url = { version = "2.4.0", features = ["serde"] } utoipa = { version = "3.3.0", features = ["preserve_order", "time"] } utoipa-swagger-ui = { version = "3.1.3", features = ["actix-web"] } uuid = { version = "1.3.3", features = ["serde", "v4"] } +validator = "0.16.0" openssl = "0.10.55" x509-parser = "0.15.0" sha-1 = { version = "0.9"} diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 7b20c3865d15..410e3c1113b1 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "olap")] +pub mod user; + // ID generation pub(crate) const ID_LENGTH: usize = 20; pub(crate) const MAX_ID_LENGTH: usize = 64; @@ -52,3 +55,6 @@ pub const ROUTING_CONFIG_ID_LENGTH: usize = 10; pub const LOCKER_REDIS_PREFIX: &str = "LOCKER_PM_TOKEN"; pub const LOCKER_REDIS_EXPIRY_SECONDS: u32 = 60 * 15; // 15 minutes + +#[cfg(any(feature = "olap", feature = "oltp"))] +pub const JWT_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24 * 2; // 2 days diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs new file mode 100644 index 000000000000..3a71fed01a12 --- /dev/null +++ b/crates/router/src/consts/user.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "olap")] +pub const MAX_NAME_LENGTH: usize = 70; +#[cfg(feature = "olap")] +pub const MAX_COMPANY_NAME_LENGTH: usize = 70; + +// USER ROLES +#[cfg(any(feature = "olap", feature = "oltp"))] +pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin"; diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index 817fafdae520..b7023fe5ae46 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -18,6 +18,8 @@ pub mod payments; pub mod payouts; pub mod refunds; pub mod routing; +#[cfg(feature = "olap")] +pub mod user; pub mod utils; #[cfg(all(feature = "olap", feature = "kms"))] pub mod verification; diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index dc1d56721e88..810c079987eb 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -2,6 +2,8 @@ pub mod api_error_response; pub mod customers_error_response; pub mod error_handlers; pub mod transformers; +#[cfg(feature = "olap")] +pub mod user; pub mod utils; use std::fmt::Display; @@ -13,6 +15,8 @@ use diesel_models::errors as storage_errors; pub use redis_interface::errors::RedisError; use scheduler::errors as sch_errors; use storage_impl::errors as storage_impl_errors; +#[cfg(feature = "olap")] +pub use user::*; pub use self::{ api_error_response::ApiErrorResponse, diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs new file mode 100644 index 000000000000..b4d48365dc84 --- /dev/null +++ b/crates/router/src/core/errors/user.rs @@ -0,0 +1,78 @@ +use common_utils::errors::CustomResult; + +use crate::services::ApplicationResponse; + +pub type UserResult = CustomResult; +pub type UserResponse = CustomResult, UserErrors>; + +#[derive(Debug, thiserror::Error)] +pub enum UserErrors { + #[error("User InternalServerError")] + InternalServerError, + #[error("InvalidCredentials")] + InvalidCredentials, + #[error("UserExists")] + UserExists, + #[error("EmailParsingError")] + EmailParsingError, + #[error("NameParsingError")] + NameParsingError, + #[error("PasswordParsingError")] + PasswordParsingError, + #[error("CompanyNameParsingError")] + CompanyNameParsingError, + #[error("MerchantAccountCreationError: {0}")] + MerchantAccountCreationError(String), + #[error("InvalidEmailError")] + InvalidEmailError, + #[error("DuplicateOrganizationId")] + DuplicateOrganizationId, +} + +impl common_utils::errors::ErrorSwitch for UserErrors { + fn switch(&self) -> api_models::errors::types::ApiErrorResponse { + use api_models::errors::types::{ApiError, ApiErrorResponse as AER}; + let sub_code = "UR"; + match self { + Self::InternalServerError => { + AER::InternalServerError(ApiError::new("HE", 0, "Something Went Wrong", None)) + } + Self::InvalidCredentials => AER::Unauthorized(ApiError::new( + sub_code, + 1, + "Incorrect email or password", + None, + )), + Self::UserExists => AER::BadRequest(ApiError::new( + sub_code, + 3, + "An account already exists with this email", + None, + )), + Self::EmailParsingError => { + AER::BadRequest(ApiError::new(sub_code, 7, "Invalid Email", None)) + } + Self::NameParsingError => { + AER::BadRequest(ApiError::new(sub_code, 8, "Invalid Name", None)) + } + Self::PasswordParsingError => { + AER::BadRequest(ApiError::new(sub_code, 9, "Invalid Password", None)) + } + Self::CompanyNameParsingError => { + AER::BadRequest(ApiError::new(sub_code, 14, "Invalid Company Name", None)) + } + Self::MerchantAccountCreationError(error_message) => { + AER::InternalServerError(ApiError::new(sub_code, 15, error_message, None)) + } + Self::InvalidEmailError => { + AER::BadRequest(ApiError::new(sub_code, 16, "Invalid Email", None)) + } + Self::DuplicateOrganizationId => AER::InternalServerError(ApiError::new( + sub_code, + 21, + "An Organization with the id already exists", + None, + )), + } + } +} diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs new file mode 100644 index 000000000000..710dc9281bfa --- /dev/null +++ b/crates/router/src/core/user.rs @@ -0,0 +1,81 @@ +use api_models::user as api; +use diesel_models::enums::UserStatus; +use error_stack::IntoReport; +use masking::{ExposeInterface, Secret}; +use router_env::env; + +use super::errors::{UserErrors, UserResponse}; +use crate::{ + consts::user as consts, routes::AppState, services::ApplicationResponse, types::domain, +}; + +pub async fn connect_account( + state: AppState, + request: api::ConnectAccountRequest, +) -> UserResponse { + let find_user = state + .store + .find_user_by_email(request.email.clone().expose().expose().as_str()) + .await; + + if let Ok(found_user) = find_user { + let user_from_db: domain::UserFromStorage = found_user.into(); + + user_from_db.compare_password(request.password)?; + + let user_role = user_from_db.get_role_from_db(state.clone()).await?; + let jwt_token = user_from_db + .get_jwt_auth_token(state.clone(), user_role.org_id) + .await?; + + return Ok(ApplicationResponse::Json(api::ConnectAccountResponse { + token: Secret::new(jwt_token), + merchant_id: user_role.merchant_id, + name: user_from_db.get_name(), + email: user_from_db.get_email(), + verification_days_left: None, + user_role: user_role.role_id, + user_id: user_from_db.get_user_id().to_string(), + })); + } else if find_user + .map_err(|e| e.current_context().is_db_not_found()) + .err() + .unwrap_or(false) + { + if matches!(env::which(), env::Env::Production) { + return Err(UserErrors::InvalidCredentials).into_report(); + } + + let new_user = domain::NewUser::try_from(request)?; + let _ = new_user + .get_new_merchant() + .get_new_organization() + .insert_org_in_db(state.clone()) + .await?; + let user_from_db = new_user + .insert_user_and_merchant_in_db(state.clone()) + .await?; + let user_role = new_user + .insert_user_role_in_db( + state.clone(), + consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(), + UserStatus::Active, + ) + .await?; + let jwt_token = user_from_db + .get_jwt_auth_token(state.clone(), user_role.org_id) + .await?; + + return Ok(ApplicationResponse::Json(api::ConnectAccountResponse { + token: Secret::new(jwt_token), + merchant_id: user_role.merchant_id, + name: user_from_db.get_name(), + email: user_from_db.get_email(), + verification_days_left: None, + user_role: user_role.role_id, + user_id: user_from_db.get_user_id().to_string(), + })); + } else { + Err(UserErrors::InternalServerError.into()) + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 5cd0b6cbea5f..e106eb06a766 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -146,6 +146,7 @@ pub fn mk_app( .service(routes::Analytics::server(state.clone())) .service(routes::Routing::server(state.clone())) .service(routes::Gsm::server(state.clone())) + .service(routes::User::server(state.clone())) } #[cfg(all(feature = "olap", feature = "kms"))] diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index ac5c14200600..745433c2074b 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -23,6 +23,8 @@ pub mod payouts; pub mod refunds; #[cfg(feature = "olap")] pub mod routing; +#[cfg(feature = "olap")] +pub mod user; #[cfg(all(feature = "olap", feature = "kms"))] pub mod verification; pub mod webhooks; @@ -38,7 +40,7 @@ pub use self::app::Verify; pub use self::app::{ ApiKeys, AppState, BusinessProfile, Cache, Cards, Configs, Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, - PaymentMethods, Payments, Refunds, Webhooks, + PaymentMethods, Payments, Refunds, User, Webhooks, }; #[cfg(feature = "stripe")] pub use super::compatibility::stripe::StripeApis; diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index 9153e9e747f6..a8eda22402c3 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -64,7 +64,10 @@ pub async fn retrieve_merchant_account( ) -> HttpResponse { let flow = Flow::MerchantsAccountRetrieve; let merchant_id = mid.into_inner(); - let payload = web::Json(admin::MerchantId { merchant_id }).into_inner(); + let payload = web::Json(admin::MerchantId { + merchant_id: merchant_id.to_owned(), + }) + .into_inner(); api::server_wrap( flow, @@ -72,7 +75,11 @@ pub async fn retrieve_merchant_account( &req, payload, |state, _, req| get_merchant_account(state, req), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -130,7 +137,13 @@ pub async fn update_merchant_account( &req, json_payload.into_inner(), |state, _, req| merchant_account_update(state, &merchant_id, req), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -203,7 +216,13 @@ pub async fn payment_connector_create( &req, json_payload.into_inner(), |state, _, req| create_payment_connector(state, req, &merchant_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -236,7 +255,7 @@ pub async fn payment_connector_retrieve( let flow = Flow::MerchantConnectorsRetrieve; let (merchant_id, merchant_connector_id) = path.into_inner(); let payload = web::Json(admin::MerchantConnectorId { - merchant_id, + merchant_id: merchant_id.clone(), merchant_connector_id, }) .into_inner(); @@ -249,7 +268,11 @@ pub async fn payment_connector_retrieve( |state, _, req| { retrieve_payment_connector(state, req.merchant_id, req.merchant_connector_id) }, - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -285,9 +308,13 @@ pub async fn payment_connector_list( flow, state, &req, - merchant_id, + merchant_id.to_owned(), |state, _, merchant_id| list_payment_connectors(state, merchant_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -328,7 +355,13 @@ pub async fn payment_connector_update( &req, json_payload.into_inner(), |state, _, req| update_payment_connector(state, &merchant_id, &merchant_connector_id, req), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -362,7 +395,7 @@ pub async fn payment_connector_delete( let (merchant_id, merchant_connector_id) = path.into_inner(); let payload = web::Json(admin::MerchantConnectorId { - merchant_id, + merchant_id: merchant_id.clone(), merchant_connector_id, }) .into_inner(); @@ -372,7 +405,11 @@ pub async fn payment_connector_delete( &req, payload, |state, _, req| delete_payment_connector(state, req.merchant_id, req.merchant_connector_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -419,7 +456,13 @@ pub async fn business_profile_create( &req, payload, |state, _, req| create_business_profile(state, req, &merchant_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -431,7 +474,7 @@ pub async fn business_profile_retrieve( path: web::Path<(String, String)>, ) -> HttpResponse { let flow = Flow::BusinessProfileRetrieve; - let (_, profile_id) = path.into_inner(); + let (merchant_id, profile_id) = path.into_inner(); api::server_wrap( flow, @@ -439,7 +482,11 @@ pub async fn business_profile_retrieve( &req, profile_id, |state, _, profile_id| retrieve_business_profile(state, profile_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -460,7 +507,13 @@ pub async fn business_profile_update( &req, json_payload.into_inner(), |state, _, req| update_business_profile(state, &profile_id, &merchant_id, req), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -498,9 +551,13 @@ pub async fn business_profiles_list( flow, state, &req, - merchant_id, + merchant_id.clone(), |state, _, merchant_id| list_business_profile(state, merchant_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs index c2e289cd0f7e..1f71f1dc2800 100644 --- a/crates/router/src/routes/api_keys.rs +++ b/crates/router/src/routes/api_keys.rs @@ -53,7 +53,13 @@ pub async fn api_key_create( ) .await }, - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -91,7 +97,13 @@ pub async fn api_key_retrieve( &req, (&merchant_id, &key_id), |state, _, (merchant_id, key_id)| api_keys::retrieve_api_key(state, merchant_id, key_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -173,7 +185,13 @@ pub async fn api_key_revoke( &req, (&merchant_id, &key_id), |state, _, (merchant_id, key_id)| api_keys::revoke_api_key(state, merchant_id, key_id), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id: merchant_id.clone(), + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -213,11 +231,15 @@ pub async fn api_key_list( flow, state, &req, - (limit, offset, merchant_id), + (limit, offset, merchant_id.clone()), |state, _, (limit, offset, merchant_id)| async move { api_keys::list_api_keys(state, merchant_id, limit, offset).await }, - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { merchant_id }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 67662961ed44..c34c542d1b6c 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -19,7 +19,7 @@ use super::routing as cloud_routing; #[cfg(all(feature = "olap", feature = "kms"))] use super::verification::{apple_pay_merchant_registration, retrieve_apple_pay_verified_domains}; #[cfg(feature = "olap")] -use super::{admin::*, api_keys::*, disputes::*, files::*, gsm::*}; +use super::{admin::*, api_keys::*, disputes::*, files::*, gsm::*, user::*}; use super::{cache::*, health::*, payment_link::*}; #[cfg(any(feature = "olap", feature = "oltp"))] use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; @@ -710,3 +710,17 @@ impl Verify { ) } } + +pub struct User; + +#[cfg(feature = "olap")] +impl User { + pub fn server(state: AppState) -> Scope { + web::scope("/user") + .app_data(web::Data::new(state)) + .service(web::resource("/signin").route(web::post().to(user_connect_account))) + .service(web::resource("/signup").route(web::post().to(user_connect_account))) + .service(web::resource("/v2/signin").route(web::post().to(user_connect_account))) + .service(web::resource("/v2/signup").route(web::post().to(user_connect_account))) + } +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 4e6fc1870f56..ae573e871627 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -24,6 +24,7 @@ pub enum ApiIdentifier { PaymentLink, Routing, Gsm, + User, } impl From for ApiIdentifier { @@ -134,6 +135,8 @@ impl From for ApiIdentifier { | Flow::GsmRuleRetrieve | Flow::GsmRuleUpdate | Flow::GsmRuleDelete => Self::Gsm, + + Flow::UserConnectAccount => Self::User, } } } diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 5ed73df1c175..ed36721da445 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -4,7 +4,7 @@ pub mod helpers; use actix_web::{web, Responder}; use api_models::payments::HeaderPayload; use error_stack::report; -use router_env::{instrument, tracing, types, Flow}; +use router_env::{env, instrument, tracing, types, Flow}; use crate::{ self as app, @@ -118,7 +118,10 @@ pub async fn payments_create( api::AuthFlow::Merchant, ) }, - &auth::ApiKeyAuth, + match env::which() { + env::Env::Production => &auth::ApiKeyAuth, + _ => auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + }, locking_action, ) .await @@ -249,7 +252,11 @@ pub async fn payments_retrieve( HeaderPayload::default(), ) }, - &*auth_type, + auth::auth_type( + &*auth_type, + &auth::JWTAuth, + req.headers(), + ), locking_action, ) .await @@ -828,7 +835,7 @@ pub async fn payments_list( &req, payload, |state, auth, req| payments::list_payments(state, auth.merchant_account, req), - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await @@ -848,7 +855,7 @@ pub async fn payments_list_by_filter( &req, payload, |state, auth, req| payments::apply_filters_on_payments(state, auth.merchant_account, req), - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await @@ -868,7 +875,7 @@ pub async fn get_filters_for_payments( &req, payload, |state, auth, req| payments::get_filters_for_payments(state, auth.merchant_account, req), - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs index c20f3fbf975d..d1f5cb56fe23 100644 --- a/crates/router/src/routes/refunds.rs +++ b/crates/router/src/routes/refunds.rs @@ -37,7 +37,7 @@ pub async fn refunds_create( &req, json_payload.into_inner(), |state, auth, req| refund_create_core(state, auth.merchant_account, auth.key_store, req), - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await @@ -88,7 +88,7 @@ pub async fn refunds_retrieve( refund_retrieve_core, ) }, - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await @@ -202,7 +202,7 @@ pub async fn refunds_list( &req, payload.into_inner(), |state, auth, req| refund_list(state, auth.merchant_account, req), - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await @@ -235,7 +235,7 @@ pub async fn refunds_filter_list( &req, payload.into_inner(), |state, auth, req| refund_filter_list(state, auth.merchant_account, req), - &auth::ApiKeyAuth, + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 9252c360a9ce..b87116f47fc5 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -14,7 +14,7 @@ use router_env::{ use crate::{ core::{api_locking, routing}, routes::AppState, - services::{api as oss_api, authentication as oss_auth, authentication as auth}, + services::{api as oss_api, authentication as auth}, }; #[cfg(feature = "olap")] @@ -30,11 +30,11 @@ pub async fn routing_create_config( state, &req, json_payload.into_inner(), - |state, auth: oss_auth::AuthenticationData, payload| { + |state, auth: auth::AuthenticationData, payload| { routing::create_routing_config(state, auth.merchant_account, auth.key_store, payload) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -55,7 +55,7 @@ pub async fn routing_link_config( state, &req, path.into_inner(), - |state, auth: oss_auth::AuthenticationData, algorithm_id| { + |state, auth: auth::AuthenticationData, algorithm_id| { routing::link_routing_config( state, auth.merchant_account, @@ -65,7 +65,7 @@ pub async fn routing_link_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -87,11 +87,11 @@ pub async fn routing_retrieve_config( state, &req, algorithm_id, - |state, auth: oss_auth::AuthenticationData, algorithm_id| { + |state, auth: auth::AuthenticationData, algorithm_id| { routing::retrieve_routing_config(state, auth.merchant_account, algorithm_id) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -114,7 +114,7 @@ pub async fn routing_retrieve_dictionary( state, &req, query.into_inner(), - |state, auth: oss_auth::AuthenticationData, query_params| { + |state, auth: auth::AuthenticationData, query_params| { routing::retrieve_merchant_routing_dictionary( state, auth.merchant_account, @@ -122,7 +122,7 @@ pub async fn routing_retrieve_dictionary( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -138,11 +138,11 @@ pub async fn routing_retrieve_dictionary( state, &req, (), - |state, auth: oss_auth::AuthenticationData, _| { + |state, auth: auth::AuthenticationData, _| { routing::retrieve_merchant_routing_dictionary(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -168,11 +168,11 @@ pub async fn routing_unlink_config( state, &req, payload.into_inner(), - |state, auth: oss_auth::AuthenticationData, payload_req| { + |state, auth: auth::AuthenticationData, payload_req| { routing::unlink_routing_config(state, auth.merchant_account, payload_req) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -188,11 +188,11 @@ pub async fn routing_unlink_config( state, &req, (), - |state, auth: oss_auth::AuthenticationData, _| { + |state, auth: auth::AuthenticationData, _| { routing::unlink_routing_config(state, auth.merchant_account, auth.key_store) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -213,11 +213,11 @@ pub async fn routing_update_default_config( state, &req, json_payload.into_inner(), - |state, auth: oss_auth::AuthenticationData, updated_config| { + |state, auth: auth::AuthenticationData, updated_config| { routing::update_default_routing_config(state, auth.merchant_account, updated_config) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -236,11 +236,11 @@ pub async fn routing_retrieve_default_config( state, &req, (), - |state, auth: oss_auth::AuthenticationData, _| { + |state, auth: auth::AuthenticationData, _| { routing::retrieve_default_routing_config(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -268,7 +268,7 @@ pub async fn routing_retrieve_linked_config( routing::retrieve_linked_routing_config(state, auth.merchant_account, query_params) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, @@ -284,11 +284,11 @@ pub async fn routing_retrieve_linked_config( state, &req, (), - |state, auth: oss_auth::AuthenticationData, _| { + |state, auth: auth::AuthenticationData, _| { routing::retrieve_linked_routing_config(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), #[cfg(feature = "release")] &auth::JWTAuth, api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs new file mode 100644 index 000000000000..0ff11ce087b5 --- /dev/null +++ b/crates/router/src/routes/user.rs @@ -0,0 +1,31 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::user as user_api; +use router_env::Flow; + +use super::AppState; +use crate::{ + core::{api_locking, user}, + services::{ + api, + authentication::{self as auth}, + }, +}; + +pub async fn user_connect_account( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::UserConnectAccount; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + |state, _, req_body| user::connect_account(state, req_body), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 631e9a5c189d..21f33f0fa0b8 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -1,6 +1,8 @@ pub mod api; pub mod authentication; pub mod encryption; +#[cfg(feature = "olap")] +pub mod jwt; pub mod logger; #[cfg(feature = "kms")] diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 0a7f5189b904..da4dec2eec8a 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -9,6 +9,10 @@ use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use masking::{PeekInterface, StrongSecret}; use serde::Serialize; +#[cfg(feature = "olap")] +use super::jwt; +#[cfg(feature = "olap")] +use crate::consts; use crate::{ configs::settings, core::{ @@ -71,6 +75,37 @@ impl AuthenticationType { } } +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AuthToken { + pub user_id: String, + pub merchant_id: String, + pub role_id: String, + pub exp: u64, + pub org_id: String, +} + +#[cfg(feature = "olap")] +impl AuthToken { + pub async fn new_token( + user_id: String, + merchant_id: String, + role_id: String, + settings: &settings::Settings, + org_id: String, + ) -> errors::UserResult { + let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); + let exp = jwt::generate_exp(exp_duration)?.as_secs(); + let token_payload = Self { + user_id, + merchant_id, + role_id, + exp, + org_id, + }; + jwt::generate_jwt(&token_payload, settings).await + } +} + pub trait AuthInfo { fn get_merchant_id(&self) -> Option<&str>; } @@ -366,14 +401,58 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { - let mut token = get_jwt(request_headers)?; - token = strip_jwt_token(token)?; - decode_jwt::(token, state) - .await - .map(|_| ((), AuthenticationType::NoAuth)) + let payload = parse_jwt_payload::(request_headers, state).await?; + Ok(( + (), + AuthenticationType::MerchantJWT { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +pub struct JWTAuthMerchantFromRoute { + pub merchant_id: String, +} + +#[async_trait] +impl AuthenticateAndFetch<(), A> for JWTAuthMerchantFromRoute +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + + // Check if token has access to merchantID that has been requested through query param + if payload.merchant_id != self.merchant_id { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + Ok(( + (), + AuthenticationType::MerchantJWT { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) } } +pub async fn parse_jwt_payload(headers: &HeaderMap, state: &A) -> RouterResult +where + T: serde::de::DeserializeOwned, + A: AppStateInfo + Sync, +{ + let token = get_jwt_from_authorization_header(headers)?; + let payload = decode_jwt(token, state).await?; + + Ok(payload) +} + #[derive(serde::Deserialize)] struct JwtAuthPayloadFetchMerchantAccount { merchant_id: String, @@ -389,9 +468,9 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { - let mut token = get_jwt(request_headers)?; - token = strip_jwt_token(token)?; - let payload = decode_jwt::(token, state).await?; + let payload = + parse_jwt_payload::(request_headers, state) + .await?; let key_store = state .store() .get_merchant_key_store_by_merchant_id( @@ -595,14 +674,16 @@ pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult .transpose() } -pub fn get_jwt(headers: &HeaderMap) -> RouterResult<&str> { +pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&str> { headers .get(crate::headers::AUTHORIZATION) .get_required_value(crate::headers::AUTHORIZATION)? .to_str() .into_report() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to convert JWT token to string") + .attach_printable("Failed to convert JWT token to string")? + .strip_prefix("Bearer ") + .ok_or(errors::ApiErrorResponse::InvalidJwtToken.into()) } pub fn strip_jwt_token(token: &str) -> RouterResult<&str> { diff --git a/crates/router/src/services/jwt.rs b/crates/router/src/services/jwt.rs new file mode 100644 index 000000000000..b69a21583919 --- /dev/null +++ b/crates/router/src/services/jwt.rs @@ -0,0 +1,42 @@ +use common_utils::errors::CustomResult; +use error_stack::{IntoReport, ResultExt}; +use jsonwebtoken::{encode, EncodingKey, Header}; +use masking::PeekInterface; + +use super::authentication; +use crate::{configs::settings::Settings, core::errors::UserErrors}; + +pub fn generate_exp( + exp_duration: std::time::Duration, +) -> CustomResult { + std::time::SystemTime::now() + .checked_add(exp_duration) + .ok_or(UserErrors::InternalServerError)? + .duration_since(std::time::UNIX_EPOCH) + .into_report() + .change_context(UserErrors::InternalServerError) +} + +pub async fn generate_jwt( + claims_data: &T, + settings: &Settings, +) -> CustomResult +where + T: serde::ser::Serialize, +{ + let jwt_secret = authentication::get_jwt_secret( + &settings.secrets, + #[cfg(feature = "kms")] + external_services::kms::get_kms_client(&settings.kms).await, + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to obtain JWT secret")?; + encode( + &Header::default(), + claims_data, + &EncodingKey::from_secret(jwt_secret.peek().as_bytes()), + ) + .into_report() + .change_context(UserErrors::InternalServerError) +} diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs index 44123850d468..c93f96eaf09e 100644 --- a/crates/router/src/types/domain.rs +++ b/crates/router/src/types/domain.rs @@ -5,9 +5,13 @@ mod merchant_account; mod merchant_connector_account; mod merchant_key_store; pub mod types; +#[cfg(feature = "olap")] +pub mod user; pub use address::*; pub use customer::*; pub use merchant_account::*; pub use merchant_connector_account::*; pub use merchant_key_store::*; +#[cfg(feature = "olap")] +pub use user::*; diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs new file mode 100644 index 000000000000..c053b0f15448 --- /dev/null +++ b/crates/router/src/types/domain/user.rs @@ -0,0 +1,483 @@ +use std::{collections::HashSet, ops, str::FromStr}; + +use api_models::{admin as admin_api, organization as api_org, user as user_api}; +use common_utils::pii; +use diesel_models::{ + enums::UserStatus, + organization as diesel_org, + organization::Organization, + user as storage_user, + user_role::{UserRole, UserRoleNew}, +}; +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, PeekInterface, Secret}; +use once_cell::sync::Lazy; +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + consts::user as consts, + core::{ + admin, + errors::{UserErrors, UserResult}, + }, + db::StorageInterface, + routes::AppState, + services::authentication::AuthToken, + types::transformers::ForeignFrom, + utils::user::password, +}; + +#[derive(Clone)] +pub struct UserName(Secret); + +impl UserName { + pub fn new(name: Secret) -> UserResult { + let name = name.expose(); + let is_empty_or_whitespace = name.trim().is_empty(); + let is_too_long = name.graphemes(true).count() > consts::MAX_NAME_LENGTH; + + let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; + let contains_forbidden_characters = name.chars().any(|g| forbidden_characters.contains(&g)); + + if is_empty_or_whitespace || is_too_long || contains_forbidden_characters { + Err(UserErrors::NameParsingError.into()) + } else { + Ok(Self(name.into())) + } + } + + pub fn get_secret(self) -> Secret { + self.0 + } +} + +impl TryFrom for UserName { + type Error = error_stack::Report; + + fn try_from(value: pii::Email) -> UserResult { + Self::new(Secret::new( + value + .peek() + .split_once('@') + .ok_or(UserErrors::InvalidEmailError)? + .0 + .to_string(), + )) + } +} + +#[derive(Clone, Debug)] +pub struct UserEmail(pii::Email); + +static BLOCKED_EMAIL: Lazy> = Lazy::new(|| { + let blocked_emails_content = include_str!("../../utils/user/blocker_emails.txt"); + let blocked_emails: HashSet = blocked_emails_content + .lines() + .map(|s| s.trim().to_owned()) + .collect(); + blocked_emails +}); + +impl UserEmail { + pub fn new(email: Secret) -> UserResult { + let email_string = email.expose(); + let email = + pii::Email::from_str(&email_string).change_context(UserErrors::EmailParsingError)?; + + if validator::validate_email(&email_string) { + let (_username, domain) = match email_string.as_str().split_once('@') { + Some((u, d)) => (u, d), + None => return Err(UserErrors::EmailParsingError.into()), + }; + + if BLOCKED_EMAIL.contains(domain) { + return Err(UserErrors::InvalidEmailError.into()); + } + Ok(Self(email)) + } else { + Err(UserErrors::EmailParsingError.into()) + } + } + + pub fn from_pii_email(email: pii::Email) -> UserResult { + let email_string = email.peek(); + if validator::validate_email(email_string) { + let (_username, domain) = match email_string.split_once('@') { + Some((u, d)) => (u, d), + None => return Err(UserErrors::EmailParsingError.into()), + }; + if BLOCKED_EMAIL.contains(domain) { + return Err(UserErrors::InvalidEmailError.into()); + } + Ok(Self(email)) + } else { + Err(UserErrors::EmailParsingError.into()) + } + } + + pub fn into_inner(self) -> pii::Email { + self.0 + } + + pub fn get_secret(self) -> Secret { + (*self.0).clone() + } +} + +impl TryFrom for UserEmail { + type Error = error_stack::Report; + + fn try_from(value: pii::Email) -> Result { + Self::from_pii_email(value) + } +} + +impl ops::Deref for UserEmail { + type Target = Secret; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone)] +pub struct UserPassword(Secret); + +impl UserPassword { + pub fn new(password: Secret) -> UserResult { + let password = password.expose(); + if password.is_empty() { + Err(UserErrors::PasswordParsingError.into()) + } else { + Ok(Self(password.into())) + } + } + + pub fn get_secret(&self) -> Secret { + self.0.clone() + } +} + +#[derive(Clone)] +pub struct UserCompanyName(String); + +impl UserCompanyName { + pub fn new(company_name: String) -> UserResult { + let company_name = company_name.trim(); + let is_empty_or_whitespace = company_name.is_empty(); + let is_too_long = company_name.graphemes(true).count() > consts::MAX_COMPANY_NAME_LENGTH; + + let is_all_valid_characters = company_name + .chars() + .all(|x| x.is_alphanumeric() || x.is_ascii_whitespace() || x == '_'); + if is_empty_or_whitespace || is_too_long || !is_all_valid_characters { + Err(UserErrors::CompanyNameParsingError.into()) + } else { + Ok(Self(company_name.to_string())) + } + } + + pub fn get_secret(self) -> String { + self.0 + } +} + +#[derive(Clone)] +pub struct NewUserOrganization(diesel_org::OrganizationNew); + +impl NewUserOrganization { + pub async fn insert_org_in_db(self, state: AppState) -> UserResult { + state + .store + .insert_organization(self.0) + .await + .map_err(|e| { + if e.current_context().is_db_unique_violation() { + e.change_context(UserErrors::DuplicateOrganizationId) + } else { + e.change_context(UserErrors::InternalServerError) + } + }) + .attach_printable("Error while inserting organization") + } + + pub fn get_organization_id(&self) -> String { + self.0.org_id.clone() + } +} + +impl From for NewUserOrganization { + fn from(_value: user_api::ConnectAccountRequest) -> Self { + let new_organization = api_org::OrganizationNew::new(None); + let db_organization = ForeignFrom::foreign_from(new_organization); + Self(db_organization) + } +} + +#[derive(Clone)] +pub struct NewUserMerchant { + merchant_id: String, + company_name: Option, + new_organization: NewUserOrganization, +} + +impl NewUserMerchant { + pub fn get_company_name(&self) -> Option { + self.company_name.clone().map(UserCompanyName::get_secret) + } + + pub fn get_merchant_id(&self) -> String { + self.merchant_id.clone() + } + + pub fn get_new_organization(&self) -> NewUserOrganization { + self.new_organization.clone() + } + + pub async fn check_if_already_exists_in_db(&self, state: AppState) -> UserResult<()> { + if state + .store + .get_merchant_key_store_by_merchant_id( + self.get_merchant_id().as_str(), + &state.store.get_master_key().to_vec().into(), + ) + .await + .is_ok() + { + return Err(UserErrors::MerchantAccountCreationError(format!( + "Merchant with {} already exists", + self.get_merchant_id() + ))) + .into_report(); + } + Ok(()) + } + + pub async fn create_new_merchant_and_insert_in_db(&self, state: AppState) -> UserResult<()> { + self.check_if_already_exists_in_db(state.clone()).await?; + Box::pin(admin::create_merchant_account( + state.clone(), + admin_api::MerchantAccountCreate { + merchant_id: self.get_merchant_id(), + metadata: None, + locker_id: None, + return_url: None, + merchant_name: self.get_company_name().map(Secret::new), + webhook_details: None, + publishable_key: None, + organization_id: Some(self.new_organization.get_organization_id()), + merchant_details: None, + routing_algorithm: None, + parent_merchant_id: None, + payment_link_config: None, + sub_merchants_enabled: None, + frm_routing_algorithm: None, + intent_fulfillment_time: None, + payout_routing_algorithm: None, + primary_business_details: None, + payment_response_hash_key: None, + enable_payment_response_hash: None, + redirect_to_merchant_with_http_post: None, + }, + )) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Error while creating a merchant")?; + Ok(()) + } +} + +impl TryFrom for NewUserMerchant { + type Error = error_stack::Report; + + fn try_from(value: user_api::ConnectAccountRequest) -> UserResult { + let merchant_id = format!("merchant_{}", common_utils::date_time::now_unix_timestamp()); + let new_organization = NewUserOrganization::from(value); + + Ok(Self { + company_name: None, + merchant_id, + new_organization, + }) + } +} + +#[derive(Clone)] +pub struct NewUser { + user_id: String, + name: UserName, + email: UserEmail, + password: UserPassword, + new_merchant: NewUserMerchant, +} + +impl NewUser { + pub fn get_user_id(&self) -> String { + self.user_id.clone() + } + + pub fn get_email(&self) -> UserEmail { + self.email.clone() + } + + pub fn get_name(&self) -> Secret { + self.name.clone().get_secret() + } + + pub fn get_new_merchant(&self) -> NewUserMerchant { + self.new_merchant.clone() + } + + pub async fn insert_user_in_db( + &self, + db: &dyn StorageInterface, + ) -> UserResult { + match db.insert_user(self.clone().try_into()?).await { + Ok(user) => Ok(user.into()), + Err(e) => { + if e.current_context().is_db_unique_violation() { + return Err(e.change_context(UserErrors::UserExists)); + } else { + return Err(e.change_context(UserErrors::InternalServerError)); + } + } + } + .attach_printable("Error while inserting user") + } + + pub async fn insert_user_and_merchant_in_db( + &self, + state: AppState, + ) -> UserResult { + let db = state.store.as_ref(); + let merchant_id = self.get_new_merchant().get_merchant_id(); + self.new_merchant + .create_new_merchant_and_insert_in_db(state.clone()) + .await?; + let created_user = self.insert_user_in_db(db).await; + if created_user.is_err() { + let _ = admin::merchant_account_delete(state, merchant_id).await; + }; + created_user + } + + pub async fn insert_user_role_in_db( + self, + state: AppState, + role_id: String, + user_status: UserStatus, + ) -> UserResult { + let now = common_utils::date_time::now(); + let user_id = self.get_user_id(); + + state + .store + .insert_user_role(UserRoleNew { + merchant_id: self.get_new_merchant().get_merchant_id(), + status: user_status, + created_by: user_id.clone(), + last_modified_by: user_id.clone(), + user_id, + role_id, + created_at: now, + last_modified_at: now, + org_id: self + .get_new_merchant() + .get_new_organization() + .get_organization_id(), + }) + .await + .change_context(UserErrors::InternalServerError) + } +} + +impl TryFrom for storage_user::UserNew { + type Error = error_stack::Report; + + fn try_from(value: NewUser) -> UserResult { + let hashed_password = password::generate_password_hash(value.password.get_secret())?; + Ok(Self { + user_id: value.get_user_id(), + name: value.get_name(), + email: value.get_email().into_inner(), + password: hashed_password, + ..Default::default() + }) + } +} + +impl TryFrom for NewUser { + type Error = error_stack::Report; + + fn try_from(value: user_api::ConnectAccountRequest) -> UserResult { + let user_id = uuid::Uuid::new_v4().to_string(); + let email = value.email.clone().try_into()?; + let name = UserName::try_from(value.email.clone())?; + let password = UserPassword::new(value.password.clone())?; + let new_merchant = NewUserMerchant::try_from(value)?; + + Ok(Self { + user_id, + name, + email, + password, + new_merchant, + }) + } +} + +pub struct UserFromStorage(pub storage_user::User); + +impl From for UserFromStorage { + fn from(value: storage_user::User) -> Self { + Self(value) + } +} + +impl UserFromStorage { + pub fn get_user_id(&self) -> &str { + self.0.user_id.as_str() + } + + pub fn compare_password(&self, candidate: Secret) -> UserResult<()> { + match password::is_correct_password(candidate, self.0.password.clone()) { + Ok(true) => Ok(()), + Ok(false) => Err(UserErrors::InvalidCredentials.into()), + Err(e) => Err(e), + } + } + + pub fn get_name(&self) -> Secret { + self.0.name.clone() + } + + pub fn get_email(&self) -> pii::Email { + self.0.email.clone() + } + + pub async fn get_jwt_auth_token(&self, state: AppState, org_id: String) -> UserResult { + let role_id = self.get_role_from_db(state.clone()).await?.role_id; + let merchant_id = state + .store + .find_user_role_by_user_id(self.get_user_id()) + .await + .change_context(UserErrors::InternalServerError)? + .merchant_id; + AuthToken::new_token( + self.0.user_id.clone(), + merchant_id, + role_id, + &state.conf, + org_id, + ) + .await + } + + pub async fn get_role_from_db(&self, state: AppState) -> UserResult { + state + .store + .find_user_role_by_user_id(self.get_user_id()) + .await + .change_context(UserErrors::InternalServerError) + } +} diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 558044028f7a..aadb714e8ce2 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -1,6 +1,8 @@ pub mod custom_serde; pub mod db_utils; pub mod ext_traits; +#[cfg(feature = "olap")] +pub mod user; #[cfg(feature = "kv_store")] pub mod storage_partitioning; diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs new file mode 100644 index 000000000000..c72e4b9feb3c --- /dev/null +++ b/crates/router/src/utils/user.rs @@ -0,0 +1 @@ +pub mod password; diff --git a/crates/router/src/utils/user/blocker_emails.txt b/crates/router/src/utils/user/blocker_emails.txt new file mode 100644 index 000000000000..e29e1b2d86f4 --- /dev/null +++ b/crates/router/src/utils/user/blocker_emails.txt @@ -0,0 +1,2349 @@ +020.co.uk +123.com +123box.net +123india.com +123mail.cl +123mail.org +123qwe.co.uk +138mail.com +141.ro +150mail.com +150ml.com +16mail.com +1963chevrolet.com +1963pontiac.com +1netdrive.com +1st-website.com +1stpd.net +2-mail.com +20after4.com +21cn.com +24h.co.jp +24horas.com +271soundview.com +2die4.com +2mydns.com +2net.us +3000.it +3ammagazine.com +3email.com +3xl.net +444.net +4email.com +4email.net +4newyork.com +50mail.com +55mail.cc +5fm.za.com +6210.hu +6sens.com +702mail.co.za +7110.hu +8848.net +8m.com +8m.net +8x.com.br +8u8.com +8u8.hk +8u8.tw +a-topmail.at +about.com +abv.bg +acceso.or.cr +access4less.net +accessgcc.com +acmemail.net +adiga.com +adinet.com.uy +adres.nl +advalvas.be +aeiou.pt +aeneasmail.com +afrik.com +afropoets.com +aggies.com +ahaa.dk +aichi.com +aim.com +airpost.net +aiutamici.com +aklan.com +aknet.kg +alabama.usa.com +alaska.usa.com +alavatotal.com +albafind.com +albawaba.com +alburaq.net +aldeax.com +aldeax.com.ar +alex4all.com +aliyun.com +alexandria.cc +algeria.com +alice.it +allmail.net +alskens.dk +altavista.se +altbox.org +alternativagratis.com +alum.com +alunos.unipar.br +alvilag.hu +amenworld.com +america.hm +americamail.com +amnetsal.com +amorous.com +ananzi.co.za +anet.ne.jp +anfmail.com +angelfire.com +animail.net +aniverse.com +anjungcafe.com +another.com +antedoonsub.com +antwerpen.com +anunciador.net +anytimenow.com +aon.at +apexmail.com +apollo.lv +approvers.net +aprava.com +apropo.ro +arcor.de +argentina.com +arizona.usa.com +arkansas.usa.com +armmail.com +army.com +arnet.com.ar +aroma.com +arrl.net +aruba.it +asheville.com +asia-links.com +asiamail.com +assala.com +assamesemail.com +asurfer.com +atl.lv +atlas.cz +atlas.sk +atozasia.com +atreillou.com +att.net +au.ru +aubenin.com +aus-city.com +aussiemail.com.au +avasmail.com.mv +axarnet.com +ayna.com +azet.sk +babbalu.com +badgers.com +bakpaka.com +bakpaka.net +balochistan.org +baluch.com +bama-fan.com +bancora.net +bankersmail.com +barlick.net +beeebank.com +beehive.org +been-there.com +beirut.com +belizehome.com +belizemail.net +belizeweb.com +bellsouth.net +berlin.de +bestmail.us +bflomail.com +bgnmail.com +bharatmail.com +big-orange.com +bigboss.cz +bigfoot.com +bigger.com +bigmailbox.com +bigmir.net +bigstring.com +bip.net +bigpond.com +bitwiser.com +biz.by +bizhosting.com +black-sea.ro +blackburnmail.com +blackglobalnetwork.net +blink182.net +blue.devils.com +bluebottle.com +bluemail.ch +blumail.org +blvds.com +bol.com.br +bolando.com +bollywood2000.com +bollywoodz.com +bombka.dyn.pl +bonbon.net +boom.com +bootmail.com +bostonoffice.com +box.az +boxbg.com +boxemail.com +brain.com.pk +brasilia.net +bravanese.com +brazilmail.com.br +breathe.com +brestonline.com +brfree.com.br +brujula.net +btcc.org +buffaloes.com +bulgaria.com +bulldogs.com +bumerang.ro +burntmail.com +butch-femme.net +buzy.com +buzzjakkerz.com +c-box.cz +c3.hu +c4.com +cadinfo.net +calcfacil.com.br +calcware.org +california.usa.com +callnetuk.com +camaroclubsweden.com +canada-11.com +canada.com +canal21.com +canoemail.com +caramail.com +cardblvd.com +care-mail.com +care2.com +caress.com +carioca.net +cashette.com +casino.com +casinomail.com +cataloniamail.com +catalunyamail.com +cataz.com +catcha.com +catholic.org +caths.co.uk +caxess.net +cbrmail.com +cc.lv +cemelli.com +centoper.it +centralpets.com +centrum.cz +centrum.sk +centurylink.net +cercaziende.it +cgac.es +chaiyo.com +chaiyomail.com +chance2mail.com +channelonetv.com +charter.net +chattown.com +checkitmail.at +chelny.com +cheshiremail.com +chil-e.com +chillimail.com +china.com +christianmail.org +ciaoweb.it +cine.com +ciphercom.net +circlemail.com +cititrustbank1.cjb.net +citromail.hu +citynetusa.com +ciudad.com.ar +claramail.com +classicmail.co.za +cliffhanger.com +clix.pt +close2you.net +cluemail.com +clujnapoca.ro +collegeclub.com +colombia.com +colorado.usa.com +comcast.net +comfortable.com +compaqnet.fr +compuserve.com +computer.net +computermail.net +computhouse.com +conevyt.org.mx +connect4free.net +connecticut.usa.com +coolgoose.com +coolkiwi.com +coollist.com +coxinet.net +coolmail.com +coolmail.net +coolsend.com +cooltoad.com +cooperation.net +copacabana.com +copticmail.com +corporateattorneys.com +corporation.net +correios.net.br +correomagico.com +cosmo.com +cosmosurf.net +cougars.com +count.com +countrybass.com +couple.com +criticalpath.net +critterpost.com +crosspaths.net +crosswinds.net +cryingmail.com +cs.com +csucsposta.hu +cumbriamail.com +curio-city.com +custmail.com +cwazy.co.uk +cwazy.net +cww.de +cyberaccess.com.pk +cybergirls.dk +cyberguys.dk +cybernet.it +cymail.net +dabsol.net +dada.net +dadanet.it +dailypioneer.com +damuc.org.br +dansegulvet.com +darkhorsefan.net +data54.com +davegracey.com +dayzers.com +daum.net +dbmail.com +dcemail.com +dcsi.net +deacons.com +deadlymob.org +deal-maker.com +dearriba.com +degoo.com +delajaonline.org +delaware.usa.com +delfi.lv +delhimail.com +demon.deacons.com +desertonline.com +desidrivers.com +deskpilot.com +despammed.com +detik.com +devils.com +dexara.net +dhmail.net +di-ve.com +didamail.com +digitaltrue.com +direccion.com +director-general.com +diri.com +discardmail.com +discoverymail.net +disinfo.net +djmillenium.com +dmailman.com +dnsmadeeasy.com +do.net.ar +dodgeit.com +dogmail.co.uk +doityourself.com +domaindiscover.com +domainmanager.com +doneasy.com +dontexist.org +dores.com +dostmail.com +dot5hosting.com +dotcom.fr +dotnow.com +dott.it +doubt.com +dplanet.ch +dragoncon.net +dragonfans.com +dropzone.com +dserver.org +dubaiwebcity.com +dublin.ie +dustdevil.com +dynamitemail.com +dyndns.org +e-apollo.lv +e-hkma.com +e-mail.cz +e-mail.ph +e-mailanywhere.com +e-milio.com +e-tapaal.com +e-webtec.com +earthalliance.com +earthling.net +eastmail.com +eastrolog.com +easy-pages.com +easy.com +easyinfomail.co.za +easypeasy.com +echina.com +ecn.org +ecplaza.net +eircom.net +edsamail.com.ph +educacao.te.pt +edumail.co.za +eeism.com +ego.co.th +ekolay.net +elforotv.com.ar +elitemail.org +elsitio.com +eltimon.com +elvis.com +email.com.br +email.cz +email.bg +email.it +email.lu +email.lviv.ua +email.nu +email.ro +email.si +email2me.com +emailacc.com +emailaccount.com +emailaddresses.com +emailchoice.com +emailcorner.net +emailn.de +emailengine.net +emailengine.org +emailgaul.com +emailgroups.net +emailhut.net +emailpinoy.com +emailplanet.com +emailplus.org +emailuser.net +ematic.com +embarqmail.com +embroideryforums.com +eml.cc +emoka.ro +emptymail.com +enel.net +enelpunto.net +england.com +enterate.com.ar +entryweb.it +entusiastisk.com +enusmail.com +epatra.com +epix.net +epomail.com +epost.de +eprompter.com +eqqu.com +eramail.co.za +eresmas.com +eriga.lv +ertelecom.ru +esde-s.org +esfera.cl +estadao.com.br +etllao.com +euromail.net +euroseek.com +euskalmail.com +evafan.com +everyday.com.kh +everymail.net +everyone.net +execs2k.com +executivemail.co.za +expn.com +ezilon.com +ezrs.com +f-m.fm +facilmail.com +fadrasha.net +fadrasha.org +faithhighway.com +faithmail.com +familymailbox.com +familyroll.com +familysafeweb.net +fan.com +fan.net +faroweb.com +fast-email.com +fast-mail.org +fastem.com +fastemail.us +fastemailer.com +fastermail.com +fastest.cc +fastimap.com +fastmailbox.net +fastmessaging.com +fastwebmail.it +fawz.net +fea.st +federalcontractors.com +fedxmail.com +feelings.com +female.ru +fepg.net +ffanet.com +fiberia.com +filipinolinks.com +financesource.com +findmail.com +fiscal.net +flashmail.com +flipcode.com +florida.usa.com +floridagators.com +fmail.co.uk +fmailbox.com +fmgirl.com +fmguy.com +fnmail.com +footballer.com +foxmail.com +forfree.at +forsythmissouri.org +fortuncity.com +forum.dk +free.com.pe +free.fr +free.net.nz +freeaccess.nl +freegates.be +freeghana.com +freehosting.nl +freei.co.th +freeler.nl +freemail.globalsite.com.br +freemuslim.net +freenet.de +freenet.kg +freeola.net +freepgs.com +freesbee.fr +freeservers.com +freestart.hu +freesurf.ch +freesurf.fr +freesurf.nl +freeuk.com +freeuk.net +freeweb.it +freewebemail.com +freeyellow.com +frisurf.no +frontiernet.net +fsmail.net +fsnet.co.uk +ftml.net +fuelie.org +fun-greetings-jokes.com +fun.21cn.com +fusemail.com +fut.es +gala.net +galmail.co.za +gamebox.net +gamecocks.com +gawab.com +gay.com +gaymailbox.com +gaza.net +gazeta.pl +gci.net +gdi.net +geeklife.com +gemari.or.id +genxemail.com +geopia.com +georgia.usa.com +getmail.no +ggaweb.ch +giga4u.de +gjk.dk +glay.org +glendale.net +globalfree.it +globomail.com +globalpinoy.com +globalsite.com.br +globalum.com +globetrotter.net +go-bama.com +go-cavs.com +go-chargers.com +go-dawgs.com +go-gators.com +go-hogs.com +go-irish.com +go-spartans.com +go-tigers.com +go.aggies.com +go.air-force.com +go.badgers.com +go.big-orange.com +go.blue.devils.com +go.buffaloes.com +go.bulldogs.com +go.com +go.cougars.com +go.dores.com +go.gamecocks.com +go.huskies.com +go.longhorns.com +go.mustangs.com +go.rebels.com +go.ro +go.ru +go.terrapins.com +go.wildcats.com +go.wolverines.com +go.yellow-jackets.com +go2net.com +go4.it +gofree.co.uk +golfemail.com +goliadtexas.com +gomail.com.ua +gonowmail.com +gonuts4free.com +googlemail.com +goplay.com +gorontalo.net +gotmail.com +gotomy.com +govzone.com +grad.com +graffiti.net +gratisweb.com +gtechnics.com +guate.net +guessmail.com +gwalla.com +h-mail.us +haberx.com +hailmail.net +halejob.com +hamptonroads.com +handbag.com +hanmail.net +happemail.com +happycounsel.com +hawaii.com +hawaii.usa.com +hayahaya.tg +hedgeai.com +heesun.net +heremail.com +hetnet.nl +highveldmail.co.za +hildebrands.de +hingis.org +hispavista.com +hitmanrecords.com +hockeyghiaccio.com +hockeymail.com +holapuravida.com +home.no.net +home.ro +home.se +homelocator.com +homemail.co.za +homenetmail.com +homestead.com +homosexual.net +hongkong.com +hong-kong-1.com +hopthu.com +hosanna.net +hot.ee +hotbot.com +hotbox.ru +hotcoolmail.com +hotdak.com +hotfire.net +hotinbox.com +hotpop.com +hotvoice.com +hour.com +howling.com +huhmail.com +humour.com +hurra.de +hush.ai +hush.com +hushmail.com +huskies.com +hutchcity.com +i-france.com +i-p.com +i12.com +i2828.com +ibatam.com +ibest.com.br +ibizdns.com +icafe.com +ice.is +icestorm.com +icq.com +icqmail.com +icrazy.com +id.ru +idaho.usa.com +idirect.com +idncafe.com +ieg.com.br +iespalomeras.net +iespana.es +ifrance.com +ig.com.br +ignazio.it +illinois.usa.com +ilse.net +ilse.nl +imail.ru +imailbox.com +imap-mail.com +imap.cc +imapmail.org +imel.org +in-box.net +inbox.com +inbox.ge +inbox.lv +inbox.net +inbox.ru +in.com +incamail.com +indexa.fr +india.com +indiamail.com +indiana.usa.com +indiatimes.com +induquimica.org +inet.com.ua +infinito.it +infoapex.com +infohq.com +infomail.es +infomart.or.jp +infosat.net +infovia.com.ar +inicia.es +inmail.sk +inmail24.com +inoutbox.com +intelnet.net.gt +intelnett.com +interblod.com +interfree.it +interia.pl +interlap.com.ar +intermail.hu +internet-e-mail.com +internet-mail.org +internet.lu +internetegypt.com +internetemails.net +internetkeno.com +internetmailing.net +inwind.it +iobox.com +iobox.fi +iol.it +iol.pt +iowa.usa.com +ip3.com +ipermitmail.com +iqemail.com +iquebec.com +iran.com +irangate.net +iscool.net +islandmama.com +ismart.net +isonews2.com +isonfire.com +isp9.net +ispey.com +itelgua.com +itloox.com +itmom.com +ivenus.com +iwan-fals.com +iwon.com +ixp.net +japan.com +jaydemail.com +jedrzejow.pl +jetemail.net +jingjo.net +jippii.fi +jmail.co.za +jojomail.com +jovem.te.pt +joymail.com +jubii.dk +jubiipost.dk +jumpy.it +juno.com +justemail.net +justmailz.com +k.ro +kaazoo.com +kabissa.org +kaixo.com +kalluritimes.com +kalpoint.com +kansas.usa.com +katamail.com +kataweb.it +kayafmmail.co.za +keko.com.ar +kentucky.usa.com +keptprivate.com +kimo.com +kiwitown.com +klik.it +klikni.cz +kmtn.ru +koko.com +kolozsvar.ro +kombud.com +koreanmail.com +kotaksuratku.info +krunis.com +kukamail.com +kuronowish.com +kyokodate.com +kyokofukada.net +ladymail.cz +lagoon.nc +lahaonline.com +lamalla.net +lancsmail.com +land.ru +laposte.net +latinmail.com +lawyer.com +lawyersmail.com +lawyerzone.com +lebanonatlas.com +leehom.net +leonardo.it +leonlai.net +letsjam.com +letterbox.org +letterboxes.org +levele.com +lexpress.net +libero.it +liberomail.com +libertysurf.net +libre.net +lightwines.org +linkmaster.com +linuxfreemail.com +lionsfan.com.au +livedoor.com +llandudno.com +llangollen.com +lmxmail.sk +loggain.net +loggain.nu +lolnetwork.net +london.com +longhorns.com +look.com +looksmart.co.uk +looksmart.com +looksmart.com.au +loteria.net +lotonazo.com +louisiana.usa.com +louiskoo.com +loveable.com +lovemail.com +lovingjesus.com +lpemail.com +luckymail.com +luso.pt +lusoweb.pt +luukku.com +lycosmail.com +mac.com +machinecandy.com +macmail.com +mad.scientist.com +madcrazy.com +madonno.com +madrid.com +mag2.com +magicmail.co.za +magik-net.com +mail-atlas.net +mail-awu.de +mail-box.cz +mail.by +mail-center.com +mail-central.com +mail-jp.org +mail-online.dk +mail-page.com +mail-x-change.com +mail.austria.com +mail.az +mail.de +mail.be +mail.bg +mail.bulgaria.com +mail.co.za +mail.dk +mail.ee +mail.goo.ne.jp +mail.gr +mail.lawguru.com +mail.md +mail.mn +mail.org +mail.pf +mail.pt +mail.ru +mail.yahoo.co.jp +mail15.com +mail3000.com +mail333.com +mail8.com +mailandftp.com +mailandnews.com +mailas.com +mailasia.com +mailbg.com +mailblocks.com +mailbolt.com +mailbox.as +mailbox.co.za +mailbox.gr +mailbox.hu +mailbox.sk +mailc.net +mailcan.com +mailcircuit.com +mailclub.fr +mailclub.net +maildozy.com +mailfly.com +mailforce.net +mailftp.com +mailglobal.net +mailhaven.com +mailinator.com +mailingaddress.org +mailingweb.com +mailisent.com +mailite.com +mailme.dk +mailmight.com +mailmij.nl +mailnew.com +mailops.com +mailpanda.com +mailpersonal.com +mailroom.com +mailru.com +mails.de +mailsent.net +mailserver.dk +mailservice.ms +mailsnare.net +mailsurf.com +mailup.net +mailvault.com +mailworks.org +maine.usa.com +majorana.martina-franca.ta.it +maktoob.com +malayalamtelevision.net +malayalapathram.com +male.ru +manager.de +manlymail.net +mantrafreenet.com +mantramail.com +mantraonline.com +marihuana.ro +marijuana.nl +marketweighton.com +maryland.usa.com +masrawy.com +massachusetts.usa.com +mauimail.com +mbox.com.au +mcrmail.com +me.by +me.com +medicinatv.com +meetingmall.com +megamail.pt +menara.ma +merseymail.com +mesra.net +messagez.com +metacrawler.com +mexico.com +miaoweb.net +michigan.usa.com +micro2media.com +miesto.sk +mighty.co.za +milacamn.net +milmail.com +mindless.com +mindviz.com +minnesota.usa.com +mississippi.usa.com +missouri.usa.com +mixmail.com +ml1.net +ml2clan.com +mlanime.com +mm.st +mmail.com +mobimail.mn +mobsters.com +mobstop.com +modemnet.net +modomail.com +moldova.com +moldovacc.com +monarchy.com +montana.usa.com +montevideo.com.uy +moomia.com +moose-mail.com +mosaicfx.com +motormania.com +movemail.com +mr.outblaze.com +mrspender.com +mscold.com +msnzone.cn +mundo-r.com +muslimsonline.com +mustangs.com +mxs.de +myblue.cc +mycabin.com +mycity.com +mycommail.com +mycool.com +mydomain.com +myeweb.com +myfastmail.com +myfunnymail.com +mygrande.net +mykolab.com +mygamingconsoles.com +myiris.com +myjazzmail.com +mymacmail.com +mymail.dk +mymail.ph.inter.net +mymail.ro +mynet.com +mynet.com.tr +myotw.net +myopera.com +myownemail.com +mypersonalemail.com +myplace.com +myrealbox.com +myspace.com +myt.mu +myway.com +mzgchaos.de +n2.com +n2business.com +n2mail.com +n2software.com +nabble.com +name.com +nameplanet.com +nanamail.co.il +nanaseaikawa.com +nandomail.com +naseej.com +nastything.com +national-champs.com +nativeweb.net +narod.ru +nate.com +naveganas.com +naver.com +nebraska.usa.com +nemra1.com +nenter.com +nerdshack.com +nervhq.org +net.hr +net4b.pt +net4jesus.com +net4you.at +netbounce.com +netcabo.pt +netcape.net +netcourrier.com +netexecutive.com +netfirms.com +netkushi.com +netmongol.com +netpiper.com +netposta.net +netscape.com +netscape.net +netscapeonline.co.uk +netsquare.com +nettaxi.com +netti.fi +networld.com +netzero.com +netzero.net +neustreet.com +nevada.usa.com +newhampshire.usa.com +newjersey.usa.com +newmail.com +newmail.net +newmail.ok.com +newmail.ru +newmexico.usa.com +newspaperemail.com +newyork.com +newyork.usa.com +newyorkcity.com +nfmail.com +nicegal.com +nightimeuk.com +nightly.com +nightmail.com +nightmail.ru +noavar.com +noemail.com +nonomail.com +nokiamail.com +noolhar.com +northcarolina.usa.com +northdakota.usa.com +nospammail.net +nowzer.com +ny.com +nyc.com +nz11.com +nzoomail.com +o2.pl +oceanfree.net +ocsnet.net +oddpost.com +odeon.pl +odmail.com +offshorewebmail.com +ofir.dk +ohio.usa.com +oicexchange.com +ok.ru +oklahoma.usa.com +ole.com +oleco.net +olympist.net +omaninfo.com +onatoo.com +ondikoi.com +onebox.com +onenet.com.ar +onet.pl +ongc.net +oninet.pt +online.ie +online.ru +onlinewiz.com +onobox.com +open.by +openbg.com +openforyou.com +opentransfer.com +operamail.com +oplusnet.com +orange.fr +orangehome.co.uk +orange.es +orange.jo +orange.pl +orbitel.bg +orcon.net.nz +oregon.usa.com +oreka.com +organizer.net +orgio.net +orthodox.com +osite.com.br +oso.com +ourbrisbane.com +ournet.md +ourprofile.net +ourwest.com +outgun.com +ownmail.net +oxfoot.com +ozu.es +pacer.com +paginasamarillas.com +pakistanmail.com +pandawa.com +pando.com +pandora.be +paris.com +parsimail.com +parspage.com +patmail.com +pattayacitythailand.com +pc4me.us +pcpostal.com +penguinmaster.com +pennsylvania.usa.com +peoplepc.com +peopleweb.com +personal.ro +personales.com +peru.com +petml.com +phreaker.net +pigeonportal.com +pilu.com +pimagop.com +pinoymail.com +pipni.cz +pisem.net +planet-school.de +planetaccess.com +planetout.com +plasa.com +playersodds.com +playful.com +pluno.com +plusmail.com.br +pmail.net +pnetmail.co.za +pobox.ru +pobox.sk +pochtamt.ru +pochta.ru +poczta.fm +poetic.com +pogowave.com +polbox.com +pop3.ru +pop.co.th +popmail.com +poppymail.com +popsmail.com +popstar.com +portafree.com +portaldosalunos.com +portugalmail.com +portugalmail.pt +post.cz +post.expart.ne.jp +post.pl +post.sk +posta.ge +postaccesslite.com +postiloota.net +postinbox.com +postino.ch +postino.it +postmaster.co.uk +postpro.net +praize.com +press.co.jp +primposta.com +printesamargareta.ro +private.21cn.com +probemail.com +profesional.com +profession.freemail.com.br +proinbox.com +promessage.com +prontomail.com +provincial.net +publicaccounting.com +punkass.com +puppy.com.my +q.com +qatar.io +qlmail.com +qq.com +qrio.com +qsl.net +qudsmail.com +queerplaces.com +quepasa.com +quick.cz +quickwebmail.com +r-o-o-t.com +r320.hu +raakim.com +rbcmail.ru +racingseat.com +radicalz.com +radiojobbank.com +ragingbull.com +raisingadaughter.com +rallye-webmail.com +rambler.ru +ranmamail.com +ravearena.com +ravemail.co.za +razormail.com +real.ro +realemail.net +reallyfast.biz +reallyfast.info +rebels.com +recife.net +recme.net +rediffmailpro.com +redseven.de +redwhitearmy.com +relia.com +revenue.com +rexian.com +rhodeisland.usa.com +ritmes.net +rn.com +roanokemail.com +rochester-mail.com +rock.com +rocketmail.com +rockfan.com +rockinghamgateway.com +rojname.com +rol.ro +rollin.com +rome.com +romymichele.com +royal.net +rpharmacist.com +rt.nl +ru.ru +rushpost.com +russiamail.com +rxpost.net +s-mail.com +saabnet.com +sacbeemail.com +sacmail.com +safe-mail.net +safe-mailbox.com +saigonnet.vn +saint-mike.org +samilan.net +sandiego.com +sanook.com +sanriotown.com +sapibon.com +sapo.pt +saturnfans.com +sayhi.net +sbcglobal.com +scfn.net +schweiz.org +sci.fi +sciaga.pl +scrapbookscrapbook.com +seapole.com +search417.com +seark.com +sebil.com +secretservices.net +secure-jlnet.com +seductive.com +sendmail.ru +sendme.cz +sent.as +sent.at +sent.com +serga.com.ar +sermix.com +server4free.de +serverwench.com +sesmail.com +sexmagnet.com +seznam.cz +shadango.com +she.com +shuf.com +siamlocalhost.com +siamnow.net +sify.com +sinamail.com +singapore.com +singmail.com +singnet.com.sg +siraj.org +sirindia.com +sirunet.com +sister.com +sina.com +sina.cn +sinanail.com +sistersbrothers.com +sizzling.com +slamdunkfan.com +slickriffs.co.uk +slingshot.com +slo.net +slomusic.net +smartemail.co.uk +smtp.ru +snail-mail.net +sndt.net +sneakemail.com +snoopymail.com +snowboarding.com +so-simple.org +socamail.com +softhome.net +sohu.com +sol.dk +solidmail.com +soon.com +sos.lv +soundvillage.org +southcarolina.usa.com +southdakota.usa.com +space.com +spacetowns.com +spamex.com +spartapiet.com +speed-racer.com +speedpost.net +speedymail.org +spils.com +spinfinder.com +sportemail.com +spray.net +spray.no +spray.se +spymac.com +srbbs.com +srilankan.net +ssan.com +ssl-mail.com +stade.fr +stalag13.com +stampmail.com +starbuzz.com +starline.ee +starmail.com +starmail.org +starmedia.com +starspath.com +start.com.au +start.no +stribmail.com +student.com +student.ednet.ns.ca +studmail.com +sudanmail.net +suisse.org +sunbella.net +sunmail1.com +sunpoint.net +sunrise.ch +sunumail.sn +sunuweb.net +suomi24.fi +superdada.it +supereva.com +supereva.it +supermailbox.com +superposta.com +surf3.net +surfassistant.com +surfsupnet.net +surfy.net +surimail.com +surnet.cl +sverige.nu +svizzera.org +sweb.cz +swift-mail.com +swissinfo.org +swissmail.net +switzerland.org +syom.com +syriamail.com +t-mail.com +t-net.net.ve +t2mail.com +tabasheer.com +talk21.com +talkcity.com +tangmonkey.com +tatanova.com +taxcutadvice.com +techemail.com +technisamail.co.za +teenmail.co.uk +teenmail.co.za +tejary.com +telebot.com +telefonica.net +telegraf.by +teleline.es +telinco.net +telkom.net +telpage.net +telstra.com +telenet.be +telusplanet.net +tempting.com +tenchiclub.com +tennessee.usa.com +terrapins.com +texas.usa.com +texascrossroads.com +tfz.net +thai.com +thaimail.com +thaimail.net +the-fastest.net +the-quickest.com +thegame.com +theinternetemail.com +theoffice.net +thepostmaster.net +theracetrack.com +theserverbiz.com +thewatercooler.com +thewebpros.co.uk +thinkpost.net +thirdage.com +thundermail.com +tim.it +timemail.com +tin.it +tinati.net +tiscalinet.it +tjohoo.se +tkcity.com +tlcfan.com +tlen.pl +tmicha.net +todito.com +todoperros.com +tokyo.com +topchat.com +topmail.com.ar +topmail.dk +topmail.co.ie +topmail.co.in +topmail.co.nz +topmail.co.uk +topmail.co.za +topsurf.com +toquedequeda.com +torba.com +torchmail.com +totalmail.com +totalsurf.com +totonline.net +tough.com +toughguy.net +trav.se +trevas.net +tripod-mail.com +triton.net +trmailbox.com +tsamail.co.za +turbonett.com +turkey.com +tvnet.lv +twc.com +typemail.com +u2club.com +uae.ac +ubbi.com +ubbi.com.br +uboot.com +ugeek.com +uk2.net +uk2net.com +ukr.net +ukrpost.net +ukrpost.ua +uku.co.uk +ulimit.com +ummah.org +unbounded.com +unicum.de +unimail.mn +unitedemailsystems.com +universal.pt +universia.cl +universia.edu.ve +universia.es +universia.net.co +universia.net.mx +universia.pr +universia.pt +universiabrasil.net +unofree.it +uol.com.ar +uol.com.br +uole.com +uolmail.com +uomail.com +uraniomail.com +urbi.com.br +ureach.com +usanetmail.com +userbeam.com +utah.usa.com +uyuyuy.com +v-sexi.com +v3mail.com +valanides.com +vegetarisme.be +velnet.com +velocall.com +vercorreo.com +verizonmail.com +vermont.usa.com +verticalheaven.com +veryfast.biz +veryspeedy.net +vfemail.net +vietmedia.com +vip.gr +virgilio.it +virgin.net +virginia.usa.com +virtual-mail.com +visitmail.com +visto.com +vivelared.com +vjtimail.com +vnn.vn +vsnl.com +vsnl.net +vodamail.co.za +voila.fr +volkermord.com +vosforums.com +w.cn +walla.com +walla.co.il +wallet.com +wam.co.za +wanex.ge +wap.hu +wapda.com +wapicode.com +wappi.com +warpmail.net +washington.usa.com +wassup.com +waterloo.com +waumail.com +wazmail.com +wearab.net +web-mail.com.ar +web.de +web.nl +web2mail.com +webaddressbook.com +webbworks.com +webcity.ca +webdream.com +webemaillist.com +webindia123.com +webinfo.fi +webjump.com +webl-3.br.inter.net +webmail.co.yu +webmail.co.za +webmails.com +webmailv.com +webpim.cc +webspawner.com +webstation.com +websurfer.co.za +webtopmail.com +webtribe.net +webtv.net +weedmail.com +weekonline.com +weirdness.com +westvirginia.usa.com +whale-mail.com +whipmail.com +who.net +whoever.com +wildcats.com +wildmail.com +williams.net.ar +winning.com +winningteam.com +winwinhosting.com +wisconsin.usa.com +witelcom.com +witty.com +wolverines.com +wooow.it +workmail.co.za +worldcrossing.com +worldemail.com +worldmedic.com +worldonline.de +wowmail.com +wp.pl +wprost.pl +wrongmail.com +wtonetwork.com +wurtele.net +www.com +www.consulcredit.it +wyoming.usa.com +x-mail.net +xasa.com +xfreehosting.com +xmail.net +xmsg.com +xnmsn.cn +xoom.com +xtra.co.nz +xuite.net +xpectmore.com +xrea.com +xsmail.com +xzapmail.com +y7mail.com +yahala.co.il +yaho.com +yalla.com.lb +ya.com +yeah.net +ya.ru +yahoomail.com +yam.com +yamal.info +yapost.com +yawmail.com +yebox.com +yehey.com +yellow-jackets.com +yellowstone.net +yenimail.com +yepmail.net +yifan.net +yopmail.com +your-mail.com +yours.com +yourwap.com +yyhmail.com +z11.com +z6.com +zednet.co.uk +zeeman.nl +ziplip.com +zipmail.com.br +zipmax.com +zmail.pt +zmail.ru +zona-andina.net +zonai.com +zoneview.net +zonnet.nl +zoho.com +zoomshare.com +zoznam.sk +zubee.com +zuvio.com +zwallet.com +zworg.com +zybermail.com +zzn.com +126.com +139.com +163.com +188.com +189.cn +263.net +9.cn +vip.126.com +vip.163.com +vip.188.com +vip.sina.com +vip.sohu.com +vip.sohu.net +vip.tom.com +vip.qq.com +vipsohu.net +clovermail.net +mail-on.us +chewiemail.com +offcolormail.com +powdermail.com +tightmail.com +toothandmail.com +tushmail.com +openmail.cc +expressmail.dk +4xn.de +5x2.de +5x2.me +aufdrogen.de +auf-steroide.de +besser-als-du.de +brainsurfer.de +chillaxer.de +cyberkriminell.de +danneben.so +freemailen.de +freemailn.de +ist-der-mann.de +ist-der-wahnsinn.de +ist-echt.so +istecht.so +ist-genialer.de +ist-schlauer.de +ist-supersexy.de +kann.so +mag-spam.net +mega-schlau.de +muss.so +nerd4life.de +ohne-drogen-gehts.net +on-steroids.de +scheint.so +staatsterrorist.de +super-gerissen.de +unendlich-schlau.de +vip-client.de +will-keinen-spam.de +zu-geil.de +rbox.me +rbox.co +tunome.com +acatperson.com +adogperson.com +all4theskins.com +allsportsrock.com +alwaysgrilling.com +alwaysinthekitchen.com +alwayswatchingmovies.com +alwayswatchingtv.com +asylum.com +basketball-email.com +beabookworm.com +beagolfer.com +beahealthnut.com +believeinliberty.com +bestcoolcars.com +bestjobcandidate.com +besure2vote.com +bigtimecatperson.com +bigtimedogperson.com +bigtimereader.com +bigtimesportsfan.com +blackvoices.com +capsfanatic.com +capshockeyfan.com +capsred.com +car-nut.net +cat-person.com +catpeoplerule.com +chat-with-me.com +cheatasrule.com +crazy4baseball.com +crazy4homeimprovement.com +crazy4mail.com +crazyaboutfilms.net +crazycarfan.com +crazyforemail.com +crazymoviefan.com +descriptivemail.com +differentmail.com +dog-person.com +dogpeoplerule.com +easydoesit.com +expertrenovator.com +expressivemail.com +fanaticos.com +fanofbooks.com +fanofcomputers.com +fanofcooking.com +fanoftheweb.com +fieldmail.com +fleetmail.com +focusedonprofits.com +focusedonreturns.com +futboladdict.com +games.com +getintobooks.com +hail2theskins.com +hitthepuck.com +i-dig-movies.com +i-love-restaurants.com +idigcomputers.com +idigelectronics.com +idigvideos.com +ilike2helpothers.com +ilike2invest.com +ilike2workout.com +ilikeelectronics.com +ilikeworkingout.com +ilovehomeprojects.com +iloveourteam.com +iloveworkingout.com +in2autos.net +interestedinthejob.com +intomotors.com +iwatchrealitytv.com +lemondrop.com +love2exercise.com +love2workout.com +lovefantasysports.com +lovetoexercise.com +luvfishing.com +luvgolfing.com +luvsoccer.com +mail4me.com +majorgolfer.com +majorshopaholic.com +majortechie.com +mcom.com +motor-nut.com +moviefan.com +mycapitalsmail.com +mycatiscool.com +myfantasyteamrules.com +myteamisbest.com +netbusiness.com +news-fanatic.com +newspaperfan.com +onlinevideosrock.com +realbookfan.com +realhealthnut.com +realitytvaddict.net +realitytvnut.com +reallyintomusic.com +realtravelfan.com +redskinscheer.com +redskinsfamily.com +redskinsfancentral.com +redskinshog.com +redskinsrule.com +redskinsspecialteams.com +redskinsultimatefan.com +scoutmail.com +skins4life.com +stargate2.com +stargateatlantis.com +stargatefanclub.com +stargatesg1.com +stargateu.com +switched.com +t-online.de +thegamefanatic.com +total-techie.com +totalfoodnut.com +totally-into-cooking.com +totallyintobaseball.com +totallyintobasketball.com +totallyintocooking.com +totallyintofootball.com +totallyintogolf.com +totallyintohockey.com +totallyintomusic.com +totallyintoreading.com +totallyintosports.com +totallyintotravel.com +totalmoviefan.com +travel2newplaces.com +tvchannelsurfer.com +ultimateredskinsfan.com +videogamesrock.com +volunteeringisawesome.com +wayintocomputers.com +whatmail.com +when.com +wild4music.com +wildaboutelectronics.com +workingaroundthehouse.com +workingonthehouse.com +writesoon.com +xmasmail.com +arab.ir +denmark.ir +egypt.ir +icq.ir +ir.ae +iraq.ir +ire.ir +ireland.ir +irr.ir +jpg.ir +ksa.ir +kuwait.ir +london.ir +paltalk.ir +spain.ir +sweden.ir +tokyo.ir +111mail.com +123iran.com +37.com +420email.com +4degreez.com +4-music-today.com +actingbiz.com +allhiphop.com +anatomicrock.com +animeone.com +asiancutes.com +a-teens.net +ausi.com +autoindia.com +autopm.com +barriolife.com +b-boy.com +beautifulboy.com +bgay.com +bicycledata.com +bicycling.com +bigheavyworld.com +bigmailbox.net +bikerheaven.net +bikermail.com +billssite.com +blackandchristian.com +blackcity.net +blackvault.com +bmxtrix.com +boarderzone.com +boatnerd.com +bolbox.com +bongmail.com +bowl.com +butch-femme.org +byke.com +calle22.com +cannabismail.com +catlovers.com +certifiedbitches.com +championboxing.com +chatway.com +chillymail.com +classprod.com +classycouples.com +congiu.net +coolshit.com +corpusmail.com +cyberunlimited.org +cycledata.com +darkfear.com +darkforces.com +dirtythird.com +dopefiends.com +draac.com +drakmail.net +dr-dre.com +dreamstop.com +egypt.net +emailfast.com +envirocitizen.com +escapeartist.com +ezsweeps.com +famous.as +farts.com +feelingnaughty.com +firemyst.com +freeonline.com +fudge.com +funkytimes.com +gamerssolution.com +gazabo.net +glittergrrrls.com +goatrance.com +goddess.com +gohip.com +gospelcity.com +gothicgirl.com +grapemail.net +greatautos.org +guy.com +haitisurf.com +happyhippo.com +hateinthebox.com +houseofhorrors.com +hugkiss.com +hullnumber.com +idunno4recipes.com +ihatenetscape.com +intimatefire.com +irow.com +jazzemail.com +juanitabynum.com +kanoodle.com +kickboxing.com +kidrock.com +kinkyemail.com +kool-things.com +latinabarbie.com +latinogreeks.com +leesville.com +loveemail.com +lowrider.com +lucky7lotto.net +madeniggaz.net +mailbomb.com +marillion.net +megarave.com +mofa.com +motley.com +music.com +musician.net +musicsites.com +netbroadcaster.com +netfingers.com +net-surf.com +nocharge.com +operationivy.com +paidoffers.net +pcbee.com +persian.com +petrofind.com +phunkybitches.com +pikaguam.com +pinkcity.net +pitbullmail.com +planetsmeg.com +poop.com +poormail.com +potsmokersnet.com +primetap.com +project420.com +prolife.net +puertoricowow.com +puppetweb.com +rapstar.com +rapworld.com +rastamall.com +ratedx.net +ravermail.com +relapsecult.com +remixer.com +rockeros.com +romance106fm.com +singalongcenter.com +sketchyfriends.com +slayerized.com +smartstocks.com +soulja-beatz.org +specialoperations.com +speedymail.net +spells.com +superbikeclub.com +superintendents.net +surfguiden.com +sweetwishes.com +tattoodesign.com +teamster.net +teenchatnow.com +the5thquarter.com +theblackmarket.com +tombstone.ws +troamail.org +u2tours.com +vitalogy.org +whatisthis.com +wrestlezone.com +abha.cc +agadir.cc +ahsa.ws +ajman.cc +ajman.us +ajman.ws +albaha.cc +algerie.cc +alriyadh.cc +amman.cc +aqaba.cc +arar.ws +aswan.cc +baalbeck.cc +bahraini.cc +banha.cc +bizerte.cc +blida.info +buraydah.cc +cameroon.cc +dhahran.cc +dhofar.cc +djibouti.cc +dominican.cc +eritrea.cc +falasteen.cc +fujairah.cc +fujairah.us +fujairah.ws +gabes.cc +gafsa.cc +giza.cc +guinea.cc +hamra.cc +hasakah.com +hebron.tv +homs.cc +ibra.cc +irbid.ws +ismailia.cc +jadida.cc +jadida.org +jerash.cc +jizan.cc +jouf.cc +kairouan.cc +karak.cc +khaimah.cc +khartoum.cc +khobar.cc +kuwaiti.tv +kyrgyzstan.cc +latakia.cc +lebanese.cc +lubnan.cc +lubnan.ws +madinah.cc +maghreb.cc +manama.cc +mansoura.tv +marrakesh.cc +mascara.ws +meknes.cc +muscat.tv +muscat.ws +nabeul.cc +nabeul.info +nablus.cc +nador.cc +najaf.cc +omani.ws +omdurman.cc +oran.cc +oued.info +oued.org +oujda.biz +oujda.cc +pakistani.ws +palmyra.cc +palmyra.ws +portsaid.cc +qassem.cc +quds.cc +rabat.cc +rafah.cc +ramallah.cc +safat.biz +safat.info +safat.us +safat.ws +salalah.cc +salmiya.biz +sanaa.cc +seeb.cc +sfax.ws +sharm.cc +sinai.cc +siria.cc +sousse.cc +sudanese.cc +suez.cc +tabouk.cc +tajikistan.cc +tangiers.cc +tanta.cc +tayef.cc +tetouan.cc +timor.cc +tunisian.cc +urdun.cc +yanbo.cc +yemeni.cc +yunus.cc +zagazig.cc +zambia.cc +5005.lv +a.org.ua +bmx.lv +company.org.ua +coolmail.ru +dino.lv +eclub.lv +e-mail.am +fit.lv +hacker.am +human.lv +iphon.biz +latchess.com +loveis.lv +lv-inter.net +pookmail.com +sexriga.lv diff --git a/crates/router/src/utils/user/password.rs b/crates/router/src/utils/user/password.rs new file mode 100644 index 000000000000..cff17863c32d --- /dev/null +++ b/crates/router/src/utils/user/password.rs @@ -0,0 +1,43 @@ +use argon2::{ + password_hash::{ + rand_core::OsRng, Error as argon2Err, PasswordHash, PasswordHasher, PasswordVerifier, + SaltString, + }, + Argon2, +}; +use common_utils::errors::CustomResult; +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, Secret}; + +use crate::core::errors::UserErrors; + +pub fn generate_password_hash( + password: Secret, +) -> CustomResult, UserErrors> { + let salt = SaltString::generate(&mut OsRng); + + let argon2 = Argon2::default(); + let password_hash = argon2 + .hash_password(password.expose().as_bytes(), &salt) + .into_report() + .change_context(UserErrors::InternalServerError)?; + Ok(Secret::new(password_hash.to_string())) +} + +pub fn is_correct_password( + candidate: Secret, + password: Secret, +) -> CustomResult { + let password = password.expose(); + let parsed_hash = PasswordHash::new(&password) + .into_report() + .change_context(UserErrors::InternalServerError)?; + let result = Argon2::default().verify_password(candidate.expose().as_bytes(), &parsed_hash); + match result { + Ok(_) => Ok(true), + Err(argon2Err::Password) => Ok(false), + Err(e) => Err(e), + } + .into_report() + .change_context(UserErrors::InternalServerError) +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 0c9751aee440..9cd678083959 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -243,6 +243,8 @@ pub enum Flow { GsmRuleUpdate, /// Gsm Rule Delete flow GsmRuleDelete, + /// User connect account + UserConnectAccount, } ///