<?php

$isHttps = (
    !empty($_SERVER['HTTPS'])
    && $_SERVER['HTTPS'] !== 'off'
);

session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'secure' => $isHttps,
    'httponly' => true,
    'samesite' => 'Lax'
]);

session_start();

// =============================
// CONFIG
// =============================
$USER_FILE = __DIR__ . '/admin/users.json';
$ISSUER = 'Schul IT-Cockpit';
$REMEMBER_DAYS = 30;

// =============================
// CSRF PROTECTION
// =============================

// CSRF-Token erzeugen
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// CSRF-Token für HTML ausgeben
function csrf_token(): string
{
    return htmlspecialchars(
        $_SESSION['csrf_token'],
        ENT_QUOTES,
        'UTF-8'
    );
}

// CSRF-Token überprüfen
function verify_csrf(): void
{
    $submittedToken = $_POST['csrf_token'] ?? '';
    $sessionToken = $_SESSION['csrf_token'] ?? '';

    if (
        !is_string($submittedToken) ||
        !is_string($sessionToken) ||
        $submittedToken === '' ||
        $sessionToken === '' ||
        !hash_equals($sessionToken, $submittedToken)
    ) {
        http_response_code(403);
        exit('CSRF-Validierung fehlgeschlagen. Bitte Seite neu laden.');
    }
}

// =============================
// USER STORAGE
// =============================
function load_users($file) {
    if (!file_exists($file)) {
        return [];
    }

    return json_decode(
        file_get_contents($file),
        true
    ) ?: [];
}

function save_users($file, $users) {
    file_put_contents(
        $file,
        json_encode(
            $users,
            JSON_PRETTY_PRINT
        )
    );
}

$users = load_users($USER_FILE);

// =============================
// DEMO USER
// =============================
if (empty($users)) {

    $secret = 'JBSWY3DPEHPK3PXP';

    $users['admin'] = [
        'password' => password_hash(
            'admin123',
            PASSWORD_DEFAULT
        ),
        'secret' => $secret,
        'backup_codes' => [],
        'remember_tokens' => []
    ];

    save_users($USER_FILE, $users);
}

// =============================
// BACKUP CODES
// =============================
function generate_backup_codes() {

    $codes = [];

    for ($i = 0; $i < 8; $i++) {
        $codes[] = strtoupper(
            bin2hex(random_bytes(4))
        );
    }

    return $codes;
}

function use_backup_code(&$user, $code) {

    $index = array_search(
        $code,
        $user['backup_codes']
    );

    if ($index !== false) {

        unset($user['backup_codes'][$index]);

        $user['backup_codes'] =
            array_values($user['backup_codes']);

        return true;
    }

    return false;
}

// =============================
// REMEMBER DEVICE
// =============================
function remember_device(
    $username,
    &$user,
    $days
) {

    $token = bin2hex(
        random_bytes(32)
    );

    $user['remember_tokens'][$token] =
        time() + ($days * 86400);

    setcookie(
        'remember_device',
        $username . ':' . $token,
        time() + ($days * 86400),
        '/',
        '',
        false,
        true
    );
}

function check_remember_device(&$users) {

    if (empty($_COOKIE['remember_device'])) {
        return false;
    }

    $parts = explode(
        ':',
        $_COOKIE['remember_device'],
        2
    );

    if (count($parts) !== 2) {
        return false;
    }

    [$username, $token] = $parts;

    if (!isset($users[$username])) {
        return false;
    }

    $user =& $users[$username];

    if (
        !isset(
            $user['remember_tokens'][$token]
        )
    ) {
        return false;
    }

    if (
        $user['remember_tokens'][$token]
        < time()
    ) {

        unset(
            $user['remember_tokens'][$token]
        );

        return false;
    }

    $_SESSION['logged_in'] = $username;

    return true;
}

// =============================
// AUTO LOGIN VIA COOKIE
// =============================
check_remember_device($users);

// =============================
// TOTP
// =============================
function base32_decode_custom($b32) {

    $alphabet =
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

    $b32 = strtoupper($b32);

    $binary = '';

    for (
        $i = 0;
        $i < strlen($b32);
        $i++
    ) {

        $pos = strpos(
            $alphabet,
            $b32[$i]
        );

        if ($pos !== false) {

            $binary .= str_pad(
                decbin($pos),
                5,
                '0',
                STR_PAD_LEFT
            );
        }
    }

    $bytes = '';

    for (
        $i = 0;
        $i < strlen($binary);
        $i += 8
    ) {

        $byte = substr(
            $binary,
            $i,
            8
        );

        if (strlen($byte) == 8) {

            $bytes .= chr(
                bindec($byte)
            );
        }
    }

    return $bytes;
}

function get_totp(
    $secret,
    $slice = null
) {

    if ($slice === null) {
        $slice = floor(time() / 30);
    }

    $key = base32_decode_custom($secret);

    $time =
        pack('N*', 0)
        .
        pack('N*', $slice);

    $hash = hash_hmac(
        'sha1',
        $time,
        $key,
        true
    );

    $offset =
        ord(substr($hash, -1))
        & 0x0F;

    $value =
        unpack(
            'N',
            substr(
                $hash,
                $offset,
                4
            )
        )[1]
        & 0x7fffffff;

    return str_pad(
        $value % 1000000,
        6,
        '0',
        STR_PAD_LEFT
    );
}

function verify_totp(
    $secret,
    $code
) {

    $slice = floor(time() / 30);

    for (
        $i = -1;
        $i <= 1;
        $i++
    ) {

        if (
            get_totp(
                $secret,
                $slice + $i
            ) === $code
        ) {
            return true;
        }
    }

    return false;
}

// =============================
// LOGOUT
// =============================

if (
    $_SERVER['REQUEST_METHOD'] === 'POST'
    &&
    isset($_POST['logout'])
) {

    // CSRF prüfen
    verify_csrf();

    // Remember-Cookie löschen
    setcookie(
        'remember_device',
        '',
        time() - 3600,
        '/',
        '',
        false,
        true
    );

    // Session löschen
    $_SESSION = [];

    if (
        ini_get('session.use_cookies')
    ) {

        $params =
            session_get_cookie_params();

        setcookie(
            session_name(),
            '',
            time() - 42000,
            $params['path'],
            $params['domain'],
            $params['secure'],
            $params['httponly']
        );
    }

    session_destroy();

    header(
        'Location: login.php'
    );

    exit;
}

// =============================
// LOGIN
// =============================

$error = '';

// =============================
// LOGIN STEP 1
// =============================

if (
    isset(
        $_POST['username'],
        $_POST['password']
    )
) {

    // CSRF prüfen
    verify_csrf();

    $u = $_POST['username'];

    if (
        isset($users[$u])
        &&
        password_verify(
            $_POST['password'],
            $users[$u]['password']
        )
    ) {

        $_SESSION['tmp_user'] = $u;

        // Entscheidung:
        // 2FA bereits eingerichtet?
        if (
            empty(
                $users[$u]['2fa_initialized']
            )
        ) {

            $_SESSION['setup_2fa'] = true;

        } else {

            $_SESSION['verify_2fa'] = true;
        }

    } else {

        $error = 'Login falsch';
    }
}

// =============================
// 2FA SETUP (FIRST LOGIN)
// =============================

if (
    isset(
        $_POST['code'],
        $_SESSION['tmp_user'],
        $_SESSION['setup_2fa']
    )
) {

    // CSRF prüfen
    verify_csrf();

    $u = $_SESSION['tmp_user'];

    $user =& $users[$u];

    $code = strtoupper(
        trim($_POST['code'])
    );

    if (
        verify_totp(
            $user['secret'],
            $code
        )
    ) {

        // 2FA aktiviert
        $users[$u]['2fa_initialized'] = true;

        save_users(
            $USER_FILE,
            $users
        );

        // Session-ID erneuern
        session_regenerate_id(true);

        $_SESSION['logged_in'] = $u;

        unset(
            $_SESSION['tmp_user']
        );

        unset(
            $_SESSION['setup_2fa']
        );

        header(
            'Location: /protected/dashboard.php'
        );

        exit;

    } else {

        $error = '2FA Code falsch';
    }
}

// =============================
// LOGIN STEP 2 (2FA)
// =============================

if (
    isset(
        $_POST['code'],
        $_SESSION['tmp_user'],
        $_SESSION['verify_2fa']
    )
) {

    // CSRF prüfen
    verify_csrf();

    $u = $_SESSION['tmp_user'];

    $user =& $users[$u];

    $code = strtoupper(
        trim($_POST['code'])
    );

    if (
        verify_totp(
            $user['secret'],
            $code
        )
        ||
        use_backup_code(
            $user,
            $code
        )
    ) {

        // Session-ID erneuern
        session_regenerate_id(true);

        $_SESSION['logged_in'] = $u;

        unset(
            $_SESSION['tmp_user']
        );

        unset(
            $_SESSION['verify_2fa']
        );

        // Gerät merken
        if (
            isset($_POST['remember'])
        ) {

            remember_device(
                $u,
                $user,
                $REMEMBER_DAYS
            );
        }

        save_users(
            $USER_FILE,
            $users
        );

        header(
            'Location: /protected/dashboard.php'
        );

        exit;

    } else {

        $error =
            '2FA oder Backup Code falsch';
    }
}

// =============================
// ALREADY LOGGED IN
// =============================

if (
    isset($_SESSION['logged_in'])
) {

    header(
        'Location: /protected/dashboard.php'
    );

    exit;
}

?>

<!DOCTYPE html>
<html>

<head>

<meta charset="UTF-8">

<title>Login</title>

<link rel="stylesheet" href="/assets/login.css">

</head>

<body>

<div class="box">

<?php if ($error): ?>

<div class="error">
    <?= htmlspecialchars(
        $error,
        ENT_QUOTES,
        'UTF-8'
    ) ?>
</div>

<?php endif; ?>


<?php if (!isset($_SESSION['tmp_user'])): ?>

<!-- =============================
     STEP 1 - LOGIN
     ============================= -->

<h3>Login</h3>

<form method="post">

    <!-- CSRF TOKEN -->
    <input
        type="hidden"
        name="csrf_token"
        value="<?= csrf_token() ?>"
    >

    <input
        name="username"
        placeholder="Username"
        required
        autocomplete="username"
    >

    <input
        name="password"
        type="password"
        placeholder="Passwort"
        required
        autocomplete="current-password"
    >

    <button type="submit">
        Login
    </button>

</form>


<?php else:

$u = $_SESSION['tmp_user'];

$secret =
    $users[$u]['secret'];

$otpauth =
    "otpauth://totp/"
    . $ISSUER
    . ":"
    . $u
    . "?secret="
    . $secret
    . "&issuer="
    . $ISSUER;

$qr =
    "https://api.qrserver.com/v1/create-qr-code/"
    . "?size=200x200&data="
    . urlencode($otpauth);

?>

<!-- =============================
     STEP 2 - 2FA
     ============================= -->

<h3>2FA Code</h3>

<form method="post">

    <!-- CSRF TOKEN -->
    <input
        type="hidden"
        name="csrf_token"
        value="<?= csrf_token() ?>"
    >

    <input
        name="code"
        placeholder="Code oder Backup"
        required
        autofocus
        autocomplete="one-time-code"
    >

    <label>

        <input
            type="checkbox"
            name="remember"
            value="1"
        >

        Gerät merken
        (<?= $REMEMBER_DAYS ?> Tage)

    </label>

    <button type="submit">
        Bestätigen
    </button>

</form>


<?php if (
    isset($_SESSION['setup_2fa'])
): ?>

<img
    src="<?= htmlspecialchars(
        $qr,
        ENT_QUOTES,
        'UTF-8'
    ) ?>"
    alt="2FA QR-Code"
>

<?php endif; ?>


<?php endif; ?>

</div>

</body>

</html>