diff --git a/apps/backend/src/auth/auth.module.ts b/apps/backend/src/auth/auth.module.ts index 77b36147c..359dcbabf 100644 --- a/apps/backend/src/auth/auth.module.ts +++ b/apps/backend/src/auth/auth.module.ts @@ -11,16 +11,26 @@ import { NestRegisterCommandHandler, NestUpdateProfileCommandHandler, } from './commands/index.js' +import { CryptService } from './crypt.service.js' import { JwtStrategy } from './jwt.strategy.js' import { LocalStrategy } from './local.strategy.js' import { NestGetMeQueryHandler } from './queries/index.js' +import { NestUserService } from './user.service.js' const CommandHandlers = [NestLgoinCommandHandler, NestRegisterCommandHandler, NestUpdateProfileCommandHandler] const QueryHandlers = [NestGetMeQueryHandler] @Module({ imports: [CqrsModule, UserModule, AuthzModule, PassportModule, MemberModule], - providers: [AuthService, LocalStrategy, JwtStrategy, ...CommandHandlers, ...QueryHandlers], + providers: [ + AuthService, + LocalStrategy, + JwtStrategy, + ...CommandHandlers, + ...QueryHandlers, + CryptService, + NestUserService, + ], exports: [AuthService], controllers: [AuthController], }) diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index 1b6a15e23..630ece5b8 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -2,15 +2,16 @@ import { Injectable } from '@nestjs/common' import { type ConfigType } from '@nestjs/config' import { CommandBus, QueryBus } from '@nestjs/cqrs' import type { IQueryUser } from '@undb/core' -import { UserFactory, WithUserColor, WithUserEmail, WithUserId, WithUserPassword, WithUsername } from '@undb/core' +import { WithUserEmail } from '@undb/core' import { GetMeQuery, LoginCommand, RegisterCommand, UpdateProfileCommand } from '@undb/cqrs' -import * as bcrypt from 'bcrypt' import { NestMemberCreateService } from '../authz/member/member-create.service.js' import type { authConfig } from '../configs/auth.config.js' import { InjectAuthConfig } from '../configs/auth.config.js' import { UserService } from '../core/user/user.service.js' +import { CryptService } from './crypt.service.js' import { InvalidPassword } from './errors/invalid-password.error.js' import { UserNotFound } from './errors/user-not-found.error.js' +import { NestUserService } from './user.service.js' @Injectable() export class AuthService { @@ -21,6 +22,8 @@ export class AuthService { @InjectAuthConfig() private readonly config: ConfigType, private readonly memberService: NestMemberCreateService, + private readonly userService: NestUserService, + private readonly crypt: CryptService, ) {} async validateUser(email: string, pass: string): Promise { @@ -29,7 +32,7 @@ export class AuthService { throw new UserNotFound() } - const isPasswordMatch = await bcrypt.compare(pass, user.password) + const isPasswordMatch = await this.crypt.compare(pass, user.password) if (!isPasswordMatch) { throw new InvalidPassword() } @@ -37,8 +40,7 @@ export class AuthService { } async register(email: string, password: string) { - const hashedPassword = await bcrypt.hash(password, 10) - return this.commandBus.execute(new RegisterCommand({ email, password: hashedPassword })) + return this.commandBus.execute(new RegisterCommand({ email, password })) } async login(user: IQueryUser) { @@ -60,16 +62,7 @@ export class AuthService { const exists = await this.usersService.exists(email) if (exists) return - const hashedPassword = await bcrypt.hash(admin.password, 10) - const user = UserFactory.create( - WithUserEmail.fromString(admin.email), - WithUserPassword.fromString(hashedPassword), - WithUserId.create(), - WithUsername.fromEmail(admin.email), - WithUserColor.random(), - ) - - await this.usersService.insert(user) + const user = await this.userService.register(admin.email, admin.password) await this.memberService.grantDefault(user) } } diff --git a/apps/backend/src/auth/commands/register.command.handler.ts b/apps/backend/src/auth/commands/register.command.handler.ts index d587f034b..db198e797 100644 --- a/apps/backend/src/auth/commands/register.command.handler.ts +++ b/apps/backend/src/auth/commands/register.command.handler.ts @@ -3,6 +3,7 @@ import { type IUserRepository } from '@undb/core' import { RegisterCommand, RegisterCommandHandler } from '@undb/cqrs' import { NestMemberCreateService } from '../../authz/member/member-create.service.js' import { InjectUserRepository } from '../../core/user/adapters/index.js' +import { NestUserService } from '../user.service.js' @CommandHandler(RegisterCommand) export class NestRegisterCommandHandler extends RegisterCommandHandler { @@ -10,7 +11,8 @@ export class NestRegisterCommandHandler extends RegisterCommandHandler { @InjectUserRepository() protected readonly repo: IUserRepository, protected readonly memberService: NestMemberCreateService, + protected readonly userService: NestUserService, ) { - super(repo, memberService) + super(repo, memberService, userService) } } diff --git a/apps/backend/src/auth/crypt.service.ts b/apps/backend/src/auth/crypt.service.ts new file mode 100644 index 000000000..f70c1006e --- /dev/null +++ b/apps/backend/src/auth/crypt.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common' +import type { PasswordCryptor } from '@undb/core' +import * as bcrypt from 'bcrypt' + +@Injectable() +export class CryptService implements PasswordCryptor { + hash(password: string): Promise { + return bcrypt.hash(password, 10) + } + + compare(p1: string, p2: string): Promise { + return bcrypt.compare(p1, p2) + } +} diff --git a/apps/backend/src/auth/user.service.ts b/apps/backend/src/auth/user.service.ts new file mode 100644 index 000000000..4655cde83 --- /dev/null +++ b/apps/backend/src/auth/user.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common' +import { UserService, type IUserRepository } from '@undb/core' +import { InjectUserRepository } from '../core/user/adapters/index.js' +import { CryptService } from './crypt.service.js' + +@Injectable() +export class NestUserService extends UserService { + constructor(cryptor: CryptService, @InjectUserRepository() repo: IUserRepository) { + super(cryptor, repo) + } +} diff --git a/packages/core/src/user/services/index.ts b/packages/core/src/user/services/index.ts index a7de48ed6..ec6cbe4dd 100644 --- a/packages/core/src/user/services/index.ts +++ b/packages/core/src/user/services/index.ts @@ -1 +1,2 @@ export * from './anonymous.service.js' +export * from './user.service.js' diff --git a/packages/core/src/user/services/user.service.ts b/packages/core/src/user/services/user.service.ts new file mode 100644 index 000000000..c98450591 --- /dev/null +++ b/packages/core/src/user/services/user.service.ts @@ -0,0 +1,40 @@ +import { WithUserColor } from '../specifications/user-color.js' +import { WithUserEmail } from '../specifications/user-email.specification.js' +import { WithUserId } from '../specifications/user-id.specification.js' +import { WithUserPassword } from '../specifications/user-password.specification.js' +import { WithUsername } from '../specifications/username.specification.js' +import { UserFactory } from '../user.factory.js' +import type { User } from '../user.js' +import type { IUserRepository } from '../user.repository.js' + +export interface IUserService { + register(email: string, password: string): Promise +} + +export interface PasswordCryptor { + hash(password: string): Promise + compare(p1: string, p2: string): Promise +} + +export class UserService implements IUserService { + constructor( + protected readonly cryptor: PasswordCryptor, + protected readonly repo: IUserRepository, + ) {} + + async register(email: string, password: string): Promise { + const hashedPassword = await this.cryptor.hash(password) + + const user = UserFactory.create( + WithUserEmail.fromString(email), + WithUserPassword.fromString(hashedPassword), + WithUserId.create(), + WithUsername.fromEmail(email), + WithUserColor.random(), + ) + + await this.repo.insert(user) + + return user + } +} diff --git a/packages/cqrs/src/commands/login/login.command.handler.ts b/packages/cqrs/src/commands/login/login.command.handler.ts index c11b955bb..877a993b2 100644 --- a/packages/cqrs/src/commands/login/login.command.handler.ts +++ b/packages/cqrs/src/commands/login/login.command.handler.ts @@ -1,5 +1,5 @@ import type { ICommandHandler } from '@undb/domain' -import { ILoginCommandOutput } from './login.command.interface.js' +import type { ILoginCommandOutput } from './login.command.interface.js' import type { LoginCommand } from './login.command.js' type ILoginCommandHandler = ICommandHandler diff --git a/packages/cqrs/src/commands/register/register.command.handler.ts b/packages/cqrs/src/commands/register/register.command.handler.ts index 1e65552e9..ae0492536 100644 --- a/packages/cqrs/src/commands/register/register.command.handler.ts +++ b/packages/cqrs/src/commands/register/register.command.handler.ts @@ -1,6 +1,6 @@ import type { IMemberCreateService } from '@undb/authz' -import type { IUserRepository } from '@undb/core' -import { UserFactory, WithUserColor, WithUserEmail, WithUserId, WithUserPassword, WithUsername } from '@undb/core' +import type { IUserRepository, IUserService } from '@undb/core' +import { WithUserEmail } from '@undb/core' import type { ICommandHandler } from '@undb/domain' import type { IRegisterCommandOutput } from './register.command.interface.js' import type { RegisterCommand } from './register.command.js' @@ -11,22 +11,14 @@ export class RegisterCommandHandler implements IRegisterCommandHandler { constructor( protected readonly repo: IUserRepository, protected readonly memberService: IMemberCreateService, + protected readonly svc: IUserService, ) {} async execute({ email, password }: RegisterCommand): Promise { const exists = await this.repo.exists(WithUserEmail.fromString(email)) if (exists) throw new Error('user already exists') - const user = UserFactory.create( - WithUserEmail.fromString(email), - WithUserPassword.fromString(password), - WithUserId.create(), - WithUsername.fromEmail(email), - WithUserColor.random(), - ) - - await this.repo.insert(user) - await this.memberService.grantDefault(user) + const user = await this.svc.register(email, password) return { email, sub: user.userId.value } }