<?php
// jabali-sso-__JABALI_NONCE__.php — auto-generated by jabali-agent for install __JABALI_INSTALL_ID__.
// Self-deleting single-use admin login. See ADR-0040.

declare(strict_types=1);

// 1. Reject speculative navigation (prefetch / prerender / preview) and
//    non-GET methods before consuming any single-use state. Per Fetch
//    Metadata spec, Sec-Purpose is set ONLY on speculative requests
//    (prefetch, prerender, `prefetch;prerender`, and any future values)
//    and omitted on real user-initiated navigation. Using "any non-empty
//    Sec-Purpose" is forward-compatible with new speculation types —
//    strict equality against 'prefetch' missed Chrome's `prefetch;prerender`
//    and caused the first SSO file consumption to come from a prerender,
//    leaving the user's real click with a 404.
$secPurpose = $_SERVER['HTTP_SEC_PURPOSE'] ?? '';
$purpose    = $_SERVER['HTTP_PURPOSE']     ?? '';
$xMoz       = $_SERVER['HTTP_X_MOZ']       ?? '';
$xPurpose   = $_SERVER['HTTP_X_PURPOSE']   ?? '';
if ($secPurpose !== ''
 || $purpose === 'prefetch'
 || stripos($xMoz, 'prefetch') !== false
 || $xPurpose === 'preview'
 || ($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
    http_response_code(204);
    exit;
}

// 2. Atomic lock claim. Second concurrent open fails LOCK_NB and exits
//    without side effects. unlink alone is insufficient because PHP
//    keeps the inode alive on open file handles — flock closes the race.
$lock = @fopen(__FILE__, 'r');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
    http_response_code(409);
    exit;
}

// 3. Inline TTL check (defence in depth — systemd reaper sweeps every 30s
//    so worst-case stranded-file lifetime is 60+30 = 90s).
if (filemtime(__FILE__) + __JABALI_TTL_SECONDS__ < time()) {
    http_response_code(410);
    exit;
}

// 4. Load WordPress.
require_once __JABALI_WP_LOAD_PATH__;

// 5. Resolve the admin user by username. Agent baked __JABALI_ADMIN_USERNAME__
//    in at write time from the install row's admin_username column (the
//    canonical, schema-stable identifier — admin_user_id was never added
//    to application_installs and would require a migration + backfill).
//    get_user_by('login', ...) is the same lookup wp-login.php uses; the
//    user_can() check guards against the user existing but not having
//    admin caps on this site (multisite, demoted user, etc.).
$admin_username = __JABALI_ADMIN_USERNAME__;
$user = get_user_by('login', $admin_username);
if (!$user || !user_can($user, 'manage_options')) {
    // Migrated / scan-discovered installs may not carry the real
    // admin_username on the panel row. Fall back to the first user
    // with administrator role on this site (WP_User_Query honours
    // the wp_options table → works on single-site + multisite).
    $q = new WP_User_Query([
        'role'    => 'administrator',
        'number'  => 1,
        'orderby' => 'ID',
        'order'   => 'ASC',
    ]);
    $candidates = $q->get_results();
    if (!empty($candidates)) {
        $user = $candidates[0];
    }
    if (!$user || !user_can($user, 'manage_options')) {
        error_log('jabali-sso: no administrator user on site for install __JABALI_INSTALL_ID__');
        http_response_code(500);
        exit;
    }
    error_log('jabali-sso: admin_username mismatch, fell back to first administrator (' . $user->user_login . ') for install __JABALI_INSTALL_ID__');
}

// 6. Sign the operator in. Don't set "remember me" — one-shot session.
wp_set_current_user($user->ID);
wp_set_auth_cookie($user->ID, false, is_ssl());

// 7. Audit on the WP side. Don't log the admin uid —
//    wp-content/debug.log can be world-readable when WP_DEBUG_LOG is on.
error_log('jabali-sso: admin login completed for install __JABALI_INSTALL_ID__');

// 8. Don't leak the filename via Referer to wp-admin's third-party assets.
header('Referrer-Policy: no-referrer');

// 9. Unlink LAST — after auth cookie is set. Transient failure between
//    here and the redirect leaves the file on disk for a retry; reaper
//    sweeps stranded files within 90s either way.
@unlink(__FILE__);

// 10. Redirect to /wp-admin and exit.
wp_safe_redirect(admin_url());
exit;
