'not logged in'], 401); if (empty($sessionTenant['secret_encrypted'])) { jsonResponse(['has_secret' => false]); } $plain = masterDecrypt($sessionTenant['secret_encrypted'], $MASTER_KEY); jsonResponse(['has_secret' => true, 'secret' => $plain]); } if ($route === '/api/account/secret' && $method === 'POST') { if (!$sessionTenant) jsonResponse(['error' => 'not logged in'], 401); $input = json_decode(file_get_contents('php://input'), true) ?: []; // Either accept a user-supplied secret OR generate a random one if (!empty($input['generate'])) { $secret = bin2hex(random_bytes(24)); // 48-char hex } else { $secret = (string)($input['secret'] ?? ''); if (strlen($secret) < 8) { jsonResponse(['error' => 'secret must be at least 8 chars'], 400); } } $db->prepare('UPDATE tenants SET secret_hash = :sh, secret_encrypted = :se WHERE id = :id') ->execute([ 'sh' => password_hash($secret, PASSWORD_BCRYPT), 'se' => masterEncrypt($secret, $MASTER_KEY), 'id' => $sessionTenant['id'], ]); jsonResponse(['ok' => true, 'secret' => $secret]); } if ($route === '/api/account/secret' && $method === 'DELETE') { if (!$sessionTenant) jsonResponse(['error' => 'not logged in'], 401); $db->prepare('UPDATE tenants SET secret_hash = NULL, secret_encrypted = NULL WHERE id = :id') ->execute(['id' => $sessionTenant['id']]); jsonResponse(['ok' => true]); } if ($route === '/api/account/password' && $method === 'POST') { if (!$sessionTenant) jsonResponse(['error' => 'not logged in'], 401); // Rate-limit password changes — a stolen session cookie could otherwise // brute-force the `current` field server-side with no friction. if (isRateLimited($db, 'account-password', 10)) jsonResponse(['error' => 'too many attempts'], 429); $input = json_decode(file_get_contents('php://input'), true) ?: []; $current = (string)($input['current'] ?? ''); $new = (string)($input['new'] ?? ''); if (strlen($new) < 10) jsonResponse(['error' => 'new password must be at least 10 chars'], 400); if (!password_verify($current, $sessionTenant['password_hash'])) { jsonResponse(['error' => 'current password is wrong'], 401); } if ($current === $new) { jsonResponse(['error' => 'new password must differ from current'], 400); } $db->prepare('UPDATE tenants SET password_hash = :p, password_changed = 1 WHERE id = :id') ->execute([ 'p' => password_hash($new, PASSWORD_BCRYPT), 'id' => $sessionTenant['id'], ]); jsonResponse(['ok' => true]); }