teqo-backup/docs/plans/c149-conectar-google-oauth.md

8.2 KiB

C149 — Google Calendar: botão Conectar Google (OAuth) substitui a configuração manual de service account

Status: rascunho Atualizado em: 2026-08-27 Issue: #934 Priority: P2 Model: composer-2.5 Depends: #932 (C147) Impeccable: B — encaixe na agenda (configuração do sync) Rascunho UI: docs/plans/c149-conectar-google-oauth-ui-draft.html Appetite: ~2 dias eng; um outcome verificável — o candidato/coordenador conecta a conta Google do calendário da campanha clicando um botão (consent do Google), sem criar service account nem colar JSON Responsável: —

Intenção

Ativar o espelho Google Calendar (C114) hoje exige cerimônia de engenharia: criar service account no GCP, baixar JSON, base64, colar em env, compartilhar o calendário com o e-mail da conta de serviço. O dono do produto: "This manual configuration of the service account plus calendar isn't ideal — isn't there a way to expose a button that allows the candidate to sync a calendar to it?" O registro C114 recusou OAuth por pessoa (N staff escrevendo no calendário pessoal de cada um). O pedido atual é diferente: uma concessão OAuth em que candidato/coordenador autoriza o Teqo a gerenciar o calendário da campanha — variante não coberta pelo registro, vinda como nova evidência do dono. Configuração vira um clique.

Persona e fluxo

  • Persona / contexto: candidato ou coordenador geral, no computador (mesa), configurando o espelho da agenda uma única vez — e voltando só quando algo quebra.
  • Job principal: "conecto a conta Google do calendário da campanha ao Teqo sem ajuda de engenharia".
  • Fluxo desejado: na configuração do sync da agenda → clica "Conectar com o Google" → tela de consent do Google (escolhe a conta da campanha) → volta ao Teqo com estado "conectado" → o sync passa a usar essa conexão; se o token quebrar um dia, o estado mostra "erro" com caminho de reconexão (clicar de novo).
  • Anti-goals de produto: não é OAuth por pessoa (C114 continua valendo — UMA concessão, calendário da campanha); não é migração obrigatória (service account segue funcionando como fallback); não mexe na seleção de qual calendário (C150) nem no motor de reconciliação; leader não vê nem conecta (lockdown).

Esboço de fluxo (B)

[/campanha/agenda — configuração do sync] → "Conectar com o Google" → consent Google (conta da campanha)
→ callback de volta ao Teqo → estado "conectado" → sync usa a conexão (service account vira fallback)

Rascunho UI (gate)

  • Rascunho UI (gate): docs/plans/c149-conectar-google-oauth-ui-draft.html — cenas: não configurado (botão), conectado, erro de token com reconexão, mobile.

Objetivo e aceite

  • O candidato/coordenador conecta a conta Google do calendário da campanha clicando um botão (consent do Google) — sem criar service account, sem colar JSON, sem tocar em GCP.
  • Estado da conexão visível e honesto: conectado | erro | não configurado; erro de token mostra caminho de reconexão em um clique.
  • Falha de token/Google não corrompe dados: Teqo continua SoT; o espelho degrada para "erro", nada é apagado nem duplicado.
  • Funciona atrás do túnel Cloudflare: o callback OAuth usa a URL pública correta (nada de localhost vazando no redirect).
  • Guardrails: o token nunca aparece em log; armazenamento fail-closed (sem conexão → sem sync, sem crash); desconectar revoga o acesso no Teqo e orienta revogar no Google; leader lockdown intocado.

Dados (intenção)

  • Vou apresentar dados? Não — a conexão é infraestrutura do espelho (saída); nenhuma leitura nova de dados no Teqo. O calendarList (escopo readonly) alimenta apenas a escolha de calendário do C150.
  • Decisões desbloqueadas: candidato/coordenador decide (uma vez) qual conta Google autoriza; diante de "erro", o mesmo ator decide reconectar ou desconectar.
  • Forma: adiada ao plano de implementação — aqui só as restrições: estado ternário, nunca exibir o token, sem inventar estados intermediários.

Dados da decisão (literais)

  • Escopos OAuth mínimos: https://www.googleapis.com/auth/calendar.events + https://www.googleapis.com/auth/calendar.calendarlist.readonly (o segundo basta para listar calendários no C150 — sem escopo além do necessário).
  • Estados da conexão (UI + registro): conectado | erro | não configurado — exatamente esses três.
  • Env de credencial que vira fallback (não renomear nem remover nesta entrega): GOOGLE_CALENDAR_SERVICE_ACCOUNT_KEY.
  • Restrição dura de produto: o token DEVE sobreviver sem re-consentimento semanal (o caminho técnico — app unverified vs verificação Google — fica em aberto, ver Questões).

Direção no codebase (hipótese)

  • Áreas prováveis: configuração do sync na agenda /campanha/agenda (botão/estado), rotas de callback OAuth em src/app/(campaign)/campanha/…, utilitários Google perto de googleCalendarSync.ts/googleCalendarClient.ts, armazenamento da conexão na collection googleCalendarSync (onde calendarId já vive).
  • Precedente a olhar: plano C114 (decisão "não OAuth por pessoa"); par readGoogleServiceAccountCredentials/googleCalendarClient (REST hand-rolled com jose, sem lib Google hoje); WebAuthn de campaignUser (precedente de cerimônia de credencial com rota fora de (app)).
  • Risco de acoplamento: o token é credencial da conta Google da campanha — nunca logar, fail-closed, desconectar revoga; leader lockdown; C147 merged antes (o sync funcionando/diagnosticado) para não brigar no mesmo client.

Dependências

  • Dura: C147 — o sync precisa estar funcionando/diagnosticado antes de trocar o modelo de credencial (também evita conflito de arquivos no client).

Fora de escopo

  • Escolha/listagem de qual calendário conectar → C150 (aqui só entrega a conexão e o estado).
  • Migração/remoção obrigatória da service account (fica como fallback; aposentar é entrega futura).
  • OAuth por pessoa (anti-goal C114, reafirmado) e leitura de agenda pessoal para conflito de horário (item separado, se um dia existir).
  • Motor de reconciliação do sync (janela, locks, hooks C114/C122) — intocado.

Rabbit holes de produto

  • "Verificação completa do app no Google." Se alguém "só completar": auditoria, casework, semanas de fila — para uso interno. Corte neste item: caminho unverified (uso interno, <100 usuários) com aviso no consent; verificação só se o produto um dia precisar.
  • "Desconectar + limpar tudo + migrar quem usa service account." Corte: desconectar = revogar no Teqo + orientação de revogar no Google; ninguém é obrigado a migrar nesta entrega.
  • "Seletor de calendário dentro do botão." Se alguém "só completar": puxa o C150 para dentro. Corte: a conexão autoriza; escolher o calendário é o item seguinte.

Questões em aberto (produto)

  • Manter a service account como fallback depois que o OAuth existir? Opções: A) sim, como caminho técnico até o OAuth estar comprovado em produção | B) aposentar na mesma entrega. Recomendação: A — aposentar é entrega separada. (assumido — validar com produto)
  • Quem pode conectar/desconectar? Opções: A) candidato + coordenador | B) só candidato | C) também advisor. Recomendação: A — é a credencial da campanha inteira; advisor não autoriza credencial global, leader lockdown. (assumido — validar)
  • App unverified ou verificação Google? Opções: A) unverified (aviso no consent, token não expira em ~7 dias) | B) verificação completa (sem aviso, custo de auditoria). Recomendação: A — uso interno, <100 usuários; a restrição dura é o token sobreviver sem re-consentimento semanal. (trade-off do aviso é a decisão de produto)

Referências

  • Rascunho UI (gate): docs/plans/c149-conectar-google-oauth-ui-draft.html
  • Plan C114 (c114-google-calendar-push-agenda-oficial.md — decisão "não OAuth por pessoa"); C147 (dependência dura); C150 (seleção de calendário, fora daqui)
  • src/utilities/googleCalendarSync.ts, src/utilities/googleCalendarClient.ts, collection googleCalendarSync (calendarId)
  • AGENTS-campaign.md — leader lockdown / papéis de /campanha