CreaRack-SL

zoho.ts · Librería OAuth y API helpers para Zoho (Service Account)

zoho.ts · Librería OAuth y API helpers para Zoho (Service Account)

Ruta: functions/_lib/zoho.ts
Tamaño: 204 LOC
Introducida en: PR#44 (2026-05-16)

Librería central que encapsula toda la lógica de autenticación OAuth y comunicación con la API de Zoho para el modelo de Service Account del workspace. Consumida por los 5 endpoints Pages Functions de la integración.


Tipos exportados

ZohoEnv

export interface ZohoEnv {
  DB: D1Database;
  ZOHO_CLIENT_ID: string;
  ZOHO_CLIENT_SECRET: string;
}

Interface de las variables de entorno CF Pages que necesita la lib. Se usa como genérico de PagesFunction<ZohoEnv> en todos los endpoints Zoho.

ZohoTokenRow

export interface ZohoTokenRow {
  id: number;
  refresh_token: string;
  access_token: string | null;
  access_token_expires_at: string | null;
  dc: string;
  granted_by: string;
  granted_at: string;
  updated_at: string;
}

Mapea la fila singleton de zoho_oauth_tokens (id=1).

ExchangeResult

export interface ExchangeResult {
  refresh_token: string;
  access_token: string;
  expires_in: number;
  api_domain: string;
}

Resultado de intercambiar un authorization code por tokens.


Constantes

ZOHO_SCOPES

ZohoCalendar.event.READ,ZohoCalendar.calendar.READ,ZohoMail.tasks.READ,ZohoMail.accounts.READ

Scopes OAuth solicitados en el consent screen. Solo lectura en Fase A.


Funciones exportadas

Helpers de hostname

accountsHost(dc: string): string   // accounts.zoho.{dc}
mailApiHost(dc: string): string    // mail.zoho.{dc}
calendarApiHost(dc: string): string // calendar.zoho.{dc}

Construyen el hostname correcto según la región Zoho (eu, com, in…).


buildAuthorizeUrl(env, state, redirectUri, dc?)

Construye la URL del consent screen de Zoho con todos los parámetros necesarios:

  • response_type=code
  • access_type=offline + prompt=consent → garantiza que Zoho emita refresh_token
  • state anti-CSRF inyectado

exchangeCodeForTokens(env, code, redirectUri, dc)

Intercambia el authorization code por refresh_token + access_token vía POST a accounts.zoho.{dc}/oauth/v2/token. Lanza error si la respuesta no es OK o no incluye ambos tokens.


getStoredToken(env)

async getStoredToken(env: ZohoEnv): Promise<ZohoTokenRow | null>

Lee la fila singleton id=1 de zoho_oauth_tokens. Devuelve null si Zoho no ha sido autorizado aún.


getValidAccessToken(env)

async getValidAccessToken(env: ZohoEnv): Promise<{ token: string; dc: string }>

Función principal consumida por los endpoints de datos. Flujo:

  1. getStoredToken() → error si no existe (Zoho no autorizado).
  2. Comprueba expiración: access_token_expires_at > Date.now() → devuelve token cacheado.
  3. Si expirado → refreshAccessToken() (privada): llama a Zoho, persiste nuevo token en D1 con margen de 60 s, devuelve el fresco.

El margen de 60 s evita usar un token que expire durante la request en curso.


zohoFetch(env, url, init?)

async zohoFetch(env: ZohoEnv, url: string, init?: RequestInit): Promise<Response>

Wrapper sobre fetch que inyecta automáticamente el header Authorization: Zoho-oauthtoken {token} y Accept: application/json. Llama a getValidAccessToken() internamente. Todos los endpoints de datos lo usan en lugar de fetch directo.


upsertToken(env, refreshToken, accessToken, expiresIn, dc, grantedBy)

Persiste o sobreescribe el singleton en zoho_oauth_tokens usando INSERT ... ON CONFLICT(id) DO UPDATE. Calcula access_token_expires_at restando 60 s a expiresIn.


deleteToken(env)

DELETE FROM zoho_oauth_tokens WHERE id = 1. Usado por DELETE /api/zoho/status para revocar.


Funciones privadas (no exportadas)

refreshAccessToken(env, row) (privada)

POST a accounts.zoho.{dc}/oauth/v2/token con grant_type=refresh_token. Actualiza la fila D1 y devuelve el nuevo access_token. Solo la llama getValidAccessToken().


Variables de entorno requeridas (CF Pages Secrets)

VariableDescripción
ZOHO_CLIENT_IDClient ID de la app Zoho OAuth registrada
ZOHO_CLIENT_SECRETClient Secret de la app Zoho OAuth registrada
DBBinding D1 del workspace

Diagrama de dependencias

start.ts ──────────────────────────┐
callback.ts ── exchangeCodeForTokens ─┤
                upsertToken           ├── zoho.ts (lib)
calendar.ts ── zohoFetch ─────────────┤      │
tasks.ts ──── zohoFetch ─────────────┤      └── D1 (zoho_oauth_tokens)
status.ts ─── getStoredToken ─────────┘          (zoho_oauth_state)
              deleteToken

Véase también

  • [[feature—workspace—zoho-fase-a]]
  • [[decision—20260516—integracion-zoho-workspace]]
  • [[workspace—que-es-workspace]]