extend TOTP to basic auth & OIDC. Fix share btn galleryview.

This commit is contained in:
Ryan
2025-04-05 22:22:47 -04:00
committed by GitHub
parent 899b04e49a
commit 5100e8bf3b
12 changed files with 466 additions and 230 deletions

View File

@@ -1,84 +1,153 @@
<?php
// verifyTOTPSetup.php
// totp_verify.php
require_once 'vendor/autoload.php';
require_once 'config.php';
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
http_response_code(403);
echo json_encode(["error" => "Not authenticated"]);
exit;
// Secure session cookie
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '', // your domain
'secure' => true, // only over HTTPS
'httponly' => true,
'samesite' => 'Lax'
]);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
// Verify CSRF token from request headers.
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
http_response_code(403);
echo json_encode(["error" => "Invalid CSRF token"]);
exit;
}
// Ensure Content-Type is JSON.
// JSON + CSP
header('Content-Type: application/json');
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';");
// Read and decode the JSON request body.
$input = json_decode(file_get_contents("php://input"), true);
if (!isset($input['totp_code']) || strlen(trim($input['totp_code'])) !== 6 || !ctype_digit(trim($input['totp_code']))) {
http_response_code(400);
echo json_encode(["error" => "A valid 6-digit TOTP code is required"]);
exit;
}
try {
// standardized error helper
function respond($status, $code, $message, $data = []) {
http_response_code($code);
echo json_encode([
'status' => $status,
'code' => $code,
'message' => $message,
'data' => $data
]);
exit;
}
$totpCode = trim($input['totp_code']);
$username = $_SESSION['username'] ?? '';
if (empty($username)) {
http_response_code(400);
echo json_encode(["error" => "Username not found in session"]);
exit;
}
// Ratelimit TOTP attempts
if (!isset($_SESSION['totp_failures'])) {
$_SESSION['totp_failures'] = 0;
}
if ($_SESSION['totp_failures'] >= 5) {
respond('error', 429, 'Too many TOTP attempts. Please try again later.');
}
/**
* Retrieves the current user's TOTP secret from users.txt.
*
* @param string $username
* @return string|null The decrypted TOTP secret or null if not found.
*/
function getUserTOTPSecret($username) {
global $encryptionKey;
// Define the path to your users file.
$usersFile = USERS_DIR . USERS_FILE;
if (!file_exists($usersFile)) {
/**
* Helper: Get a user's role from users.txt
*/
function getUserRole(string $username): ?string {
$usersFile = USERS_DIR . USERS_FILE;
if (!file_exists($usersFile)) return null;
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$parts = explode(':', trim($line));
if (count($parts) >= 3 && $parts[0] === $username) {
return trim($parts[2]);
}
}
return null;
}
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$parts = explode(':', trim($line));
// Assuming format: username:hashedPassword:role:encryptedTOTPSecret
if (count($parts) >= 4 && $parts[0] === $username && !empty($parts[3])) {
return decryptData($parts[3], $encryptionKey);
}
// Must be authenticated or pending TOTP
if (
!(
(isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true)
|| isset($_SESSION['pending_login_user'])
)
) {
respond('error', 403, 'Not authenticated');
}
return null;
}
// Retrieve the user's TOTP secret.
$totpSecret = getUserTOTPSecret($username);
if (!$totpSecret) {
// CSRF check
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
respond('error', 403, 'Invalid CSRF token');
}
// Parse & validate input
$input = json_decode(file_get_contents("php://input"), true);
$code = trim($input['totp_code'] ?? '');
if (!preg_match('/^\d{6}$/', $code)) {
respond('error', 400, 'A valid 6-digit TOTP code is required');
}
// LOGIN flow (BasicAuth or OIDC)
if (isset($_SESSION['pending_login_user'])) {
$username = $_SESSION['pending_login_user'];
$totpSecret = $_SESSION['pending_login_secret'];
$tfa = new \RobThree\Auth\TwoFactorAuth('FileRise');
if (!$tfa->verifyCode($totpSecret, $code)) {
$_SESSION['totp_failures']++;
respond('error', 400, 'Invalid TOTP code');
}
// success → complete login
session_regenerate_id(true);
$_SESSION['authenticated'] = true;
$_SESSION['username'] = $username;
$_SESSION['isAdmin'] = (getUserRole($username) === "1");
$_SESSION['folderOnly'] = loadUserPermissions($username);
unset($_SESSION['pending_login_user'], $_SESSION['pending_login_secret'], $_SESSION['totp_failures']);
respond('ok', 200, 'Login successful');
}
// SETUPVERIFICATION flow
$username = $_SESSION['username'] ?? '';
if (!$username) {
respond('error', 400, 'Username not found in session');
}
/**
* Helper: retrieve the user's TOTP secret from users.txt
*/
function getUserTOTPSecret(string $username): ?string {
global $encryptionKey;
$usersFile = USERS_DIR . USERS_FILE;
if (!file_exists($usersFile)) return null;
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$parts = explode(':', trim($line));
if (count($parts) >= 4 && $parts[0] === $username && !empty($parts[3])) {
return decryptData($parts[3], $encryptionKey);
}
}
return null;
}
$totpSecret = getUserTOTPSecret($username);
if (!$totpSecret) {
respond('error', 500, 'TOTP secret not found. Please set up TOTP again.');
}
$tfa = new \RobThree\Auth\TwoFactorAuth('FileRise');
if (!$tfa->verifyCode($totpSecret, $code)) {
$_SESSION['totp_failures']++;
respond('error', 400, 'Invalid TOTP code');
}
// success
unset($_SESSION['totp_failures']);
respond('ok', 200, 'TOTP successfully verified');
} catch (\Throwable $e) {
// log error internally, then generic response
error_log("totp_verify error: " . $e->getMessage());
http_response_code(500);
echo json_encode(["error" => "TOTP secret not found. Please try setting up TOTP again."]);
echo json_encode([
'status' => 'error',
'code' => 500,
'message' => 'Internal server error'
]);
exit;
}
// Verify the provided TOTP code.
$tfa = new \RobThree\Auth\TwoFactorAuth('FileRise');
if (!$tfa->verifyCode($totpSecret, $totpCode)) {
http_response_code(400);
echo json_encode(["error" => "Invalid TOTP code."]);
exit;
}
// If needed, you could update a flag or store the confirmation in the user record here.
// Return a successful response.
echo json_encode(["success" => true, "message" => "TOTP successfully verified."]);
?>
}