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:
- Abrir ventana de incógnito por cada miembro.
- Autenticarse en CF Access con sus credenciales (magic link al inbox de cada uno).
- Navegar a
/api/oauth/zoho/start. - 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ámetro | Descripción |
|---|---|
| (sin parámetro) | Autoriza la cuenta Zoho del actor (comportamiento normal s68) |
?as=Edu | Edu re-autoriza su propia cuenta explícitamente |
?as=Dani | Edu autoriza la cuenta Zoho de Dani |
?as=Txell | Edu autoriza la cuenta Zoho de Txell |
Control de acceso:
?as=Xsolo funciona si el actor esEdu(comprobado viaresolveTeamMember). 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ón | Formato created_by | Descripció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=Xestá protegida por CF Access (middleware) + comprobación deactor === 'Edu'en el handler. - El campo
emaildel 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
stateUUID.
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]]