前兩天整理了 Google 第三方登入,今天要寫的是註冊登入同樣也常用到的功能 - 信箱驗證。同樣也會用到 Google 的服務。
讓我們先理解整個驗證流程是怎麼運作的:
1. 使用者註冊
↓
2. 系統生成驗證碼 (Token)
↓
3. 寄送驗證信到使用者信箱
↓
4. 使用者點擊驗證連結
↓
5. 後端驗證 Token 是否正確
↓
6. 更新使用者為「已驗證」狀態
↓
7. 完成!可以正常使用所有功能
想要用程式寄信,不能直接用你的 Google 帳號密碼,這樣太不安全,要申請一個專門給應用程式用的密碼。
重要! 一定要先開啟兩步驟驗證,才能使用應用程式密碼功能。
⚠️ 注意! 這組密碼只會顯示一次,請立刻複製起來!
在 .env
檔案中加入以下設定:
# Email 設定
EMAILER_USER=你的gmail帳號@gmail.com
EMAILER_PASSWORD=chup qcbi sjcz vbfd # 剛剛複製的應用程式密碼
我們需要用 nodemailer
來寄信:
npm install nodemailer
npm install --save-dev @types/nodemailer
首先要在 User 資料表中加上驗證相關的欄位。
// src/models/user.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
@Column()
name: string;
// 新增:信箱驗證相關欄位
@Column({ default: false })
isEmailVerified: boolean;
@Column({ nullable: true })
emailVerificationToken: string;
@Column({ nullable: true })
emailVerificationExpires: Date;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
欄位說明:
isEmailVerified
: 標記信箱是否已驗證emailVerificationToken
: 驗證用的 TokenemailVerificationExpires
: Token 過期時間// src/services/email.service.ts
// src/services/email.service.ts
import nodemailer from 'nodemailer';
import dotenv from 'dotenv';
dotenv.config();
class EmailService {
private transporter;
constructor() {
this.transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAILER_USER,
pass: process.env.EMAILER_PASSWORD,
},
});
}
/**
* 寄送驗證信
*/
async sendVerificationEmail(
email: string,
name: string,
verificationToken: string
): Promise<void> {
const verificationUrl = `${process.env.FRONTEND_URL}/verify-email?token=${verificationToken}`;
const mailOptions = {
from: process.env.EMAILER_USER,
to: email,
subject: '請驗證您的信箱',
html: `
<p>哈囉 ${name},</p>
<p>請點擊以下連結驗證您的信箱:</p>
<p><a href="${verificationUrl}">${verificationUrl}</a></p>
<p>此連結將在 24 小時後失效。</p>
`,
};
try {
await this.transporter.sendMail(mailOptions);
console.log('驗證信已寄送至:', email);
} catch (error) {
console.error('寄信失敗:', error);
throw new Error('寄送驗證信失敗');
}
}
/**
* 寄送密碼重設信
*/
async sendPasswordResetEmail(
email: string,
name: string,
resetToken: string
): Promise<void> {
const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
const mailOptions = {
from: process.env.EMAILER_USER,
to: email,
subject: '重設您的密碼',
html: `
<p>哈囉 ${name},</p>
<p>請點擊以下連結重設您的密碼:</p>
<p><a href="${resetUrl}">${resetUrl}</a></p>
<p>此連結將在 1 小時後失效。</p>
`,
};
try {
await this.transporter.sendMail(mailOptions);
console.log('密碼重設信已寄送至:', email);
} catch (error) {
console.error('寄信失敗:', error);
throw new Error('寄送密碼重設信失敗');
}
}
}
export default new EmailService();
別忘了在 .env
加上前端網址:
# Email 設定
EMAILER_USER=你的gmail帳號@gmail.com
EMAILER_PASSWORD=你的應用程式密碼
# 前端網址
FRONTEND_URL=http://localhost:3001
成功的話會看到: