Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementação de duplo fator de autenticação TOTP + códigos de recuperação (backend) #1769

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

lspaulucio
Copy link

@lspaulucio lspaulucio commented Jul 27, 2024

Mudanças realizadas

Essa PR traz a implementação do backend para configuração do duplo fator de autenticação TOTP (Time-based one-time password).
O TOTP é um tipo de MFA (Múltiplo Fator de Autenticação). Ele se baseia na geração de um token ou código de acesso exclusivos baseado no tempo.
Dessa forma, ao se configurar o TOTP no momento do login além de e-mail e senha será solicitado o token de acesso.
A utilização de um outro fator de autenticação traz benefícios de segurança para o usuário uma vez que se sua conta for hackeada o invasor ainda precisará informar o token de acesso para que seja possível acessar a conta. Vários sites já implementam esse tipo de MFA, como por exemplo o próprio GitHub.
Também foi incluída nessa PR a implementação do backend para a utilização de códigos de recuperação, que podem ser usados para acessar a conta nas situações onde o usuário tenha perdido o acesso ao TOTP.

Endpoints Afetados

  • Criação de novas migrations para inclusão dos atributos totp na tabela de usuário:
    • totp_secret: Armazena a secret TOTP criptografada
    • totp_recovery_codes: Armazena os códigos de recuperação criptografados
  • Inclusão da package otpauth para implementação do TOTP
  • Criado novo endpoint para geração do TOTP: /api/v1/mfa/totp
  • Criação de novas variáveis de ambiente no arquivo .env para criptografia do TOTP
  • Criado novo model otp.js
  • Incluído novos campos referentes ao TOTP nos models: authorization.js, user.js e validator.js
  • Incluído exigência do envio do totp, caso tenha sido habilitado, no endpoint /api/v1/recovery
  • Realizados ajustes no endpoint /api/v1/session para inclusão de verificação TOTP
  • Criação de testes para o novo endpoint TOTP tests/integration/api/v1/mfa/totp
  • Realizado alguns ajustes e inclusão de novos testes em:
    • tests/integration/api/v1/recovery
    • tests/integration/api/v1/sessions
    • tests/integration/api/v1/_use-cases
    • tests/integration/api/v1/user
    • tests/integration/api/v1/users/[username]

Tipo de mudança

  • Nova funcionalidade

Checklist:

  • As modificações não geram novos logs de erro ou aviso (warning).
  • Eu adicionei testes que provam que a correção ou novo recurso funciona conforme esperado.
  • Tanto os novos testes quanto os antigos estão passando localmente.

Atividades necessárias "Pré-deploy"

  • Criar novas variáveis de ambiente
  • Rodar migrations

Observação: é minha primeira contribuição em um projeto open source, então se algo não ficou implementado da melhor forma me avisem para que possa ajustar, principalmente nas alterações de interface 😅.

Copy link

vercel bot commented Jul 27, 2024

@lspaulucio is attempting to deploy a commit to the TabNews Team on Vercel.

A member of the Team first needs to authorize it.

@lspaulucio
Copy link
Author

Essa PR é basicamente a mesma que a #1709.
Havia feito umas alterações e acabei não conseguindo reabri-lá.

@lspaulucio
Copy link
Author

Alguns pontos que o @Rafatcb levantou e estávamos discutindo na PR anterior (#1709)

  1. Vamos mudar para que a ativação e desativação do TOTP seja feito no mesmo endpoint?
  2. Vamos manter o booleano ou vamos deixar apenas o secret para indicar se o usuário está com o TOTP habilitado ou não?

@Rafatcb Rafatcb added back Envolve modificações no backend novo recurso Nova funcionalidade/recurso segurança Melhoria de segurança labels Jul 28, 2024
Copy link
Collaborator

@Rafatcb Rafatcb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Show, @lspaulucio! Não revisei o código, essa minha revisão é apenas sobre as alterações necessárias na interface da API e do banco de dados.

Aproveito para mencionar que editei o texto do seu PR para não fecharmos o issue #1171 após o merge, porque este PR contém apenas a parte do backend.

  1. Vamos mudar para que a ativação e desativação do TOTP seja feito no mesmo endpoint?

Me parece uma boa simplificação. Refleti sobre o que discutimos no outro PR e sobre o que é necessário para implementar esse novo recurso. Acredito que os endpoints envolvidos são algo como:

  • GET /api/v1/mfa/totp: Retorna a string necessária para o cliente gerar o código QR, e o usuário gerar e salvar o TOTP com um aplicativo.
  • PATCH /api/v1/users/[username]: Envio do totp_secret e totp_token. Responsável por adicionar ou remover o TOTP à conta do usuário.
    • Se totp_secret estiver definido, verifica se o usuário não possui totp_secret, e se o totp_token enviado é válido. Se sim, atualiza o usuário com o totp_secret informado.
    • Se totp_secret for null, verifica o totp_token enviado e o totp_secret atual do usuário, se for válido, salva o totp_token = NULL.
  • POST /api/v1/sessions: Caso o usuário tenha um totp_secret salvo no banco de dados, é necessário o envio do totp_token para que seja validado.

Com isso, podemos remover também o POST /api/v1/mfa/totp/verify, porque existem apenas dois momentos onde é necessário verificar o código:

  1. Ao criar um novo código para a conta, e isso seria verificado no PATCH /api/v1/users/[username].
  2. Ao acessar a conta ou realizar outra operação que necessite a segunda camada de verificação. Atualmente, isso seria realizado no POST /api/v1/sessions.

Faz sentido a API ser dessa forma? É possível realizar todas as operações necessárias ou eu esqueci de algum detalhe? Me corrijam se eu estiver errado.

Não tenho certeza se alterar o PATCH /api/v1/users/[username] é melhor do que criar um novo endpoint para isso, visto que limitaria nossas opções para adicionar uma confirmação extra ao retornar códigos de recuperação para o usuário (como o próprio GitHub e outros sites), mas exigir essa confirmação no futuro seria uma breaking change. Imagens de como é no GitHub:

  1. Geração do código QR.

Código QR e input para digitar o token.

  1. Códigos de recuperação (ainda é possível cancelar a operação).

Opção de download dos códigos de recuperação.

  1. Finalização.

Botão "Done".

Fonte das imagens.

  1. Vamos manter o booleano ou vamos deixar apenas o secret para indicar se o usuário está com o TOTP habilitado ou não?

Acho que não tem necessidade de manter o totp_enabled, podemos verificar isso pelo totp_secret 👍

SELECT totp_enabled IS NOT NULL AS totp_enabled FROM users;

@aprendendofelipe você pode revisar aqui também? Assim o @lspaulucio não implementa algo sem necessidade, caso você tenha uma visão diferente.

@lspaulucio
Copy link
Author

Oi @Rafatcb, obrigado pelos comentários!

Vou tentar colocar aqui abaixo o que eu havia pensado/implementado inicialmente, talvez possa facilitar o entendimento de quem for ajudar na revisão.

Inicialmente, havia pensado nesses endpoints aqui:

  • /api/v1/mfa/totp/enable - Endpoint que geraria e habilitaria um novo TOTP para o usuário;
  • /api/v1/mfa/totp/disable - Endpoint que desabilitaria o TOTP para o usuário;
  • /api/v1/mfa/totp/verify - Endpoint que realizaria a verificação do código TOTP;

Mas pensando agora, e vendo também o que você havia falado aqui:

Com isso, podemos remover também o POST /api/v1/mfa/totp/verify, porque existem apenas dois momentos onde é necessário verificar o código:
Ao criar um novo código para a conta, e isso seria verificado no PATCH /api/v1/users/[username].
Ao acessar a conta ou realizar outra operação que necessite a segunda camada de verificação. Atualmente, isso seria realizado no POST /api/v1/sessions.

Acho que poderíamos simplificar tudo isso em um único endpoint /api/v1/mfa/totp/configure, ficaria da seguinte forma:

  • GET: Retornaria a string necessária para geração do QR-Code e configuração do TOTP no aplicativo do usuário;
  • POST ou PATCH: Realizaria a habilitação do TOTP para o usuário no BD;
  • DELETE: Realizaria a desativação do TOTP para o usuário.

Com relação aos pontos que você trouxe:

POST /api/v1/sessions: Caso o usuário tenha um totp_secret salvo no banco de dados, é necessário o envio do totp_token para que seja validado.

Eu havia pensado dessa forma mesmo, realizei os ajustes nesse endpoint para realizar essa verificação.

Não tenho certeza se alterar o PATCH /api/v1/users/[username] é melhor do que criar um novo endpoint para isso, visto que limitaria nossas opções para adicionar uma confirmação extra ao retornar códigos de recuperação para o usuário (como o próprio GitHub e outros sites), mas exigir essa confirmação no futuro seria uma breaking change.

Também não gosto da ideia de usar esse endpoint /api/v1/users/[username] para isso 😅
Acho melhor separar e usar um endpoint específico, no caso penso que poderia ser o /api/v1/mfa/totp/configure. Ele que seria encarregado de gerar, habilitar e remover o TOTP da conta do usuário.

Com relação aos códigos de recuperação, eu fiz um pouco diferente 😅 (não sei se foi a melhor forma 😄)
Como eu havia falado na issue #1171, na minha implementação eu não utilizei códigos de recuperação para esses casos (na época nem havia pensado nisso 😂 )
O que fiz para esse caso foi uma alteração no fluxo de recuperação de conta, de forma que caso um usuário venha a perder o token, basta ele realizar o fluxo normal de recuperação que o TOTP é removido, e ai se ele desejar basta configurar novamente um novo TOTP.

@Rafatcb
Copy link
Collaborator

Rafatcb commented Jul 30, 2024

Acho que poderíamos simplificar tudo isso em um único endpoint /api/v1/mfa/totp/configure, ficaria da seguinte forma:

Com um único endpoint a semântica não fica correta, porque o GET retornará algo que não tem relação aos outros métodos mencionados — suponha que o usuário tenha um token cadastrado, o GET não retornará algo relacionado ao token dele. Seriam necessários ao menos 2 endpoints.

Como eu havia falado na issue #1171, na minha implementação eu não utilizei códigos de recuperação para esses casos (na época nem havia pensado nisso 😂 )

Acho que adicionarmos os códigos de recuperação agora pode ser uma complicação sem necessidade, atrasando o PR por causa do esforço extra necessário. Conheço serviços grandes que, caso você queira trocar de aplicativo autenticador, precisa entrar em contato com o suporte... Não é uma boa referência, mas só estou mencionando porque dá para funcionar assim, pelo menos a princípio.

O que fiz para esse caso foi uma alteração no fluxo de recuperação de conta, de forma que caso um usuário venha a perder o token, basta ele realizar o fluxo normal de recuperação que o TOTP é removido, e ai se ele desejar basta configurar novamente um novo TOTP.

Me parece que isso tiraria o sentido do TOTP. Imagine que o invasor tem acesso ao e-mail, mas não ao token. Nesse caso, ele conseguiria burlar a proteção do TOTP.

Copy link
Collaborator

@aprendendofelipe aprendendofelipe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Vamos mudar para que a ativação e desativação do TOTP seja feito no mesmo endpoint?

Me parece uma boa simplificação. Refleti sobre o que discutimos no outro PR e sobre o que é necessário para implementar esse novo recurso. Acredito que os endpoints envolvidos são algo como:

  • GET /api/v1/mfa/totp: Retorna a string necessária para o cliente gerar o código QR, e o usuário gerar e salvar o TOTP com um aplicativo.

  • PATCH /api/v1/users/[username]: Envio do totp_secret e totp_token. Responsável por adicionar ou remover o TOTP à conta do usuário.

    • Se totp_secret estiver definido, verifica se o usuário não possui totp_secret, e se o totp_token enviado é válido. Se sim, atualiza o usuário com o totp_secret informado.
    • Se totp_secret for null, verifica o totp_token enviado e o totp_secret atual do usuário, se for válido, salva o totp_token = NULL.
  • POST /api/v1/sessions: Caso o usuário tenha um totp_secret salvo no banco de dados, é necessário o envio do totp_token para que seja validado.

Com isso, podemos remover também o POST /api/v1/mfa/totp/verify, porque existem apenas dois momentos onde é necessário verificar o código:

  1. Ao criar um novo código para a conta, e isso seria verificado no PATCH /api/v1/users/[username].
  2. Ao acessar a conta ou realizar outra operação que necessite a segunda camada de verificação. Atualmente, isso seria realizado no POST /api/v1/sessions.

Faz sentido a API ser dessa forma? É possível realizar todas as operações necessárias ou eu esqueci de algum detalhe? Me corrijam se eu estiver errado.

Faz todo sentido ser exatamente assim 👍


Não tenho certeza se alterar o PATCH /api/v1/users/[username] é melhor do que criar um novo endpoint para isso, visto que limitaria nossas opções para adicionar uma confirmação extra ao retornar códigos de recuperação para o usuário (como o próprio GitHub e outros sites), mas exigir essa confirmação no futuro seria uma breaking change.

Acho que o PATCH /api/v1/users/[username] é a melhor opção para o momento. E cuidando para isso só ser possível com update:user e não com update:user:others. Isso é feito pelo authorization.filterInput, onde apenas no update:user irá validar o totp_secret.

Questões extras para pensarmos são:

  • Se o usuário estiver com o TOTP habilitado, vamos exigir o totp_token para qualquer modificação no cadastro do usuário, ou apenas para algumas? Quais? Deve ser mais simples pedir para tudo.
  • No caso da moderação usando a update:user:others, devemos pedir o totp_token do moderador ou não é necessário? Acho melhor pedir, mas isso depende também da resposta para a pergunta acima.

  1. Vamos manter o booleano ou vamos deixar apenas o secret para indicar se o usuário está com o TOTP habilitado ou não?

Acho que não tem necessidade de manter o totp_enabled...

Concordo que o booleano não é necessário! 👍


Como eu havia falado na issue #1171, na minha implementação eu não utilizei códigos de recuperação para esses casos (na época nem havia pensado nisso 😂 )

Acho que adicionarmos os códigos de recuperação agora pode ser uma complicação sem necessidade, atrasando o PR por causa do esforço extra necessário. Conheço serviços grandes que, caso você queira trocar de aplicativo autenticador, precisa entrar em contato com o suporte... Não é uma boa referência, mas só estou mencionando porque dá para funcionar assim, pelo menos a princípio.

Dá para funcionar assim, mas não vale a pena, pois teríamos que confiar unicamente no email enviado pelo usuário pedindo a recuperação, o que não seria nada mais seguro do que o dito abaixo. Por isso eu evitaria adicionar esse trabalho manual.


O que fiz para esse caso foi uma alteração no fluxo de recuperação de conta, de forma que caso um usuário venha a perder o token, basta ele realizar o fluxo normal de recuperação que o TOTP é removido, e ai se ele desejar basta configurar novamente um novo TOTP.

Me parece que isso tiraria o sentido do TOTP. Imagine que o invasor tem acesso ao e-mail, mas não ao token. Nesse caso, ele conseguiria burlar a proteção do TOTP.

Para melhorar isso, acho bom pedir a confirmação da senha junto ao token enviado pelo email. Assim como não deveria ser possível recuperar a senha sem fornecer o totp_token, caso esteja habilitado.

@lspaulucio
Copy link
Author

Vamos mudar para que a ativação e desativação do TOTP seja feito no mesmo endpoint?

Me parece uma boa simplificação. Refleti sobre o que discutimos no outro PR e sobre o que é necessário para implementar esse novo recurso. Acredito que os endpoints envolvidos são algo como:

Show, já vou iniciar os ajustes para a remoção do booleano.

Acho que o PATCH /api/v1/users/[username] é a melhor opção para o momento. E cuidando para isso só ser possível com update:user e não com update:user:others. Isso é feito pelo authorization.filterInput, onde apenas no update:user irá validar o totp_secret.

Certo, vou fazer os ajustes para que a habilitação do TOTP seja feito nesse endpoint.
Com isso, só vai existir o endpoint GET /api/v1/mfa/totp do TOTP para a geração do código, correto?

Como eu havia falado na issue #1171, na minha implementação eu não utilizei códigos de recuperação para esses casos (na época nem havia pensado nisso 😂 )

Acho que adicionarmos os códigos de recuperação agora pode ser uma complicação sem necessidade, atrasando o PR por causa do esforço extra necessário. Conheço serviços grandes que, caso você queira trocar de aplicativo autenticador, precisa entrar em contato com o suporte... Não é uma boa referência, mas só estou mencionando porque dá para funcionar assim, pelo menos a princípio.

Dá para funcionar assim, mas não vale a pena, pois teríamos que confiar unicamente no email enviado pelo usuário pedindo a recuperação, o que não seria nada mais seguro do que o dito abaixo. Por isso eu evitaria adicionar esse trabalho manual.

Para melhorar isso, acho bom pedir a confirmação da senha junto ao token enviado pelo email. Assim como não deveria ser possível recuperar a senha sem fornecer o totp_token, caso esteja habilitado.

Perfeito, concordo com vocês, na época não havia pensado nessa questão. Achei essa ideia de solicitar a confirmação da senha junto do token enviado por email ou de fornecer o totp_token na recuperação de senha (quando habilitado) bem legal 😄

Acho que adicionarmos os códigos de recuperação agora pode ser uma complicação sem necessidade, atrasando o PR por causa do esforço extra necessário. Conheço serviços grandes que, caso você queira trocar de aplicativo autenticador, precisa entrar em contato com o suporte... Não é uma boa referência, mas só estou mencionando porque dá para funcionar assim, pelo menos a princípio.

Vou dar uma pesquisada sobre esses códigos de recuperação para ver se é muito complexo implementar. Talvez o esforço necessário não seja tão alto assim para o benefício que vai trazer.

Questões extras para pensarmos são:
- Se o usuário estiver com o TOTP habilitado, vamos exigir o totp_token para qualquer modificação no cadastro do usuário, ou apenas para algumas? Quais? Deve ser mais simples pedir para tudo.
- No caso da moderação usando a update:user:others, devemos pedir o totp_token do moderador ou não é necessário? Acho melhor pedir, mas isso depende também da resposta para a pergunta acima.

Sei que isso varia de sistema para sistema, existem os que:

  1. Solicitam o TOTP apenas na autenticação.
  2. Solicitam o TOTP na autenticação e em qualquer alteração de configurações relacionadas à segurança do usuário. Porém, para as demais alterações do usuário (não relacionadas à segurança) não solicitam confirmação do TOTP.
  3. Solicitam o TOTP para qualquer alteração de configuração ou de cadastro do usuário.

Para mim, pensando do ponto de vista do usuário, um sistema que solicita o TOTP para qualquer alteração pode acabar atrapalhando a experiência. Porém, para o TabNews eu acho que não iria ficar tão ruim pois podemos solicitar apenas na hora que o usuário for salvar as alterações. Futuramente, se a quantidade de opções de alterações for muito grande podemos alterar para solicitar confirmação apenas para alguns campos...

Mas para esse primeiro momento acho que podemos solicitar apenas na autenticação do usuário mesmo para simplificar, o que acham?

@Rafatcb
Copy link
Collaborator

Rafatcb commented Aug 7, 2024

Com isso, só vai existir o endpoint GET /api/v1/mfa/totp do TOTP para a geração do código, correto?

Isso.

Vou dar uma pesquisada sobre esses códigos de recuperação para ver se é muito complexo implementar. Talvez o esforço necessário não seja tão alto assim para o benefício que vai trazer.

Não é difícil não. São como se fossem senhas que podem ser usadas uma única vez. Eu nunca usei, então não tenho certeza como faz para gerar novas, caso você use todas. Acho que é válido ler os FAQs de outras plataformas para termos um comportamento similar.

Se o usuário estiver com o TOTP habilitado, vamos exigir o totp_token para qualquer modificação no cadastro do usuário, ou apenas para algumas? Quais? Deve ser mais simples pedir para tudo.

Eu acredito que devemos pedir apenas para alterar nome de usuário, senha e e-mail, pois são os dados mais sensíveis/importantes. Temos um usuário (marlon) que utiliza a API para atualizar a descrição todo dia, e exigir o TOTP para isso seria uma complicação desnecessária (além de possivelmente quebrar a integração sem ele perceber, caso ative o TOTP).

No caso da moderação usando a update:user:others, devemos pedir o totp_token do moderador ou não é necessário? Acho melhor pedir, mas isso depende também da resposta para a pergunta acima.

Mas para esse primeiro momento acho que podemos solicitar apenas na autenticação do usuário mesmo para simplificar, o que acham?

Faz sentido pedir quando um moderador edita, sim. Também fará sentido pedir no nuke e talvez em outras ações. Creio que podemos simplificar inicialmente. Não me parece que seria complicado adaptar o código da API para isso, mas a UI pode ser um pouco mais trabalhosa.

Acredito que se esse PR incluir a funcionalidade do TOTP, a exigência dele no login, e os códigos de recuperação, já estará pronto para mesclar, e podemos tratar os outros pontos em outro PR (exigência do TOTP na edição, nuke etc.).

@Rafatcb Rafatcb added the pendente Aguardando ação do autor do Pull Request label Aug 13, 2024
@lspaulucio
Copy link
Author

lspaulucio commented Aug 28, 2024

@Rafatcb e @aprendendofelipe fiquei com algumas dúvidas nos pontos abaixo:

  1. Para os códigos de recuperação do TOTP, após o envio de algum código válido vamos reenviar o totp_secret configurado (que o usuário perdeu) ou vamos desabilitar o TOTP e o usuário terá que refazer uma nova configuração depois? Ou vocês veem alguma outra opção 😅

  2. Vamos exigir login e senha na recuperação do TOTP?

A grande maioria dos sistemas que implementam essa funcionalidade só oferecem a opção de códigos de recuperação após o usuário digitar o login e senha, pois a validação do TOTP é feita após essa etapa.

Dessa forma, havia pensado em criar o endpoint /api/v1/recovery/mfa/totp para essa finalidade de recuperação do TOTP. Acham que faz sentido?

Além disso, conforme o @aprendendofelipe havia comentado aqui:

Para melhorar isso, acho bom pedir a confirmação da senha junto ao token enviado pelo email. Assim como não deveria ser possível recuperar a senha sem fornecer o totp_token, caso esteja habilitado.

No endpoint /api/v1/recovery seria incluída uma exigência do envio do token_totp (caso o usuário tenha habilitado) para que o e-mail de recuperação seja enviado.

  1. O usuário poderá obter/visualizar os códigos de recuperação cadastrados?

Pelo que verifiquei alguns sistemas só permitem a visualização desses códigos uma única vez na configuração do TOTP. Já outros como o GitHub permitem que o usuário visualize os códigos cadastrados a qualquer momento nas configurações da conta.

@Rafatcb
Copy link
Collaborator

Rafatcb commented Aug 28, 2024

  1. Para os códigos de recuperação do TOTP, após o envio de algum código válido vamos reenviar o totp_secret configurado (que o usuário perdeu) ou vamos desabilitar o TOTP e o usuário terá que refazer uma nova configuração depois? Ou vocês veem alguma outra opção 😅

Eu acabei de fazer um teste no Discord.

  • Eu já tinha TOTP, resolvi desativar e ativar, ele gerou novos códigos de recuperação.
  • Tem um botão para visualizar os códigos, que pede senha e um token enviado por e-mail.
  • Ao lado de cada código, tem algo que lembra um checkbox, provavelmente para indicar se o código já foi usado.
  • Também tem um botão para gerar novos códigos.

Eu gostei dessas opções, e acho que podemos simplificar.

  • Se a pessoa entrar usando o código, não precisa gerar novos. O usuário pode estar sem acesso ao TOTP mas com acesso ao código, e decide usar um no momento.
  • Se a pessoa quiser gerar novos códigos, é só desativar o TOTP e ativar novamente.
  • Não ter como a pessoa consultar os códigos novamente, ao menos nessa implementação inicial. Se quiser, basta seguir o tópico anterior.
  1. Vamos exigir login e senha na recuperação do TOTP?

Não entendi o que é "recuperação do TOTP". Se a pessoa perdeu o TOTP, pode usar os códigos de recuperação. Se não tem acesso aos códigos, usa o TOTP. E uma vez que ela tenha acesso à algum dos dois, ela consegue desabilitar o TOTP.

No endpoint /api/v1/recovery seria incluída uma exigência do envio do token_totp (caso o usuário tenha habilitado) para que o e-mail de recuperação seja enviado.

No PATCH, sim. No POST, não, ou seja, o e-mail é enviado independente dessa validação.

  1. O usuário poderá obter/visualizar os códigos de recuperação cadastrados?

Como comentei ali em cima, acho que por ora podemos seguir sem permitir visualizar novamente, porque existe uma forma do usuário contornar essa necessidade, realizando de outra forma.

Meus comentários fazem sentido? Ficou com alguma dúvida?

@lspaulucio lspaulucio force-pushed the feat/mfa-totp branch 3 times, most recently from 6a1f359 to 223c83f Compare September 3, 2024 22:36
@lspaulucio
Copy link
Author

Oi @Rafatcb
Sim, seus comentários fizeram sentido e ajudaram bastante 😄

Finalizei a implementação do TOTP com os códigos de recuperação 😀

No filterOutput do authorization.js eu havia colocado o totp_recovery_codes na feature read:user:self para que após a ativação do TOTP o usuário tivesse como retorno os códigos de recuperação, contudo percebi que ao fazer uma requisição GET no /api/v1/user esses códigos de recuperação acabavam sendo retornados também. Para evitar isso fiz a inclusão dos códigos de recuperação apenas no PATCH no endpoint /api/v1/users/[username] após passar pelo filterOutput, não consegui ver uma outra forma de evitar que os códigos viessem no GET do /api/v1/user.

Vi que a main de onde comecei a implementação já teve "algumas" atualizações 😅
Vocês acham melhor eu fazer um rebase para já pegar as diferenças e fazer as correções de conflitos antes de vocês começarem a revisar?

@Rafatcb
Copy link
Collaborator

Rafatcb commented Sep 4, 2024

Vi que a main de onde comecei a implementação já teve "algumas" atualizações 😅
Vocês acham melhor eu fazer um rebase para já pegar as diferenças e fazer as correções de conflitos antes de vocês começarem a revisar?

Ainda não vi as alterações feitas, mas está com arquivos com conflitos, então fazer o rebase deve ajudar na revisão, sim.

@aprendendofelipe
Copy link
Collaborator

Show @lspaulucio!

Para facilitar a revisão, além de atualizar a branch, seria bom remover do PR qualquer alteração que não seja essencial para o TOTP. Isso inclui evitar refatorações ou mudanças de estilo em partes do código que não estão diretamente relacionadas ao TOTP, como adicionar ou remover linhas em branco, por exemplo. Melhorias em mensagens de erro e outros ajustes podem ser feitas em um PR específico.

Em relação aos códigos de recuperação, vocês não acham que é uma funcionalidade diferente, e que merece um PR próprio? Mesmo que decidam manter tudo no mesmo PR, acho que é fundamental separar o código e os commits.

Além disso, acho importante documentar (pode ser na descrição do PR) as etapas necessárias para a implantação dessa funcionalidade, tanto em homologação quanto em produção. O que precisa ser feito antes de mesclar o código e o que deve ser feito depois? Talvez seja necessário mais de um PR, como mesclar uma parte primeiro, rodar migrations, adicionar variáveis de ambiente, e depois mesclar o restante.

Por fim, se já estiver tudo funcional e testado, seria legal você mesmo fazer uma revisão prévia. Isso ajuda a eliminar mudanças que não são essenciais e a melhorar a qualidade geral, como eliminar aqueles "ifs hadouken".

@lspaulucio
Copy link
Author

Beleza!
Vou fazer a atualização da branch e separar os commits, assim que finalizar aviso aqui 😄

@lspaulucio lspaulucio force-pushed the feat/mfa-totp branch 3 times, most recently from 0818dbe to d6c9ba0 Compare September 8, 2024 16:12
@lspaulucio lspaulucio changed the title Implementação de duplo fator de autenticação TOTP (backend) Implementação de duplo fator de autenticação TOTP + códigos de recuperação (backend) Sep 9, 2024
@lspaulucio
Copy link
Author

lspaulucio commented Sep 9, 2024

Fala pessoal!

Finalizei as alterações 🙂
Separei os códigos da implementação do TOTP e dos códigos de recuperação em 2 commits.
Ajustei o título e a descrição desse PR acrescentando as informações dos códigos de recuperação. Também fiz a inclusão na descrição do PR das atividades de "pré-deploy" que visualizei como sendo necessárias antes da implantação.

Se vocês acharem que fica melhor quebrar essa PR em outras para facilitar me avisem, que ai faço a criação de outros PRs 😁

@lspaulucio lspaulucio force-pushed the feat/mfa-totp branch 2 times, most recently from 8a3c43f to 8642e62 Compare September 11, 2024 00:24
@Rafatcb Rafatcb removed the pendente Aguardando ação do autor do Pull Request label Sep 18, 2024
Copy link
Collaborator

@Rafatcb Rafatcb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lspaulucio, mais uma vez, muito obrigado por este PR! Sinto que ele está muito bem encaminhado, e a culpa dele estar aberto a tanto tempo não é sua. Estamos vendo outras coisas e ficamos sem tempo para revisar o PR aqui. Se você não possui tempo para continuar com esse PR, sem problemas, outra pessoa pode dar continuidade depois.

Falando por mim, estou com dificuldade em revisar e até deixei algumas dúvidas na revisão. Se você possui algum link de onde se inspirou para as decisões de implementação, pode compartilhar porque talvez me ajude a revisar.

Eu não consegui revisar tudo, e não precisa implementar o que eu revisei, porque talvez outro revisor discorde, a não ser que você já queira resolver os comentários mais simples que deixei (como a mudança em uma condicional, o try/catch, caso realmente não seja usado, e o teste que mencionei new URL()).

Comment on lines +2 to +7
pgm.addColumns('users', {
totp_recovery_codes: {
type: 'varchar(416)',
notNull: false,
},
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acho melhor isso ficar numa tabela separada, não sei se concordarão comigo.

Numa tabela própria, onde cada linha é um código associado à um usuário e com a indicação se o código já foi usado ou não.

@@ -162,6 +166,7 @@ function filterOutput(user, feature, output) {
features: output.features,
tabcoins: output.tabcoins,
tabcash: output.tabcash,
totp_enabled: output.totp_secret !== null ? true : false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pequena simplificação:

Suggested change
totp_enabled: output.totp_secret !== null ? true : false,
totp_enabled: output.totp_secret !== null,

return totp.validate({ token }) !== null;
}

function makeCode(length = 10) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analisando o código, vejo dois grandes blocos de comportamento no models/otp:

  • TOTP.
  • Códigos de recuperação.

Sugiro deixarmos ambos em modelos separados, porque além de um não depender do outro, o TOTP não depende de nada do projeto, mas os códigos de recuperação utilizam o models/user. Se formos usar uma tabela separada para os códigos de recuperação, então, faz ainda mais sentido ter um modelo próprio.

Pensando mais sobre o assunto, me parece que devemos passar o código dos "códigos de recuperação" para junto do models/recovery. Ambos estão relacionados à recuperação de acesso à conta, e ter ou não TOTP influencia em como o acesso deve ser recuperado.

O que vocês acham?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Se der para deixar os códigos de recuperação em um model separado, é melhor

Comment on lines +28 to +43
function encryptData(data) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(cryptoConfigurations.algorithm, cryptoConfigurations.key, iv);
const encryptedData = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);

return Buffer.concat([iv, encryptedData]).toString('hex');
}

function decryptData(encrypted) {
const data = Buffer.from(encrypted, 'hex');
const iv = data.subarray(0, 16);
const encryptedData = data.subarray(16);
const decipher = crypto.createDecipheriv(cryptoConfigurations.algorithm, cryptoConfigurations.key, iv);

return decipher.update(encryptedData, 'binary', 'utf-8') + decipher.final('utf-8');
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essas duas funções podem ser generalizadas para um módulo separado? Ou esse processo é muito específico do TOTP?

Estou com certa dificuldade em entender as etapas necessárias para a criptografia, visto que não tenho tanta familiaridade com o assunto. Você obteve viu esse código (ou algo parecido) em um artigo ou documentação? Se sim, por favor, compartilhe o link caso encontrar.

}

function validateUserTotp(userEncryptedSecret, token) {
const userSecret = decryptData(userEncryptedSecret);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um detalhe: apesar do encryptData e decryptData estarem presentes em models/otp, em nenhum momento ele chama a função encryptData, então não vejo sentido em chamar o decryptData aqui.

Me parece que essa responsabilidade deveria ser de quem encriptou, e assim o models/otp trabalha diretamente com a string não-criptografada.

}

async function validateAndMarkRecoveryCode(targetUser, recoveryCode) {
const recoveryCodes = JSON.parse(decryptData(targetUser.totp_recovery_codes));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O mesmo que meu último comentário sobre decryptData.

@@ -16,3 +16,5 @@ EMAIL_HTTP_PORT=1080
EMAIL_USER=
EMAIL_PASSWORD=
UNDER_MAINTENANCE={"methodsAndPaths":["POST /api/v1/under-maintenance-test$"]}
TOTP_SECRET_KEY="ha0wFlJJXdeKwANNgoB8jT2Op1iQ0pfGWuqnK8oMTVg="
TOTP_ENCRYPTION_METHOD="aes-256-cbc"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Como já disse, não entendo muito de criptografia. Pode me dizer porque essa foi a opção escolhida? Talvez seja óbvio, mas eu realmente não sei. Vi alguns lugares dizendo que aes-256-gcm pode ser melhor, mas não estudei a fundo para ver se faz sentido no nosso cenário.

Comment on lines +28 to +31
try {
response.status(200).json({ totp: totp.toString() });
} catch (err) {
throw new ServiceError({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esse trecho de código lançou um erro em alguma situação?

pages/api/v1/users/[username]/index.public.js Show resolved Hide resolved
expect(responseBody).toStrictEqual({
totp: `otpauth://totp/TabNews:${username}?issuer=TabNews&${secret}&algorithm=SHA1&digits=6&period=30`,
});
expect(secret).toHaveLength(39);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uma pequena melhoria nesse teste: ao invés de verificar o length=39, separar o secret= e validar diretamente o tamanho do segredo.

Provavelmente é mais fácil obter o parâmetro usando new URL() do que split..

@lspaulucio
Copy link
Author

@lspaulucio, mais uma vez, muito obrigado por este PR! Sinto que ele está muito bem encaminhado, e a culpa dele estar aberto a tanto tempo não é sua. Estamos vendo outras coisas e ficamos sem tempo para revisar o PR aqui. Se você não possui tempo para continuar com esse PR, sem problemas, outra pessoa pode dar continuidade depois.

Oi @Rafatcb, sem problemas, eu lembro que você já havia comentado que estavam priorizando outras funcionalidades mais urgentes 😃

Falando por mim, estou com dificuldade em revisar e até deixei algumas dúvidas na revisão. Se você possui algum link de onde se inspirou para as decisões de implementação, pode compartilhar porque talvez me ajude a revisar.

Seguem abaixo os principais links que dei uma lida quando estava realizando a implementação:

Melhores práticas:

OTP:

Criptografia:

Eu não consegui revisar tudo, e não precisa implementar o que eu revisei, porque talvez outro revisor discorde, a não ser que você já queira resolver os comentários mais simples que deixei (como a mudança em uma condicional, o try/catch, caso realmente não seja usado, e o teste que mencionei new URL()).

Beleza, essa semana eu não devo conseguir mexer porque estou com umas outras atividades pra fazer, mas acho que na semana que vem já consigo dar uma olhada nos comentários/sugestões que vocês colocaram, ai os casos mais simples já vou ajustando conforme a sugestão de vocês.

Acredito que algumas coisas talvez não tenham ficado implementadas da melhor forma 😅, mas ai nas revisões a gente vai discutindo e eu vou adequando/refazendo de acordo com as sugestões de vocês.

@Rafatcb
Copy link
Collaborator

Rafatcb commented Oct 24, 2024

@lspaulucio Obrigado pela resposta rápida! Se eu conseguir um tempo para ler os links antes de você iniciar as alterações, talvez eu consiga também continuar a revisão, ou então já resolver algum comentário meu de dúvida.

Quando eu continuar a revisão, marco este comentário como outdated para não poluir a thread.

@lspaulucio
Copy link
Author

Oi pessoal!
Acabei me enrolando com outras coisas e não consegui dar andamento nessa PR nesses dois últimos meses 😢.

Dei uma olhada nos comentários que o @Rafatcb fez e acho que para facilitar a revisão de vocês (e as minhas correções também 😅) o melhor seria quebrar o PR em dois, igual foi sugerido pelo @aprendendofelipe nesse comentário aqui.

Só fico com receio de que as sugestões já feitas para a parte dos códigos de recuperação se percam. Mas qualquer coisa eu faço uma cópia delas e incluo lá no novo PR.

O que acham? Posso seguir dessa forma?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
back Envolve modificações no backend novo recurso Nova funcionalidade/recurso segurança Melhoria de segurança
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants