Pular para conteúdo

2FA - Autenticação de Dois Fatores

O que é este Node?

O 2FA é o node responsável por implementar autenticação de dois fatores no Lumina Flow Builder. Ele gera secrets, verifica tokens TOTP e cria QR codes para apps autenticadores.

Por que este Node existe?

Autenticação de dois fatores adiciona camada extra de segurança. O 2FA existe para:

  1. Segurança Adicional: Proteger contas mesmo se senha for comprometida
  2. Compatibilidade Universal: Funcionar com apps como Google Authenticator, Authy, Microsoft Authenticator
  3. Códigos Temporários: Gerar tokens de 6 dígitos que expiram a cada 30 segundos
  4. Códigos de Backup: Fornecer códigos alternativos para recuperação de acesso
  5. Padrão TOTP: Implementar Time-based One-Time Password (RFC 6238)

Como funciona internamente?

Quando o 2FA é executado, o sistema:

  1. Identifica operação: Determina se deve gerar secret, verificar token ou gerar QR code
  2. Operação Generate Secret:
  3. Cria secret aleatório de 32 caracteres
  4. Gera 10 códigos de backup
  5. Cria URL otpauth para apps autenticadores
  6. Retorna secret em base32
  7. Operação Verify Token:
  8. Recebe secret e código de 6 dígitos
  9. Valida usando algoritmo TOTP
  10. Permite window de 2 períodos (60s) para compensar dessincronia
  11. Retorna válido ou inválido
  12. Operação Generate QR:
  13. Cria URL otpauth com secret, label e issuer
  14. Gera QR code como Data URL (base64)
  15. Permite scan direto em apps autenticadores

Código interno (security-executor.service.ts:173-191):

private async execute2FA(parameters: any, context: any): Promise<any> {
  const { operation, secret, token, label, issuer } = parameters;

  this.logger.log(`🔐 2FA - Operation: ${operation}`);

  switch (operation) {
    case 'generate_secret':
      return this.generate2FASecret(label, issuer);

    case 'verify_token':
      return this.verify2FAToken(secret, token);

    case 'generate_qr':
      return this.generate2FAQR(secret, label, issuer);

    default:
      throw new Error(`Unsupported 2FA operation: ${operation}`);
  }
}

Quando você DEVE usar este Node?

Use 2FA sempre que precisar de autenticação de dois fatores:

Casos de uso

  1. Proteção de Contas: "Preciso adicionar 2FA para contas administrativas"
  2. Transações Sensíveis: "Preciso confirmar transferências bancárias com código 2FA"
  3. Acesso a Dados Críticos: "Preciso 2FA antes de acessar informações confidenciais"
  4. Compliance: "Preciso atender requisitos de segurança que exigem MFA"
  5. Recovery de Conta: "Preciso fornecer códigos de backup para recuperação"

Quando NÃO usar 2FA

  • Autenticação Simples: Use AUTH para autenticação básica com senha
  • Tokens JWT: Use TOKEN para autenticação baseada em tokens
  • OAuth Social: Use OAUTH para login com Google/Facebook

Parâmetros Detalhados

operation (string, obrigatório)

O que é: Define qual operação 2FA será executada.

Valores possíveis: - generate_secret: Gera novo secret para configuração inicial - verify_token: Verifica código de 6 dígitos do usuário - generate_qr: Gera QR code para scan no app autenticador

Flow completo para testar (Generate Secret):

{
  "name": "Teste 2FA - Generate Secret",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Gerar Secret",
        "parameters": {
          "operation": "generate_secret",
          "label": "Lumina App",
          "issuer": "Lumina Flow Builder"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Mostrar Secret",
        "parameters": {
          "message": "Secret: {{2fa_1.secret}}\nCódigos backup: {{2fa_1.backupCodes}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Gera secret 2FA. Deve retornar secret base32 e 10 códigos de backup.

secret (string, obrigatório para verify_token e generate_qr)

O que é: Secret gerado anteriormente na operação generate_secret, em formato base32.

Flow completo para testar:

{
  "name": "Teste 2FA - Secret",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Gerar Secret",
        "parameters": {
          "operation": "generate_secret",
          "label": "App Teste"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Salvar Secret",
        "parameters": {
          "name": "meuSecret",
          "value": "{{2fa_1.secret}}"
        }
      }
    },
    {
      "id": "2fa_2",
      "type": "2fa",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Gerar QR",
        "parameters": {
          "operation": "generate_qr",
          "secret": "{{meuSecret}}",
          "label": "App Teste"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "QR Gerado",
        "parameters": {
          "message": "QR Code criado para o secret!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "variable_1" },
    { "source": "variable_1", "target": "2fa_2" },
    { "source": "2fa_2", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Gera secret e usa para criar QR code. Deve funcionar perfeitamente.

token (string, obrigatório para verify_token)

O que é: Código de 6 dígitos gerado pelo app autenticador do usuário.

Flow completo para testar:

{
  "name": "Teste 2FA - Verify Token",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Gerar Secret",
        "parameters": {
          "operation": "generate_secret",
          "label": "Lumina"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Instruções",
        "parameters": {
          "message": "Configure no app autenticador com secret: {{2fa_1.secret}}"
        }
      }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Digite Código",
        "parameters": {
          "message": "Digite código de 6 dígitos do app:",
          "variableName": "codigo"
        }
      }
    },
    {
      "id": "2fa_2",
      "type": "2fa",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Verificar",
        "parameters": {
          "operation": "verify_token",
          "secret": "{{2fa_1.secret}}",
          "token": "{{codigo}}"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "Código válido: {{2fa_2.valid}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "message_1" },
    { "source": "message_1", "target": "input_1" },
    { "source": "input_1", "target": "2fa_2" },
    { "source": "2fa_2", "target": "message_2" },
    { "source": "message_2", "target": "end_1" }
  ]
}

Teste: Configure no Google Authenticator e digite código. Deve validar corretamente.

label (string, opcional)

O que é: Nome da aplicação exibido no app autenticador.

Padrão: "Default App"

Flow completo para testar:

{
  "name": "Teste 2FA - Label",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Nome App",
        "parameters": {
          "message": "Digite o nome da aplicação:",
          "variableName": "nomeApp"
        }
      }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Gerar com Label",
        "parameters": {
          "operation": "generate_secret",
          "label": "{{nomeApp}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Confirmação",
        "parameters": {
          "message": "2FA configurado para: {{nomeApp}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Digite "Minha Empresa". O app autenticador mostrará esse nome.

issuer (string, opcional)

O que é: Nome da organização emissora, exibido junto com label no app autenticador.

Padrão: "Default Issuer"

Flow completo para testar:

{
  "name": "Teste 2FA - Issuer",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Gerar com Issuer",
        "parameters": {
          "operation": "generate_secret",
          "label": "jose.roberto@lumina.com",
          "issuer": "Lumina Corp"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "2FA criado: Lumina Corp (jose.roberto@lumina.com)"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: No app autenticador aparecerá "Lumina Corp (jose.roberto@lumina.com)".

Parâmetros

Campo Tipo Obrigatório Descrição
operation string Sim Operação: generate_secret, verify_token, generate_qr
secret string Condicional Secret base32 (obrigatório para verify_token e generate_qr)
token string Condicional Código de 6 dígitos (obrigatório para verify_token)
label string Não Nome da aplicação (padrão: "Default App")
issuer string Não Nome da organização (padrão: "Default Issuer")

Exemplo 1: Setup Completo de 2FA

Objetivo: Demonstrar configuração completa de autenticação de dois fatores

JSON para Importar

{
  "name": "Setup Completo de 2FA",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Email",
        "parameters": {
          "message": "Digite seu email:",
          "variableName": "email"
        }
      }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Gerar Secret",
        "parameters": {
          "operation": "generate_secret",
          "label": "{{email}}",
          "issuer": "Lumina Flow"
        }
      }
    },
    {
      "id": "2fa_2",
      "type": "2fa",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Gerar QR Code",
        "parameters": {
          "operation": "generate_qr",
          "secret": "{{2fa_1.secret}}",
          "label": "{{email}}",
          "issuer": "Lumina Flow"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Instruções",
        "parameters": {
          "message": "1. Abra Google Authenticator\n2. Escaneie QR Code\n3. Salve códigos backup: {{2fa_1.backupCodes}}"
        }
      }
    },
    {
      "id": "input_2",
      "type": "input",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Confirmar Setup",
        "parameters": {
          "message": "Digite código do app para confirmar:",
          "variableName": "codigoConfirmacao"
        }
      }
    },
    {
      "id": "2fa_3",
      "type": "2fa",
      "position": { "x": 1300, "y": 100 },
      "data": {
        "label": "Verificar Código",
        "parameters": {
          "operation": "verify_token",
          "secret": "{{2fa_1.secret}}",
          "token": "{{codigoConfirmacao}}"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 1500, "y": 100 },
      "data": {
        "label": "Código Válido?",
        "parameters": {
          "condition": "{{2fa_3.valid}} == true"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 1700, "y": 50 },
      "data": {
        "label": "Sucesso",
        "parameters": {
          "message": "2FA ativado com sucesso!"
        }
      }
    },
    {
      "id": "message_3",
      "type": "message",
      "position": { "x": 1700, "y": 150 },
      "data": {
        "label": "Erro",
        "parameters": {
          "message": "Código inválido. Tente novamente."
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "2fa_2" },
    { "source": "2fa_2", "target": "message_1" },
    { "source": "message_1", "target": "input_2" },
    { "source": "input_2", "target": "2fa_3" },
    { "source": "2fa_3", "target": "condition_1" },
    { "source": "condition_1", "target": "message_2", "label": "true" },
    { "source": "condition_1", "target": "message_3", "label": "false" },
    { "source": "message_2", "target": "end_1" },
    { "source": "message_3", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Digite seu email:
Usuário: jose@lumina.com
Sistema: 1. Abra Google Authenticator
2. Escaneie QR Code
3. Salve códigos backup: [ABC123, DEF456, ...]
Sistema: Digite código do app para confirmar:
Usuário: 123456
Sistema: 2FA ativado com sucesso!

Exemplo 2: Login com 2FA

Objetivo: Demonstrar login com verificação de dois fatores

JSON para Importar

{
  "name": "Login com 2FA",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Username",
        "parameters": {
          "message": "Username:",
          "variableName": "username"
        }
      }
    },
    {
      "id": "input_2",
      "type": "input",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Senha",
        "parameters": {
          "message": "Senha:",
          "variableName": "senha"
        }
      }
    },
    {
      "id": "auth_1",
      "type": "auth",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Autenticar",
        "parameters": {
          "operation": "login",
          "username": "{{username}}",
          "password": "{{senha}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Solicitar 2FA",
        "parameters": {
          "message": "Senha correta! Agora digite código 2FA:"
        }
      }
    },
    {
      "id": "input_3",
      "type": "input",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Código 2FA",
        "parameters": {
          "message": "Digite código do app:",
          "variableName": "codigo2fa"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 1300, "y": 100 },
      "data": {
        "label": "Secret do Usuário",
        "parameters": {
          "name": "userSecret",
          "value": "JBSWY3DPEHPK3PXP"
        }
      }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 1500, "y": 100 },
      "data": {
        "label": "Verificar 2FA",
        "parameters": {
          "operation": "verify_token",
          "secret": "{{userSecret}}",
          "token": "{{codigo2fa}}"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 1700, "y": 100 },
      "data": {
        "label": "2FA Válido?",
        "parameters": {
          "condition": "{{2fa_1.valid}} == true"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 1900, "y": 50 },
      "data": {
        "label": "Login Sucesso",
        "parameters": {
          "message": "Login completo! Bem-vindo, {{username}}!"
        }
      }
    },
    {
      "id": "message_3",
      "type": "message",
      "position": { "x": 1900, "y": 150 },
      "data": {
        "label": "2FA Falhou",
        "parameters": {
          "message": "Código 2FA inválido. Acesso negado."
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 2100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "input_2" },
    { "source": "input_2", "target": "auth_1" },
    { "source": "auth_1", "target": "message_1" },
    { "source": "message_1", "target": "input_3" },
    { "source": "input_3", "target": "variable_1" },
    { "source": "variable_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "condition_1" },
    { "source": "condition_1", "target": "message_2", "label": "true" },
    { "source": "condition_1", "target": "message_3", "label": "false" },
    { "source": "message_2", "target": "end_1" },
    { "source": "message_3", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Username:
Usuário: jose.roberto
Sistema: Senha:
Usuário: senha123
Sistema: Senha correta! Agora digite código 2FA:
Sistema: Digite código do app:
Usuário: 234567
Sistema: Login completo! Bem-vindo, jose.roberto!

Exemplo 3: Códigos de Backup

Objetivo: Demonstrar uso de códigos de backup quando usuário perde acesso ao app

JSON para Importar

{
  "name": "Usar Código de Backup",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "2fa_1",
      "type": "2fa",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Gerar Setup",
        "parameters": {
          "operation": "generate_secret",
          "label": "Usuario"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Mostrar Backups",
        "parameters": {
          "message": "IMPORTANTE! Salve códigos de backup:\n{{2fa_1.backupCodes}}"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Cenário",
        "parameters": {
          "message": "Simulando: Usuário perdeu telefone..."
        }
      }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Usar Backup",
        "parameters": {
          "message": "Digite um código de backup:",
          "variableName": "codigoBackup"
        }
      }
    },
    {
      "id": "message_3",
      "type": "message",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Validado",
        "parameters": {
          "message": "Código backup aceito! Acesso recuperado.\nConfigure novo 2FA."
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "2fa_1" },
    { "source": "2fa_1", "target": "message_1" },
    { "source": "message_1", "target": "message_2" },
    { "source": "message_2", "target": "input_1" },
    { "source": "input_1", "target": "message_3" },
    { "source": "message_3", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: IMPORTANTE! Salve códigos de backup:
[A1B2C3D4, E5F6G7H8, I9J0K1L2, ...]
Sistema: Simulando: Usuário perdeu telefone...
Sistema: Digite um código de backup:
Usuário: A1B2C3D4
Sistema: Código backup aceito! Acesso recuperado.
Configure novo 2FA.

Resposta do Node

Generate Secret:

{
  "success": true,
  "action": "2fa_secret_generated",
  "secret": "JBSWY3DPEHPK3PXP",
  "backupCodes": ["A1B2C3D4", "E5F6G7H8", "I9J0K1L2", ...],
  "otpauth_url": "otpauth://totp/Lumina%20App?secret=JBSWY3DPEHPK3PXP&issuer=Lumina%20Flow",
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Verify Token (válido):

{
  "success": true,
  "action": "2fa_token_verified",
  "valid": true,
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Generate QR:

{
  "success": true,
  "action": "2fa_qr_generated",
  "qrCode": "data:image/png;base64,iVBORw0KGgo...",
  "otpauth_url": "otpauth://totp/...",
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Boas Práticas

SIM:

  • Sempre forneça códigos de backup no setup inicial
  • Armazene secret 2FA criptografado no banco de dados
  • Permita window de 2 períodos (60s) para compensar dessincronia de relógio
  • Force 2FA para usuários admin e contas privilegiadas
  • Permita desativar 2FA temporariamente com códigos de backup
  • Eduque usuários a salvar códigos de backup em local seguro

NÃO:

  • Não armazene secret em plain text
  • Não permita múltiplas tentativas consecutivas (rate limiting)
  • Não force 2FA sem fornecer códigos de backup
  • Não use window muito grande (compromete segurança)
  • Não compartilhe secret entre usuários
  • Não permita desativar 2FA sem verificação adicional

Dicas

💡 Dica 1: Combine 2FA com AUTH para criar fluxo completo de login seguro

💡 Dica 2: Gere QR code e mostre para usuário escanear diretamente no app autenticador

💡 Dica 3: Implemente rate limiting após 3 tentativas falhas de código 2FA

💡 Dica 4: Envie códigos de backup por email criptografado ou SMS após setup

💡 Dica 5: Permita reconfigurar 2FA usando código de backup se usuário perder dispositivo

Próximo Node

AUTH - Autenticação e gerenciamento de sessão → TOKEN - Gerenciamento de tokens JWT → OAUTH - OAuth 2.0 flow