http: adds php api routes under do/
This commit is contained in:
Executable → Regular
+9
-1
@@ -3,9 +3,17 @@
|
|||||||
LOCAL_SMTP_USER=
|
LOCAL_SMTP_USER=
|
||||||
|
|
||||||
# The envelope-from used by the local SMTP server
|
# The envelope-from used by the local SMTP server
|
||||||
# Recommended to be the same as REMOTE_SMTP_USER, else the remote may reject it
|
# Theoretically should be "LOCAL_SMTP_FROM_NAME <LOCAL_SMTP_FROM>", but in
|
||||||
|
# practice this seems to cause bugs, while setting it to LOCAL_SMTP_FROM works
|
||||||
LOCAL_SMTP_ENVELOPE_FROM=
|
LOCAL_SMTP_ENVELOPE_FROM=
|
||||||
|
|
||||||
|
# The from address used by the local SMTP server
|
||||||
|
# Recommended to be the same as REMOTE_SMTP_USER, else the remote may reject it
|
||||||
|
LOCAL_SMTP_FROM=
|
||||||
|
|
||||||
|
# The from name used by the local SMTP server
|
||||||
|
LOCAL_SMTP_FROM_NAME=
|
||||||
|
|
||||||
# The host of the remote SMTP server, e.g. smtp.gmail.com
|
# The host of the remote SMTP server, e.g. smtp.gmail.com
|
||||||
REMOTE_SMTP_HOST=
|
REMOTE_SMTP_HOST=
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
<?php namespace JoeacNet\Http\Config;
|
<?php namespace JoeacNet\Http\Config;
|
||||||
|
|
||||||
|
function maxDailyEmails(): int|null
|
||||||
|
{
|
||||||
|
$maxDailyEmails = getenv("MAX_DAILY_EMAILS");
|
||||||
|
if (!is_numeric($maxDailyEmails)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $maxDailyEmails;
|
||||||
|
}
|
||||||
|
|
||||||
|
function contactMailbox(): string|null
|
||||||
|
{
|
||||||
|
return (string) getenv("CONTACT_MAILBOX");
|
||||||
|
}
|
||||||
|
|
||||||
|
function contactMailboxName(): string|null
|
||||||
|
{
|
||||||
|
return (string) getenv("CONTACT_MAILBOX_NAME");
|
||||||
|
}
|
||||||
|
|
||||||
function localSmtpFrom(): string|null
|
function localSmtpFrom(): string|null
|
||||||
{
|
{
|
||||||
return (string) getenv("LOCAL_SMTP_FROM");
|
return (string) getenv("LOCAL_SMTP_FROM");
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use JoeacNet\Http\Config;
|
||||||
|
use JoeacNet\Http\Db;
|
||||||
|
use JoeacNet\Http\Mail;
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../../vendor/autoload.php";
|
||||||
|
require_once __DIR__ . "/../../php/config.php";
|
||||||
|
require_once __DIR__ . "/../../php/db.php";
|
||||||
|
require_once __DIR__ . "/../../php/mail.php";
|
||||||
|
|
||||||
|
$db = Db\connectDb();
|
||||||
|
|
||||||
|
$payload = json_decode(file_get_contents("php://input"), true);
|
||||||
|
$name = getNameFromPayloadAndValidate($payload);
|
||||||
|
$email = getEmailFromPayloadAndValidate($payload);
|
||||||
|
$message = getMessageFromPayloadAndValidate($payload);
|
||||||
|
$token = getTokenFromPayloadAndValidate($payload);
|
||||||
|
$userId = getUserIdFromPayloadAndValidate($payload);
|
||||||
|
|
||||||
|
limitMaxDailyEmails($db);
|
||||||
|
verifyToken(db: $db, userId: $userId, token: $token);
|
||||||
|
Db\deleteAllTokensForUser($db, $userId);
|
||||||
|
|
||||||
|
$messageId = Mail\sendEmail(
|
||||||
|
subject: "joeac.net: $name left a message",
|
||||||
|
replyToEmail: $email,
|
||||||
|
replyToName: $name,
|
||||||
|
toEmail: Config\contactMailbox(),
|
||||||
|
toName: Config\contactMailboxName(),
|
||||||
|
body: "$name <$email> sent you a message:\n\n\n$message",
|
||||||
|
);
|
||||||
|
Db\recordSentEmail($db, $messageId);
|
||||||
|
syslog(LOG_INFO, "Sent an email to Joe. Message ID: $messageId");
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
echo $messageId;
|
||||||
|
exit();
|
||||||
|
|
||||||
|
/// functions ///
|
||||||
|
|
||||||
|
function verifyToken(PDO $db, string $userId, string $token)
|
||||||
|
{
|
||||||
|
if (!Db\isTokenValid($db, $userId, $token)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "token is not valid";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function limitMaxDailyEmails(PDO $db)
|
||||||
|
{
|
||||||
|
$countEmailsSentLast24Hours = Db\countEmailsSentLast24Hours($db);
|
||||||
|
if ($countEmailsSentLast24Hours > Config\maxDailyEmails()) {
|
||||||
|
$msg =
|
||||||
|
"$countEmailsSentLast24Hours emails have been sent in the last 24 hours, but the max daily load is " .
|
||||||
|
Config\maxDailyEmails() .
|
||||||
|
".";
|
||||||
|
syslog(LOG_WARNING, $msg);
|
||||||
|
http_response_code(500);
|
||||||
|
echo $msg;
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNameFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$name = (string) $payload["name"];
|
||||||
|
if (is_null($name) || $name == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "name must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmailFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$email = (string) $payload["email"];
|
||||||
|
if (is_null($email) || $email == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "email must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "<$email> is not a valid email address.";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $email;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$message = (string) $payload["message"];
|
||||||
|
if (is_null($message) || $message == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "message must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokenFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$token = (string) $payload["token"];
|
||||||
|
if (is_null($token) || $token == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "token must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserIdFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$userId = (string) $payload["userId"];
|
||||||
|
if (is_null($userId) || $userId == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "userId must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $userId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use JoeacNet\Http\Config;
|
||||||
|
use JoeacNet\Http\Db;
|
||||||
|
use JoeacNet\Http\Mail;
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../../php/config.php";
|
||||||
|
require_once __DIR__ . "/../../php/db.php";
|
||||||
|
require_once __DIR__ . "/../../php/mail.php";
|
||||||
|
|
||||||
|
const SEND_OTP_TYPES = ["email"];
|
||||||
|
|
||||||
|
$db = Db\connectDb();
|
||||||
|
|
||||||
|
$payload = json_decode(file_get_contents("php://input"), true);
|
||||||
|
$email = getEmailFromPayloadAndValidate($payload);
|
||||||
|
$name = getNameFromPayloadAndValidate($payload);
|
||||||
|
$type = getTypeFromPayloadAndValidate($payload);
|
||||||
|
|
||||||
|
limitMaxDailyEmails(db: $db, email: $email, name: $name);
|
||||||
|
|
||||||
|
$otp = strtoupper(bin2hex(random_bytes(3)));
|
||||||
|
$prettyOtp = substr($otp, 0, 3) . "-" . substr($otp, 3, 6);
|
||||||
|
Db\insertOtp(db: $db, userId: $email, otp: $otp);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$messageId = Mail\sendEmail(
|
||||||
|
toEmail: $email,
|
||||||
|
toName: $name,
|
||||||
|
subject: "joeac.net: your OTP is $prettyOtp",
|
||||||
|
body: <<<BODY
|
||||||
|
Someone tried to use this email address on joeac.net. If this was you,
|
||||||
|
your one-time passcode is $prettyOtp. If this wasn't you, you don't need
|
||||||
|
to do anything.
|
||||||
|
BODY,
|
||||||
|
);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Email could not be sent: $e");
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Email could not be sent: $e";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Db\recordSentEmail($db, $messageId);
|
||||||
|
syslog(LOG_INFO, "Sent OTP ($prettyOtp) to $email. Message ID: $messageId");
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
echo $messageId;
|
||||||
|
exit();
|
||||||
|
|
||||||
|
/// functions ///
|
||||||
|
|
||||||
|
function limitMaxDailyEmails(PDO $db, string $email, string $name)
|
||||||
|
{
|
||||||
|
$countEmailsSentLast24Hours = Db\countEmailsSentLast24Hours($db);
|
||||||
|
if ($countEmailsSentLast24Hours > Config\maxDailyEmails()) {
|
||||||
|
$msg =
|
||||||
|
"$name <$email> requested an OTP email, but $countEmailsSentLast24Hours emails have already been sent, whereas the max daily load is " .
|
||||||
|
Config\maxDailyEmails() .
|
||||||
|
".";
|
||||||
|
syslog(LOG_WARNING, $msg);
|
||||||
|
http_response_code(500);
|
||||||
|
echo $msg;
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNameFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$name = (string) $payload["name"];
|
||||||
|
if (is_null($name) || $name == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "name must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmailFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$email = (string) $payload["email"];
|
||||||
|
if (is_null($email) || $email == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "email must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "<$email> is not a valid email address.";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $email;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$type = (string) $payload["type"];
|
||||||
|
if (!in_array($type, SEND_OTP_TYPES)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "type must be one of: " . SEND_OTP_TYPES;
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use JoeacNet\Http\Db;
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../../php/db.php";
|
||||||
|
|
||||||
|
$db = Db\connectDb();
|
||||||
|
|
||||||
|
$payload = json_decode(file_get_contents("php://input"), true);
|
||||||
|
$guess = getGuessFromPayloadAndValidate($payload);
|
||||||
|
$leniencySecs = getLeniencySecsFromPayloadAndValidate($payload);
|
||||||
|
$userId = getUserIdFromPayloadAndValidate($payload);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Db\isOtpCorrect(
|
||||||
|
db: $db,
|
||||||
|
userId: $userId,
|
||||||
|
guess: $guess,
|
||||||
|
leniencySecs: $leniencySecs,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "OTP is not valid";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
Db\deleteAllOtpsForUser($db, $userId);
|
||||||
|
$token = bin2hex(random_bytes(256));
|
||||||
|
Db\insertSendEmailToken(db: $db, userId: $userId, token: $token);
|
||||||
|
http_response_code(200);
|
||||||
|
echo $token;
|
||||||
|
exit();
|
||||||
|
|
||||||
|
/// functions ///
|
||||||
|
|
||||||
|
function getGuessFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$guess = (string) $payload["guess"];
|
||||||
|
if (strlen($guess) != 6) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "guess must be six characters long";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $guess;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLeniencySecsFromPayloadAndValidate(array $payload): int
|
||||||
|
{
|
||||||
|
if ((bool) $payload["lenient"] ?? false) {
|
||||||
|
return 60;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserIdFromPayloadAndValidate(array $payload): string
|
||||||
|
{
|
||||||
|
$userId = (string) $payload["userId"];
|
||||||
|
if (is_null($userId) || $userId == "") {
|
||||||
|
http_response_code(400);
|
||||||
|
echo "userId must not be empty";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
return $userId;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user