query( "SELECT 1 FROM users WHERE role IN ('sysadmin','admin') LIMIT 1" )->fetchColumn(); } // Creates a sysadmin (full access incl. backups + platform update). Local-only // password row; federated linkage happens on first SSO by email match. function auth_create_sysadmin(PDO $pdo, string $name, string $email, string $password): int { $stmt = $pdo->prepare(" INSERT INTO users (name, email, password_hash, role) VALUES (?, ?, ?, 'sysadmin') "); $stmt->execute([ $name, strtolower(trim($email)), password_hash($password, PASSWORD_DEFAULT), ]); return (int) $pdo->lastInsertId(); } // Returns ['id'=>int] on success, or ['error'=>'andmore_email_taken'|'unavailable']. function auth_create_admin(PDO $pdo, string $name, string $email, string $phone, string $password): array { return _create_backoffice_user($pdo, 'admin', $name, $email, $phone, $password); } function auth_create_staff(PDO $pdo, string $name, string $email, string $phone, string $password): array { return _create_backoffice_user($pdo, 'staff', $name, $email, $phone, $password); } // Internal: create an admin/staff row and, in federated mode, link it to the // central Andmore ID (admin-vouched — the local password is mirrored upstream // so SSO_REQUIRED=true never locks out the new user). See config/sso.php // identity_upstream_link_immediate() for the upstream rules. function _create_backoffice_user(PDO $pdo, string $role, string $name, string $email, string $phone, string $password): array { if (!in_array($role, ['admin', 'staff'], true)) { throw new InvalidArgumentException("Invalid backoffice role: {$role}"); } $email = strtolower(trim($email)); $canonicalId = null; if (identity_is_federated()) { $link = identity_upstream_link_immediate($email, $name, $phone, $password); if (($link['error'] ?? '') === 'andmore_email_taken') { return ['error' => 'andmore_email_taken']; } if (($link['error'] ?? '') === 'unavailable' && identity_require_upstream()) { return ['error' => 'unavailable']; } if (isset($link['canonical_id'])) { $canonicalId = (int) $link['canonical_id']; } } $stmt = $pdo->prepare(" INSERT INTO users (name, email, phone, password_hash, role, andmore_id) VALUES (?, ?, ?, ?, ?, ?) "); $stmt->execute([ $name, $email, $phone ?: null, password_hash($password, PASSWORD_DEFAULT), $role, $canonicalId, ]); return ['id' => (int) $pdo->lastInsertId()]; }