Dashboard-Automatizase/docs/n8n-webhook-update-credential.md
Luis Erlacher 0152a2fda0 feat: add n8n API testing script for Google OAuth2 schema and existing credentials
- Implemented a bash script to test n8n API and retrieve credential schemas.
- Added types for API responses, Google Calendar, and WhatsApp instances.
- Configured Vitest for testing with React and added setup for testing-library.
2025-10-10 14:29:02 -03:00

9.9 KiB

⚠️ 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:


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 →

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:

{
  "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:

// 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:

-- Descobrir schema correto
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_name = 'credentials';

Query SQL (n8n usa TypeORM com camelCase):

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:

={{ $json.credentialDataJson }},={{ $json.credentialId }}

Opção 2 - Named Parameters (:nome):

Campo: Query

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)

{
  "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)

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:

// 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:

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:

# 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:

-- 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):

-- 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:

SELECT * FROM public.settings WHERE key = 'encryptionKey';

Erro: "permission denied"

Conceder permissões ao usuário:

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:

-- 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):

UPDATE public.credentials_entity
SET data = pgp_sym_encrypt(:data::text, ...), "updatedAt" = NOW()
WHERE id = :credentialId

Teste Manual

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": { ... }
    }
  }'