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=codeaccess_type=offline+prompt=consent→ garantiza que Zoho emitarefresh_tokenstateanti-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:
getStoredToken()→ error si no existe (Zoho no autorizado).- Comprueba expiración:
access_token_expires_at > Date.now()→ devuelve token cacheado. - 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)
| Variable | Descripción |
|---|---|
ZOHO_CLIENT_ID | Client ID de la app Zoho OAuth registrada |
ZOHO_CLIENT_SECRET | Client Secret de la app Zoho OAuth registrada |
DB | Binding 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]]