Add Hub auth/login.php endpoint — fixes Web client login
The web client calls auth/login to authenticate users, but this endpoint was missing from the Hub API. Creates: - api/hub/auth/login.php: password-based auth with token generation - Hub_Users table: stores bcrypt password hashes and session tokens - Auto-provisions on first login (creates credentials for existing agents) - Adds route to PUBLIC_ROUTES in helpers.php Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1dacefcf70
commit
c8ac6ae3fa
2 changed files with 115 additions and 0 deletions
|
|
@ -521,6 +521,8 @@ const PUBLIC_ROUTES = [
|
||||||
'/api/tasks/team/update.php',
|
'/api/tasks/team/update.php',
|
||||||
'/api/tasks/team/active.php',
|
'/api/tasks/team/active.php',
|
||||||
'/api/tasks/team/list.php',
|
'/api/tasks/team/list.php',
|
||||||
|
// hub auth
|
||||||
|
'/api/hub/auth/login.php',
|
||||||
// hub channels (agent-to-agent, no user auth)
|
// hub channels (agent-to-agent, no user auth)
|
||||||
'/api/hub/channels/create.php',
|
'/api/hub/channels/create.php',
|
||||||
'/api/hub/channels/list.php',
|
'/api/hub/channels/list.php',
|
||||||
|
|
|
||||||
113
api/hub/auth/login.php
Normal file
113
api/hub/auth/login.php
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* POST /api/hub/auth/login.php
|
||||||
|
*
|
||||||
|
* Authenticate a user (human or bot) against the Hub.
|
||||||
|
*
|
||||||
|
* Body:
|
||||||
|
* login_id string REQUIRED agent name, full address, or email
|
||||||
|
* password string REQUIRED password (checked against Hub_Users or Sprinter_Agents)
|
||||||
|
*
|
||||||
|
* Response:
|
||||||
|
* { OK: true, token: "...", user: { ... } }
|
||||||
|
* Also sets the Token response header for the proxy layer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../helpers.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
jsonResponse(['OK' => 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,
|
||||||
|
]);
|
||||||
Loading…
Add table
Reference in a new issue