diff --git a/backend/src/config.rs b/backend/src/config.rs
index 80f67b1a9..bd8066ca4 100644
--- a/backend/src/config.rs
+++ b/backend/src/config.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+use crate::storage::commercial_license_trial::CommercialLicenseTrialStorage;
use crate::storage::{postgres::PostgresStorage, Storage};
use crate::worker::do_work::TaskWebhook;
use crate::worker::setup_rabbit_mq;
@@ -112,6 +113,15 @@ impl BackendConfig {
.with_context(|| format!("Connecting to postgres DB {}", config.db_url))?;
self.storages.push(Arc::new(storage));
}
+ StorageConfig::CommercialLicenseTrial(config) => {
+ let storage =
+ CommercialLicenseTrialStorage::new(&config.db_url, config.extra.clone())
+ .await
+ .with_context(|| {
+ format!("Connecting to postgres DB {}", config.db_url)
+ })?;
+ self.storages.push(Arc::new(storage));
+ }
}
}
@@ -214,6 +224,10 @@ impl ThrottleConfig {
pub enum StorageConfig {
/// Store the email verification results in the Postgres database.
Postgres(PostgresConfig),
+ /// Store the email verification results in Reacher's DB. This storage
+ /// method is baked-in into the software for users of the Commercial
+ /// License trial.
+ CommercialLicenseTrial(PostgresConfig),
}
#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
diff --git a/backend/src/storage/commercial_license_trial.rs b/backend/src/storage/commercial_license_trial.rs
new file mode 100644
index 000000000..71c3f611d
--- /dev/null
+++ b/backend/src/storage/commercial_license_trial.rs
@@ -0,0 +1,144 @@
+// Reacher - Email Verification
+// Copyright (C) 2018-2023 Reacher
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+use super::error::StorageError;
+use super::postgres::PostgresStorage;
+use super::Storage;
+use crate::worker::do_work::{CheckEmailJobId, CheckEmailTask, TaskError};
+use async_trait::async_trait;
+use check_if_email_exists::{redact, CheckEmailOutput, LOG_TARGET};
+use serde_json::Value;
+use tracing::debug;
+
+/// Storage that's baked in the software for users of the Commercial License
+/// trial. It's really just a wrapper around the PostgresStorage, where we
+/// redact all sensitive data such as the email address.
+#[derive(Debug)]
+pub struct CommercialLicenseTrialStorage {
+ postgres_storage: PostgresStorage,
+}
+
+impl CommercialLicenseTrialStorage {
+ pub async fn new(db_url: &str, extra: Option) -> Result {
+ let postgres_storage = PostgresStorage::new(db_url, extra).await?;
+ Ok(Self { postgres_storage })
+ }
+}
+
+#[async_trait]
+impl Storage for CommercialLicenseTrialStorage {
+ async fn store(
+ &self,
+ task: &CheckEmailTask,
+ worker_output: &Result,
+ extra: Option,
+ ) -> Result<(), StorageError> {
+ let mut payload_json = serde_json::to_value(task)?;
+ if let Ok(output) = worker_output {
+ redact_across_json(&mut payload_json, &output.syntax.username);
+ }
+
+ match worker_output {
+ Ok(output) => {
+ let mut output_json = serde_json::to_value(output)?;
+ redact_across_json(&mut output_json, &output.syntax.username);
+
+ sqlx::query!(
+ r#"
+ INSERT INTO v1_task_result (payload, job_id, extra, result)
+ VALUES ($1, $2, $3, $4)
+ RETURNING id
+ "#,
+ payload_json,
+ match task.job_id {
+ CheckEmailJobId::Bulk(job_id) => Some(job_id),
+ CheckEmailJobId::SingleShot => None,
+ },
+ extra,
+ output_json,
+ )
+ .fetch_one(&self.postgres_storage.pg_pool)
+ .await?;
+ }
+ Err(err) => {
+ sqlx::query!(
+ r#"
+ INSERT INTO v1_task_result (payload, job_id, extra, error)
+ VALUES ($1, $2, $3, $4)
+ RETURNING id
+ "#,
+ payload_json,
+ match task.job_id {
+ CheckEmailJobId::Bulk(job_id) => Some(job_id),
+ CheckEmailJobId::SingleShot => None,
+ },
+ extra,
+ err.to_string(),
+ )
+ .fetch_one(&self.postgres_storage.pg_pool)
+ .await?;
+ }
+ }
+
+ debug!(target: LOG_TARGET, email=?task.input.to_email, "Wrote to DB");
+
+ Ok(())
+ }
+
+ fn get_extra(&self) -> Option {
+ self.postgres_storage.get_extra()
+ }
+}
+
+/// Redact all sensitive data by recursively traversing the JSON object.
+fn redact_across_json(value: &mut Value, username: &str) {
+ match value {
+ Value::String(s) => *s = redact(s, username),
+ Value::Array(arr) => {
+ for item in arr {
+ redact_across_json(item, username);
+ }
+ }
+ Value::Object(obj) => {
+ for (_, v) in obj {
+ redact_across_json(v, username);
+ }
+ }
+ _ => {}
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use check_if_email_exists::{check_email, CheckEmailInputBuilder};
+
+ #[tokio::test]
+ async fn should_redact_across_json() {
+ let input = CheckEmailInputBuilder::default()
+ // Checking this email will make a MX record check, but hopefully
+ // it won't resolve (since I typed it randomly), meaning that the
+ // SMTP check will be skipped.
+ .to_email("someone@adlkfjaklsdjfldksjfderlqkjeqwr.com".into())
+ .build()
+ .unwrap();
+ let output = check_email(&input).await;
+ let mut output_json = serde_json::to_value(&output).unwrap();
+ redact_across_json(&mut output_json, &output.syntax.username);
+
+ assert!(!output_json.to_string().contains("someone"));
+ }
+}
diff --git a/backend/src/storage/mod.rs b/backend/src/storage/mod.rs
index 7d6249f05..fcd1afcea 100644
--- a/backend/src/storage/mod.rs
+++ b/backend/src/storage/mod.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+pub mod commercial_license_trial;
pub mod error;
pub mod postgres;
diff --git a/core/src/util/sentry.rs b/core/src/util/sentry.rs
index 2314e5f6d..427a99b1e 100644
--- a/core/src/util/sentry.rs
+++ b/core/src/util/sentry.rs
@@ -92,7 +92,7 @@ fn error(err: SentryError, result: &CheckEmailOutput, backend_name: &str) {
/// Function to replace all usernames from email, and replace them with
/// `***@domain.com` for privacy reasons.
-fn redact(input: &str, username: &str) -> String {
+pub fn redact(input: &str, username: &str) -> String {
input.replace(username, "***")
}