cockpit_auth_login_async (CockpitAuth *self,
                          const gchar *path,
                          GIOStream *connection,
                          GHashTable *headers,
                          GAsyncReadyCallback callback,
                          gpointer user_data)
{
  GSimpleAsyncResult *result = NULL;
  CockpitSession *session;
  GError *error = NULL;
  gchar *type = NULL;
  gchar *conversation = NULL;
  gchar *authorization = NULL;
  gchar *application = NULL;

  g_return_if_fail (path != NULL);
  g_return_if_fail (headers != NULL);

  self->startups++;

  result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
                                      cockpit_auth_login_async);

  if (!can_start_auth (self))
    {
      g_message ("Request dropped; too many startup connections: %u", self->startups);
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_FAILED,
                                       "Connection closed by host");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  application = cockpit_auth_parse_application (path, NULL);
  authorization = cockpit_auth_steal_authorization (headers, connection, &type, &conversation);

  if (!application || !authorization)
    {
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                       "Authentication required");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  if (conversation)
    {
      session = g_hash_table_lookup (self->conversations, conversation);
      if (!session)
        {
          g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                           "Invalid conversation token");
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }

      g_simple_async_result_set_op_res_gpointer (result, cockpit_session_ref (session), cockpit_session_unref);
    }
  else
    {
      session = cockpit_session_launch (self, connection, headers, type, authorization, application, &error);
      if (!session)
        {
          g_simple_async_result_take_error (result, error);
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }
      g_simple_async_result_set_op_res_gpointer (result, session, cockpit_session_unref);
    }

  cockpit_session_reset (session);
  session->result = g_object_ref (result);

  session->authorization = authorization;
  authorization = NULL;

  if (conversation && !reply_authorize_challenge (session))
    {
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                       "Invalid conversation reply");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  reset_authorize_timeout (session, FALSE);

out:
  g_free (type);
  g_free (application);
  g_free (conversation);

  if (authorization)
    {
      cockpit_memory_clear (authorization, -1);
      g_free (authorization);
    }

  g_object_unref (result);
}
cockpit_auth_login_async (CockpitAuth *self,
                          const gchar *path,
                          GIOStream *connection,
                          GHashTable *headers,
                          GAsyncReadyCallback callback,
                          gpointer user_data)
{
  GSimpleAsyncResult *result = NULL;
  CockpitSession *session;
  GError *error = NULL;
  gchar *type = NULL;
  gchar *conversation = NULL;
  gchar *authorization = NULL;
  gchar *application = NULL;

  g_return_if_fail (path != NULL);
  g_return_if_fail (headers != NULL);

  self->startups++;

  result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
                                      cockpit_auth_login_async);

  if (!can_start_auth (self))
    {
      g_message ("Request dropped; too many startup connections: %u", self->startups);
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_FAILED,
                                       "Connection closed by host");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  application = cockpit_auth_parse_application (path, NULL);
  authorization = cockpit_auth_steal_authorization (headers, connection, &type, &conversation);

  if (!application || !authorization)
    {
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                       "Authentication required");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  if (conversation)
    {
      session = g_hash_table_lookup (self->conversations, conversation);
      if (!session)
        {
          g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                           "Invalid conversation token");
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }

      g_simple_async_result_set_op_res_gpointer (result, cockpit_session_ref (session), cockpit_session_unref);
    }
  else
    {
      session = cockpit_session_launch (self, connection, headers, type, authorization, application, &error);
      if (!session)
        {
          g_simple_async_result_take_error (result, error);
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }
      g_simple_async_result_set_op_res_gpointer (result, session, cockpit_session_unref);
    }

  cockpit_session_reset (session);
  session->result = g_object_ref (result);

  session->authorization = authorization;
  authorization = NULL;

  if (conversation && !reply_authorize_challenge (session))
    {
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                       "Invalid conversation reply");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  reset_authorize_timeout (session, FALSE);

out:
  g_free (type);
  g_free (application);
  g_free (conversation);

  if (authorization)
    {
      cockpit_memory_clear (authorization, -1);
      g_free (authorization);
    }

  g_object_unref (result);
}