view connectors/service.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents
children
line wrap: on
line source

#include "connectors/connector.h"
#include "connectors/auth_test_page.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>

static Connector_Store *g_store;
static const Connector_Google_Config *g_google;
static Connector_Auth_Adapter g_auth;

static const char *map_value(Seobeo_Request_Entry *map, const char *key)
{
  if (!map || !key)
    return NULL;
  for (size_t i = 0; i < Dowa_Array_Length(map); ++i) {
    if (map[i].key && !strcasecmp(map[i].key, key))
      return map[i].value;
  }
  return NULL;
}

static Seobeo_Request_Entry *json_response(
    Dowa_Arena *arena, const char *status, const char *body)
{
  Seobeo_Request_Entry *response = NULL;
  Dowa_HashMap_Push_Arena(response, "status", (char *)status, arena);
  Dowa_HashMap_Push_Arena(
      response, "content-type", "application/json", arena);
  Dowa_HashMap_Push_Arena(response, "body", (char *)body, arena);
  return response;
}

static Seobeo_Request_Entry *Auth_Test_Page(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  (void)request;
  Seobeo_Request_Entry *response = NULL;
  Dowa_HashMap_Push_Arena(response, "status", "200", arena);
  Dowa_HashMap_Push_Arena(
      response, "content-type", "text/html; charset=utf-8", arena);
  Dowa_HashMap_Push_Arena(
      response, "body", (char *)Connector_Auth_Test_Page(), arena);
  return response;
}

static const char *current_user(
    Seobeo_Request_Entry *request, boolean require_csrf, Dowa_Arena *arena)
{
  return g_auth.resolve_user
      ? g_auth.resolve_user(
          request, require_csrf, arena, g_auth.context) : NULL;
}

static boolean safe_component(const char *value)
{
  if (!value || !value[0] || strlen(value) >= CONNECTOR_ID_MAX)
    return FALSE;
  for (size_t i = 0; value[i]; ++i)
    if (!(isalnum((uint8)value[i]) || value[i] == '-' ||
          value[i] == '_' || value[i] == ':' || value[i] == '.'))
      return FALSE;
  return TRUE;
}

static Seobeo_Request_Entry *Health(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  (void)request;
  return json_response(arena, "200", "{\"ok\":true}");
}

static Seobeo_Request_Entry *Auth_Session(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  Connector_Auth_Session session;
  if (!g_auth.resolve_session ||
      !g_auth.resolve_session(request, &session, g_auth.context))
    return json_response(
        arena, "401", "{\"authenticated\":false,\"error\":\"unauthorized\"}");
  char *user_id = Dowa_JSON_Escape_String(session.user_id, 0, arena);
  char *username = Dowa_JSON_Escape_String(session.username, 0, arena);
  char *role = Dowa_JSON_Escape_String(session.role, 0, arena);
  char *csrf = Dowa_JSON_Escape_String(session.csrf_token, 0, arena);
  if (!user_id || !username || !role || !csrf)
    return json_response(arena, "500", "{\"error\":\"session_failed\"}");
  size_t length = strlen(user_id) + strlen(username) + strlen(role) +
      strlen(csrf) + 128;
  char *body = Dowa_Arena_Allocate(arena, length);
  snprintf(
      body, length,
      "{\"authenticated\":true,\"userId\":\"%s\",\"username\":\"%s\","
      "\"role\":\"%s\",\"csrfToken\":\"%s\"}",
      user_id, username, role, csrf);
  return json_response(arena, "200", body);
}

static Seobeo_Request_Entry *List_Accounts(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, FALSE, arena);
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  Connector_Account_Summary *accounts = NULL;
  if (!Connector_Store_List_Accounts(g_store, user, &accounts, arena))
    return json_response(arena, "500", "{\"error\":\"account_list_failed\"}");
  size_t capacity = 64 + Dowa_Array_Length(accounts) * 1600;
  char *body = Dowa_Arena_Allocate(arena, capacity);
  size_t offset = (size_t)snprintf(body, capacity, "{\"accounts\":[");
  for (size_t i = 0; i < Dowa_Array_Length(accounts); i++) {
    char *account_id =
        Dowa_JSON_Escape_String(accounts[i].account_id, 0, arena);
    char *provider = Dowa_JSON_Escape_String(accounts[i].provider, 0, arena);
    char *email = Dowa_JSON_Escape_String(accounts[i].email, 0, arena);
    char *scopes = Dowa_JSON_Escape_String(accounts[i].scopes, 0, arena);
    if (!account_id || !provider || !email || !scopes)
      return json_response(arena, "500", "{\"error\":\"account_list_failed\"}");
    offset += (size_t)snprintf(
        body + offset, capacity - offset,
        "%s{\"accountId\":\"%s\",\"provider\":\"%s\",\"email\":\"%s\","
        "\"expiresAt\":%lld,\"scopes\":\"%s\"}",
        i ? "," : "", account_id, provider, email,
        (long long)accounts[i].expires_at, scopes);
  }
  snprintf(body + offset, capacity - offset, "]}");
  return json_response(arena, "200", body);
}

static Seobeo_Request_Entry *AI_Tools(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  (void)request;
  return json_response(
      arena, "200",
      "{\"version\":1,"
      "\"documentation\":\"connectors/wiki/README.md\","
      "\"contextStrategy\":["
      "\"discover a connection with connector.accounts.list\","
      "\"search or list lightweight references\","
      "\"hydrate only the most relevant IDs with get operations\","
      "\"normalize bounded text with source IDs before model inference\"],"
      "\"tools\":["
      "{\"name\":\"connector.accounts.list\",\"method\":\"GET\","
      "\"path\":\"/v1/accounts\"},"
      "{\"name\":\"connector.gmail.search\",\"method\":\"GET\","
      "\"path\":\"/v1/accounts/{account_id}/gmail/messages\","
      "\"query\":[\"q\",\"maxResults\",\"pageToken\"]},"
      "{\"name\":\"connector.gmail.get\",\"method\":\"GET\","
      "\"path\":\"/v1/accounts/{account_id}/gmail/messages/{message_id}\","
      "\"query\":[\"format\",\"metadataHeaders\"]},"
      "{\"name\":\"connector.drive.search\",\"method\":\"GET\","
      "\"path\":\"/v1/accounts/{account_id}/drive/files\","
      "\"query\":[\"q\",\"pageSize\",\"pageToken\",\"fields\"]},"
      "{\"name\":\"connector.drive.get\",\"method\":\"GET\","
      "\"path\":\"/v1/accounts/{account_id}/drive/files/{file_id}\"},"
      "{\"name\":\"connector.gmail.draft\",\"method\":\"POST\","
      "\"path\":\"/v1/accounts/{account_id}/gmail/drafts\"},"
      "{\"name\":\"connector.gmail.send\",\"method\":\"POST\","
      "\"path\":\"/v1/accounts/{account_id}/gmail/send\","
      "\"confirmation\":\"policy-controlled\"}]}");
}

static Seobeo_Request_Entry *OAuth_Start(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, TRUE, arena);
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  Connector_OAuth_Start start;
  if (!Connector_OAuth_PKCE_Start(&start) ||
      !Connector_Store_Create_State(
          g_store, user, &start, (int64)time(NULL) + 600))
    return json_response(arena, "500", "{\"error\":\"oauth_start_failed\"}");
  char *url = Connector_Google_Authorization_URL(g_google, &start, arena);
  if (!url)
    return json_response(arena, "500", "{\"error\":\"oauth_start_failed\"}");
  size_t length = strlen(url) + 32;
  char *body = Dowa_Arena_Allocate(arena, length);
  snprintf(body, length, "{\"authorization_url\":\"%s\"}", url);
  return json_response(arena, "200", body);
}

static Seobeo_Request_Entry *OAuth_Callback(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, FALSE, arena);
  const char *state = map_value(request, "query_state");
  const char *code = map_value(request, "query_code");
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  if (!state || !code)
    return json_response(arena, "400", "{\"error\":\"missing_oauth_fields\"}");
  char verifier[128];
  if (!Connector_Store_Consume_State(
          g_store, user, state, (int64)time(NULL), verifier, sizeof(verifier)))
    return json_response(arena, "400", "{\"error\":\"invalid_oauth_state\"}");
  Connector_Account account;
  Connector_Provider_Error provider_error;
  Connector_Status status = Connector_Google_Exchange_Code(
      g_google, code, verifier, &account, &provider_error, arena);
  if (status != CONNECTOR_OK) {
    char *code_safe = Dowa_JSON_Escape_String(
        provider_error.code[0] ? provider_error.code : "unknown", 0, arena);
    char *description_safe = Dowa_JSON_Escape_String(
        provider_error.description, 0, arena);
    char *body = Dowa_Arena_Allocate(arena, 512);
    if (!code_safe || !description_safe || !body)
      return json_response(
          arena, "502", "{\"error\":\"token_exchange_failed\"}");
    snprintf(
        body, 512,
        "{\"error\":\"token_exchange_failed\",\"provider_error\":\"%s\","
        "\"provider_description\":\"%s\",\"provider_status\":%d}",
        code_safe, description_safe, provider_error.http_status);
    return json_response(arena, "502", body);
  }
  if (strlen(user) >= sizeof(account.user_id))
    return json_response(arena, "400", "{\"error\":\"invalid_user\"}");
  strcpy(account.user_id, user);
  if (!Connector_Store_Save_Account(g_store, &account))
    return json_response(arena, "500", "{\"error\":\"account_save_failed\"}");
  char encoded_account[CONNECTOR_ID_MAX * 3];
  char encoded_email[sizeof(account.email) * 3];
  if (!Connector_Form_Encode(
          account.account_id, encoded_account, sizeof(encoded_account)) ||
      !Connector_Form_Encode(
          account.email, encoded_email, sizeof(encoded_email)))
    return json_response(arena, "500", "{\"error\":\"redirect_failed\"}");
  char *location = Dowa_Arena_Allocate(arena, 1024);
  snprintf(
      location, 1024, "/auth-test.html?account_id=%s&email=%s",
      encoded_account, encoded_email);
  Seobeo_Request_Entry *response = NULL;
  Dowa_HashMap_Push_Arena(response, "status", "303", arena);
  Dowa_HashMap_Push_Arena(response, "Location", location, arena);
  Dowa_HashMap_Push_Arena(response, "content-type", "text/plain", arena);
  Dowa_HashMap_Push_Arena(response, "body", "Google account connected", arena);
  return response;
}

static Seobeo_Request_Entry *Disconnect(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, TRUE, arena);
  const char *account_id = map_value(request, ":account_id");
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  Connector_Account account;
  if (!safe_component(account_id) ||
      !Connector_Store_Get_Account(g_store, user, account_id, &account))
    return json_response(arena, "404", "{\"error\":\"account_not_found\"}");
  const char *token = account.refresh_token[0]
      ? account.refresh_token : account.access_token;
  if (Connector_Google_Revoke(g_google, token, arena) != CONNECTOR_OK)
    return json_response(arena, "502", "{\"error\":\"revoke_failed\"}");
  if (!Connector_Store_Delete_Account(g_store, user, account_id))
    return json_response(arena, "500", "{\"error\":\"disconnect_failed\"}");
  return json_response(arena, "200", "{\"disconnected\":true}");
}

static const char *confirmation_action(
    Connector_Operation operation, boolean overwrite)
{
  if (operation == CONNECTOR_OP_GMAIL_SEND)
    return "gmail.send";
  if (operation == CONNECTOR_OP_DRIVE_UPDATE && overwrite)
    return "drive.overwrite";
  return Connector_Operation_Name(operation);
}

static Seobeo_Request_Entry *execute_operation(
    Seobeo_Request_Entry *request, Dowa_Arena *arena,
    Connector_Operation operation, const char *method,
    const char *path_prefix, boolean append_resource, boolean overwrite)
{
  const char *user = current_user(
      request, Connector_Operation_Is_Mutation(operation), arena);
  const char *account_id = map_value(request, ":account_id");
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  if (!safe_component(account_id))
    return json_response(arena, "400", "{\"error\":\"invalid_account\"}");
  Connector_Account account;
  if (!Connector_Store_Get_Account(g_store, user, account_id, &account))
    return json_response(arena, "404", "{\"error\":\"account_not_found\"}");
  if (account.expires_at <= (int64)time(NULL) + 60) {
    if (Connector_Google_Refresh(g_google, &account, arena) != CONNECTOR_OK ||
        !Connector_Store_Save_Account(g_store, &account))
      return json_response(arena, "502", "{\"error\":\"token_refresh_failed\"}");
  }

  const char *resource = map_value(request, ":resource_id");
  char path[1024];
  if (append_resource) {
    if (!safe_component(resource))
      return json_response(arena, "400", "{\"error\":\"invalid_resource\"}");
    snprintf(path, sizeof(path), "%s/%s", path_prefix, resource);
  } else
    snprintf(path, sizeof(path), "%s", path_prefix);
  const char *body = map_value(request, "Body");
  const char *content_length = map_value(request, "Content-Length");
  size_t body_length = body ? strlen(body) : 0;
  if (content_length) {
    char *end = NULL;
    unsigned long parsed = strtoul(content_length, &end, 10);
    if (!end || *end || parsed > CONNECTOR_MAX_JSON_BYTES ||
        (parsed && !body))
      return json_response(arena, "400", "{\"error\":\"invalid_body\"}");
    body_length = parsed;
  }
  Connector_Provider_Request provider_request = {
    .method = method,
    .path = path,
    .query = map_value(request, "QueryString"),
    .content_type = map_value(request, "Content-Type"),
    .body = body,
    .body_length = body_length,
    .download_path = NULL,
    .overwrite = overwrite
  };
  char digest[65];
  if (!Connector_Request_Digest(
          user, account_id, operation, &provider_request, digest))
    return json_response(arena, "400", "{\"error\":\"invalid_request\"}");

  const char *idempotency = map_value(request, "Idempotency-Key");
  if (Connector_Operation_Is_Mutation(operation)) {
    if (!idempotency || !safe_component(idempotency))
      return json_response(
          arena, "400", "{\"error\":\"idempotency_key_required\"}");
    int32 cached_status = 0;
    char cached_body[8192];
    if (Connector_Store_Get_Idempotent(
            g_store, user, idempotency, digest, &cached_status,
            cached_body, sizeof(cached_body))) {
      char status_text[16];
      snprintf(status_text, sizeof(status_text), "%d", cached_status);
      char *copy = Dowa_String_Copy_Arena(cached_body, arena);
      return json_response(arena, status_text, copy);
    }
    if (Connector_Store_Idempotency_Conflict(
            g_store, user, idempotency, digest))
      return json_response(
          arena, "409", "{\"error\":\"idempotency_key_conflict\"}");
  }

  if (operation == CONNECTOR_OP_DRIVE_UPDATE && body &&
      (strstr(body, "\"trashed\"") || strstr(body, "\"permissions\"")))
    return json_response(
        arena, "403", "{\"error\":\"drive_mutation_not_allowed\"}");

  const char *action = confirmation_action(operation, overwrite);
  if (Connector_Store_Get_Policy(g_store, user, action) ==
      CONNECTOR_CONFIRM_ALWAYS) {
    const char *token = map_value(request, "X-Connector-Confirmation");
    if (!token || !Connector_Store_Consume_Confirmation(
                      g_store, user, digest, token, (int64)time(NULL))) {
      char *response = Dowa_Arena_Allocate(arena, 160);
      snprintf(
          response, 160,
          "{\"error\":\"confirmation_required\",\"request_digest\":\"%s\"}",
          digest);
      return json_response(arena, "409", response);
    }
  }

  Connector_Provider_Response provider_response = {0};
  Connector_Status status = Connector_Google_Execute(
      g_google, operation, &provider_request, account.access_token,
      &provider_response, arena);
  int32 http_status = status == CONNECTOR_OK
      ? provider_response.provider_status : 502;
  const char *response_body = provider_response.body
      ? provider_response.body : "{\"error\":\"provider_error\"}";
  if (Connector_Operation_Is_Mutation(operation)) {
    Connector_Store_Put_Idempotent(
        g_store, user, idempotency, digest, http_status, response_body);
    Connector_Store_Audit(
        g_store, user, account_id, Connector_Operation_Name(operation),
        digest, http_status);
  }
  char status_text[16];
  snprintf(status_text, sizeof(status_text), "%d", http_status);
  if (operation == CONNECTOR_OP_DRIVE_DOWNLOAD &&
      provider_response.body && status == CONNECTOR_OK) {
    Seobeo_Request_Entry *download = NULL;
    char *content_length = Dowa_Arena_Allocate(arena, 32);
    snprintf(
        content_length, 32, "%zu", provider_response.body_length);
    Dowa_HashMap_Push_Arena(download, "status", status_text, arena);
    Dowa_HashMap_Push_Arena(
        download, "content-type", "application/octet-stream", arena);
    Dowa_HashMap_Push_Arena(download, "body", provider_response.body, arena);
    Dowa_HashMap_Push_Arena(
        download, "content-length", content_length, arena);
    return download;
  }
  return json_response(arena, status_text, response_body);
}

#define OP_HANDLER(name, operation, method, path, resource, overwrite) \
static Seobeo_Request_Entry *name( \
    Seobeo_Request_Entry *request, Dowa_Arena *arena) { \
  return execute_operation( \
      request, arena, operation, method, path, resource, overwrite); \
}

OP_HANDLER(Drive_List, CONNECTOR_OP_DRIVE_LIST, "GET", "/drive/v3/files", FALSE, FALSE)
OP_HANDLER(Drive_Get, CONNECTOR_OP_DRIVE_GET, "GET", "/drive/v3/files", TRUE, FALSE)
OP_HANDLER(Drive_Changes, CONNECTOR_OP_DRIVE_CHANGES, "GET", "/drive/v3/changes", FALSE, FALSE)
OP_HANDLER(Drive_Create, CONNECTOR_OP_DRIVE_CREATE, "POST", "/drive/v3/files", FALSE, FALSE)
OP_HANDLER(Drive_Upload, CONNECTOR_OP_DRIVE_UPLOAD, "POST", "/upload/drive/v3/files", FALSE, FALSE)
OP_HANDLER(Drive_Update, CONNECTOR_OP_DRIVE_UPDATE, "PATCH", "/upload/drive/v3/files", TRUE, TRUE)
OP_HANDLER(Gmail_List, CONNECTOR_OP_GMAIL_LIST, "GET", "/gmail/v1/users/me/messages", FALSE, FALSE)
OP_HANDLER(Gmail_Get, CONNECTOR_OP_GMAIL_GET, "GET", "/gmail/v1/users/me/messages", TRUE, FALSE)
OP_HANDLER(Gmail_History, CONNECTOR_OP_GMAIL_HISTORY, "GET", "/gmail/v1/users/me/history", FALSE, FALSE)
OP_HANDLER(Gmail_Draft, CONNECTOR_OP_GMAIL_DRAFT_CREATE, "POST", "/gmail/v1/users/me/drafts", FALSE, FALSE)
OP_HANDLER(Gmail_Send, CONNECTOR_OP_GMAIL_SEND, "POST", "/gmail/v1/users/me/messages/send", FALSE, FALSE)

static Seobeo_Request_Entry *Drive_Download(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *query = Dowa_HashMap_Get_Ptr(request, "QueryString");
  if (query && query->value && query->value[0]) {
    size_t length = strlen(query->value) + 11;
    char *with_media = Dowa_Arena_Allocate(arena, length);
    snprintf(with_media, length, "%s&alt=media", query->value);
    query->value = with_media;
  } else
    Dowa_HashMap_Push_Arena(request, "QueryString", "alt=media", arena);
  return execute_operation(
      request, arena, CONNECTOR_OP_DRIVE_DOWNLOAD, "GET",
      "/drive/v3/files", TRUE, FALSE);
}

static Seobeo_Request_Entry *Gmail_Attachment(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *message = map_value(request, ":message_id");
  const char *attachment = map_value(request, ":resource_id");
  if (!safe_component(message) || !safe_component(attachment))
    return json_response(arena, "400", "{\"error\":\"invalid_resource\"}");
  char path[512];
  snprintf(
      path, sizeof(path), "/gmail/v1/users/me/messages/%s/attachments",
      message);
  return execute_operation(
      request, arena, CONNECTOR_OP_GMAIL_ATTACHMENT, "GET", path, TRUE, FALSE);
}

static Seobeo_Request_Entry *Create_Confirmation(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, TRUE, arena);
  const char *body = map_value(request, "Body");
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  if (!body || strlen(body) > 4096)
    return json_response(arena, "400", "{\"error\":\"invalid_body\"}");
  Dowa_JSON_Value parsed = Dowa_JSON_Parse(body, (int32)strlen(body), arena);
  if (parsed.type != DOWA_JSON_OBJECT)
    return json_response(arena, "400", "{\"error\":\"invalid_json\"}");
  char *digest = Dowa_JSON_Get_String(parsed.object_val, "request_digest");
  if (!digest || strlen(digest) != 64)
    return json_response(arena, "400", "{\"error\":\"invalid_digest\"}");
  Connector_OAuth_Start random;
  if (!Connector_OAuth_PKCE_Start(&random) ||
      !Connector_Store_Create_Confirmation(
          g_store, user, digest, random.state, (int64)time(NULL) + 300))
    return json_response(arena, "500", "{\"error\":\"confirmation_failed\"}");
  char *response = Dowa_Arena_Allocate(arena, 180);
  snprintf(
      response, 180, "{\"confirmation_token\":\"%s\",\"expires_in\":300}",
      random.state);
  return json_response(arena, "201", response);
}

static boolean configurable_confirmation_action(const char *action)
{
  return action &&
      (!strcmp(action, "gmail.send") || !strcmp(action, "drive.overwrite"));
}

static Seobeo_Request_Entry *Get_Confirmation_Policy(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, FALSE, arena);
  const char *action = map_value(request, ":action");
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  if (!configurable_confirmation_action(action))
    return json_response(arena, "400", "{\"error\":\"invalid_action\"}");
  Connector_Confirmation_Policy policy =
      Connector_Store_Get_Policy(g_store, user, action);
  char *response = Dowa_Arena_Allocate(arena, 96);
  snprintf(
      response, 96, "{\"action\":\"%s\",\"policy\":\"%s\"}",
      action, policy == CONNECTOR_CONFIRM_ALWAYS ? "always" : "never");
  return json_response(arena, "200", response);
}

static Seobeo_Request_Entry *Set_Confirmation_Policy(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  const char *user = current_user(request, TRUE, arena);
  const char *action = map_value(request, ":action");
  const char *body = map_value(request, "Body");
  if (!user)
    return json_response(arena, "401", "{\"error\":\"unauthorized\"}");
  if (!configurable_confirmation_action(action))
    return json_response(arena, "400", "{\"error\":\"invalid_action\"}");
  if (!body || strlen(body) > 256)
    return json_response(arena, "400", "{\"error\":\"invalid_body\"}");
  Dowa_JSON_Value parsed = Dowa_JSON_Parse(body, (int32)strlen(body), arena);
  if (parsed.type != DOWA_JSON_OBJECT)
    return json_response(arena, "400", "{\"error\":\"invalid_json\"}");
  const char *policy_text =
      Dowa_JSON_Get_String(parsed.object_val, "policy");
  Connector_Confirmation_Policy policy;
  if (policy_text && !strcmp(policy_text, "always"))
    policy = CONNECTOR_CONFIRM_ALWAYS;
  else if (policy_text && !strcmp(policy_text, "never"))
    policy = CONNECTOR_CONFIRM_NEVER;
  else
    return json_response(arena, "400", "{\"error\":\"invalid_policy\"}");
  if (!Connector_Store_Set_Policy(g_store, user, action, policy))
    return json_response(arena, "500", "{\"error\":\"policy_save_failed\"}");
  return json_response(arena, "200", policy == CONNECTOR_CONFIRM_ALWAYS
      ? "{\"policy\":\"always\"}" : "{\"policy\":\"never\"}");
}

static Seobeo_Request_Entry *Rejected_Mutation(
    Seobeo_Request_Entry *request, Dowa_Arena *arena)
{
  (void)request;
  return json_response(
      arena, "403",
      "{\"error\":\"operation_not_allowed\",\"allowed_mutations\":["
      "\"drive.create\",\"drive.upload\",\"drive.update\","
      "\"gmail.draft.create\",\"gmail.send\"]}");
}

void Connector_Service_Configure(
    Connector_Store *store, const Connector_Google_Config *google,
    Connector_Auth_Adapter auth)
{
  g_store = store;
  g_google = google;
  g_auth = auth;
}

void Connector_Service_Register_Routes(void)
{
  Seobeo_Router_Register("GET", "/health", Health);
  Seobeo_Router_Register("GET", "/auth-test.html", Auth_Test_Page);
  Seobeo_Router_Register("GET", "/v1/ai/tools", AI_Tools);
  Seobeo_Router_Register("GET", "/v1/auth/session", Auth_Session);
  Seobeo_Router_Register("GET", "/v1/accounts", List_Accounts);
  Seobeo_Router_Register("POST", "/v1/oauth/google/start", OAuth_Start);
  Seobeo_Router_Register("GET", "/v1/oauth/google/callback", OAuth_Callback);
  Seobeo_Router_Register("DELETE", "/v1/accounts/:account_id", Disconnect);
  Seobeo_Router_Register("POST", "/v1/confirmations", Create_Confirmation);
  Seobeo_Router_Register(
      "GET", "/v1/settings/confirmations/:action",
      Get_Confirmation_Policy);
  Seobeo_Router_Register(
      "PUT", "/v1/settings/confirmations/:action",
      Set_Confirmation_Policy);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/drive/files", Drive_List);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/drive/files/:resource_id", Drive_Get);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/drive/files/:resource_id/download", Drive_Download);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/drive/changes", Drive_Changes);
  Seobeo_Router_Register("POST", "/v1/accounts/:account_id/drive/files", Drive_Create);
  Seobeo_Router_Register("POST", "/v1/accounts/:account_id/drive/uploads", Drive_Upload);
  Seobeo_Router_Register("PATCH", "/v1/accounts/:account_id/drive/files/:resource_id", Drive_Update);
  Seobeo_Router_Register("DELETE", "/v1/accounts/:account_id/drive/files/:resource_id", Rejected_Mutation);
  Seobeo_Router_Register("POST", "/v1/accounts/:account_id/drive/files/:resource_id/permissions", Rejected_Mutation);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/gmail/messages", Gmail_List);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/gmail/messages/:resource_id", Gmail_Get);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/gmail/messages/:message_id/attachments/:resource_id", Gmail_Attachment);
  Seobeo_Router_Register("GET", "/v1/accounts/:account_id/gmail/history", Gmail_History);
  Seobeo_Router_Register("POST", "/v1/accounts/:account_id/gmail/drafts", Gmail_Draft);
  Seobeo_Router_Register("POST", "/v1/accounts/:account_id/gmail/send", Gmail_Send);
  Seobeo_Router_Register("DELETE", "/v1/accounts/:account_id/gmail/messages/:resource_id", Rejected_Mutation);
  Seobeo_Router_Register("PATCH", "/v1/accounts/:account_id/gmail/messages/:resource_id/labels", Rejected_Mutation);
  Seobeo_Router_Register("PATCH", "/v1/accounts/:account_id/gmail/messages/:resource_id/read-state", Rejected_Mutation);
}