Dashboard-Automatizase/docs/IMPLEMENTACAO-EMAIL-GOOGLE-OAUTH.md
Luis 1391fe6216 feat: enhance Google Calendar integration and update Dockerfile for environment variables
- Updated Dockerfile to include hardcoded environment variables for Next.js build.
- Enhanced Google Calendar API integration by extracting user email from id_token and adding scopes for OpenID and email access.
- Modified credential management to delete existing credentials before creating new ones in n8n.
- Updated dashboard to display connected Google Calendar email and credential details.

Story: 4.2 - Melhorar integração com Google Calendar e atualizar Dockerfile

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2025-10-12 21:16:21 -03:00

5.9 KiB

Implementação: Captura de Email do Google OAuth

Problema

O card do Google Calendar estava exibindo o email de login do Supabase, não o email da conta Google usada no OAuth.

Solução Implementada

1. Adicionar Escopos OpenID Connect

Arquivo: app/api/google-calendar/auth/route.ts

Adicionados escopos openid e email para receber id_token com informações do usuário:

const scopes = [
  "openid",                                              // ✅ NOVO
  "email",                                               // ✅ NOVO
  "https://www.googleapis.com/auth/gmail.modify",
  "https://www.googleapis.com/auth/calendar.readonly",
  "https://www.googleapis.com/auth/calendar.events",
].join(" ");

2. Extrair Email do id_token

Arquivo: app/api/google-calendar/callback/route.ts

O Google retorna um id_token (JWT) que contém o email do usuário:

// Decodificar id_token (JWT base64)
let googleEmail: string | null = null;

if (tokens.id_token) {
  try {
    const payloadBase64 = tokens.id_token.split('.')[1];
    const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf-8');
    const payload = JSON.parse(payloadJson);
    googleEmail = payload.email || null;
    console.log("[callback] Email extraído do id_token:", googleEmail);
  } catch (error) {
    console.error("[callback] Erro ao decodificar id_token:", error);
  }
}

// Fallback: Se falhar, buscar via API userinfo
if (!googleEmail && tokens.access_token) {
  const userinfoResponse = await fetch(
    'https://www.googleapis.com/oauth2/v1/userinfo?alt=json',
    {
      headers: { Authorization: `Bearer ${tokens.access_token}` },
    }
  );

  if (userinfoResponse.ok) {
    const userinfo = await userinfoResponse.json();
    googleEmail = userinfo.email || null;
  }
}

3. Salvar Email no Banco

Arquivo: app/api/google-calendar/manage-credential/route.ts

Adicionar googleEmail ao upsert no Supabase:

const { error: upsertError } = await supabase
  .schema("portal")
  .from("integrations")
  .upsert({
    user_id: user.id,
    provider: "google_calendar",
    status: "connected",
    n8n_credential_id: credentialId,
    n8n_credential_name: "refugio",
    connected_email: googleEmail,  // ✅ NOVO
    connected_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
  }, {
    onConflict: "user_id,provider",
  });

4. Exibir Email Correto no Dashboard

Arquivo: app/dashboard/page.tsx

Buscar connected_email do banco ao invés de usar user.email:

const { data, error } = await supabase
  .schema("portal")
  .from("integrations")
  .select("status, connected_at, n8n_credential_id, n8n_credential_name, connected_email")  // ✅ NOVO
  .eq("user_id", user.id)
  .eq("provider", "google_calendar")
  .maybeSingle();

if (data && data.status === "connected") {
  setCalendarConnected(true);
  setCalendarCredentialId(data.n8n_credential_id || null);
  setCalendarCredentialName(data.n8n_credential_name || null);
  setCalendarEmail(data.connected_email || null);  // ✅ USA EMAIL DO GOOGLE, NÃO DO SUPABASE
}

Fluxo Completo

1. Usuário clica em "Conectar" no Dashboard
   ↓
2. GET /api/google-calendar/auth
   - Gera URL OAuth com escopos: openid, email, calendar, gmail
   ↓
3. Usuário autoriza no Google (escolhe conta)
   ↓
4. Google redireciona para /api/google-calendar/callback
   - Recebe: code, state, id_token
   ↓
5. Callback troca code por tokens:
   - access_token
   - refresh_token
   - id_token (JWT com email do usuário)
   ↓
6. Extrai email do id_token (decodifica JWT)
   - Fallback: Busca via userinfo API
   ↓
7. POST /api/google-calendar/manage-credential
   - Cria credencial no n8n
   - Salva no Supabase: n8n_credential_id, connected_email
   ↓
8. Dashboard recarrega e exibe:
   📧 Conectado como: usuario@gmail.com  ← Email do Google OAuth
   🔑 Credencial n8n: refugio
   ID: abc123xyz

Estrutura JWT do id_token

O id_token é um JWT com 3 partes separadas por .:

eyJhbGciOi...  .  eyJlbWFpbCI6InVzZXJAZ21haWwuY29tIi...  .  signature
│              │  │                                       │
│              │  └─ Payload (contém email, sub, etc)    │
│              └─ Separador                              │
└─ Header                                 Assinatura ────┘

Decodificamos a parte 2 (payload) que contém:

{
  "email": "usuario@gmail.com",
  "email_verified": true,
  "sub": "1234567890",
  "iss": "https://accounts.google.com",
  ...
}

Compatibilidade

Não requer migration SQL - Coluna connected_email já existe na tabela Não quebra workflows n8n - Nome da credencial continua "refugio" Backward compatible - Se falhar extração, usa fallback via API

Teste

  1. Executar SQL para adicionar n8n_credential_id (se ainda não foi feito)
  2. Deletar credenciais órfãs
  3. Fazer logout e login no dashboard
  4. Clicar em "Conectar" no card Google Calendar
  5. Autorizar com conta Google diferente do email de login
  6. Verificar que o card exibe o email correto da conta Google

Logs Esperados

[auth] OAuth URL gerada para user: 7cd11392-e239-4cbb-bb38-d51272004b26
[callback] User: 7cd11392-e239-4cbb-bb38-d51272004b26 - State validated
[callback] Tokens obtidos do Google
[callback] Email extraído do id_token: usuario@gmail.com
[manage-credential] POST - User: 7cd11392-e239-4cbb-bb38-d51272004b26
[manage-credential] Google email received: usuario@gmail.com
[n8n-api] Creating new credential "refugio"
[n8n-api] Credential created successfully: abc123xyz
[manage-credential] Integration status saved to Supabase

Referências