diff --git a/api/src/modules/auth/auth.module.ts b/api/src/modules/auth/auth.module.ts index 95745c4e..840e7eff 100644 --- a/api/src/modules/auth/auth.module.ts +++ b/api/src/modules/auth/auth.module.ts @@ -1,14 +1,14 @@ import { Module } from '@nestjs/common'; -import { PasswordRecoveryService } from '@api/modules/auth/services/password-recovery.service'; import { AuthMailer } from '@api/modules/auth/services/auth.mailer'; import { NotificationsModule } from '@api/modules/notifications/notifications.module'; import { AuthenticationController } from '@api/modules/auth/authentication.controller'; import { AuthenticationModule } from '@api/modules/auth/authentication.module'; +import { RequestPasswordRecoveryHandler } from '@api/modules/auth/commands/request-password-recovery.handler'; @Module({ imports: [AuthenticationModule, NotificationsModule], controllers: [AuthenticationController], - providers: [PasswordRecoveryService, AuthMailer], + providers: [AuthMailer, RequestPasswordRecoveryHandler], exports: [AuthenticationModule, AuthMailer], }) export class AuthModule {} diff --git a/api/src/modules/auth/authentication.controller.ts b/api/src/modules/auth/authentication.controller.ts index 9e67eaaf..1b48b38f 100644 --- a/api/src/modules/auth/authentication.controller.ts +++ b/api/src/modules/auth/authentication.controller.ts @@ -5,26 +5,27 @@ import { UseInterceptors, ClassSerializerInterceptor, HttpStatus, - UnauthorizedException, } from '@nestjs/common'; import { User } from '@shared/entities/users/user.entity'; import { LocalAuthGuard } from '@api/modules/auth/guards/local-auth.guard'; import { GetUser } from '@api/modules/auth/decorators/get-user.decorator'; import { Public } from '@api/modules/auth/decorators/is-public.decorator'; -import { PasswordRecoveryService } from '@api/modules/auth/services/password-recovery.service'; import { tsRestHandler, TsRestHandler } from '@ts-rest/nest'; import { ControllerResponse } from '@api/types/controller-response.type'; import { AuthGuard } from '@nestjs/passport'; import { ResetPassword } from '@api/modules/auth/strategies/reset-password.strategy'; import { authContract } from '@shared/contracts/auth.contract'; import { AuthenticationService } from '@api/modules/auth/authentication.service'; +import { SignUp } from '@api/modules/auth/strategies/sign-up.strategy'; +import { CommandBus } from '@nestjs/cqrs'; +import { RequestPasswordRecoveryCommand } from '@api/modules/auth/commands/request-password-recovery.command'; @Controller() @UseInterceptors(ClassSerializerInterceptor) export class AuthenticationController { constructor( private authService: AuthenticationService, - private readonly passwordRecovery: PasswordRecoveryService, + private readonly commandBus: CommandBus, ) {} @Public() @@ -40,18 +41,27 @@ export class AuthenticationController { }); } + @UseGuards(AuthGuard(SignUp)) + @TsRestHandler(authContract.signUp) + async signUp(@GetUser() user: User): Promise { + return tsRestHandler(authContract.signUp, async ({ body }) => { + await this.authService.signUp(user, body); + return { + body: null, + status: 201, + }; + }); + } + @UseGuards(AuthGuard(ResetPassword)) @TsRestHandler(authContract.resetPassword) async resetPassword(@GetUser() user: User): Promise { return tsRestHandler( authContract.resetPassword, async ({ body: { password } }) => { - const userWithAccessToken = await this.passwordRecovery.resetPassword( - user, - password, - ); + await this.authService.updatePassword(user, password); return { - body: userWithAccessToken, + body: null, status: 201, }; }, @@ -65,7 +75,9 @@ export class AuthenticationController { return tsRestHandler( authContract.requestPasswordRecovery, async ({ body: { email } }) => { - await this.passwordRecovery.requestPasswordRecovery(email, origin); + await this.commandBus.execute( + new RequestPasswordRecoveryCommand(email), + ); return { body: null, status: HttpStatus.CREATED, diff --git a/api/src/modules/auth/authentication.module.ts b/api/src/modules/auth/authentication.module.ts index 83e4acea..a1621b97 100644 --- a/api/src/modules/auth/authentication.module.ts +++ b/api/src/modules/auth/authentication.module.ts @@ -11,6 +11,7 @@ import { JwtStrategy } from '@api/modules/auth/strategies/jwt.strategy'; import { TOKEN_TYPE_ENUM } from '@shared/schemas/auth/token-type.schema'; import { ResetPasswordJwtStrategy } from '@api/modules/auth/strategies/reset-password.strategy'; import { JwtManager } from '@api/modules/auth/services/jwt.manager'; +import { SignUpStrategy } from '@api/modules/auth/strategies/sign-up.strategy'; @Module({ imports: [ @@ -46,6 +47,13 @@ import { JwtManager } from '@api/modules/auth/services/jwt.manager'; }, inject: [UsersService, ApiConfigService], }, + { + provide: SignUpStrategy, + useFactory: (users: UsersService, config: ApiConfigService) => { + return new SignUpStrategy(users, config); + }, + inject: [UsersService, ApiConfigService], + }, ], exports: [UsersModule, AuthenticationService, JwtManager], }) diff --git a/api/src/modules/auth/authentication.service.ts b/api/src/modules/auth/authentication.service.ts index df603b8a..b10d84a4 100644 --- a/api/src/modules/auth/authentication.service.ts +++ b/api/src/modules/auth/authentication.service.ts @@ -2,13 +2,15 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from '@api/modules/users/users.service'; import { User } from '@shared/entities/users/user.entity'; import * as bcrypt from 'bcrypt'; -import { CommandBus } from '@nestjs/cqrs'; +import { CommandBus, EventBus } from '@nestjs/cqrs'; import { UserWithAccessToken } from '@shared/dtos/user.dto'; import { TOKEN_TYPE_ENUM } from '@shared/schemas/auth/token-type.schema'; import { CreateUserDto } from '@shared/schemas/users/create-user.schema'; import { randomBytes } from 'node:crypto'; import { SendWelcomeEmailCommand } from '@api/modules/notifications/email/commands/send-welcome-email.command'; import { JwtManager } from '@api/modules/auth/services/jwt.manager'; +import { SignUpDto } from '@shared/schemas/auth/sign-up.schema'; +import { UserSignedUpEvent } from '@api/modules/events/user-events/user-signed-up.event'; @Injectable() export class AuthenticationService { @@ -16,6 +18,7 @@ export class AuthenticationService { private readonly usersService: UsersService, private readonly jwtManager: JwtManager, private readonly commandBus: CommandBus, + private readonly eventBus: EventBus, ) {} async validateUser(email: string, password: string): Promise { const user = await this.usersService.findByEmail(email); @@ -47,10 +50,24 @@ export class AuthenticationService { return { user, accessToken }; } + async signUp(user: User, signUpDto: SignUpDto): Promise { + const { password, newPassword } = signUpDto; + if (!(await bcrypt.compare(password, user.password))) { + throw new UnauthorizedException(); + } + user.isActive = true; + await this.usersService.updatePassword(user, newPassword); + this.eventBus.publish(new UserSignedUpEvent(user.id, user.email)); + } + async verifyToken(token: string, type: TOKEN_TYPE_ENUM): Promise { if (await this.jwtManager.isTokenValid(token, type)) { return true; } throw new UnauthorizedException(); } + + async updatePassword(user: User, newPassword: string): Promise { + await this.usersService.updatePassword(user, newPassword); + } } diff --git a/api/src/modules/auth/commands/request-password-recovery.command.ts b/api/src/modules/auth/commands/request-password-recovery.command.ts new file mode 100644 index 00000000..32ad4cab --- /dev/null +++ b/api/src/modules/auth/commands/request-password-recovery.command.ts @@ -0,0 +1,3 @@ +export class RequestPasswordRecoveryCommand { + constructor(public readonly email: string) {} +} diff --git a/api/src/modules/auth/services/password-recovery.service.ts b/api/src/modules/auth/commands/request-password-recovery.handler.ts similarity index 50% rename from api/src/modules/auth/services/password-recovery.service.ts rename to api/src/modules/auth/commands/request-password-recovery.handler.ts index f3df1478..46776d84 100644 --- a/api/src/modules/auth/services/password-recovery.service.ts +++ b/api/src/modules/auth/commands/request-password-recovery.handler.ts @@ -1,38 +1,32 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { UsersService } from '@api/modules/users/users.service'; +import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs'; import { AuthMailer } from '@api/modules/auth/services/auth.mailer'; -import { EventBus } from '@nestjs/cqrs'; +import { RequestPasswordRecoveryCommand } from '@api/modules/auth/commands/request-password-recovery.command'; +import { UsersService } from '@api/modules/users/users.service'; import { PasswordRecoveryRequestedEvent } from '@api/modules/events/user-events/password-recovery-requested.event'; -import { User } from '@shared/entities/users/user.entity'; -import * as bcrypt from 'bcrypt'; +import { NotFoundException } from '@nestjs/common'; -@Injectable() -export class PasswordRecoveryService { - logger: Logger = new Logger(PasswordRecoveryService.name); +@CommandHandler(RequestPasswordRecoveryCommand) +export class RequestPasswordRecoveryHandler + implements ICommandHandler +{ constructor( private readonly users: UsersService, private readonly authMailer: AuthMailer, private readonly eventBus: EventBus, ) {} - async requestPasswordRecovery(email: string, origin: string): Promise { + async execute(command: RequestPasswordRecoveryCommand): Promise { + const { email } = command; const user = await this.users.findByEmail(email); if (!user) { - this.logger.warn( - `Email ${email} not found when trying to recover password`, - ); this.eventBus.publish(new PasswordRecoveryRequestedEvent(email, null)); - return; + throw new NotFoundException(`Email ${email} not found`); } await this.authMailer.sendPasswordRecoveryEmail({ user, - origin, + // TODO: Origin must come from env vars + origin: 'http://localhost:3000', }); this.eventBus.publish(new PasswordRecoveryRequestedEvent(email, user.id)); } - - async resetPassword(user: User, newPassword: string): Promise { - const newHashedPassword = await bcrypt.hash(newPassword, 10); - await this.users.updatePassword(user, newHashedPassword); - } } diff --git a/api/src/modules/auth/services/auth.mailer.ts b/api/src/modules/auth/services/auth.mailer.ts index a646f98c..43a413fa 100644 --- a/api/src/modules/auth/services/auth.mailer.ts +++ b/api/src/modules/auth/services/auth.mailer.ts @@ -51,12 +51,13 @@ export class AuthMailer { user: User; defaultPassword: string; }) { - const { emailConfirmationToken, expiresIn } = - await this.jwt.signEmailConfirmationToken(welcomeEmailDto.user.id); + const { signUpToken, expiresIn } = await this.jwt.signSignUpToken( + welcomeEmailDto.user.id, + ); // TODO: We need to know the URL to confirm the email, we could rely on origin but we would need to pass it through a lot of code. // probably better to have a config value for this. - const resetPasswordUrl = `TODO/auth/sign-up/${emailConfirmationToken}`; + const resetPasswordUrl = `TODO/auth/sign-up/${signUpToken}`; const htmlContent: string = `

Dear User,

diff --git a/api/src/modules/auth/services/jwt.manager.ts b/api/src/modules/auth/services/jwt.manager.ts index e06da730..6f556a6c 100644 --- a/api/src/modules/auth/services/jwt.manager.ts +++ b/api/src/modules/auth/services/jwt.manager.ts @@ -53,15 +53,15 @@ export class JwtManager { }; } - async signEmailConfirmationToken( + async signSignUpToken( userId: string, - ): Promise<{ emailConfirmationToken: string; expiresIn: string }> { - const { token: emailConfirmationToken, expiresIn } = await this.sign( + ): Promise<{ signUpToken: string; expiresIn: string }> { + const { token: signUpToken, expiresIn } = await this.sign( userId, - TOKEN_TYPE_ENUM.EMAIL_CONFIRMATION, + TOKEN_TYPE_ENUM.SIGN_UP, ); return { - emailConfirmationToken, + signUpToken: signUpToken, expiresIn, }; } @@ -71,7 +71,7 @@ export class JwtManager { try { const { id } = await this.jwt.verifyAsync(token, { secret }); switch (type) { - case TOKEN_TYPE_ENUM.EMAIL_CONFIRMATION: + case TOKEN_TYPE_ENUM.SIGN_UP: /** * If the user is already active, we don't want to allow them to confirm their email again. */ diff --git a/api/src/modules/auth/strategies/jwt.strategy.ts b/api/src/modules/auth/strategies/jwt.strategy.ts index 16a1786c..66f2dbca 100644 --- a/api/src/modules/auth/strategies/jwt.strategy.ts +++ b/api/src/modules/auth/strategies/jwt.strategy.ts @@ -23,7 +23,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { async validate(payload: JwtPayload) { const { id } = payload; const user = await this.userService.findOneBy(id); - if (!user) { + if (!user || !user.isActive) { throw new UnauthorizedException(); } return user; diff --git a/api/src/modules/auth/strategies/sign-up.strategy.ts b/api/src/modules/auth/strategies/sign-up.strategy.ts new file mode 100644 index 00000000..b3d81331 --- /dev/null +++ b/api/src/modules/auth/strategies/sign-up.strategy.ts @@ -0,0 +1,33 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UsersService } from '@api/modules/users/users.service'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; +import { TOKEN_TYPE_ENUM } from '@shared/schemas/auth/token-type.schema'; + +export type JwtPayload = { id: string }; + +export const SignUp = 'sign-up'; + +@Injectable() +export class SignUpStrategy extends PassportStrategy(Strategy, SignUp) { + constructor( + private readonly userService: UsersService, + private readonly config: ApiConfigService, + ) { + const { secret } = config.getJWTConfigByType(TOKEN_TYPE_ENUM.SIGN_UP); + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: secret, + }); + } + + async validate(payload: JwtPayload) { + const { id } = payload; + const user = await this.userService.findOneBy(id); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} diff --git a/api/src/modules/config/auth-config.handler.ts b/api/src/modules/config/auth-config.handler.ts index 8f8b6865..c48e22ed 100644 --- a/api/src/modules/config/auth-config.handler.ts +++ b/api/src/modules/config/auth-config.handler.ts @@ -25,7 +25,7 @@ export class JwtConfigHandler { ), }; - case TOKEN_TYPE_ENUM.EMAIL_CONFIRMATION: + case TOKEN_TYPE_ENUM.SIGN_UP: return { secret: this.configService.get( 'EMAIL_CONFIRMATION_TOKEN_SECRET', diff --git a/api/src/modules/events/events.enum.ts b/api/src/modules/events/events.enum.ts index 60c25cb0..22b87e9c 100644 --- a/api/src/modules/events/events.enum.ts +++ b/api/src/modules/events/events.enum.ts @@ -1,8 +1,8 @@ export enum API_EVENT_TYPES { USER_SIGNED_UP = 'user.signed_up', + USER_CREATED = 'user.created', USER_PASSWORD_RECOVERY_REQUESTED = 'user.password_recovery_requested', USER_PASSWORD_RECOVERY_REQUESTED_NON_EXISTENT = 'user.password_recovery_requested_non_existent', - EMAIL_FAILED = 'system.email.failed', // More events to come.... } diff --git a/api/src/modules/users/users.service.ts b/api/src/modules/users/users.service.ts index f8cf45b0..d064149a 100644 --- a/api/src/modules/users/users.service.ts +++ b/api/src/modules/users/users.service.ts @@ -2,6 +2,7 @@ import { ConflictException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from '@shared/entities/users/user.entity'; import { Repository } from 'typeorm'; +import * as bcrypt from 'bcrypt'; @Injectable() export class UsersService { @@ -26,7 +27,7 @@ export class UsersService { } async updatePassword(user: User, newPassword: string) { - user.password = newPassword; + user.password = await bcrypt.hash(newPassword, 10); return this.repo.save(user); } diff --git a/api/test/e2e/features/password-recovery-send-email.feature b/api/test/e2e/features/password-recovery-send-email.feature index 00a7f99c..1ec3d7b9 100644 --- a/api/test/e2e/features/password-recovery-send-email.feature +++ b/api/test/e2e/features/password-recovery-send-email.feature @@ -8,5 +8,5 @@ Feature: Password Recovery Scenario: No email should be sent if the user is not found When the user requests password recovery with an invalid email - Then the user should receive a 201 status code + Then the user should receive a 404 status code And no email should be sent diff --git a/api/test/e2e/steps/password-recovery-reset-email.steps.ts b/api/test/e2e/steps/password-recovery-reset-email.steps.ts index becd0c44..f1254b42 100644 --- a/api/test/e2e/steps/password-recovery-reset-email.steps.ts +++ b/api/test/e2e/steps/password-recovery-reset-email.steps.ts @@ -28,6 +28,9 @@ describe('Reset Password', () => { resetPasswordSecret = apiConfigService.getJWTConfigByType( TOKEN_TYPE_ENUM.RESET_PASSWORD, ).secret; + }); + + afterEach(async () => { await testManager.clearDatabase(); }); diff --git a/api/test/integration/auth/create-user.spec.ts b/api/test/integration/auth/create-user.spec.ts index 2443d652..79e6a029 100644 --- a/api/test/integration/auth/create-user.spec.ts +++ b/api/test/integration/auth/create-user.spec.ts @@ -37,9 +37,10 @@ describe('Create Users', () => { // Given a user exists with valid credentials // But the user has the role partner - const user = await testManager - .mocks() - .createUser({ role: ROLES.PARTNER, email: 'random@test.com' }); + const user = await testManager.mocks().createUser({ + role: ROLES.PARTNER, + email: 'random@test.com', + }); const { jwtToken } = await testManager.logUserIn(user); // When the user creates a new user @@ -72,7 +73,7 @@ describe('Create Users', () => { ); }); - test('An Admin registers a new user', async () => { + test('An Admin registers a new user ', async () => { // Given a admin user exists with valid credentials // beforeAll const newUser = { diff --git a/api/test/integration/auth/sign-up.spec.ts b/api/test/integration/auth/sign-up.spec.ts index 0a5a5cec..9f28e3b3 100644 --- a/api/test/integration/auth/sign-up.spec.ts +++ b/api/test/integration/auth/sign-up.spec.ts @@ -1,22 +1,20 @@ import { TestManager } from '../../utils/test-manager'; import { HttpStatus } from '@nestjs/common'; -import { ApiConfigService } from '@api/modules/config/app-config.service'; -import { JwtService } from '@nestjs/jwt'; import { TOKEN_TYPE_ENUM } from '@shared/schemas/auth/token-type.schema'; import { authContract } from '@shared/contracts/auth.contract'; import { ROLES } from '@api/modules/auth/roles.enum'; +import { JwtManager } from '@api/modules/auth/services/jwt.manager'; +import { User } from '@shared/entities/users/user.entity'; //create-user.feature describe('Create Users', () => { let testManager: TestManager; - let apiConfig: ApiConfigService; - let jwtService: JwtService; + let jwtManager: JwtManager; beforeAll(async () => { testManager = await TestManager.createTestManager(); - apiConfig = testManager.getModule(ApiConfigService); - jwtService = testManager.getModule(JwtService); + jwtManager = testManager.getModule(JwtManager); }); afterEach(async () => { @@ -30,26 +28,69 @@ describe('Create Users', () => { test('A sign-up token should not be valid if the user bound to that token has already been activated', async () => { // Given a user exists with valid credentials // But the user has the role partner - const user = await testManager.mocks().createUser({ role: ROLES.PARTNER, email: 'random@test.com', - isActive: true, }); - const { secret, expiresIn } = apiConfig.getJWTConfigByType( - TOKEN_TYPE_ENUM.EMAIL_CONFIRMATION, - ); - const token = jwtService.sign({ id: user.id }, { secret, expiresIn }); + const { signUpToken } = await jwtManager.signSignUpToken(user.id); // When the user creates a new user const response = await testManager .request() .get(authContract.validateToken.path) - .set('Authorization', `Bearer ${token}`) - .query({ tokenType: TOKEN_TYPE_ENUM.EMAIL_CONFIRMATION }); + .set('Authorization', `Bearer ${signUpToken}`) + .query({ tokenType: TOKEN_TYPE_ENUM.SIGN_UP }); expect(response.status).toBe(HttpStatus.UNAUTHORIZED); }); + + test('Sign up should fail if the auto-generated password is incorrect', async () => { + const user = await testManager.mocks().createUser({ + role: ROLES.PARTNER, + email: 'random@test.com', + isActive: false, + }); + const { signUpToken } = await jwtManager.signSignUpToken(user.id); + + const response = await testManager + .request() + .post(authContract.signUp.path) + .set('Authorization', `Bearer ${signUpToken}`) + .query({ tokenType: TOKEN_TYPE_ENUM.SIGN_UP }) + .send({ password: 'wrongpassword', newPassword: 'newpassword' }); + + expect(response.status).toBe(HttpStatus.UNAUTHORIZED); + }); + + test('Sign up should succeed if the auto-generated password is correct and the user should be activated and allowed to get a access token', async () => { + const user = await testManager.mocks().createUser({ + role: ROLES.PARTNER, + email: 'test@test.com', + isActive: false, + }); + const { signUpToken } = await jwtManager.signSignUpToken(user.id); + + const response = await testManager + .request() + .post(authContract.signUp.path) + .set('Authorization', `Bearer ${signUpToken}`) + .send({ password: user.password, newPassword: 'newpassword' }); + + expect(response.status).toBe(HttpStatus.CREATED); + const foundUser = await testManager + .getDataSource() + .getRepository(User) + .findOneBy({ id: user.id }); + + expect(foundUser.isActive).toBe(true); + + const login = await testManager + .request() + .post(authContract.login.path) + .send({ email: user.email, password: 'newpassword' }); + + expect(login.body.accessToken).toBeDefined(); + }); }); diff --git a/api/test/utils/mocks/entity-mocks.ts b/api/test/utils/mocks/entity-mocks.ts index 7748b856..ca050b05 100644 --- a/api/test/utils/mocks/entity-mocks.ts +++ b/api/test/utils/mocks/entity-mocks.ts @@ -12,6 +12,7 @@ export const createUser = async ( email: 'test@user.com', ...additionalData, password: await hash(usedPassword, salt), + isActive: true, }; await dataSource.getRepository(User).save(user); diff --git a/api/test/utils/test-manager.ts b/api/test/utils/test-manager.ts index aa7d49f3..ced8e63f 100644 --- a/api/test/utils/test-manager.ts +++ b/api/test/utils/test-manager.ts @@ -73,7 +73,10 @@ export class TestManager { } async setUpTestUser() { - const user = await createUser(this.getDataSource(), { role: ROLES.ADMIN }); + const user = await createUser(this.getDataSource(), { + role: ROLES.ADMIN, + isActive: true, + }); return logUserIn(this, user); } diff --git a/shared/contracts/auth.contract.ts b/shared/contracts/auth.contract.ts index 2c477c60..8daca1af 100644 --- a/shared/contracts/auth.contract.ts +++ b/shared/contracts/auth.contract.ts @@ -5,6 +5,7 @@ import { JSONAPIError } from "@shared/dtos/json-api.error"; import { TokenTypeSchema } from "@shared/schemas/auth/token-type.schema"; import { z } from "zod"; import { BearerTokenSchema } from "@shared/schemas/auth/bearer-token.schema"; +import { SignUpSchema } from "@shared/schemas/auth/sign-up.schema"; // TODO: This is a scaffold. We need to define types for responses, zod schemas for body and query param validation etc. @@ -19,6 +20,15 @@ export const authContract = contract.router({ }, body: LogInSchema, }, + signUp: { + method: "POST", + path: "/authentication/sign-up", + responses: { + 201: contract.type(), + 401: contract.type(), + }, + body: SignUpSchema, + }, validateToken: { method: "GET", path: "/authentication/validate-token", diff --git a/shared/schemas/auth/sign-up.schema.ts b/shared/schemas/auth/sign-up.schema.ts new file mode 100644 index 00000000..42ba70da --- /dev/null +++ b/shared/schemas/auth/sign-up.schema.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; +import { CreateUserSchema } from "@shared/schemas/users/create-user.schema"; + +export const SignUpSchema = z.object({ + password: z + .string({ message: "Password is required" }) + .min(1, "Password is required") + .min(8, "Password must be more than 8 characters") + .max(32, "Password must be less than 32 characters"), + newPassword: z + .string({ message: "Password is required" }) + .min(1, "Password is required") + .min(8, "Password must be more than 8 characters") + .max(32, "Password must be less than 32 characters"), +}); + +export type SignUpDto = z.infer; diff --git a/shared/schemas/auth/token-type.schema.ts b/shared/schemas/auth/token-type.schema.ts index fda0a032..5ce98be0 100644 --- a/shared/schemas/auth/token-type.schema.ts +++ b/shared/schemas/auth/token-type.schema.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export enum TOKEN_TYPE_ENUM { ACCESS = "access", RESET_PASSWORD = "reset-password", - EMAIL_CONFIRMATION = "email-confirmation", + SIGN_UP = "sign-up", } export const TokenTypeSchema = z.object({