Skip to content

Commit

Permalink
feat: add 2fa support (#525)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellite authored Sep 28, 2024
1 parent 47f50ec commit 2f16ab3
Show file tree
Hide file tree
Showing 72 changed files with 4,881 additions and 507 deletions.
9 changes: 9 additions & 0 deletions about.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@
</a>
</span>
</p>
<p>
QRCode.js:
<span>
https://github.com/davidshimjs/qrcodejs
<a href="https://github.com/davidshimjs/qrcodejs" target="_blank" title="<?= translate('external_url', $i18n) ?>">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</p>
</div>
</section>

Expand Down
123 changes: 123 additions & 0 deletions endpoints/user/disable_totp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

require_once '../../includes/connect_endpoint.php';
require_once '../../includes/inputvalidation.php';

if (!function_exists('trigger_deprecation')) {
function trigger_deprecation($package, $version, $message, ...$args)
{
if (PHP_VERSION_ID >= 80000) {
trigger_error(sprintf($message, ...$args), E_USER_DEPRECATED);
}
}
}

if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n),
"reload" => false
]));
}


$statement = $db->prepare('SELECT totp_enabled FROM user WHERE id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);

if ($row['totp_enabled'] == 0) {
die(json_encode([
"success" => false,
"message" => "2FA is not enabled for this user",
"reload" => true
]));
}

if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);

if (isset($data['totpCode']) && $data['totpCode'] != "") {
require_once __DIR__ . '/../../libs/OTPHP/FactoryInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/Factory.php';
require_once __DIR__ . '/../../libs/OTPHP/ParameterTrait.php';
require_once __DIR__ . '/../../libs/OTPHP/OTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/OTP.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTP.php';
require_once __DIR__ . '/../../libs/Psr/Clock/ClockInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/InternalClock.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Binary.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/EncoderInterface.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Base32.php';

$totp_code = $data['totpCode'];

$statement = $db->prepare('SELECT totp_secret FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$secret = $row['totp_secret'];

$statement = $db->prepare('SELECT backup_codes FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$backupCodes = $row['backup_codes'];

$clock = new OTPHP\InternalClock();
$totp = OTPHP\TOTP::createFromSecret($secret, $clock);

if ($totp->verify($totp_code)) {
$statement = $db->prepare('UPDATE user SET totp_enabled = 0 WHERE id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$statement->execute();

$statement = $db->prepare('DELETE FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$statement->execute();

die(json_encode([
"success" => true,
"message" => translate('success', $i18n),
"reload" => true
]));
} else {
// Compare the TOTP code agains the backup codes
$backupCodes = json_decode($backupCodes, true);
if (($key = array_search($totp_code, $backupCodes)) !== false) {
unset($backupCodes[$key]);
$statement = $db->prepare('UPDATE totp SET backup_codes = :backup_codes WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$statement->bindValue(':backup_codes', json_encode($backupCodes), SQLITE3_TEXT);
$statement->execute();

die(json_encode([
"success" => true,
"message" => translate('success', $i18n),
"reload" => true
]));
} else {
die(json_encode([
"success" => false,
"message" => translate('totp_code_incorrect', $i18n),
"reload" => false
]));
}
}

} else {
die(json_encode([
"success" => false,
"message" => translate('fields_missing', $i18n),
"reload" => false
]));
}
} else {
die(json_encode([
"success" => false,
"message" => translate('invalid_request_method', $i18n),
"reload" => false
]));
}
139 changes: 139 additions & 0 deletions endpoints/user/enable_totp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

require_once '../../includes/connect_endpoint.php';
require_once '../../includes/inputvalidation.php';

if (!function_exists('trigger_deprecation')) {
function trigger_deprecation($package, $version, $message, ...$args)
{
if (PHP_VERSION_ID >= 80000) {
trigger_error(sprintf($message, ...$args), E_USER_DEPRECATED);
}
}
}

if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}

if ($_SERVER["REQUEST_METHOD"] === "GET") {
function base32_encode($hex)
{
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$bin = '';
foreach (str_split($hex) as $char) {
$bin .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT);
}

$chunks = str_split($bin, 5);
$base32 = '';
foreach ($chunks as $chunk) {
$chunk = str_pad($chunk, 5, '0', STR_PAD_RIGHT);
$index = bindec($chunk);
$base32 .= $alphabet[$index];
}

return $base32;
}

$data = $_GET;
if (isset($data['generate']) && $data['generate'] == true) {
$secret = base32_encode(bin2hex(random_bytes(20)));
$qrCodeUrl = "otpauth://totp/Wallos:" . $_SESSION['username'] . "?secret=" . $secret . "&issuer=Wallos";
$response = [
"success" => true,
"secret" => $secret,
"qrCodeUrl" => $qrCodeUrl
];
echo json_encode($response);
}
}

if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);

if (isset($data['totpSecret']) && $data['totpSecret'] != "" && isset($data['totpCode']) && $data['totpCode'] != "") {
require_once __DIR__ . '/../../libs/OTPHP/FactoryInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/Factory.php';
require_once __DIR__ . '/../../libs/OTPHP/ParameterTrait.php';
require_once __DIR__ . '/../../libs/OTPHP/OTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/OTP.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTP.php';
require_once __DIR__ . '/../../libs/Psr/Clock/ClockInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/InternalClock.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Binary.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/EncoderInterface.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Base32.php';

$secret = $data['totpSecret'];
$totp_code = $data['totpCode'];

// Check if user already has TOTP enabled
$stmt = $db->prepare("SELECT totp_enabled FROM user WHERE id = :user_id");
$stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row['totp_enabled'] == 1) {
die(json_encode([
"success" => false,
"message" => translate('2fa_already_enabled', $i18n)
]));
}

$clock = new OTPHP\InternalClock();
$totp = OTPHP\TOTP::createFromSecret($secret, $clock);

if ($totp->verify($totp_code)) {
// Generate 10 backup codes
$backupCodes = [];
for ($i = 0; $i < 10; $i++) {
$backupCode = bin2hex(random_bytes(10));
$backupCodes[] = $backupCode;
}

// Remove old TOTP data
$stmt = $db->prepare("DELETE FROM totp WHERE user_id = :user_id");
$stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER);
$stmt->execute();

$stmt = $db->prepare("INSERT INTO totp (user_id, totp_secret, backup_codes, last_totp_used) VALUES (:user_id, :totp_secret, :backup_codes, :last_totp_used)");
$stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER);
$stmt->bindValue(':totp_secret', $secret, SQLITE3_TEXT);
$stmt->bindValue(':backup_codes', json_encode($backupCodes), SQLITE3_TEXT);
$stmt->bindValue(':last_totp_used', time(), SQLITE3_INTEGER);
$stmt->execute();

// Update user totp_enabled

$stmt = $db->prepare("UPDATE user SET totp_enabled = 1 WHERE id = :user_id");
$stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER);
$stmt->execute();

die(json_encode([
"success" => true,
"backupCodes" => $backupCodes,
"message" => translate('success', $i18n)
]));
} else {
die(json_encode([
"success" => false,
"message" => translate('totp_code_incorrect', $i18n)
]));
}

} else {
die(json_encode([
"success" => false,
"message" => translate('totp_code_incorrect', $i18n)
]));
}




}
14 changes: 14 additions & 0 deletions includes/i18n/de.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@
'upload_avatar' => "Avatar hochladen",
'file_type_error' => "Dateityp nicht unterstützt",
'user_details' => "Benutzerdetails",
'two_factor_authentication' => "Zwei-Faktor-Authentifizierung",
'two_factor_info' => "Die Zwei-Faktor-Authentifizierung fügt Ihrem Konto eine zusätzliche Sicherheitsebene hinzu.<br>Sie benötigen eine Authentifizierungs-App wie Google Authenticator, Authy oder Ente Auth, um den QR-Code zu scannen.",
"two_factor_enabled_info" => "Ihr Konto ist mit der Zwei-Faktor-Authentifizierung gesichert. Sie können sie deaktivieren, indem Sie auf die Schaltfläche oben klicken.",
"enable_two_factor_authentication" => "Zwei-Faktor-Authentifizierung aktivieren",
"2fa_already_enabled" => "Zwei-Faktor-Authentifizierung ist bereits aktiviert",
"totp_code_incorrect" => "TOTP-Code ist falsch",
"backup_codes" => "Backup-Codes",
"download_backup_codes" => "Backup-Codes herunterladen",
"copy_to_clipboard" => "In die Zwischenablage kopieren",
"totp_backup_codes_info" => "Speichern Sie diese Codes an einem sicheren Ort. Sie können sie verwenden, wenn Sie keinen Zugriff auf Ihre Authentifizierungs-App haben.",
"disable_two_factor_authentication" => "Zwei-Faktor-Authentifizierung deaktivieren",
"totp_code" => "TOTP-Code",
"monthly_budget" => "Monatliches Budget",
"budget_info" => "Das monatliche Budget wird für die Berechnung der Statistiken verwendet.",
"household" => "Haushalt",
Expand Down Expand Up @@ -344,6 +356,8 @@
"month-10" => "Oktober",
"month-11" => "November",
"month-12" => "Dezember",
// TOTP Page
"insert_totp_code" => "Bitte geben Sie den TOTP-Code ein",

];

Expand Down
14 changes: 14 additions & 0 deletions includes/i18n/el.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@
'upload_avatar' => "μεταφόρτωση άβαταρ",
'file_type_error' => "Το αρχείο πρέπει να είναι τύπου jpg, jpeg, png, webp ή gif",
'user_details' => "Λεπτομέρειες χρήστη",
'two_factor_authentication' => "Διπλής πιστοποίησης",
"two_factor_info" => "Ο έλεγχος ταυτότητας δύο παραγόντων προσθέτει ένα επιπλέον επίπεδο ασφάλειας στο λογαριασμό σας.<br>Θα χρειαστείτε μια εφαρμογή ελέγχου ταυτότητας όπως το Google Authenticator, το Authy ή το Ente Auth για να σαρώσετε τον κωδικό QR.",
"two_factor_enabled_info" => "Ο λογαριασμός σας είναι ασφαλής με τον έλεγχο ταυτότητας δύο παραγόντων. Μπορείτε να τον απενεργοποιήσετε κάνοντας κλικ στο κουμπί παραπάνω.",
"enable_two_factor_authentication" => "Ενεργοποίηση διπλής πιστοποίησης",
"2fa_already_enabled" => "Ο έλεγχος ταυτότητας δύο παραγόντων είναι ήδη ενεργοποιημένος",
"totp_code_incorrect" => "Ο κωδικός TOTP είναι εσφαλμένος",
"backup_codes" => "Κωδικοί ανάκτησης",
"download_backup_codes" => "Κατέβασε τους κωδικούς ανάκτησης",
"copy_to_clipboard" => "Αντιγραφή στο πρόχειρο",
"totp_backup_codes_info" => "Αποθηκεύστε αυτούς τους κωδικούς ανάκτησης σε ένα ασφαλές μέρος. Θα χρειαστείτε έναν από αυτούς τους κωδικούς ανάκτησης για να αποκτήσετε πρόσβαση στο λογαριασμό σας σε περίπτωση που χάσετε τη συσκευή σας.",
"disable_two_factor_authentication" => "Απενεργοποίηση διπλής πιστοποίησης",
"totp_code" => "Κωδικός TOTP",
"monthly_budget" => "Μηνιαίος προϋπολογισμός",
"budget_info" => "Ο μηνιαίος προϋπολογισμός χρησιμοποιείται για τον υπολογισμό των στατιστικών",
"household" => "Νοικοκυριό",
Expand Down Expand Up @@ -344,6 +356,8 @@
"month-10" => "Οκτώβριος",
"month-11" => "Νοέμβριος",
"month-12" => "Δεκέμβριος",
// TOTP Page
"insert_totp_code" => "Εισάγετε τον κωδικό TOTP",

];

Expand Down
14 changes: 14 additions & 0 deletions includes/i18n/en.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@
'upload_avatar' => "Upload Avatar",
'file_type_error' => "The file type supplied is not supported.",
'user_details' => "User Details",
'two_factor_authentication' => "Two Factor Authentication",
'two_factor_info' => "Two Factor Authentication adds an extra layer of security to your account.<br>You will need an authenticator app like Google Authenticator, Authy or Ente Auth to scan the QR code.",
"two_factor_enabled_info" => "Your account is secure with Two Factor Authentication. You can disable it by clicking the button above.",
"enable_two_factor_authentication" => "Enable Two Factor Authentication",
"2fa_already_enabled" => "Two Factor Authentication is already enabled",
"totp_code_incorrect" => "TOTP code is incorrect",
"backup_codes" => "Backup Codes",
"download_backup_codes" => "Download Backup Codes",
"copy_to_clipboard" => "Copy to clipboard",
"totp_backup_codes_info" => "These codes can be used to login if you lose access to your authenticator app.",
"disable_two_factor_authentication" => "Disable Two Factor Authentication",
"totp_code" => "TOTP Code",
"monthly_budget" => "Monthly Budget",
"budget_info" => "Monthly budget is used to calculate statistics",
"household" => "Household",
Expand Down Expand Up @@ -345,6 +357,8 @@
"month-10" => "October",
"month-11" => "November",
"month-12" => "December",
// TOTP Page
"insert_totp_code" => "Insert TOTP code",


];
Expand Down
14 changes: 14 additions & 0 deletions includes/i18n/es.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@
'upload_avatar' => "Subir avatar",
'file_type_error' => "El archivo debe ser una imagen en formato PNG, JPG, WEBP o SVG",
'user_details' => "Detalles del Usuario",
'two_factor_authentication' => "Autenticación de Dos Factores",
'two_factor_info' => "La autenticación de dos factores añade una capa adicional de seguridad a tu cuenta.<br>Necesitarás una aplicación de autenticación como Google Authenticator, Authy o Ente Auth para escanear el código QR.",
'two_factor_enabled_info' => "Tu cuenta está segura con la autenticación de dos factores. Puedes desactivarla haciendo clic en el botón de arriba.",
"enable_two_factor_authentication" => "Habilitar Autenticación de Dos Factores",
"2fa_already_enabled" => "La autenticación de dos factores ya está habilitada",
"totp_code_incorrect" => "El código TOTP es incorrecto",
"backup_codes" => "Códigos de Respaldo",
"download_backup_codes" => "Descargar Códigos de Respaldo",
"copy_to_clipboard" => "Copiar al Portapapeles",
"totp_backup_codes_info" => "Guarda estos códigos en un lugar seguro. Puedes usarlos si pierdes acceso a tu aplicación de autenticación.",
"disable_two_factor_authentication" => "Desactivar Autenticación de Dos Factores",
"totp_code" => "Código TOTP",
"monthly_budget" => "Presupuesto Mensual",
"budget_info" => "El presupuesto mensual se utiliza para calcular las estadísticas. Si no deseas utilizar esta función, déjalo en 0.",
"household" => "Hogar",
Expand Down Expand Up @@ -344,6 +356,8 @@
"month-10" => "Octubre",
"month-11" => "Noviembre",
"month-12" => "Diciembre",
// TOTP Page
"insert_totp_code" => "Introduce el código TOTP",

];

Expand Down
Loading

0 comments on commit 2f16ab3

Please sign in to comment.