nextcloud/all-in-one · error · \Exception

Unexpected response from deSEC during login (HTTP {code}): {

Error message

Unexpected response from deSEC during login (HTTP {code}): {body}

What it means

POST /auth/login/ returned a status other than the accepted 200/201 (and other than the 400/403 invalid-credentials case), so the manager surfaces the unexpected status code plus the raw body. Transport errors cannot produce this — they are caught earlier as 'Could not reach the deSEC API'.

Source

Thrown at php/src/Desec/DesecManager.php:258

     */
    public function loginAccount(string $email, string $password): string {
        try {
            $res = $this->guzzleClient->post($this->configurationManager->desecApiBase . '/auth/login/', [
                'json' => ['email' => $email, 'password' => $password],
            ]);
        } catch (TransferException $e) {
            throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
        }

        $code = $res->getStatusCode();
        $body = $res->getBody()->getContents();

        if ($code === 400 || $code === 403) {
            throw new \Exception('Could not log in to deSEC: invalid email address or password.');
        }

        if ($code !== 200 && $code !== 201) {
            throw new \Exception('Unexpected response from deSEC during login (HTTP ' . $code . '): ' . $body);
        }

        $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($data) || !isset($data['token']) || !is_string($data['token'])) {
            throw new \Exception('Could not extract the API token from the deSEC login response. Please try again.');
        }

        return $data['token'];
    }

    /**
     * Registers a dedyn.io domain for the authenticated account.
     * When $slug is empty a random 10-character slug is tried up to MAX_SLUG_ATTEMPTS times.
     *
     * When a specific slug is requested and creation fails because the name is unavailable
     * (HTTP 400/409) or the account's domain limit is reached (HTTP 403), the domain may
     * already belong to this very account — a user reusing a slug they registered earlier.
     * In that case we reuse the existing domain instead of failing, so an existing-account

View on GitHub (pinned to 6b788eec5e)

Solutions

  1. Read the embedded body — it carries the daemon/API's own error text
  2. On 429, wait before retrying instead of looping immediately
  3. On 5xx, check deSEC service status and retry later
  4. If a proxy is in path, bypass it for desec.io or configure it correctly
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $token = $manager->loginAccount($email, $password);
} catch (\Exception $e) {
    if (preg_match('/Unexpected response from deSEC during login \(HTTP (\d+)\)/', $e->getMessage(), $m)) {
        [$code, $body] = [$m[1], substr($e->getMessage(), strpos($e->getMessage(), '): ') + 3)];
        if ($code === '429') { scheduleRetry(60); return; }
        logError("deSEC login anomaly HTTP $code: $body");
    }
    throw $e;
}

Prevention

When it happens

Trigger: deSEC returns 429 (login rate limit), 401, 5xx, or an intermediate proxy answers with its own status; any code outside {200, 201, 400, 403}.

Common situations: Repeated login retries hitting deSEC rate limiting; desec.io incident returning 502/503; an intercepting proxy or captive portal responding instead of the API.

Related errors


AI-assisted analysis of nextcloud/all-in-one@6b788eec5e (2026-08-21). Data as JSON: /api/errors/3d9bcf592798fd6a. Report an issue: GitHub.