feat: switch email delivery from Resend to SMTP with libcurl integration

This commit is contained in:
gabdevele
2026-06-18 13:11:01 +02:00 Unverified
parent edadc258db
commit 8ffe247e41
12 changed files with 304 additions and 68 deletions
+11 -3
View File
@@ -5,8 +5,16 @@ MYSQL_PASSWORD=visione
MYSQL_ROOT_PASSWORD=root MYSQL_ROOT_PASSWORD=root
MAILPIT_SMTP_PORT=1025 MAILPIT_SMTP_PORT=1025
MAILPIT_WEB_PORT=8025 MAILPIT_WEB_PORT=8025
MAIL_PROVIDER=resend
RESEND_API_BASE_URL=https://api.resend.com MAIL_PROVIDER=smtp
RESEND_API_KEY= SMTP_HOST=127.0.0.1
SMTP_PORT=1025
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_USE_TLS=false
SMTP_USE_STARTTLS=false
SMTP_VERIFY_PEER=false
SMTP_VERIFY_HOST=false
MAIL_FROM=[email protected] MAIL_FROM=[email protected]
CORS_ALLOWED_ORIGIN=http://localhost:3000 CORS_ALLOWED_ORIGIN=http://localhost:3000
+2
View File
@@ -9,6 +9,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(Python3 COMPONENTS Interpreter REQUIRED) find_package(Python3 COMPONENTS Interpreter REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
find_package(CURL REQUIRED)
option(USE_SYSTEM_DROGON "Use an installed Drogon package instead of CPM" OFF) option(USE_SYSTEM_DROGON "Use an installed Drogon package instead of CPM" OFF)
@@ -72,6 +73,7 @@ target_include_directories(${PROJECT_NAME}
target_link_libraries(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME}
PRIVATE PRIVATE
Drogon::Drogon Drogon::Drogon
CURL::libcurl
OpenSSL::Crypto OpenSSL::Crypto
) )
+2 -2
View File
@@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential ccache cmake ninja-build git pkg-config \ build-essential ccache cmake ninja-build git pkg-config \
libssl-dev zlib1g-dev \ libcurl4-openssl-dev libssl-dev zlib1g-dev \
libmariadb-dev libmariadb-dev-compat \ libmariadb-dev libmariadb-dev-compat \
libsqlite3-dev python3 libjsoncpp-dev uuid-dev ca-certificates \ libsqlite3-dev python3 libjsoncpp-dev uuid-dev ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@@ -27,7 +27,7 @@ FROM ubuntu:22.04 AS runtime
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libssl3 ca-certificates libstdc++6 libmariadb3 libjsoncpp25 libuuid1 \ libcurl4 libssl3 ca-certificates libstdc++6 libmariadb3 libjsoncpp25 libuuid1 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
+20 -9
View File
@@ -9,7 +9,7 @@ Install native build dependencies first. On Ubuntu:
```bash ```bash
sudo apt install build-essential cmake ninja-build git pkg-config \ sudo apt install build-essential cmake ninja-build git pkg-config \
libjsoncpp-dev uuid-dev zlib1g-dev libssl-dev \ libcurl4-openssl-dev libjsoncpp-dev uuid-dev zlib1g-dev libssl-dev \
libmariadb-dev libmariadb-dev-compat libmariadb-dev libmariadb-dev-compat
``` ```
@@ -95,18 +95,29 @@ The app serves the generated JSON at `/api/v1/openapi.json` and Swagger UI at
## Email Delivery ## Email Delivery
OTP delivery uses Resend HTTP API through Drogon's async `HttpClient`. OTP delivery uses SMTP through `libcurl`. Local dev defaults to Mailpit.
Set these environment variables before starting backend: Set these environment variables before starting backend for local Mailpit:
```bash ```bash
export MAIL_PROVIDER=resend export MAIL_PROVIDER=smtp
export RESEND_API_BASE_URL=https://api.resend.com export SMTP_HOST=127.0.0.1
export RESEND_API_KEY=re_xxx export SMTP_PORT=1025
export MAIL_FROM=[email protected] export MAIL_FROM=[email protected]
``` ```
Mailpit can still run locally for manual inspection or future integrations, but For Resend in production, use SMTP credentials instead:
current backend OTP flow no longer speaks SMTP directly.
```bash
export MAIL_PROVIDER=resend
export SMTP_HOST=smtp.resend.com
export SMTP_PORT=465
export SMTP_USERNAME=resend
export SMTP_PASSWORD=re_xxx
export SMTP_USE_TLS=true
export SMTP_VERIFY_PEER=true
export SMTP_VERIFY_HOST=true
export MAIL_FROM=[email protected]
```
## Visione Lab ## Visione Lab
@@ -157,7 +168,7 @@ multi-architecture Mailpit image to avoid `exec format error` failures on ARM
servers. servers.
Set `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `MYSQL_ROOT_PASSWORD`, Set `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `MYSQL_ROOT_PASSWORD`,
`RESEND_API_KEY`, `MAIL_FROM` and `SMTP_PASSWORD`, `MAIL_FROM` and
`CORS_ALLOWED_ORIGIN` in Dokploy environment variables. Use the exact browser `CORS_ALLOWED_ORIGIN` in Dokploy environment variables. Use the exact browser
origin without quotes or a trailing slash, for example origin without quotes or a trailing slash, for example
`https://visione.studentindipendenti.it`. `https://visione.studentindipendenti.it`.
+205 -40
View File
@@ -1,74 +1,239 @@
#include "common/Email.h" #include "common/Email.h"
#include <curl/curl.h>
#include <drogon/drogon.h> #include <drogon/drogon.h>
#include <json/json.h> #include <algorithm>
#include <cstddef>
#include <cstring>
#include <memory>
#include <sstream>
namespace visione::email namespace visione::email
{ {
namespace namespace
{ {
const Json::Value &mailConfig()
{
return drogon::app().getCustomConfig()["mail"];
}
std::string configString(const std::string &key, const std::string &fallback) std::string configString(const std::string &key, const std::string &fallback)
{ {
const auto &mail = drogon::app().getCustomConfig()["mail"]; const auto &mail = mailConfig();
return mail.get(key, fallback).asString(); return mail.get(key, fallback).asString();
} }
int configInt(const std::string &key, int fallback)
{
const auto &mail = mailConfig();
return mail.get(key, fallback).asInt();
}
bool configBool(const std::string &key, bool fallback)
{
const auto &mail = mailConfig();
return mail.get(key, fallback).asBool();
}
struct SmtpDefaults
{
std::string host;
int port;
std::string username;
bool useTls;
bool useStartTls;
bool verifyPeer;
bool verifyHost;
};
SmtpDefaults smtpDefaults(const std::string &provider)
{
if (provider == "resend")
{
return {
.host = "smtp.resend.com",
.port = 465,
.username = "resend",
.useTls = true,
.useStartTls = false,
.verifyPeer = true,
.verifyHost = true,
};
}
return {
.host = "127.0.0.1",
.port = 1025,
.username = "",
.useTls = false,
.useStartTls = false,
.verifyPeer = false,
.verifyHost = false,
};
}
std::string smtpUrl(const std::string &host, int port, bool useTls)
{
return (useTls ? "smtps://" : "smtp://") + host + ":" + std::to_string(port);
}
struct UploadContext
{
const std::string *payload;
size_t offset{0};
};
size_t readPayload(char *buffer, size_t size, size_t count, void *userData)
{
auto *context = static_cast<UploadContext *>(userData);
const auto capacity = size * count;
const auto remaining = context->payload->size() - context->offset;
const auto bytesToCopy = std::min(capacity, remaining);
if (bytesToCopy == 0)
{
return 0;
}
std::memcpy(buffer,
context->payload->data() + static_cast<std::ptrdiff_t>(context->offset),
bytesToCopy);
context->offset += bytesToCopy;
return bytesToCopy;
}
bool ensureCurlInitialized()
{
static const bool initialized = []() {
const auto result = curl_global_init(CURL_GLOBAL_DEFAULT);
if (result != CURLE_OK)
{
LOG_ERROR << "curl_global_init failed: " << curl_easy_strerror(result);
return false;
}
return true;
}();
return initialized;
}
} // namespace } // namespace
std::string EmailService::deliveryName() const std::string EmailService::deliveryName() const
{ {
return configString("provider", "resend"); return configString("provider", "smtp");
} }
bool EmailService::sendOtp(const std::string &to, const std::string &code) const bool EmailService::sendOtp(const std::string &to, const std::string &code) const
{ {
const auto apiBaseUrl = configString("api_base_url", "https://api.resend.com"); const auto provider = deliveryName();
const auto apiKey = configString("api_key", ""); const auto defaults = smtpDefaults(provider);
const auto host = configString("host", defaults.host);
const auto port = configInt("port", defaults.port);
const auto username = configString("username", defaults.username);
const auto password = configString("password", "");
const auto from = configString("from", ""); const auto from = configString("from", "");
const auto useTls = configBool("use_tls", defaults.useTls);
const auto useStartTls = configBool("use_starttls", defaults.useStartTls);
const auto verifyPeer = configBool("verify_peer", defaults.verifyPeer);
const auto verifyHost = configBool("verify_host", defaults.verifyHost);
if (apiKey.empty() || from.empty()) if (host.empty() || from.empty())
{ {
LOG_ERROR << "Email delivery configuration missing: api_key or from"; LOG_ERROR << "Email delivery configuration missing: host or from";
return false;
}
if (provider == "resend" && password.empty())
{
LOG_ERROR << "Email delivery configuration missing: SMTP password for Resend";
return false; return false;
} }
auto client = drogon::HttpClient::newHttpClient(apiBaseUrl); if (!ensureCurlInitialized())
Json::Value payload;
payload["from"] = "Visione <" + from + ">";
payload["subject"] = "Codice OTP Visione";
payload["text"] = "Il tuo codice OTP per Visione e': " + code +
"\nScade tra 10 minuti.\n";
payload["to"] = Json::arrayValue;
payload["to"].append(to);
auto request = drogon::HttpRequest::newHttpJsonRequest(payload);
request->setMethod(drogon::Post);
request->setPath("/emails");
request->addHeader("Authorization", "Bearer " + apiKey);
request->addHeader("User-Agent", "visione-backend/1.0");
client->sendRequest(
request,
[recipient = to](drogon::ReqResult result, const drogon::HttpResponsePtr &response) {
if (result == drogon::ReqResult::Ok &&
response != nullptr &&
response->statusCode() == drogon::k200OK)
{ {
LOG_INFO << "OTP email queued via Resend for " << recipient; return false;
return;
} }
const auto body = response != nullptr ? response->getBody() : std::string{}; std::ostringstream payload;
LOG_ERROR << "OTP email delivery failed via Resend for " payload << "To: <" << to << ">\r\n";
<< recipient payload << "From: Visione <" << from << ">\r\n";
<< ", result=" << static_cast<int>(result) payload << "Subject: Codice OTP Visione\r\n";
<< ", status=" payload << "MIME-Version: 1.0\r\n";
<< (response != nullptr ? std::to_string(response->statusCode()) payload << "Content-Type: text/plain; charset=utf-8\r\n";
: "none") payload << "\r\n";
<< ", body=" << body; payload << "Il tuo codice OTP per Visione e': " << code << "\r\n";
}); payload << "Scade tra 10 minuti.\r\n";
const auto payloadString = payload.str();
const auto url = smtpUrl(host, port, useTls);
const auto mailFrom = "<" + from + ">";
const auto recipientAddress = "<" + to + ">";
using CurlHandle = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>;
using CurlList = std::unique_ptr<curl_slist, decltype(&curl_slist_free_all)>;
CurlHandle curl(curl_easy_init(), &curl_easy_cleanup);
if (!curl)
{
LOG_ERROR << "Could not initialize libcurl SMTP handle";
return false;
}
CurlList recipients(nullptr, &curl_slist_free_all);
recipients.reset(curl_slist_append(nullptr, recipientAddress.c_str()));
if (!recipients)
{
LOG_ERROR << "Could not build SMTP recipient list for " << to;
return false;
}
UploadContext uploadContext{.payload = &payloadString};
char errorBuffer[CURL_ERROR_SIZE] = {0};
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
curl_easy_setopt(curl.get(), CURLOPT_MAIL_FROM, mailFrom.c_str());
curl_easy_setopt(curl.get(), CURLOPT_MAIL_RCPT, recipients.get());
curl_easy_setopt(curl.get(), CURLOPT_READFUNCTION, readPayload);
curl_easy_setopt(curl.get(), CURLOPT_READDATA, &uploadContext);
curl_easy_setopt(curl.get(), CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "visione-backend/1.0");
curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 20L);
curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, errorBuffer);
curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, verifyPeer ? 1L : 0L);
curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, verifyHost ? 2L : 0L);
if (!username.empty())
{
curl_easy_setopt(curl.get(), CURLOPT_USERNAME, username.c_str());
}
if (!password.empty())
{
curl_easy_setopt(curl.get(), CURLOPT_PASSWORD, password.c_str());
}
if (useStartTls)
{
curl_easy_setopt(curl.get(), CURLOPT_USE_SSL, CURLUSESSL_ALL);
}
const auto result = curl_easy_perform(curl.get());
if (result != CURLE_OK)
{
LOG_ERROR << "OTP email delivery failed via SMTP for "
<< to
<< ", provider="
<< provider
<< ", host="
<< host
<< ", port="
<< port
<< ", error="
<< (errorBuffer[0] != '\0' ? errorBuffer : curl_easy_strerror(result));
return false;
}
LOG_INFO << "OTP email sent via SMTP for " << to
<< ", provider=" << provider
<< ", host=" << host
<< ", port=" << port;
return true; return true;
} }
} // namespace visione::email } // namespace visione::email
+10 -4
View File
@@ -64,10 +64,16 @@
"default_frontend_redirect_uri": "http://localhost:3000/auth/cie/callback" "default_frontend_redirect_uri": "http://localhost:3000/auth/cie/callback"
}, },
"mail": { "mail": {
"provider": "resend", "provider": "smtp",
"api_base_url": "https://api.resend.com", "host": "127.0.0.1",
"api_key": "", "port": 1025,
"from": "[email protected]" "username": "",
"password": "",
"from": "[email protected]",
"use_tls": false,
"use_starttls": false,
"verify_peer": false,
"verify_host": false
}, },
"uploads": { "uploads": {
"public_base_path": "/uploads", "public_base_path": "/uploads",
+8 -1
View File
@@ -14,7 +14,14 @@ services:
DB_NAME: ${DB_NAME:-visione} DB_NAME: ${DB_NAME:-visione}
DB_TIMEOUT: ${DB_TIMEOUT:-30} DB_TIMEOUT: ${DB_TIMEOUT:-30}
MAIL_PROVIDER: ${MAIL_PROVIDER:-resend} MAIL_PROVIDER: ${MAIL_PROVIDER:-resend}
RESEND_API_BASE_URL: ${RESEND_API_BASE_URL:-https://api.resend.com} SMTP_HOST: ${SMTP_HOST:-smtp.resend.com}
SMTP_PORT: ${SMTP_PORT:-465}
SMTP_USERNAME: ${SMTP_USERNAME:-resend}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
SMTP_USE_STARTTLS: ${SMTP_USE_STARTTLS:-false}
SMTP_VERIFY_PEER: ${SMTP_VERIFY_PEER:-true}
SMTP_VERIFY_HOST: ${SMTP_VERIFY_HOST:-true}
RESEND_API_KEY: ${RESEND_API_KEY:-} RESEND_API_KEY: ${RESEND_API_KEY:-}
MAIL_FROM: ${MAIL_FROM:[email protected]} MAIL_FROM: ${MAIL_FROM:[email protected]}
CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN:-http://localhost:3000} CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN:-http://localhost:3000}
+1 -1
View File
@@ -32,7 +32,7 @@ Authorization: Bearer <token>
- `POST /auth/register` - `POST /auth/register`
citizen registration without password citizen registration without password
- `POST /auth/otp/request` - `POST /auth/otp/request`
queues OTP email through configured mail provider sends OTP email through configured SMTP provider
- `POST /auth/otp/verify` - `POST /auth/otp/verify`
consumes OTP, returns bearer token consumes OTP, returns bearer token
- `POST /auth/cie/start` - `POST /auth/cie/start`
+1 -1
View File
@@ -24,7 +24,7 @@ generate public read-only intervention links.
## Runtime Model ## Runtime Model
- Auth uses bearer tokens from `api_tokens`. - Auth uses bearer tokens from `api_tokens`.
- OTP login uses `otp_challenges` and async email delivery through configured mail provider. - OTP login uses `otp_challenges` and SMTP email delivery through configured mail provider.
- CIE login uses external Django RP service: - CIE login uses external Django RP service:
- Visione creates local `state` - Visione creates local `state`
- Django RP completes CIE/OIDC and sends signed identity payload to Visione - Django RP completes CIE/OIDC and sends signed identity payload to Visione
+2 -2
View File
@@ -2582,7 +2582,7 @@
}, },
"/api/v1/auth/otp/request": { "/api/v1/auth/otp/request": {
"post": { "post": {
"description": "Queues a one-time code by email through configured mail provider.", "description": "Sends a one-time code by email through configured SMTP provider.",
"operationId": "AuthController.requestOtp", "operationId": "AuthController.requestOtp",
"parameters": [ ], "parameters": [ ],
"requestBody": { "requestBody": {
@@ -3312,7 +3312,7 @@
}, },
"/api/v1/reports": { "/api/v1/reports": {
"get": { "get": {
"description": "Lists reports visible to the authenticated user.", "description": "Lists reports visible to the authenticated user. Citizens see own reports only; operators and admins can read all reports. Admin can use this endpoint for global read-only overview screens.",
"operationId": "ReportController.list", "operationId": "ReportController.list",
"parameters": [ "parameters": [
{ {
+42 -5
View File
@@ -5,8 +5,24 @@
#include "repositories/MigrationRunner.h" #include "repositories/MigrationRunner.h"
#include <cstdio> #include <cstdio>
#include <fstream>
#include <cstdlib> #include <cstdlib>
#include <fstream>
#include <string_view>
namespace
{
bool parseEnvBool(const char *value)
{
if (value == nullptr)
{
return false;
}
const std::string_view normalized(value);
return normalized == "1" || normalized == "true" || normalized == "TRUE" ||
normalized == "yes" || normalized == "on";
}
} // namespace
int main() int main()
{ {
@@ -19,6 +35,7 @@ int main()
{ {
configFile >> config; configFile >> config;
// DB config from env
if (const char* envHost = std::getenv("DB_HOST")) if (const char* envHost = std::getenv("DB_HOST"))
config["db_clients"][0]["host"] = envHost; config["db_clients"][0]["host"] = envHost;
if (const char* envPort = std::getenv("DB_PORT")) if (const char* envPort = std::getenv("DB_PORT"))
@@ -32,16 +49,35 @@ int main()
if (const char* envTimeout = std::getenv("DB_TIMEOUT")) if (const char* envTimeout = std::getenv("DB_TIMEOUT"))
config["db_clients"][0]["timeout"] = std::stod(envTimeout); config["db_clients"][0]["timeout"] = std::stod(envTimeout);
// Mail config from env
if (const char* envMailProvider = std::getenv("MAIL_PROVIDER")) if (const char* envMailProvider = std::getenv("MAIL_PROVIDER"))
config["custom_config"]["mail"]["provider"] = envMailProvider; config["custom_config"]["mail"]["provider"] = envMailProvider;
if (const char* envMailApiBaseUrl = std::getenv("RESEND_API_BASE_URL")) if (const char* envSmtpHost = std::getenv("SMTP_HOST"))
config["custom_config"]["mail"]["api_base_url"] = envMailApiBaseUrl; config["custom_config"]["mail"]["host"] = envSmtpHost;
if (const char* envMailApiKey = std::getenv("RESEND_API_KEY")) if (const char* envSmtpPort = std::getenv("SMTP_PORT"))
config["custom_config"]["mail"]["api_key"] = envMailApiKey; config["custom_config"]["mail"]["port"] = std::stoi(envSmtpPort);
if (const char* envSmtpUsername = std::getenv("SMTP_USERNAME"))
config["custom_config"]["mail"]["username"] = envSmtpUsername;
if (const char* envSmtpPassword = std::getenv("SMTP_PASSWORD"))
config["custom_config"]["mail"]["password"] = envSmtpPassword;
else if (const char* envResendApiKey = std::getenv("RESEND_API_KEY"))
config["custom_config"]["mail"]["password"] = envResendApiKey;
if (const char* envSmtpUseTls = std::getenv("SMTP_USE_TLS"))
config["custom_config"]["mail"]["use_tls"] = parseEnvBool(envSmtpUseTls);
if (const char* envSmtpUseStartTls = std::getenv("SMTP_USE_STARTTLS"))
config["custom_config"]["mail"]["use_starttls"] = parseEnvBool(envSmtpUseStartTls);
if (const char* envSmtpVerifyPeer = std::getenv("SMTP_VERIFY_PEER"))
config["custom_config"]["mail"]["verify_peer"] = parseEnvBool(envSmtpVerifyPeer);
if (const char* envSmtpVerifyHost = std::getenv("SMTP_VERIFY_HOST"))
config["custom_config"]["mail"]["verify_host"] = parseEnvBool(envSmtpVerifyHost);
if (const char* envMailFrom = std::getenv("MAIL_FROM")) if (const char* envMailFrom = std::getenv("MAIL_FROM"))
config["custom_config"]["mail"]["from"] = envMailFrom; config["custom_config"]["mail"]["from"] = envMailFrom;
// CORS config from env
if (const char* envCorsAllowedOrigin = std::getenv("CORS_ALLOWED_ORIGIN")) if (const char* envCorsAllowedOrigin = std::getenv("CORS_ALLOWED_ORIGIN"))
config["custom_config"]["cors"]["allowed_origin"] = envCorsAllowedOrigin; config["custom_config"]["cors"]["allowed_origin"] = envCorsAllowedOrigin;
// CIE auth config from env
if (const char* envCieServiceBaseUrl = std::getenv("CIE_AUTH_SERVICE_BASE_URL")) if (const char* envCieServiceBaseUrl = std::getenv("CIE_AUTH_SERVICE_BASE_URL"))
config["custom_config"]["cie_auth"]["service_base_url"] = envCieServiceBaseUrl; config["custom_config"]["cie_auth"]["service_base_url"] = envCieServiceBaseUrl;
if (const char* envCieStartPath = std::getenv("CIE_AUTH_START_PATH")) if (const char* envCieStartPath = std::getenv("CIE_AUTH_START_PATH"))
@@ -52,6 +88,7 @@ int main()
config["custom_config"]["cie_auth"]["state_ttl_minutes"] = std::stoi(envCieStateTtl); config["custom_config"]["cie_auth"]["state_ttl_minutes"] = std::stoi(envCieStateTtl);
if (const char* envCieFrontendRedirect = std::getenv("CIE_AUTH_DEFAULT_FRONTEND_REDIRECT_URI")) if (const char* envCieFrontendRedirect = std::getenv("CIE_AUTH_DEFAULT_FRONTEND_REDIRECT_URI"))
config["custom_config"]["cie_auth"]["default_frontend_redirect_uri"] = envCieFrontendRedirect; config["custom_config"]["cie_auth"]["default_frontend_redirect_uri"] = envCieFrontendRedirect;
drogon::app().loadConfigJson(config); drogon::app().loadConfigJson(config);
} }
else else
+1 -1
View File
@@ -328,7 +328,7 @@ Json::Value AuthService::requestOtp(const Json::Value &body)
email::EmailService mail; email::EmailService mail;
if (!mail.sendOtp(user[0]["email"].as<std::string>(), code)) if (!mail.sendOtp(user[0]["email"].as<std::string>(), code))
{ {
LOG_WARN << "Could not queue OTP email through " << mail.deliveryName(); LOG_WARN << "Could not send OTP email through " << mail.deliveryName();
return failure("otp_email_failed"); return failure("otp_email_failed");
} }