167 lines
5.1 KiB
PHP
167 lines
5.1 KiB
PHP
<?php
|
|
// totp_setup.php
|
|
|
|
require_once 'vendor/autoload.php';
|
|
require_once 'config.php';
|
|
|
|
use Endroid\QrCode\Builder\Builder;
|
|
use Endroid\QrCode\Writer\PngWriter;
|
|
use RobThree\Auth\Algorithm;
|
|
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
|
|
|
|
// Define the respond() helper if not already defined.
|
|
if (!function_exists('respond')) {
|
|
function respond($status, $code, $message, $data = []) {
|
|
http_response_code($code);
|
|
echo json_encode([
|
|
'status' => $status,
|
|
'code' => $code,
|
|
'message' => $message,
|
|
'data' => $data
|
|
]);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Allow access if the user is authenticated or pending TOTP.
|
|
if (!((isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) || isset($_SESSION['pending_login_user']))) {
|
|
http_response_code(403);
|
|
exit;
|
|
}
|
|
|
|
// Retrieve CSRF token from GET parameter or request headers.
|
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
|
$receivedToken = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
|
|
|
if ($receivedToken !== $_SESSION['csrf_token']) {
|
|
echo json_encode(["error" => "Invalid CSRF token"]);
|
|
http_response_code(403);
|
|
exit;
|
|
}
|
|
|
|
$username = $_SESSION['username'] ?? '';
|
|
if (!$username) {
|
|
http_response_code(400);
|
|
exit;
|
|
}
|
|
|
|
// Set header to output a PNG image.
|
|
header("Content-Type: image/png");
|
|
|
|
// Define the path to your users.txt file.
|
|
$usersFile = USERS_DIR . USERS_FILE;
|
|
|
|
/**
|
|
* Updates the TOTP secret for the given user in users.txt.
|
|
*
|
|
* @param string $username
|
|
* @param string $encryptedSecret The encrypted TOTP secret.
|
|
*/
|
|
function updateUserTOTPSecret($username, $encryptedSecret) {
|
|
global $usersFile;
|
|
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
$newLines = [];
|
|
foreach ($lines as $line) {
|
|
$parts = explode(':', trim($line));
|
|
if (count($parts) < 3) {
|
|
$newLines[] = $line;
|
|
continue;
|
|
}
|
|
if ($parts[0] === $username) {
|
|
// If a fourth field exists, update it; otherwise, append it.
|
|
if (count($parts) >= 4) {
|
|
$parts[3] = $encryptedSecret;
|
|
} else {
|
|
$parts[] = $encryptedSecret;
|
|
}
|
|
$newLines[] = implode(':', $parts);
|
|
} else {
|
|
$newLines[] = $line;
|
|
}
|
|
}
|
|
file_put_contents($usersFile, implode(PHP_EOL, $newLines) . PHP_EOL, LOCK_EX);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current user's TOTP secret from users.txt (if present).
|
|
*
|
|
* @param string $username
|
|
* @return string|null The decrypted TOTP secret or null if not found.
|
|
*/
|
|
function getUserTOTPSecret($username) {
|
|
global $usersFile, $encryptionKey;
|
|
if (!file_exists($usersFile)) {
|
|
return null;
|
|
}
|
|
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
foreach ($lines as $line) {
|
|
$parts = explode(':', trim($line));
|
|
if (count($parts) >= 4 && $parts[0] === $username && !empty($parts[3])) {
|
|
return decryptData($parts[3], $encryptionKey);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the global OTPAuth URL from admin configuration.
|
|
*
|
|
* @return string Global OTPAuth URL template or an empty string if not set.
|
|
*/
|
|
function getGlobalOtpauthUrl() {
|
|
global $encryptionKey;
|
|
$adminConfigFile = USERS_DIR . 'adminConfig.json';
|
|
if (file_exists($adminConfigFile)) {
|
|
$encryptedContent = file_get_contents($adminConfigFile);
|
|
$decryptedContent = decryptData($encryptedContent, $encryptionKey);
|
|
if ($decryptedContent !== false) {
|
|
$config = json_decode($decryptedContent, true);
|
|
if (isset($config['globalOtpauthUrl']) && !empty($config['globalOtpauthUrl'])) {
|
|
return $config['globalOtpauthUrl'];
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
$tfa = new \RobThree\Auth\TwoFactorAuth(
|
|
new GoogleChartsQrCodeProvider(), // QR code provider
|
|
'FileRise', // issuer
|
|
6, // number of digits
|
|
30, // period in seconds
|
|
Algorithm::Sha1 // enum case from your Algorithm enum
|
|
);
|
|
|
|
// Retrieve the current TOTP secret for the user.
|
|
$totpSecret = getUserTOTPSecret($username);
|
|
if (!$totpSecret) {
|
|
// If no TOTP secret exists, generate a new one.
|
|
$totpSecret = $tfa->createSecret();
|
|
$encryptedSecret = encryptData($totpSecret, $encryptionKey);
|
|
updateUserTOTPSecret($username, $encryptedSecret);
|
|
}
|
|
|
|
// Determine the otpauth URL to use.
|
|
$globalOtpauthUrl = getGlobalOtpauthUrl();
|
|
if (!empty($globalOtpauthUrl)) {
|
|
$label = "FileRise:" . $username;
|
|
$otpauthUrl = str_replace(
|
|
["{label}", "{secret}"],
|
|
[urlencode($label), $totpSecret],
|
|
$globalOtpauthUrl
|
|
);
|
|
} else {
|
|
$label = urlencode("FileRise:" . $username);
|
|
$issuer = urlencode("FileRise");
|
|
$otpauthUrl = "otpauth://totp/{$label}?secret={$totpSecret}&issuer={$issuer}";
|
|
}
|
|
|
|
// Build the QR code using Endroid QR Code.
|
|
$result = Builder::create()
|
|
->writer(new PngWriter())
|
|
->data($otpauthUrl)
|
|
->build();
|
|
|
|
header('Content-Type: ' . $result->getMimeType());
|
|
echo $result->getString();
|
|
?>
|