false, 'ERROR' => 'method_not_allowed'], 405); } $body = readJsonBody(); $loginId = trim($body['login_id'] ?? ''); $password = $body['password'] ?? ''; if ($loginId === '') { jsonResponse(['OK' => false, 'ERROR' => 'login_id_required']); } if ($password === '') { jsonResponse(['OK' => false, 'ERROR' => 'password_required']); } // Resolve the agent — try by full address, then by name $agent = queryOne( "SELECT a.*, u.PasswordHash, u.Token, u.TokenExpiresAt FROM Sprinter_Agents a LEFT JOIN Hub_Users u ON u.AgentID = a.ID WHERE a.FullAddress = ? AND a.IsActive = 1", [$loginId] ); if (!$agent) { // Try by agent name (e.g. "john" → "sprinter.payfrit.john") $agent = queryOne( "SELECT a.*, u.PasswordHash, u.Token, u.TokenExpiresAt FROM Sprinter_Agents a LEFT JOIN Hub_Users u ON u.AgentID = a.ID WHERE a.AgentName = ? AND a.IsActive = 1", [$loginId] ); } if (!$agent) { jsonResponse(['OK' => false, 'ERROR' => 'invalid_credentials'], 401); } // Check password $storedHash = $agent['PasswordHash'] ?? null; if ($storedHash === null) { // No Hub_Users row yet — auto-provision on first login // For MVP: accept the password, hash it, create the row and token $hash = password_hash($password, PASSWORD_BCRYPT); $token = bin2hex(random_bytes(32)); $expires = date('Y-m-d H:i:s', strtotime('+30 days')); queryTimed( "INSERT INTO Hub_Users (AgentID, PasswordHash, Token, TokenExpiresAt) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE PasswordHash = VALUES(PasswordHash), Token = VALUES(Token), TokenExpiresAt = VALUES(TokenExpiresAt)", [(int)$agent['ID'], $hash, $token, $expires] ); } else { // Verify password if (!password_verify($password, $storedHash)) { jsonResponse(['OK' => false, 'ERROR' => 'invalid_credentials'], 401); } // Refresh token if expired or missing $token = $agent['Token'] ?? ''; $expiresAt = $agent['TokenExpiresAt'] ?? ''; if ($token === '' || (strtotime($expiresAt) < time())) { $token = bin2hex(random_bytes(32)); $expires = date('Y-m-d H:i:s', strtotime('+30 days')); queryTimed( "UPDATE Hub_Users SET Token = ?, TokenExpiresAt = ? WHERE AgentID = ?", [$token, $expires, (int)$agent['ID']] ); } } // Return user info mapped for the web client $user = [ 'ID' => (int) $agent['ID'], 'AgentName' => $agent['AgentName'], 'FullAddress' => $agent['FullAddress'], 'ProjectName' => $agent['ProjectName'], 'AgentType' => $agent['AgentType'], 'Role' => $agent['Role'], 'ServerHost' => $agent['ServerHost'], 'IsActive' => (bool) $agent['IsActive'], 'CreatedAt' => toISO8601($agent['CreatedAt']), ]; // Set Token header so hub-proxy.php forwards it to the client header('Token: ' . $token); jsonResponse([ 'OK' => true, 'token' => $token, 'user' => $user, ]);