diff --git a/backend/src/entities/email-verification.entity.ts b/backend/src/entities/email-verification.entity.ts new file mode 100644 index 0000000..f49655f --- /dev/null +++ b/backend/src/entities/email-verification.entity.ts @@ -0,0 +1,33 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +import { UserCredentials } from './user-credentials.entity'; + +@Entity() +export class EmailVerification { + @PrimaryGeneratedColumn('uuid') + public id: string; + + @Column() + public token: string; + + @Column() + public expiresAt: Date; + + @OneToOne(() => UserCredentials) + @JoinColumn({ name: 'userCredentialsId' }) + public user: UserCredentials; + + @CreateDateColumn() + public createdAt: Date; + + @UpdateDateColumn() + public updatedAt: Date; +} diff --git a/backend/src/entities/index.ts b/backend/src/entities/index.ts index 6bca565..6d8bde7 100644 --- a/backend/src/entities/index.ts +++ b/backend/src/entities/index.ts @@ -1,2 +1,3 @@ export * from './user-credentials.entity'; export * from './user-data.entity'; +export * from './email-verification.entity'; diff --git a/backend/src/modules/auth-module/auth.module.ts b/backend/src/modules/auth-module/auth.module.ts index 2d14cd7..5688f34 100644 --- a/backend/src/modules/auth-module/auth.module.ts +++ b/backend/src/modules/auth-module/auth.module.ts @@ -5,6 +5,7 @@ import { UserCredentials } from 'src/entities'; import { SendgridModule } from '../sendgrid-module/sendgrid.module'; import { UserModule } from '../user-module/user.module'; +import { VerifyModule } from '../verify-module/verify.module'; import { AuthController } from './controller/auth.controller'; import { UserCredentialsRepository } from './repositories/user-credentials.repository'; @@ -17,6 +18,7 @@ import { AccessTokenStrategy, RefreshTokenStrategy } from './strategies'; imports: [ UserModule, SendgridModule, + VerifyModule, JwtModule.register({}), TypeOrmModule.forFeature([UserCredentials]), ], diff --git a/backend/src/modules/auth-module/services/auth.service.ts b/backend/src/modules/auth-module/services/auth.service.ts index 53f6d24..5bc70eb 100644 --- a/backend/src/modules/auth-module/services/auth.service.ts +++ b/backend/src/modules/auth-module/services/auth.service.ts @@ -31,12 +31,15 @@ export class AuthService { await this.userDataRepository.createInitialUserData(user); - // TODO Send email confirmation - // await this.passwordConfirmationMailService.sendPasswordConfirmationMail( - // user.email - // ); + const token = + await this.emailVerificationService.generateEmailVerificationToken( + user.id + ); - // await this.emailVerificationService.generateEmailVerificationToken(user.id); + await this.passwordConfirmationMailService.sendPasswordConfirmationMail( + user.email, + token + ); return this.generateAndPersistTokens(user.id, user.email); } diff --git a/backend/src/modules/database-module/database-config.ts b/backend/src/modules/database-module/database-config.ts index e355798..066f829 100644 --- a/backend/src/modules/database-module/database-config.ts +++ b/backend/src/modules/database-module/database-config.ts @@ -1,6 +1,6 @@ import { ConfigService } from '@nestjs/config'; import { TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { UserCredentials, UserData } from 'src/entities'; +import { EmailVerification, UserCredentials, UserData } from 'src/entities'; export const databaseConfigFactory = ( configService: ConfigService @@ -13,5 +13,5 @@ export const databaseConfigFactory = ( database: configService.get('DB_NAME'), synchronize: true, logging: true, - entities: [UserCredentials, UserData], + entities: [UserCredentials, UserData, EmailVerification], }); diff --git a/backend/src/modules/sendgrid-module/services/password-confirmation.mail.service.ts b/backend/src/modules/sendgrid-module/services/password-confirmation.mail.service.ts index 53af5aa..de78836 100644 --- a/backend/src/modules/sendgrid-module/services/password-confirmation.mail.service.ts +++ b/backend/src/modules/sendgrid-module/services/password-confirmation.mail.service.ts @@ -16,18 +16,23 @@ export class PasswordConfirmationMailService extends BaseMailService { super(sendGridApiKey); } - public async sendPasswordConfirmationMail(to: string): Promise { + public async sendPasswordConfirmationMail( + to: string, + token: string + ): Promise { const templateId: string = this.templateConfigService.getTemplateId( this.PASSWORD_CONFIRMATION_EMAIL ); + const encodedToken = encodeURIComponent(token); + const mailoptions: SendGridMailApi.MailDataRequired = { to, from: { email: 'info@igor-propisnov.com', name: 'Ticket App' }, templateId: templateId, dynamicTemplateData: { name: 'Mara', - buttonUrl: 'https://igor-propisnov.com', + buttonUrl: `http://localhost:4200/?token=${encodedToken}`, }, }; diff --git a/backend/src/modules/verify-module/repositories/email-verify.repository.ts b/backend/src/modules/verify-module/repositories/email-verify.repository.ts new file mode 100644 index 0000000..5fd075e --- /dev/null +++ b/backend/src/modules/verify-module/repositories/email-verify.repository.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EmailVerification } from 'src/entities'; +import { Repository } from 'typeorm'; + +@Injectable() +export class EmailVerifyRepository { + public constructor( + @InjectRepository(EmailVerification) + private readonly repository: Repository + ) {} + + public async createEmailVerification( + token: string, + expiresAt: Date, + userId: string + ): Promise { + await this.repository.save({ + token, + expiresAt, + user: { id: userId }, + }); + } +} diff --git a/backend/src/modules/verify-module/repositories/index.ts b/backend/src/modules/verify-module/repositories/index.ts new file mode 100644 index 0000000..251e136 --- /dev/null +++ b/backend/src/modules/verify-module/repositories/index.ts @@ -0,0 +1 @@ +export * from './email-verify.repository'; diff --git a/backend/src/modules/verify-module/services/email-verification.service.ts b/backend/src/modules/verify-module/services/email-verification.service.ts new file mode 100644 index 0000000..80b93e1 --- /dev/null +++ b/backend/src/modules/verify-module/services/email-verification.service.ts @@ -0,0 +1,27 @@ +import { randomBytes } from 'crypto'; + +import { Injectable } from '@nestjs/common'; + +import { EmailVerifyRepository } from '../repositories'; + +@Injectable() +export class EmailVerificationService { + public constructor( + private readonly emailVerifyRepository: EmailVerifyRepository + ) {} + + public async generateEmailVerificationToken(userId: string): Promise { + const token = randomBytes(32).toString('hex'); + + // TODO Check users local time zone and set expiration time accordingly + const expiration = new Date(Date.now() + 24 * 60 * 60 * 1000); + + this.emailVerifyRepository.createEmailVerification( + token, + expiration, + userId + ); + + return token; + } +} diff --git a/backend/src/modules/verify-module/services/index.ts b/backend/src/modules/verify-module/services/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/modules/verify-module/verify.module.ts b/backend/src/modules/verify-module/verify.module.ts new file mode 100644 index 0000000..f506909 --- /dev/null +++ b/backend/src/modules/verify-module/verify.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { EmailVerification } from 'src/entities'; + +import { EmailVerifyRepository } from './repositories'; +import { EmailVerificationService } from './services/email-verification.service'; + +@Module({ + imports: [ConfigModule, TypeOrmModule.forFeature([EmailVerification])], + providers: [EmailVerifyRepository, EmailVerificationService], + controllers: [], + exports: [EmailVerificationService], +}) +export class VerifyModule {}