[譯] 使用 Nodemailer 輕鬆構建能經過電子郵件重置密碼的 React 應用程序

重置 JavaScript 應用程序中的密碼沒那麼複雜javascript

MERN 應用程序中的密碼重置

在我尚未真正動手嘗試,幫個人 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

Nodemailer — The Magic Bullet

當我開始處處搜索個人密碼重置功能的解決方案時,我發現了許多推薦 nodemail 的文章。react

當我訪問它的官網的時候,我最早讀到的是:android

Nodemailer 是 Node.js 的一個模塊,它能夠輕鬆地發送電子郵件。該項目於 2010 年開始,當時沒有更好的解決方案來發送電子郵件消息,今天它是大多數 Node.js 用戶默認選擇的解決方案。 —— Nodemailerios

你知道嗎?這並非在開玩笑。很輕鬆發郵件並不困難。git

固然在我開始以前,我作了更多的調查來確保我對這項技術更有信心,而我在 NPMGithub 上看見的讓我放心。

Nodemailer 有:

  • NPM 每週下載量超過 615,000 次,
  • Github 上超過 10,000 個 star,
  • 截止目前已有 206 個版本,
  • 超過 2,500 個依賴包,
  • 自 2010 年以來,它以某種形式或時尚存在。

好吧,這彷佛值得我在本身的項目裏面試一試。

在代碼中實現 Nodemailer(前端和後端)

個人密碼重置功能不須要不少花哨的東西,只須要:

  • 一種將電子郵件發送到用戶地址的方法,
  • 郵件會包含一個連接,連接會重定向到個人網站上的受保護頁面,用戶在這些頁面中重置密碼,
  • 而後他們可使用新密碼登陸,
  • 我還但願密碼重置連接在一段時間後過時從而具備更好的安全性。

我是這麼作的。

前端代碼(客戶端文件夾)—— 發送重置郵件

我首先從 React 代碼開始,由於我必須有一個頁面,用戶能夠輸入他們的電子郵件地址並使用包含重置連接的電子郵件。

ForgotPassword.js

好吧,我知道這是一個很大的截圖,但我會將其分解(我在 VS Code 中使用了 Polacode 來製做這個漂亮的截圖,僅供參考)。若是要複製/粘貼實際代碼,能夠去看看倉庫

你真正應該關注的是組件的 sendEmail 方法和 render 方法。其他的代碼只是設置初始狀態和變量,以及按鈕和元素的樣式。

渲染方法

請注意 render 方法內部,我有一個簡單的輸入框來讓用戶輸入其電子郵件地址,按下提交按鈕會觸發 this.sendEmail() 方法。除此以外,若是用戶沒有輸入電子郵件,或者若是服務器回覆電子郵件已成功發送或者它不是可識別的地址,我會內置一些錯誤和成功處理。

發送電子郵件功能

全部的 HTTP 請求都是使用 Axios 來完成的,這使得服務器進行 AJAX 調用很是容易,在我看來,這甚至比內置的 Web API fetch() 都簡單。

當用戶輸入他們的電子郵件時,會向服務器發出一個 POST 請求,並等待服務器響應。若是郵件地址找不到,我能夠告訴用戶地址輸錯了;或者用戶還沒註冊,他們能夠進入一個註冊頁面並建立一個新的帳戶;若是郵件地址與咱們數據庫中的地址匹配,他們將會收到提示密碼重置連接已成功發送到他們的電子郵件地址的消息。

咱們如今轉到後端代碼

後端代碼(API 文件夾)—— 發送重置郵件

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 步:發送郵件

這個文件的最後一步其實是把我以前建立的代碼片斷放在一塊兒: transportermailOptionstoken 而且使用 Nodemailer 的 sendMail() 功能。若是它工做了,我會獲得返回碼爲 200 的響應,而後我用這個響應來觸發對客戶端的成功調用,若是出錯,我會在日誌裏記錄下錯誤以便查看哪裏出錯了。

啓用 Gmail 發送重置電子郵件

在設置傳輸器電子郵件時,至少在使用 Gmail 時,須要注意一個額外的陷阱,即全部電子郵件都是從傳輸器發送過來的。

爲了可以從賬戶發送電子郵件,必須禁用兩步驗證,而且必須將 「Allow less secure apps」 的設置切換爲開啓。見下面的截圖。爲此,從這裏進入了設置中心,並將其打開。

如今,我能夠很順利地發送重置電子郵件。若是您遇到問題,請查看 Nodemailer 的常見問題解答以獲取更多幫助。

這是你應該看到的屏幕,你能夠在其中打開不太安全的應用程序。這也是使用虛擬電子郵件賬戶而不是實際的 Gmail 賬戶的另外一個緣由。

前端代碼 —— 更新密碼屏幕

太棒了,如今用戶應該能在郵箱中收到重置電子郵件了,看起來像這樣。

這是一個簡單的電子郵件,但它完成了我須要它作的事情。

若是你有留意的話,第三行是一個指向個人網站(在本地 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

這是用戶提交他的密碼以進行更新時調用的第二條路由。

再一次,我發現數據庫中的用戶(usernamereset 上的路由傳回客戶端並保持在應用程序的狀態,直到調用更新函數),我使用個人 bcrypt 模塊散列新的密碼(就像個人 Passport.js 中間件在最初將新用戶寫入數據庫時執行),用新的散列值更新數據庫中該用戶的 password,並將 resetPasswordTokenresetPasswordExpires 列設置爲 null,所以同一個連接不能屢次使用。

一旦完成,服務器就會給客戶端返回一個狀態碼爲 200 的響應,其中包含成功消息 「Password updated」。

你已經經過電子郵件成功重置用戶的密碼。並不難。

結論

乍一看,經過電子郵件連接重置用戶密碼彷佛有點使人生畏。但 Nodemailer 幫咱們簡化了一個主要部分(處理電子郵件)。一旦完成,它只是服務器端的幾條路由,並在客戶端輸入,以便爲用戶更新密碼。

幾周以後再回來(個人博客)看看,我會寫關於使用 Puppeteer 和 headeless Chrome 進行端到端的測試或其它和 web 開發相關的內容,因此請關注我,以避免你錯過。

感謝閱讀,我但願這能讓你瞭解如何使用 Nodemailer 爲 MERN 應用程序發送密碼重置電子郵件。點贊和分享我將會很是感謝。

若是您喜歡閱讀本文,您可能還會喜歡個人其餘一些博客:


參考資料和更多資源:

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索