# ⚠️ DOCUMENTO OBSOLETO **Este documento descreve uma abordagem antiga que não é mais recomendada.** Para o fluxo atual de OAuth Google Calendar com interceptação de callback, consulte: - [Google Calendar Callback Interception](./google-calendar-callback-interception.md) ✅ RECOMENDADO --- # Configuração do Webhook n8n para Atualizar Credencial Google ## ⚡ TL;DR - Escolha seu caminho ### 🟢 Caminho Fácil (RECOMENDADO) Configure OAuth **manualmente** no n8n UI (1 clique). [Pular para solução →](#️-solução-alternativa-simples-recomendada) **Vantagens:** Simples, rápido, sem código, funciona 100% ### 🔴 Caminho Complexo (AVANÇADO) Criar webhook que atualiza credencial via SQL no PostgreSQL. Continue lendo ↓ **Desvantagens:** Requer pgcrypto, acesso superuser, configuração SQL complexa --- ## Problema A API REST do n8n tem um bug no schema de validação de credenciais OAuth2 do Google (`googleOAuth2Api`). O schema valida `grantType` mas não permite esse campo devido a `additionalProperties: false`. ## Solução (Caminho Complexo) Criar um workflow no n8n que atualiza a credencial diretamente no banco de dados PostgreSQL. --- ## Workflow no n8n ### 1. Webhook (Trigger) - **URL**: `/webhook/update-google-credential` - **Método**: `POST` - **Autenticação**: Nenhuma (ou adicionar secret token) **Body esperado:** ```json { "userId": "uuid-do-usuario", "credentialId": "Y9Izvj7XzExRNw2W", "credentialData": { "clientId": "...", "clientSecret": "...", "scope": "https://www.googleapis.com/auth/calendar", "oauthTokenData": { "access_token": "ya29...", "refresh_token": "1//0g...", "scope": "https://www.googleapis.com/auth/calendar", "token_type": "Bearer", "expiry_date": 1735689600000 } } } ``` --- ### 2. Set Node (Preparar Dados) Criar variável `credentialDataJson` com o JSON stringify dos dados: ```javascript // Code: const credentialData = $json.credentialData; return { json: { credentialDataJson: JSON.stringify(credentialData), credentialId: $json.credentialId, userId: $json.userId } }; ``` --- ### 3. Postgres Node (Atualizar Credencial) **Operação:** Execute Query **IMPORTANTE:** Verificar o schema das tabelas do n8n primeiro: ```sql -- Descobrir schema correto SELECT table_schema, table_name FROM information_schema.tables WHERE table_name = 'credentials'; ``` **Query SQL** (n8n usa TypeORM com camelCase): ```sql UPDATE public.credentials_entity SET data = pgp_sym_encrypt( $1::text, (SELECT value FROM public.settings WHERE key = 'encryptionKey') ), "updatedAt" = NOW() WHERE id = $2 RETURNING id, name, type, "updatedAt"; ``` **IMPORTANTE:** - Tabela: `credentials_entity` (não `credentials`) - Coluna: `updatedAt` com aspas duplas (camelCase do TypeORM) **Configuração de Parâmetros no n8n:** **Opção 1 - Parâmetros Posicionais ($1, $2):** Campo **Options > Query Parameters**: `data,credentialId` Campo **Options > Query Parameters Values**: ```javascript ={{ $json.credentialDataJson }},={{ $json.credentialId }} ``` **Opção 2 - Named Parameters (:nome):** Campo: **Query** ```sql UPDATE public.credentials_entity SET data = pgp_sym_encrypt(:data::text, (SELECT value FROM public.settings WHERE key = 'encryptionKey')), "updatedAt" = NOW() WHERE id = :credentialId RETURNING id, name, type, "updatedAt"; ``` Campo: **Options > Query Parameters** = `data,credentialId` Campo: **Options > Query Parameters Values** = `={{ $json.credentialDataJson }},={{ $json.credentialId }}` --- ### 4. Response Node (Retornar Sucesso) ```json { "success": true, "credentialId": "{{ $json.id }}", "updatedAt": "{{ $json.updated_at }}", "message": "Credencial atualizada com sucesso" } ``` --- ## Configuração do Postgres no n8n ### Credencial PostgreSQL - **Host**: `localhost` (ou host do banco n8n) - **Database**: `n8n` - **User**: `n8n` - **Password**: senha do banco **IMPORTANTE:** O usuário precisa ter permissão para: - Ler a tabela `settings` - Atualizar a tabela `credentials` --- ## Variáveis de Ambiente (.env.local) ```env N8N_UPDATE_CREDENTIAL_WEBHOOK=https://n8n.automatizase.com.br/webhook/update-google-credential N8N_GOOGLE_CREDENTIAL_ID=Y9Izvj7XzExRNw2W ``` --- ## Fluxo Completo 1. **Usuário clica "Conectar"** → Redireciona para Google OAuth 2. **Google autoriza** → Callback `/auth/google/callback?code=...` 3. **Callback Next.js** → Chama `/api/google-calendar/exchange-token` 4. **API Next.js**: - Troca `code` por tokens no Google - Envia POST para webhook n8n com tokens 5. **Webhook n8n**: - Encripta dados com `pgp_sym_encrypt` - Atualiza credencial no banco - Retorna sucesso 6. **Popup fecha** → Credencial atualizada! --- ## Segurança ### Opção 1: Token Secreto Adicionar header de autenticação: ```javascript // No Next.js: headers: { "Content-Type": "application/json", "X-Webhook-Token": process.env.N8N_WEBHOOK_SECRET } // No n8n (IF node antes do Postgres): {{ $('Webhook').item.json.headers['x-webhook-token'] === 'seu-token-secreto' }} ``` ### Opção 2: IP Whitelist Configurar firewall para aceitar apenas IP do servidor Next.js. --- ## ⚠️ SOLUÇÃO ALTERNATIVA SIMPLES (RECOMENDADA) Se você está tendo problemas com SQL/pgcrypto, existe uma solução **muito mais simples**: ### Configurar OAuth Manualmente no n8n (1 vez) 1. **Acesse n8n UI** → Credentials 2. **Edite** a credencial `Y9Izvj7XzExRNw2W` (Google Calendar OAuth2) 3. Clique em **"Connect My Account"** 4. Complete o OAuth flow do Google 5. **Pronto!** A credencial fica salva e o n8n renova os tokens automaticamente ### Vantagens ✅ **Simples**: Não precisa SQL, pgcrypto, ou webhook ✅ **Seguro**: n8n gerencia encriptação automaticamente ✅ **Auto-renovação**: n8n renova `access_token` quando expira usando `refresh_token` ✅ **Sem código**: Apenas configuração via interface ### Quando usar esta solução - Se você encontrou **qualquer** erro de SQL acima - Se não tem acesso superuser ao PostgreSQL - Se prefere simplicidade e confiabilidade - Se vai usar a mesma conta Google para todos os usuários ### E o botão "Conectar" no portal? Você pode: **Opção 1:** Remover o botão (credencial já configurada) **Opção 2:** Fazer o botão apenas **verificar** se está conectado: ```typescript const handleCheckConnection = async () => { // Apenas verifica status via n8n const response = await fetch('/api/google-calendar/status'); const { connected } = await response.json(); if (connected) { alert('Google Calendar já está conectado!'); } else { alert('Configure a credencial no n8n UI primeiro.'); } }; ``` **Opção 3:** Criar **múltiplas credenciais** no n8n (uma por usuário): - `google-calendar-user-1` - `google-calendar-user-2` - etc. E selecionar dinamicamente no workflow baseado em `userId`. --- ## Troubleshooting ### Erro: "relation 'credentials' does not exist" **Causa:** n8n pode estar usando SQLite (padrão) ou schema diferente no PostgreSQL. **Solução 1:** Verificar tipo de banco do n8n: ```bash # Checar variável de ambiente echo $DB_TYPE # sqlite ou postgresdb # Ou verificar n8n config cat ~/.n8n/config # procurar por database.type ``` **Solução 2:** Se for PostgreSQL, descobrir o schema e tabela: ```sql -- Conectar no banco do n8n e executar: SELECT table_schema, table_name FROM information_schema.tables WHERE table_name LIKE '%credentials%' OR table_name = 'settings'; ``` Resultado esperado: ``` table_schema | table_name -------------+------------------- public | credentials_entity public | settings ``` **IMPORTANTE:** n8n usa TypeORM, então: - Tabelas: `credentials_entity`, `workflow_entity`, etc. - Colunas: `updatedAt`, `createdAt` (camelCase com aspas duplas) **Solução 3:** Se n8n usa SQLite (banco padrão): - **NÃO é possível** atualizar credenciais via SQL externo - Use a abordagem manual: configure OAuth uma vez no n8n UI ### Erro: function pgp_sym_encrypt does not exist **Causa:** Extensão `pgcrypto` não está habilitada no PostgreSQL. **Solução:** Habilitar a extensão (requer superuser): ```sql -- Conectar como superuser (postgres) CREATE EXTENSION IF NOT EXISTS pgcrypto; -- Verificar se foi habilitada \dx pgcrypto ``` Se não tiver permissão de superuser, **NÃO é possível** encriptar via SQL. Nesse caso, use a **Solução Alternativa Simples** abaixo. ### Erro: "encryptionKey not found" Verificar se existe a chave de encriptação: ```sql SELECT * FROM public.settings WHERE key = 'encryptionKey'; ``` ### Erro: "permission denied" Conceder permissões ao usuário: ```sql GRANT SELECT ON public.settings TO n8n; GRANT UPDATE ON public.credentials TO n8n; ``` ### Erro: column "updated_at" does not exist (sugere "updatedAt") **Causa:** n8n usa TypeORM que nomeia colunas em camelCase. **Solução:** Use aspas duplas em colunas camelCase: ```sql -- ERRADO UPDATE credentials_entity SET updated_at = NOW() ... -- CORRETO UPDATE credentials_entity SET "updatedAt" = NOW() ... ``` **Colunas comuns do n8n:** - `"updatedAt"`, `"createdAt"` (com aspas!) - `id`, `name`, `type`, `data` (sem aspas, são lowercase) ### Erro: Parâmetros não substituídos ($1, $2 aparecem literalmente) **Causa:** Formato incorreto dos parâmetros no Postgres Node. **Solução:** Use o formato correto na aba Options: 1. **Query Parameters**: `data,credentialId` (sem símbolos $) 2. **Query Parameters Values**: `={{ $json.credentialDataJson }},={{ $json.credentialId }}` Ou use named parameters (`:nome`): ```sql UPDATE public.credentials_entity SET data = pgp_sym_encrypt(:data::text, ...), "updatedAt" = NOW() WHERE id = :credentialId ``` ### Teste Manual ```bash curl -X POST https://n8n.automatizase.com.br/webhook/update-google-credential \ -H "Content-Type: application/json" \ -d '{ "userId": "test-user", "credentialId": "Y9Izvj7XzExRNw2W", "credentialData": { "clientId": "...", "clientSecret": "...", "oauthTokenData": { ... } } }' ```