前兩天整理了 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
成功的話會看到:
