CreaRack-SL

Zoho OAuth modo admin — ?as=X para pre-autorizar cuentas en onboarding

Zoho OAuth modo admin — ?as=X para pre-autorizar cuentas en onboarding

Extensión del flujo OAuth Zoho per-user (introducido en s68) que permite a Edu autorizar las cuentas Zoho de Dani y Txell desde su propia sesión CF Access, sin que ellos tengan que pasar por el flujo individualmente. Diseñado para el onboarding nocturno de s68, cuando Edu tiene las credenciales Zoho de los tres miembros pero los demás aún no tienen sesión activa en el workspace.

Motivación

Sin este modo, pre-autorizar las cuentas de Dani y Txell requeriría:

  1. Abrir ventana de incógnito por cada miembro.
  2. Autenticarse en CF Access con sus credenciales (magic link al inbox de cada uno).
  3. Navegar a /api/oauth/zoho/start.
  4. Completar el consent screen de Zoho.

Con ?as=X, Edu completa todo el proceso desde su sesión activa en ≈3 URLs.

Endpoints afectados

GET /api/oauth/zoho/start[?as=Edu|Dani|Txell]

Archivo: functions/api/oauth/zoho/start.ts

ParámetroDescripción
(sin parámetro)Autoriza la cuenta Zoho del actor (comportamiento normal s68)
?as=EduEdu re-autoriza su propia cuenta explícitamente
?as=DaniEdu autoriza la cuenta Zoho de Dani
?as=TxellEdu autoriza la cuenta Zoho de Txell

Control de acceso:

  • ?as=X solo funciona si el actor es Edu (comprobado via resolveTeamMember). Cualquier otro miembro recibe 403.
  • ?as=<valor_inválido> (no es Edu/Dani/Txell) recibe 400.

GET /api/oauth/zoho/callback

Archivo: functions/api/oauth/zoho/callback.ts

Parsea el campo created_by del state OAuth, que ahora es JSON {email, target} en lugar del email plano (formato pre-s68).

Flujo completo modo admin

Edu (sesión CF Access activa)
  │
  ├─ GET /api/oauth/zoho/start?as=Dani
  │    ├─ resolveActorEmail(request) → "edu@crearack.com"
  │    ├─ resolveTeamMember(request) → "Edu" ✓
  │    ├─ isTeamMember("Dani") → true ✓
  │    ├─ targetMember = "Dani"
  │    ├─ stateValue = JSON {"email":"edu@crearack.com","target":"Dani"}
  │    ├─ INSERT zoho_oauth_state (state=<uuid>, created_by=stateValue)
  │    └─ Redirect → Zoho consent screen
  │
  ├─ [Edu se loguea con credenciales Zoho de Dani en el consent screen]
  │
  └─ GET /api/oauth/zoho/callback?code=...&state=<uuid>
       ├─ Valida state contra zoho_oauth_state (single-use, TTL 1h)
       ├─ parseStateValue(stateRow.created_by)
       │    → { email: "edu@crearack.com", target: "Dani" }
       ├─ isTeamMember("Dani") → true → teamMember = "Dani"
       ├─ exchangeCodeForTokens(...)
       ├─ upsertToken(env, "Dani", ..., audit_email="edu@crearack.com")
       └─ Resuelve zoho_email real de Dani via /api/accounts

Formato del estado OAuth

El campo created_by de la tabla zoho_oauth_state cambió de formato en PR#46:

VersiónFormato created_byDescripción
Pre-s68"edu@crearack.com"Email plano del actor
s68 (PR previo)"edu@crearack.com"Email plano (igual)
PR#46+'{"email":"edu@crearack.com","target":"Dani"}'JSON con actor (auditoría) + target (token destino)

El callback mantiene compatibilidad hacia atrás: si JSON.parse falla (estado antiguo), interpreta created_by como email plano y deriva el team_member por mapeo normal.

URLs de uso rápido (post-deploy)

# Re-autorizar cuenta propia de Edu
https://workspace.crearack.com/api/oauth/zoho/start

# Pre-autorizar Dani (Edu se loguea con creds de Dani en Zoho consent)
https://workspace.crearack.com/api/oauth/zoho/start?as=Dani

# Pre-autorizar Txell (Edu se loguea con creds de Txell en Zoho consent)
https://workspace.crearack.com/api/oauth/zoho/start?as=Txell

Consideraciones de seguridad

  • La autorización del ?as=X está protegida por CF Access (middleware) + comprobación de actor === 'Edu' en el handler.
  • El campo email del state JSON sirve como auditoría — registra quién inició el OAuth real, aunque el token se guarde en nombre de otro.
  • El state es single-use (se consume en el callback) con TTL de 1 hora. No hay ventana de reutilización.
  • El callback no re-valida que el actor CF Access siga siendo Edu (el callback es público por diseño — Zoho redirige desde sus servidores). La seguridad recae en la opacidad + unicidad del state UUID.

Véase también

  • [[entity—workspace—function—resolve-actor-email]]
  • [[entity—workspace—table—zoho-oauth-state]]
  • [[entity—workspace—table—zoho-oauth-tokens]]
  • [[feature—workspace—zoho-oauth-flow]]