- 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)
193 lines
5.9 KiB
Markdown
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/)
|