Criar uma senha forte que você possa realmente lembrar não é fácil. Os usuários que usam senhas exclusivas para diferentes sites (o que é ideal) têm maior probabilidade de esquecer suas senhas. Portanto, é fundamental adicionar um recurso que permita aos usuários redefinir a senha com segurança quando a esquecerem.

Este artigo é sobre como você pode construir um recurso seguro de redefinição de senha com Node.js e Express .

Primeiro, para acompanhar este tutorial, aqui estão alguns requisitos a serem observados:

  • Você deve ter um conhecimento básico de Javascript e Node.js
  • Você tem o NodeJS instalado ou pode baixar e instalar a versão mais recente do Node.js
  • Você tem o banco de dados MongoDB instalado ou pode baixar e instalar o banco de dados MongoDB. Outra opção é criar uma conta no site MongoDB e configurar um banco de dados gratuito

Agora, vamos começar.

Criação de um fluxo de trabalho para redefinição de senha

O fluxo de trabalho para redefinir sua senha pode assumir diferentes formas e tamanhos, dependendo de quão utilizável e seguro você deseja que seu aplicativo seja.

Neste artigo, implementaremos um design padrão de redefinição de senha segura. O diagrama abaixo mostra como o fluxo de trabalho do recurso funcionará.

  • Solicitar redefinição de senha se o usuário existir
  • Validar as informações do usuário
  • Forneça informações de identificação
  • Redefinir a senha ou rejeitar a solicitação de verificação de senha
    Redefinir fluxo de trabalho de senha

Construindo um projeto para redefinição de senha

Vamos criar um projeto simples para demonstrar como o recurso de redefinição de senha pode ser implementado. Observe que você pode encontrar o projeto concluído na redefinição de senha com Node.js no GitHub , ou você também pode pular para a seção de redefinição de senha deste tutorial.

Vamos primeiro inicializar nosso projeto com o gerenciador de pacotes npm.

Execute npm init em seu Terminal/Prompt de Comando e siga as instruções para configurar o projeto.

Estrutura de pastas e arquivos em seu projeto

Nossa estrutura de pastas será semelhante a esta:

  • Controladores
    • auth.controller.js
  • Serviços
    • auth.service.js
  • modelos
    • user.model.js
    • token.model.js
  • Rotas
    • index.route.js
  • Utils
    • Emails
      • modelo
        • requestResetPassword.handlebars
        • resetPassword.handlebars
      • sendEmail.js
  • index.js
  • db.js
  • package.json

Dependências para configurar seu projeto com redefinição de senha

Execute o código abaixo para instalar as dependências que usaremos neste projeto com o npm.

 npm install bcrypt, cors, dotenv, express, express-async-errors, guiador, jsonwebtoken, mongoose, nodemailer, nodemon

Usaremos:

  • bcrypt para hash de senhas e redefinir tokens
  • cors para desativar o Compartilhamento de recursos de origem cruzada, pelo menos para fins de desenvolvimento
  • dotenv para permitir que nosso processo de nó tenha acesso às variáveis ​​de ambiente
  • express-async-errors para capturar todos os erros assíncronos para que toda a nossa base de código não fique cheia de try-catch
  • guidão como um mecanismo de modelagem para enviar e-mails em HTML
  • mongoose como um driver para fazer a interface com o banco de dados MongoDB
  • nodemailer para enviar e-mails
  • nodemon para reiniciar o servidor quando um arquivo muda

Configurar variáveis ​​de ambiente

Algumas variáveis ​​variam em nosso aplicativo dependendo do ambiente em que estamos executando nosso aplicativo. Para essas variáveis, vamos adicioná-las às nossas variáveis ​​de ambiente.

Crie um arquivo .env em seu diretório raiz e cole as seguintes variáveis ​​dentro. Estamos adicionando um sal bcrypt, nosso url de banco de dados, um JWT_SECRET e um url de cliente. Você verá como os usaremos à medida que prosseguirmos.

 BCRYPT_SALT=10
DB_URL=mongodb://127.0.0.1: 27017/testDB
JWT_SECRET=mfefkuhio3k2rjkofn2mbikbkwjhnkj
CLIENT_URL=localhost://8090

Conectando-se ao banco de dados MongoDB

Vamos criar uma conexão com nosso MongoDB. Este código deve estar em seu arquivo db.js no diretório raiz.

db.js

 const mongoose=require ("mongoose");
let DB_URL=process.env.DB_URL;//aqui estamos usando o Url do MongoDB que definimos em nosso arquivo ENV module.exports=conexão de função assíncrona () { tentar { aguarde mongoose.connect ( DB_URL, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true, autoIndex: true, }, (erro)=> { if (erro) retorna um novo erro ("Falha ao conectar ao banco de dados"); console.log ("conectado"); } ); } catch (erro) { console.log (erro); }
};

Configurando o aplicativo Express

Vamos construir nosso ponto de entrada do aplicativo e disponibilizá-lo na porta 8080. Copie e cole no seu arquivo index.js no diretório raiz.

 require ("express-async-errors");
require ("dotenv"). config ();
const express=require ("express");
const app=express ();
conexão const=requer ("./db");
const cors=require ("cors"); porta const=8080;
(função assíncrona db () { aguardar conexão ();
}) (); app.use (cors ());
app.use (express.json ());
app.use ("/api/v1", require ("./routes/index.route"));
app.use ((erro, req, res, próximo)=> { res.status (500).json ({erro: mensagem de erro});
}); app.listen (porta, ()=> { console.log ("Ouvindo a porta", porta);
}); module.exports=app;

Configurando token e modelos de usuário

Precisamos criar um modelo de usuário e token para configurar nosso sistema de redefinição de senha.

1. Modelo de token

Nosso modelo de token terá um tempo de expiração de cerca de 1 hora. Nesse período, espera-se que o usuário conclua o processo de redefinição de senha, caso contrário, o token será excluído do banco de dados. Com o MongoDB, você não precisa escrever código adicional para fazer isso funcionar. Basta definir expira em seu campo de data, assim:

 createdAt: { tipo: data, padrão: Date.now, expira: 3600,//este é o tempo de expiração }, 

Esta é a aparência de nosso modelo.

Você deve adicionar este código ao arquivo token.model.js dentro do diretório do modelo.

models/token.model.js

 const mongoose=require ("mongoose");
esquema const=mongoose.Schema;
const tokenSchema=new Schema ({ ID do usuário: { tipo: Schema.Types.ObjectId, obrigatório: verdadeiro, ref:"usuário", }, símbolo: { tipo: String, obrigatório: verdadeiro, }, criado em: { tipo: data, padrão: Date.now, expira: 3600,//este é o tempo de expiração em segundos },
});
module.exports=mongoose.model ("Token", tokenSchema);

2. Modelo de usuário

O modelo do usuário descreverá como os dados do usuário serão salvos.

E, é claro, antes de discutir a criação de um mecanismo seguro de redefinição de senha, precisamos garantir que estamos criando e armazenando uma senha segura em primeiro lugar.

Lembre-se de que armazenar senhas em texto simples é uma prática ruim e nunca deve ser feito. Em vez disso, use um algoritmo unilateral seguro e forte para hash e armazenar senhas, de preferência uma com um salt. Em nosso caso, usaremos bcrypt , conforme mostrado no código abaixo.

Usaremos bcrypt para fazer hash de senhas para que até nós, como os administradores não saberiam as senhas-você não deveria saber as senhas dos seus usuários e será quase impossível descobrir o contrário, caso o banco de dados seja sequestrado.

 const mongoose=require ("mongoose");
const bcrypt=require ("bcrypt");
esquema const=mongoose.Schema;
const bcryptSalt=process.env.BCRYPT_SALT;
const userSchema=new Schema ( { nome: { tipo: String, trim: true, obrigatório: verdadeiro, único: verdadeiro, }, o email: { tipo: String, trim: true, único: verdadeiro, obrigatório: verdadeiro, }, senha: {type: String}, }, { timestamps: true, }
);
userSchema.pre ("salvar", função assíncrona (próxima) { if (! this.isModified ("senha")) { retornar próximo (); } const hash=await bcrypt.hash (this.password, Number (bcryptSalt)); this.password=hash; Próximo();
});
module.exports=mongoose.model ("usuário", userSchema);

Antes que a senha seja salva, usamos o gancho pré-salvo do MongoDB para fazer o hash da senha primeiro nesta parte do código. Estamos usando um salt de 10, conforme mostrado em nosso arquivo .env , porque isso é forte o suficiente para reduzir a possibilidade de bandidos adivinharem as senhas dos usuários.

 userSchema.pre ("salvar", função assíncrona (próximo) { if (! this.isModified ("senha")) { retornar próximo (); } const hash=await bcrypt.hash (this.password, Number (bcryptSalt)); this.password=hash; Próximo();
});

Criar serviços para o processo de redefinição de senha

Teremos três serviços para iniciar e processar completamente nosso processo de redefinição de senha.

1. Serviço de inscrição

Claro, não podemos redefinir a senha de um usuário se ele não tiver uma conta. Portanto, este serviço criará uma conta para um novo usuário.

O código abaixo deve estar no arquivo service/auth.service.js :

 const JWT=require ("jsonwebtoken");
const User=require ("../models/User.model");
Const Token=require ("../models/Token.model");
const sendEmail=require ("../utils/email/sendEmail");
const crypto=require ("crypto");
const bcrypt=require ("bcrypt"); inscrição const=assíncrona (dados)=> { deixe o usuário=esperar User.findOne ({email: data.email}); if (usuário) { lance novo erro ("Email já existe"); } usuário=novo usuário (dados); token const=JWT.sign ({id: user._id}, JWTSecret); esperar user.save (); return (data={ userId: user._id, email: user.email, nome: usuário.nome, token: token, });
};

2. Serviço de solicitação de redefinição de senha

Seguindo nosso fluxo de trabalho, a primeira etapa é solicitar um token de redefinição de senha, certo?

Então, vamos começar por aí. No código abaixo, verificamos se o usuário existe. Se o usuário existir, verificamos se existe um token existente que foi criado para este usuário. Se houver, excluímos o token.

 const user=await User.findOne ({email}); if (! usuário) { lançar novo erro ("O usuário não existe"); } deixe o token=esperar o Token.findOne ({userId: user._id}); if (token) { aguardar token.deleteOne () };

Agora, preste atenção a esta parte: vamos criar um novo token aleatório usando a API de criptografia Node.js. Este é o token que enviaremos ao usuário:

let resetToken=crypto.randomBytes (32).toString ("hex");

Agora, crie um hash deste token, que salvaremos no banco de dados porque salvar resetToken simples em nosso banco de dados é o mesmo que salvar senhas em texto simples, e isso irá anular todo o propósito de configurar uma senha segura redefinir.

No entanto, enviaremos o token simples para o e-mail dos usuários conforme mostrado no código abaixo e, na próxima seção, verificaremos o token e permitiremos que criem uma nova senha.

service/auth.service.js

 const JWT=require ("jsonwebtoken");
const User=require ("../models/User.model");
Const Token=require ("../models/Token.model");
const sendEmail=require ("../utils/email/sendEmail");
const crypto=require ("crypto");
const bcrypt=require ("bcrypt"); const requestPasswordReset=async (email)=> { const user=await User.findOne ({email}); if (! usuário) lança novo erro ("O usuário não existe"); deixe o token=esperar o Token.findOne ({userId: user._id}); if (token) aguarda token.deleteOne (); deixe resetToken=crypto.randomBytes (32).toString ("hex"); const hash=await bcrypt.hash (resetToken, Number (bcryptSalt)); aguardar novo Token ({ userId: user._id, token: hash, createdAt: Date.now (), }).Salve (); const link=`$ {clientURL}/passwordReset? token=$ {resetToken} & id=$ {user._id}`; sendEmail (user.email,"Solicitação de redefinição de senha", {name: user.name, link: link,},"./template/requestResetPassword.handlebars"); link de retorno;
};

O link de redefinição de senha contém o token e a ID do usuário, que serão usados ​​para verificar a identidade do usuário antes que ele possa redefinir a senha.

Ele também contém o URL do cliente. O domínio raiz em que o usuário terá que clicar para continuar o processo de redefinição.

 const link=`$ {clientURL}/passwordReset? token=$ {resetToken} & id=$ {user._id}`;

Você pode encontrar a função sendEmail no repositório GitHub aqui e o modelo de e-mail aqui . Estamos usando nodemailer e mecanismo de modelagem de guidão para enviar o e-mail.

1. Serviço de redefinição de senha

Oh, olhe! Recebi um e-mail para confirmar que fiz a solicitação de redefinição de senha. Agora vou clicar no link a seguir.

Confirmação de e-mail para redefinição de senha

Depois de clicar no link, você será direcionado à página de redefinição de senha. Você pode construir esse front-end com qualquer tecnologia que desejar, pois esta é apenas uma API.

Neste ponto, enviaremos de volta o token , uma nova senha e uma id de usuário para verificar o usuário e criar uma nova senha posteriormente.

Aqui está o que estaremos enviando de volta para a API de redefinição de senha será semelhante a:

 { "token":"4f546b55258a10288c7e28650fbebcc51d1252b2a69823f8cd1c65144c69664e", "userId":"600205cc5fdfce952e9813d8", "senha":"kjgjgkflgk.hlkhol"
}

Será processado pelo código abaixo.

service/auth.service.js

 const resetPassword=async (userId, token, senha)=> { deixe passwordResetToken=aguarde Token.findOne ({userId}); if (! passwordResetToken) { lançar um novo erro ("token de redefinição de senha inválida ou expirada"); } const isValid=await bcrypt.compare (token, passwordResetToken.token); if (! isValid) { lançar um novo erro ("token de redefinição de senha inválida ou expirada"); } const hash=await bcrypt.hash (senha, número (bcryptSalt)); esperar User.updateOne ( {_id: userId}, {$ set: {password: hash}}, {novo: verdadeiro} ); const user=await User.findById ({_id: userId}); enviar email( user.email, "Redefinição de senha com sucesso", { nome: usuário.nome, }, "./template/resetPassword.handlebars" ); esperar passwordResetToken.deleteOne (); return true;
};

Observe como estamos usando bcrypt para comparar o token que o servidor recebeu com a versão em hash no banco de dados?

 const isValid=await bcrypt.compare (token, passwordResetToken.token);

Se eles forem iguais, prosseguiremos com o hash da nova senha…

 const hash=await bcrypt.hash (senha, número (bcryptSalt));

… e atualize a conta do usuário com a nova senha.

 aguarda User.updateOne ( {_id: userId}, {$ set: {password: hash}}, {novo: verdadeiro} );

Para manter o usuário informado a cada etapa, enviaremos um e-mail para confirmar a alteração da senha e excluir o token do banco de dados.

Até agora, conseguimos criar os serviços para inscrever um novo usuário e redefinir sua senha.

Bravo.

Controladores para serviços de redefinição de senha

Aqui estão os controladores para cada um desses serviços. Os controladores coletam dados do usuário, enviam-nos aos serviços para processar os dados e, em seguida, devolvem o resultado ao usuário.

controllers/auth.controller.js

 const { inscrever-se, requestPasswordReset, resetPassword,
}=requer ("../services/auth.service"); const signUpController=async (req, res, next)=> { const signupService=espera inscrição (req.body); return res.json (signupService);
}; const resetPasswordRequestController=async (req, res, next)=> { const requestPasswordResetService=await requestPasswordReset ( req.body.email ); return res.json (requestPasswordResetService);
}; const resetPasswordController=async (req, res, next)=> { const resetPasswordService=await resetPassword ( req.body.userId, req.body.token, req.body.password ); return res.json (resetPasswordService);
}; module.exports={ signUpController, resetPasswordRequestController, resetPasswordController,
};

Parabéns, você criou um recurso seguro de redefinição de senha para seus usuários.

O que você pode fazer a seguir?

Aqui estão algumas coisas que você pode fazer para tornar este recurso de redefinição de senha mais seguro.

  • Você pode pedir aos usuários que forneçam respostas para suas perguntas de segurança (eles já devem ter criado essas perguntas e respostas).
  • Use serviços de hospedagem seguros. Um serviço de hospedagem inseguro pode criar um backdoor para você como administrador. Se o seu serviço de hospedagem não estiver funcionando direito, os bandidos podem comprometer seu próprio sistema, mesmo que ele pareça seguro.
  • Implemente a autenticação de dois fatores (2FA) com o Google Authenticator ou SMS. Dessa forma, mesmo que o e-mail do usuário tenha sido comprometido, o hacker também precisará ter o telefone do usuário.
  • Não armazene as senhas dos usuários. É estressante ter que redefinir sua senha toda vez que você a esquece. Em vez disso, você pode permitir que seus usuários façam login com serviços já existentes, como Facebook, Github, GMail e semelhantes. Dessa forma, eles não precisarão se lembrar da senha nunca mais. Eles só precisarão se preocupar com suas senhas nesses sites de terceiros.

Se quiser brincar com o projeto concluído, você pode conferir no GitHub .

Conclusão

A segurança digital não é uma piada. Os hackers estão sempre em busca de novas maneiras de hackear sistemas, então você deve estar sempre em busca de novas maneiras de dificultar a vida deles.

Seus usuários confiam em você para proteger seus dados e você não quer decepcioná-los. De que outras formas você recomendaria para tornar um recurso de redefinição de senha mais seguro? Obrigado por ler.

A postagem Implementando uma redefinição de senha segura no Node.js apareceu primeiro no LogRocket Blog .

Source link