重置 JavaScript 应用程序中的密码没那么复杂javascript
在我尚未真正动手尝试,帮个人 MERN 应用程序构建基于电子邮件的密码重置功能时,我高估了这么作的难度。据我所知,在 JavaScript 应用程序中发送电子邮件是很困难的,但我仍然想尝试一下。html
几个月来,为了磨练个人 JavaScript 全栈技能,我一直在慢慢构建这个应用并把它添加到一个用户注册服务。前端
首先,我使用 React 做为前端,Express/Node.js 后端和 Docker 驱动的 MySQL 数据库来构建这个应用。我经过 docker-compose.yml
来用一个命令启动整个应用程序(若是你想阅读更多关于我使用 Docker 进行开发的内容,你能够看看这篇博文)。java
在我开始构建应用程序以后,我使用 Passport.js 和 JSON Web Tokens(JWTs)在应用程序中添加权限校验。若是你对这个感兴趣的话,能够去阅读这篇文章去体会其中的好(nüè)玩(xīn)之处。我花了不少时间 —— 我遇到了不少障碍,使我屡次停滞不前。可是决心和我没法解决一个问题一旦在我脑海中生根,我会努力将问题想出来而后继续前进。node
当我决定解决经过电子邮件发送密码重置连接的问题时(就像真实的网站同样,包括我本身在内的用户不可避免地会忘记他们的密码),我以为本身会更加痛苦。尽管实际上每一个网站都有这个功能,可是作起来不可能那么简单。可是我错了,我很高兴我错了。mysql
当我开始处处搜索个人密码重置功能的解决方案时,我发现了许多推荐 nodemail 的文章。react
当我访问它的官网的时候,我最早读到的是:android
Nodemailer 是 Node.js 的一个模块,它能够轻松地发送电子邮件。该项目于 2010 年开始,当时没有更好的解决方案来发送电子邮件消息,今天它是大多数 Node.js 用户默认选择的解决方案。 —— Nodemailerios
你知道吗?这并非在开玩笑。很轻松发邮件并不困难。git
固然在我开始以前,我作了更多的调查来确保我对这项技术更有信心,而我在 NPM 和 Github 上看见的让我放心。
Nodemailer 有:
好吧,这彷佛值得我在本身的项目里面试一试。
个人密码重置功能不须要不少花哨的东西,只须要:
我是这么作的。
我首先从 React 代码开始,由于我必须有一个页面,用户能够输入他们的电子邮件地址并使用包含重置连接的电子邮件。
ForgotPassword.js
好吧,我知道这是一个很大的截图,但我会将其分解(我在 VS Code 中使用了 Polacode 来制做这个漂亮的截图,仅供参考)。若是要复制/粘贴实际代码,能够去看看仓库。
你真正应该关注的是组件的 sendEmail
方法和 render
方法。其他的代码只是设置初始状态和变量,以及按钮和元素的样式。
渲染方法
请注意 render
方法内部,我有一个简单的输入框来让用户输入其电子邮件地址,按下提交按钮会触发 this.sendEmail()
方法。除此以外,若是用户没有输入电子邮件,或者若是服务器回复电子邮件已成功发送或者它不是可识别的地址,我会内置一些错误和成功处理。
发送电子邮件功能
全部的 HTTP 请求都是使用 Axios 来完成的,这使得服务器进行 AJAX 调用很是容易,在我看来,这甚至比内置的 Web API fetch()
都简单。
当用户输入他们的电子邮件时,会向服务器发出一个 POST 请求,并等待服务器响应。若是邮件地址找不到,我能够告诉用户地址输错了;或者用户还没注册,他们能够进入一个注册页面并建立一个新的帐户;若是邮件地址与咱们数据库中的地址匹配,他们将会收到提示密码重置连接已成功发送到他们的电子邮件地址的消息。
咱们如今转到后端代码
forgotPassword.js
后端代码涉及到更多。这就是 Nodemailer 发挥做用的地方。
用户输入的电子邮件地址进入 forgotPassword
路由时,Sequelize 方法首先要作的是检查该电子邮件是否存在于个人数据库中。若是用户没有收到通知,他们可能输入错误,若是确实存在,则会启动一系列其余事件。
只有将它们所有衔接起来这一点,一开始作起来有点难。
第 1 步:生成令牌
确认电子邮件已经关联到数据库的某个用户以后,第一步要作的,是生成能够关联到用户帐户的令牌,并设置该令牌的有效时间。
Node.js 有一个叫作 Crypto 的内置模块,它提供加密功能,这是一种高级的说法,我能够用 crypto.randomBytes(20).toString('hex');
这行代码很简单的生成一个惟一的哈希令牌。而后,我将这个新令牌保存到数据库中用户的配置文件中,名为 resetPasswordToken
。我还设置了该令牌有效期的时间戳。发送连接后,我使连接的有效期为 1 小时 —— Date.now() + 36000
。
第 2 步:建立 Nodemailer 传输
接下来,我建立了 transporter
方法其实是发送密码重置电子邮件连接的账户。
我选择使用 Gmail,由于我我的使用 Gmail,我建立了一个新的虚拟账户来发送电子邮件。因为我不想把这个虚拟帐户的一些凭证提供给任何人,所以我把凭证放在一个 .env
文件中,而且这个文件是被包含在 .gitignore
中的,所以它永远不会提交给 Github 或其它任何地方。
NPM 包 [dotenv](https://www.npmjs.com/package/dotenv)
用于读取文件的内容和将邮件的地址和密码插入到 Nodemailer 的 createTransport
方法中。
第 3 步:建立邮件选项
第三步是建立电子邮件模板,Nodemailer 中它叫作 mailOptions
,用户将会看到这些信息(这也是他们从前端输入通过验证的电子邮件地址被使用的地方)。
有完整的第三方库可使用 Nodemailer 模块制做精美的电子邮件,但我只想要一封简单的电子邮件,因此我本身制做了这个。
它包含发送(from
)邮件的电子邮件地址(mySqlDemoEmail@gmail.com,对我来讲地址是这个),用户的邮件地址在 to
框中,subject
行则是用来存放重置密码连接的行,而且 text
是一个包含一些信息和网站 URL 重置路由的简单字符串,包括我以前建立的令牌,添加到最后。这将容许我验证用户是他们在点击连接并转到网站重置密码时所说的用户。
第 4 步:发送邮件
这个文件的最后一步其实是把我以前建立的代码片断放在一块儿: transporter
、mailOptions
和 token
而且使用 Nodemailer 的 sendMail()
功能。若是它工做了,我会获得返回码为 200 的响应,而后我用这个响应来触发对客户端的成功调用,若是出错,我会在日志里记录下错误以便查看哪里出错了。
在设置传输器电子邮件时,至少在使用 Gmail 时,须要注意一个额外的陷阱,即全部电子邮件都是从传输器发送过来的。
为了可以从账户发送电子邮件,必须禁用两步验证,而且必须将 “Allow less secure apps” 的设置切换为开启。见下面的截图。为此,从这里进入了设置中心,并将其打开。
如今,我能够很顺利地发送重置电子邮件。若是您遇到问题,请查看 Nodemailer 的常见问题解答以获取更多帮助。
太棒了,如今用户应该能在邮箱中收到重置电子邮件了,看起来像这样。
若是你有留意的话,第三行是一个指向个人网站(在本地 3031 端口运行)的连接,另外一个叫作“重置”的页面,后面接着我在第一步中使用 Node.js crypto
模块生成的一个散列令牌。
当用户单击此连接时,他们将被定向到应用程序中名为“密码重置屏幕”的新页面,该页面只能使用有效的令牌访问。若是令牌已过时或无效,用户将看到一个错误屏幕,其中包含回家或尝试发送新的密码重置电子邮件的连接。
这是重置屏幕的 React 代码。
ResetPassword.js
这里有三个主要的组件来完成繁重的工做。
初始组件装载了生命周期方法
一旦进入页面中,这个方法就会被触发。它从 URL 查询参数中提取令牌,并将其传给服务器的 reset
路由来验证令牌是否合法。
而后,若是服务器响应 “a-ok”,这个令牌是有效的并会与用户关联,若是响应 “no”,那么这个令牌会由于某些缘由而失效。
更改密码功能
若是用户通过身份验证并容许重置密码,则会触发这个方法。它还会访问服务器上的特定路由 updatePasswordViaEmail
(我这么作,是由于我也为用户提供了另一个路由,让他们在已登陆的状态下更改密码),而且一旦将更新的密码保存到数据库中,成功响应的消息就会被发送回客户端。
渲染方法
该组件的最后一部分是 render
方法。最初,在验证令牌的有效性时,会显示 loading
消息。
若是连接在某种程度上是无效的,则 error
消息将显示在屏幕上,其中包含返回主屏幕或忘记密码页面的连接。
若是用户有权重置密码,他们会有一个输入新的密码输入功能的叫作 updatePassword()
方法,一旦服务器响应成功更新密码,update
布尔值会被设置为 true,并显示 Your password has been successfully reset...
的消息和登陆按钮。
好的,这个项目已经到了最后的阶段。这是你在服务端须要的最后两个路由。这两个方法对应我刚才在 React ResetPassword.js
组件中在客户端进行的两种方法。
resetPassword.js
这是在 componentDidMount
客户端上调用生命周期方法的路由。它检查从连接的查询参数的 resetPasswordToken
和日期时间戳传递的内容,以确保一切正常。
你会注意到 resetPasswordExpires
参数具备奇怪的 $gt: Date.now()
参数。这是一个 运算符别名比较器,Sequelize 容许我使用它,全部的 $gt:
表明的都是“优先级高于”,不管它和谁去比较,在这种状况下,它将当前时间与发送重置密码电子邮件时保存到数据库的到期时间戳进行比较,以确保在发送电子邮件后不到一小时内重置密码。
只要两个参数都对该用户有效,就会向客户端发送成功的响应,而且用户能够继续密码重置。
updatePasswordViaEmail.js
这是用户提交他的密码以进行更新时调用的第二条路由。
再一次,我发现数据库中的用户(username
从 reset
上的路由传回客户端并保持在应用程序的状态,直到调用更新函数),我使用个人 bcrypt
模块散列新的密码(就像个人 Passport.js 中间件在最初将新用户写入数据库时执行),用新的散列值更新数据库中该用户的 password
,并将 resetPasswordToken
和 resetPasswordExpires
列设置为 null,所以同一个连接不能屡次使用。
一旦完成,服务器就会给客户端返回一个状态码为 200 的响应,其中包含成功消息 “Password updated”。
你已经经过电子邮件成功重置用户的密码。并不难。
乍一看,经过电子邮件连接重置用户密码彷佛有点使人生畏。但 Nodemailer 帮咱们简化了一个主要部分(处理电子邮件)。一旦完成,它只是服务器端的几条路由,并在客户端输入,以便为用户更新密码。
几周以后再回来(个人博客)看看,我会写关于使用 Puppeteer 和 headeless Chrome 进行端到端的测试或其它和 web 开发相关的内容,因此请关注我,以避免你错过。
感谢阅读,我但愿这能让你了解如何使用 Nodemailer 为 MERN 应用程序发送密码重置电子邮件。点赞和分享我将会很是感谢。
若是您喜欢阅读本文,您可能还会喜欢个人其余一些博客:
参考资料和更多资源:
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。