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

193 lines
5.9 KiB
Markdown

# 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:
```typescript
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:
```typescript
// 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:
```typescript
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`:
```typescript
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:
```json
{
"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
- [Google OAuth 2.0 Documentation](https://developers.google.com/identity/protocols/oauth2/web-server)
- [OpenID Connect Specification](https://openid.net/specs/openid-connect-core-1_0.html)
- [JWT.io - Debugger para tokens](https://jwt.io/)