feat(identity-service): 添加密码设置和短信验证功能
- 添加 bcrypt 依赖用于密码哈希 - 添加 passwordHash 字段到 UserAccount 模型 - 添加 VerifySmsCodeCommand 和 SetPasswordCommand - 添加 VerifySmsCodeDto 和 SetPasswordDto - 添加数据库迁移 add_password_hash 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2662409d80
commit
9452d14962
|
|
@ -25,6 +25,7 @@
|
|||
"@prisma/client": "^5.7.0",
|
||||
"@scure/bip32": "^1.3.2",
|
||||
"@scure/bip39": "^1.2.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"bech32": "^2.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
|
|
@ -43,6 +44,7 @@
|
|||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/jsonwebtoken": "^9.0.0",
|
||||
|
|
@ -2689,6 +2691,16 @@
|
|||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bcrypt": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||
"integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
|
|
@ -3898,6 +3910,20 @@
|
|||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/bech32": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
|
|
@ -8357,6 +8383,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
||||
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-emoji": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
|
||||
|
|
@ -8387,6 +8422,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"@prisma/client": "^5.7.0",
|
||||
"@scure/bip32": "^1.3.2",
|
||||
"@scure/bip39": "^1.2.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"bech32": "^2.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
|
|
@ -62,6 +63,7 @@
|
|||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/jsonwebtoken": "^9.0.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable: Add password_hash column to user_accounts
|
||||
ALTER TABLE "user_accounts" ADD COLUMN "password_hash" VARCHAR(100);
|
||||
|
|
@ -12,6 +12,7 @@ model UserAccount {
|
|||
accountSequence String @unique @map("account_sequence") @db.VarChar(12) // 格式: D + YYMMDD + 5位序号
|
||||
|
||||
phoneNumber String? @unique @map("phone_number") @db.VarChar(20)
|
||||
passwordHash String? @map("password_hash") @db.VarChar(100) // bcrypt 哈希密码
|
||||
nickname String @db.VarChar(100)
|
||||
avatarUrl String? @map("avatar_url") @db.Text
|
||||
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ export * from './unfreeze-account.dto';
|
|||
export * from './request-key-rotation.dto';
|
||||
export * from './generate-backup-codes.dto';
|
||||
export * from './recover-by-backup-code.dto';
|
||||
export * from './verify-sms-code.dto';
|
||||
export * from './set-password.dto';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { IsString, MinLength, MaxLength, Matches } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class SetPasswordDto {
|
||||
@ApiProperty({
|
||||
example: 'Abc123456',
|
||||
description: '登录密码,6-20位,包含字母和数字',
|
||||
})
|
||||
@IsString()
|
||||
@MinLength(6, { message: '密码长度至少6位' })
|
||||
@MaxLength(20, { message: '密码长度不能超过20位' })
|
||||
@Matches(/^(?=.*[A-Za-z])(?=.*\d).+$/, {
|
||||
message: '密码需包含字母和数字',
|
||||
})
|
||||
password: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { IsString, Matches, IsIn } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class VerifySmsCodeDto {
|
||||
@ApiProperty({ example: '13800138000', description: '手机号' })
|
||||
@IsString()
|
||||
@Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' })
|
||||
phoneNumber: string;
|
||||
|
||||
@ApiProperty({ example: '123456', description: '6位验证码' })
|
||||
@IsString()
|
||||
@Matches(/^\d{6}$/, { message: '验证码格式错误' })
|
||||
smsCode: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'REGISTER',
|
||||
description: '验证码类型',
|
||||
enum: ['REGISTER', 'LOGIN', 'BIND', 'RECOVER'],
|
||||
})
|
||||
@IsString()
|
||||
@IsIn(['REGISTER', 'LOGIN', 'BIND', 'RECOVER'], { message: '无效的验证码类型' })
|
||||
type: string;
|
||||
}
|
||||
|
|
@ -157,6 +157,21 @@ export class MarkMnemonicBackedUpCommand {
|
|||
constructor(public readonly userId: string) {}
|
||||
}
|
||||
|
||||
export class VerifySmsCodeCommand {
|
||||
constructor(
|
||||
public readonly phoneNumber: string,
|
||||
public readonly smsCode: string,
|
||||
public readonly type: 'REGISTER' | 'LOGIN' | 'BIND' | 'RECOVER',
|
||||
) {}
|
||||
}
|
||||
|
||||
export class SetPasswordCommand {
|
||||
constructor(
|
||||
public readonly userId: string,
|
||||
public readonly password: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
// ============ Results ============
|
||||
|
||||
// 钱包状态
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ export interface UserAccountEntity {
|
|||
userId: bigint;
|
||||
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||
phoneNumber: string | null;
|
||||
passwordHash: string | null; // bcrypt 哈希密码
|
||||
nickname: string;
|
||||
avatarUrl: string | null;
|
||||
inviterSequence: string | null; // 格式: D + YYMMDD + 5位序号
|
||||
|
|
|
|||
Loading…
Reference in New Issue