本指南添加 reset-password 功能。
reset-password 功能让用户在证明自己拥有该账户后设置新密码。它把 otp 或 magic-link 这样的功能用作验证策略。这些功能不仅可以用于登录,也可以被其他需要验证账户所有权的流程复用。
- 已完成修改密码的项目
添加 reset password 功能
Section titled “添加 reset password 功能”添加功能
运行 CLI 命令:
brkpt auth add reset-passwordCLI 会添加一个新的 features/reset-password/ 文件夹。
校验 DTO
reset password 功能使用固定的请求结构。本指南已经启用了 ValidationPipe,所以只需为生成的 DTO 添加校验装饰器。
import { IsString } from 'class-validator';
export class SendDto { @IsString() target!: string;
@IsString() strategy!: string;
@IsString() method!: string;}import { IsOptional, IsString, MinLength } from 'class-validator';
export class ResetDto { @IsOptional() @IsString() target?: string;
@IsString() strategy!: string;
@IsString() proof!: string;
@IsString() @MinLength(6) newPassword!: string;}strategy 选择用来证明账户所有权的验证策略。本指南同时启用了 otp 和 magic-link。
proof 包含该策略生成的验证凭证:otp 对应一个 OTP 验证码,magic-link 对应一个令牌。
在重置步骤中 target 是可选的,因为不同策略解析已验证 target 的方式不同。OTP 需要 target 来定位并校验验证码,而 magic link 可以直接从它的令牌中还原出 target。
实现适配器
创建一个适配器,根据已验证的 target 查找用户并更新其密码:
文件夹src/
文件夹brkpt-auth/
文件夹adapters/
- reset-password.adapter.ts
import { Injectable } from '@nestjs/common';import * as bcrypt from 'bcrypt';
import { User } from '../../../generated/prisma/client';import { PrismaService } from '../../prisma/prisma.service';import { ResetPasswordPort } from '../features/reset-password/reset-password.port';
@Injectable()export class ResetPasswordAdapter implements ResetPasswordPort<User> { constructor(private readonly prisma: PrismaService) {}
async findUserByTarget(method: string, target: string): Promise<User | null> { switch (method) { case 'email': return this.prisma.user.findUnique({ where: { email: target }, }); } return null; }
async updatePassword(user: User, newPassword: string): Promise<void> { const password = await bcrypt.hash(newPassword, 10); await this.prisma.user.update({ where: { id: user.id }, data: { password }, }); }
extractUserIdFromUser(user: User): number { return user.id; }}注册功能
更新 features.ts,把 ResetPasswordAdapter 传给 resetPasswordFeature:
import { BlacklistAdapter } from './adapters/blacklist.adapter';import { ChangePasswordAdapter } from './adapters/change-password.adapter';import { CoreAdapter } from './adapters/core.adapter';import { CredentialsAdapter } from './adapters/credentials.adapter';import { MagicLinkAdapter } from './adapters/magic-link.adapter';import { OAuthAdapter } from './adapters/oauth.adapter';import { OtpAdapter } from './adapters/otp.adapter';import { ResetPasswordAdapter } from './adapters/reset-password.adapter';import { SessionAdapter } from './adapters/session.adapter';import { FeatureConfig } from './common/interfaces';import { blacklistFeature } from './features/blacklist/blacklist.feature';import { changePasswordFeature } from './features/change-password/change-password.feature';import { coreFeature } from './features/core/core.feature';import { credentialsFeature } from './features/credentials/credentials.feature';import { EmailMagicLinkDriver } from './features/magic-link/drivers/email.driver';import { magicLinkFeature } from './features/magic-link/magic-link.feature';import { GithubOAuthDriver } from './features/oauth/drivers/github.driver';import { GoogleOAuthDriver } from './features/oauth/drivers/google.driver';import { oauthFeature } from './features/oauth/oauth.feature';import { EmailOtpDriver } from './features/otp/drivers/email.driver';import { otpFeature } from './features/otp/otp.feature';import { resetPasswordFeature } from './features/reset-password/reset-password.feature';import { sessionFeature } from './features/session/session.feature';
export const features: FeatureConfig[] = [ coreFeature(CoreAdapter), blacklistFeature(BlacklistAdapter), credentialsFeature(CredentialsAdapter), sessionFeature(SessionAdapter), oauthFeature(OAuthAdapter, GoogleOAuthDriver, GithubOAuthDriver), otpFeature(OtpAdapter, EmailOtpDriver), magicLinkFeature(MagicLinkAdapter, EmailMagicLinkDriver), changePasswordFeature(ChangePasswordAdapter), resetPasswordFeature(ResetPasswordAdapter),];配置 magic link 回调
Section titled “配置 magic link 回调”结合前面几篇指南的配置,OTP 可以直接使用。要让 magic link 也能用于重置密码,需要添加一个 resetPassword 回调 URL:
magicLink: { expiresIn: '5m', callbackUrls: { authenticate: 'http://localhost:3000/auth/magic-link/authenticate', resetPassword: 'http://localhost:3000/auth/reset-password/reset', },},启动应用:
pnpm start:devnpm run start:devyarn start:devreset-password 功能新增两个端点:
| Method | Path | Description |
|---|---|---|
POST |
/auth/reset-password/send |
发送密码重置验证 |
POST |
/auth/reset-password/reset |
重置密码 |
用 OTP 重置密码
使用 OTP 验证策略,发送验证目标,然后连同新密码一起提交验证凭证:
POST /auth/reset-password/sendContent-Type: application/json
{ "strategy": "otp", "method": "email"}邮件中包含用于密码重置流程的 OTP:
Subject: Your OTP code to reset your password
Your OTP code is: 127419提交 target、验证码和新密码:
POST /auth/reset-password/resetContent-Type: application/json
{ "strategy": "otp", "proof": "127419", "newPassword": "new-password"}OTP 在校验时需要 target,如果省略,请求会失败并返回 Target is required for OTP verification。
用 magic link 重置密码
使用 magic link 验证策略,链接中的令牌会被用作验证凭证:
POST /auth/reset-password/sendContent-Type: application/json
{ "strategy": "magic-link", "method": "email"}邮件中包含类似这样的链接:
Subject: Your magic link to reset your password
Click the link to continue: http://localhost:3000/auth/reset-password/reset?token=<magic-link-token>在这个纯后端的示例中,从链接中复制令牌,连同新密码一起提交:
POST /auth/reset-password/resetContent-Type: application/json
{ "strategy": "magic-link", "proof": "<magic-link-token>", "newPassword": "new-password"}magic link 验证在重置请求中不需要 target,因为已验证的 target 会从令牌中还原出来。
密码重置成功后,该用户已有的会话都会被撤销。