- 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)
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
- Executar SQL para adicionar
n8n_credential_id(se ainda não foi feito) - Deletar credenciais órfãs
- Fazer logout e login no dashboard
- Clicar em "Conectar" no card Google Calendar
- Autorizar com conta Google diferente do email de login
- 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