NodeJS-Nodemailer

NodeJS如何實現寄信

前言

Mail寄送功能使用 Gmail 當作郵件伺服器,所以需先開通權限,開通後,就可以慢慢新增寄信的功能,以及額外優化的部份。

Gmail-低安全性應用程式


nodemailer - 發信功能的介接

安裝

1
$ npm install nodemailer --save

範例

實現基本寄信的功能,步驟如下

  1. 載入 Mail模組
  2. 設定 SMTP 相關資料
  3. 撰寫Mail相關內容
  4. 發送郵件

不過在實際寄信的過程,有出現錯誤訊息為 self signed certificate in certificate chain,此錯誤訊息,將在後面小節額外說明。

起手式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var express = require('express');
var router = express.Router();
var nodemailer = require('nodemailer'); // Mail模組

//設定 SMTP 相關資料
var transporter = nodemailer.createTransport({
service: 'Gmail',
secureConnection: true, // 使用SSL方式(安全方式,防止被竊取信息)
auth: {
user: 'Gmail帳號', // generated ethereal user
pass: 'Gmail密碼' // generated ethereal password
},
tls: {
// 不得檢查服務器所發送的憑證
rejectUnauthorized: false
}
});


//撰寫Mail相關內容
var mailOptions = {
from: '"王康寶" <wang.kanboo@gmail.com>', // sender address
to: 'wang.kanboo@gmail.com', // list of receivers
subject: 'Node測試寄信', // Subject line
text: '測試寄信內容', // plain text body
html: '<p>HTML version of the message</p>'
};


//發送郵件
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
return console.log(error);
}

//轉址
res.redirect('/contact/review');
})

TLS - 傳輸層安全性協定

在實際寄信的過程,有出現錯誤訊息為 self signed certificate in certificate chain
原因為Gmail為HTTPS協定,會發送的 CA 簽名的證書
而Node.js 客戶機驗證CA 簽名的證書,方法是根據CA 的公用證書檢查CA 簽名的證書,
不過因為我們沒有 CA 的公用證書,所以我們需增加一個參數 rejectUnauthorized ,去忽略此驗證的動作。

新增 tls 的 rejectUnauthorized
1
2
3
4
5
6
7
8
9
10
11
12
13
//設定 SMTP 相關資料
var transporter = nodemailer.createTransport({
service: 'Gmail',
secureConnection: true, // 使用SSL方式(安全方式,防止被竊取信息)
auth: {
user: 'Gmail帳號', // generated ethereal user
pass: 'Gmail密碼' // generated ethereal password
},
+ tls: {
+ // 不得檢查服務器所發送的憑證
+ rejectUnauthorized: false
+ }
});

nodemailer 新增附件

擷取 撰寫Mail相關內容 這一部份,新增 attachments 參數,並且該如何正確取得伺服器電腦上的附件。

注意的點:

  1. 附件路徑需設定 完整路徑
  2. 可透過 process.cwd() 取得目前專案的目錄,在延伸至放附件的路徑,如:path: process.cwd() + '/public/attach/image.png'
  3. 另外可新增 cid 的值,可供 郵件html 渲染出附件檔案。
新增附件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//撰寫Mail相關內容
var mailOptions = {
from: '"王康寶" <wang.kanboo@gmail.com>', // sender address
to: 'wang.kanboo@gmail.com', // list of receivers
subject: 'Node測試寄信', // Subject line
html: req.body.description +
'<br/><br/>' +
+ '<img src="cid:image_001"/>' + // 引用attachments的cid
'<br/>' +
'來自電子郵件:' + req.body.email, // plain text body
+ attachments: [{
+ filename: 'text.txt',
+ content: '軍整風綠業新!'
+ }, {
+ filename: 'image.png',
+ path: process.cwd() + '/public/attach/image.png', // 附件路徑
+ cid: 'image_001' //cid可被郵件使用
+ }]
};

結果


CSURF - 阻擋跨站攻擊

為了預防 跨站請求偽造 的攻擊,所以我們使用 CSURF模組 來協助我們驗證是否為本伺服器所發出的請求,簡單來說,就是利用 後端session前端cookie 的配合,達成此驗證功能。

安裝

1
$ npm install csurf --save

範例

  1. 載入模組後,建立CSURF驗證
  2. get、post 之前,都需新增 middlewares 的卡控判斷。
  3. 在前端表單,新增一段 在 csrfToken的HTML,供驗證使用。
後端.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var express = require('express');
var router = express.Router();
var nodemailer = require('nodemailer'); // Mail模組
var csrf = require('csurf'); // 阻擋跨站攻擊(CSURF)

// 建立一個cookie供CSURF驗證 - setup route middlewares
var csrfProtection = csrf({
cookie: true
});


//傳入CSURF的驗證
router.get('/', csrfProtection, function (req, res) {
res.render('contact', {
csrfToken: req.csrfToken() //傳入csrfToken驗證資料
});
});


//接收資料時,驗證csrfToken是否正確
router.post('/post', csrfProtection, function (req, res) {

/*
your code
*/

res.redirect('/contact/review');

});
前端表單:紀錄由後端提供的csrfToken
1
2
3
4
5
6
7
8
9
10
11
12
<form action="/contact/post" method="post">
+ <input type="hidden" name="_csrf" value="<%= csrfToken %>">

<h1>聯絡我們</h1>
<div>
<label for="email">電子郵件:</label>
<input type="text" name="email" id="email"/>
</div>
<div>
<input type="submit" value="送出" />
</div>
</form>

dotenv - 環境變數設定

可以將一些比較機密性的資料(帳號、密碼…等),設置在環境變數裡,這樣的話,就算我們將專案上傳至github等公共環境,也不會將重要的資訊曝露在外。

安裝

1
$ npm install dotenv --save

範例

  1. 在專案根目錄新增檔案 .env,並在將機密資料設定於此。
  2. 修改後端app.js,機密資料 改取自 dotenv環境變數。
.env檔案
1
2
gmailUser = Gmail帳號
gmailPass = Gmail密碼
後端.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var express = require('express');
var router = express.Router();
var nodemailer = require('nodemailer'); // Mail模組
var csrf = require('csurf'); // 阻擋跨站攻擊(CSURF)
+require('dotenv').config(); // 環境變數設定

//設定 SMTP 相關資料
var transporter = nodemailer.createTransport({
service: 'Gmail',
secureConnection: true, // 使用SSL方式(安全方式,防止被竊取信息)
auth: {
+ // 帳密取自 dotenv環境變數
+ user: process.env.gmailUser, // generated ethereal user
+ pass: process.env.gmailPass // generated ethereal password
},
tls: {
// do not fail on invalid certs
rejectUnauthorized: false
}
});

connect-flash - message的暫存器

簡單來說,flash是一個暫存器,而且暫存器裡面的值使用過一次即被清空,這樣的特性很方面用來做網站的提示信息。

安裝

1
$ npm install connect-flash --save

範例

注意:flash要配合session使用

後端app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var express = require('express');
var session = require("express-session");
var flash = require('connect-flash'); // message的暫存器

var app = express();

//使用connect-flash前,需先設定session相關設定,才能使用
app.use(session({
secret: 'iamkanboo',
resave: true,
saveUninitialized: true
}));

app.use(flash()); // 啟用 message的暫存器(session設定完,才use)

驗證欄位是否有填寫,若有卡控訊息,則紀錄在 flash 內,並傳入 render ,供前端渲染畫面。

後端contact.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
router.get('/', csrfProtection, function (req, res) {
res.render('contact', {
csrfToken: req.csrfToken(),
errors: req.flash('errors') //傳入 卡控訊息
});
});


router.post('/post', csrfProtection, function (req, res) {
//欄位卡控
if (req.body.username == '') {
console.log('username是空的');
req.flash('errors', 'username是空的'); // 紀錄 卡控訊息
res.redirect('/contact');
return;
}

/* your code */

//轉址
res.redirect('/contact/review');

});
前端:顯示卡控訊息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form action="/contact/post" method="post">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">

+ <%= errors %>

<h1>聯絡我們</h1>
<div>
<label for="username">姓  名:</label>
<input type="text" name="username" id="username"/>
</div>
<div>
<label for="email">電子郵件:</label>
<input type="text" name="email" id="email"/>
</div>
<div>
<input type="submit" value="送出訊息" />
</div>
</form>

參考來源