From 866f01d3a5338cda9c8f609158220d4ec3ee0a09 Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Wed, 14 Jun 2023 14:20:07 -0400 Subject: [PATCH 1/7] Added emailVerified field to User DB and verifyEmail field to Project DB --- packages/server/prisma/schema.prisma | 2 ++ packages/server/src/project/dto/project.dto.ts | 10 ++++++++++ packages/server/src/project/model/project.model.ts | 3 +++ packages/server/src/user/model/user.model.ts | 3 +++ 4 files changed, 18 insertions(+) diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index d0a2c94..7bcc55a 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -27,6 +27,7 @@ model User { Project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) Invite Invite? @relation(name: "acceptedBy") InviteSent Invite[] @relation(name: "invitedBy") + emailVerified Boolean? @default(false) } model Project { @@ -46,6 +47,7 @@ model Project { emailAuth Boolean? @default(true) allowSignup Boolean? @default(true) Invite Invite[] + verifyEmail Boolean? @default(false) } model Invite { diff --git a/packages/server/src/project/dto/project.dto.ts b/packages/server/src/project/dto/project.dto.ts index 6ede76c..0d4ce73 100644 --- a/packages/server/src/project/dto/project.dto.ts +++ b/packages/server/src/project/dto/project.dto.ts @@ -60,6 +60,11 @@ export class ProjectCreateInput implements Omit { @IsNotEmpty() @Field({ nullable: true }) allowSignup: boolean; + + @IsNotEmpty() + @IsDefined() + @Field() + verifyEmail: boolean; } @InputType() diff --git a/packages/server/src/project/model/project.model.ts b/packages/server/src/project/model/project.model.ts index 9761830..c59d760 100644 --- a/packages/server/src/project/model/project.model.ts +++ b/packages/server/src/project/model/project.model.ts @@ -38,4 +38,7 @@ export class ProjectModel { @Field({ nullable: true }) deletedAt?: Date; + + @Field({ nullable: true }) + verifyEmail?: boolean; } diff --git a/packages/server/src/user/model/user.model.ts b/packages/server/src/user/model/user.model.ts index d6053e4..5f9b9ff 100644 --- a/packages/server/src/user/model/user.model.ts +++ b/packages/server/src/user/model/user.model.ts @@ -34,4 +34,7 @@ export class UserModel { @Field({ nullable: true }) deletedAt: Date; + + @Field({ nullable: true }) + emailVerified?: boolean; } From 8c5fa9ccb50261d4389f87a4e9730b51e5e8d751 Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Wed, 14 Jun 2023 14:20:48 -0400 Subject: [PATCH 2/7] adding migration --- .../migration.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/server/prisma/migrations/20230614175901_added_email_verification_fields_to_db_and_models/migration.sql diff --git a/packages/server/prisma/migrations/20230614175901_added_email_verification_fields_to_db_and_models/migration.sql b/packages/server/prisma/migrations/20230614175901_added_email_verification_fields_to_db_and_models/migration.sql new file mode 100644 index 0000000..1b06091 --- /dev/null +++ b/packages/server/prisma/migrations/20230614175901_added_email_verification_fields_to_db_and_models/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "verifyEmail" BOOLEAN DEFAULT false; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "emailVerified" BOOLEAN DEFAULT false; From 871a490d4965a11238599cdc2827ded685da3d75 Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Wed, 14 Jun 2023 15:12:33 -0400 Subject: [PATCH 3/7] Moved verifyEmail field to project settings --- packages/server/src/project/model/project-settings.model.ts | 3 +++ packages/server/src/project/model/project.model.ts | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/project/model/project-settings.model.ts b/packages/server/src/project/model/project-settings.model.ts index 1b10038..4aefe6c 100644 --- a/packages/server/src/project/model/project-settings.model.ts +++ b/packages/server/src/project/model/project-settings.model.ts @@ -12,4 +12,7 @@ export class ProjectSettingsModel { @Field() allowSignup: boolean; + + @Field({ nullable: true }) + verifyEmail?: boolean; } diff --git a/packages/server/src/project/model/project.model.ts b/packages/server/src/project/model/project.model.ts index c59d760..9761830 100644 --- a/packages/server/src/project/model/project.model.ts +++ b/packages/server/src/project/model/project.model.ts @@ -38,7 +38,4 @@ export class ProjectModel { @Field({ nullable: true }) deletedAt?: Date; - - @Field({ nullable: true }) - verifyEmail?: boolean; } From 75d8a71d1355c897512d9adfe965392fa40219d0 Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Wed, 14 Jun 2023 16:05:00 -0400 Subject: [PATCH 4/7] added model details to client side --- packages/client/src/graphql/graphql.ts | 8 ++++---- packages/client/src/graphql/project/project.graphql | 1 + packages/client/src/graphql/project/project.ts | 3 ++- packages/client/src/graphql/user/user.graphql | 1 + packages/client/src/graphql/user/user.ts | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/client/src/graphql/graphql.ts b/packages/client/src/graphql/graphql.ts index e692d6a..3e18dd6 100644 --- a/packages/client/src/graphql/graphql.ts +++ b/packages/client/src/graphql/graphql.ts @@ -193,6 +193,7 @@ export type ProjectCreateInput = { muiTheme?: InputMaybe; name: Scalars['String']; redirectUrl?: InputMaybe; + verifyEmail: Scalars['Boolean']; }; export type ProjectModel = { @@ -215,12 +216,14 @@ export type ProjectModel = { export type ProjectSettingsInput = { allowSignup?: InputMaybe; displayProjectName?: InputMaybe; + verifyEmail: Scalars['Boolean']; }; export type ProjectSettingsModel = { __typename?: 'ProjectSettingsModel'; allowSignup: Scalars['Boolean']; displayProjectName: Scalars['Boolean']; + verifyEmail?: Maybe; }; export type Query = { @@ -261,10 +264,6 @@ export type QueryProjectUsersArgs = { projectId: Scalars['String']; }; -export type QueryUsersArgs = { - projectId: Scalars['ID']; -}; - export type ResetDto = { code: Scalars['String']; email: Scalars['String']; @@ -277,6 +276,7 @@ export type UserModel = { createdAt: Scalars['DateTime']; deletedAt?: Maybe; email?: Maybe; + emailVerified?: Maybe; fullname?: Maybe; id: Scalars['ID']; projectId: Scalars['String']; diff --git a/packages/client/src/graphql/project/project.graphql b/packages/client/src/graphql/project/project.graphql index 888f666..b1d2c56 100644 --- a/packages/client/src/graphql/project/project.graphql +++ b/packages/client/src/graphql/project/project.graphql @@ -8,6 +8,7 @@ query getProject($id: String!) { settings { displayProjectName allowSignup + verifyEmail } authMethods { googleAuth diff --git a/packages/client/src/graphql/project/project.ts b/packages/client/src/graphql/project/project.ts index bd440c9..4df9f03 100644 --- a/packages/client/src/graphql/project/project.ts +++ b/packages/client/src/graphql/project/project.ts @@ -18,7 +18,7 @@ export type GetProjectQuery = { logo?: string | null; muiTheme: any; redirectUrl?: string | null; - settings: { __typename?: 'ProjectSettingsModel'; displayProjectName: boolean; allowSignup: boolean }; + settings: { __typename?: 'ProjectSettingsModel'; displayProjectName: boolean; allowSignup: boolean; verifyEmail?: boolean | null }; authMethods: { __typename?: 'ProjectAuthMethodsModel'; googleAuth: boolean; emailAuth: boolean }; }; }; @@ -34,6 +34,7 @@ export const GetProjectDocument = gql` settings { displayProjectName allowSignup + verifyEmail } authMethods { googleAuth diff --git a/packages/client/src/graphql/user/user.graphql b/packages/client/src/graphql/user/user.graphql index ae0a27e..e68a429 100644 --- a/packages/client/src/graphql/user/user.graphql +++ b/packages/client/src/graphql/user/user.graphql @@ -1,5 +1,6 @@ query getUser($id: ID!) { getUser(id: $id) { id + emailVerified } } diff --git a/packages/client/src/graphql/user/user.ts b/packages/client/src/graphql/user/user.ts index 5705466..5c9e06c 100644 --- a/packages/client/src/graphql/user/user.ts +++ b/packages/client/src/graphql/user/user.ts @@ -9,12 +9,13 @@ export type GetUserQueryVariables = Types.Exact<{ id: Types.Scalars['ID']; }>; -export type GetUserQuery = { __typename?: 'Query'; getUser: { __typename?: 'UserModel'; id: string } }; +export type GetUserQuery = { __typename?: 'Query'; getUser: { __typename?: 'UserModel'; id: string; emailVerified?: boolean | null } }; export const GetUserDocument = gql` query getUser($id: ID!) { getUser(id: $id) { id + emailVerified } } `; From 542764609b9183e7ae08e87ed264d9989fc4ce19 Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Wed, 14 Jun 2023 16:10:27 -0400 Subject: [PATCH 5/7] Created email-verification module and controller --- packages/server/src/app.module.ts | 4 +++- .../email-verification.controller.spec.ts | 18 ++++++++++++++++++ .../email-verification.controller.ts | 4 ++++ .../email-verification.module.ts | 9 +++++++++ .../email-verification.service.spec.ts | 18 ++++++++++++++++++ .../email-verification.service.ts | 4 ++++ 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/email-verification/email-verification.controller.spec.ts create mode 100644 packages/server/src/email-verification/email-verification.controller.ts create mode 100644 packages/server/src/email-verification/email-verification.module.ts create mode 100644 packages/server/src/email-verification/email-verification.service.spec.ts create mode 100644 packages/server/src/email-verification/email-verification.service.ts diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index 3da649a..df57b7d 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -12,6 +12,7 @@ import { InviteModule } from './invite/invite.module'; import { NotificationModule } from './notification/notification.module'; import { JwtModule } from './jwt/jwt.module'; import { TelemetryModule } from './telemetry/telemetry.module'; +import { EmailVerificationModule } from './email-verification/email-verification.module'; @Module({ imports: [ @@ -30,7 +31,8 @@ import { TelemetryModule } from './telemetry/telemetry.module'; InviteModule, NotificationModule, JwtModule, - TelemetryModule + TelemetryModule, + EmailVerificationModule ] }) export class AppModule implements NestModule { diff --git a/packages/server/src/email-verification/email-verification.controller.spec.ts b/packages/server/src/email-verification/email-verification.controller.spec.ts new file mode 100644 index 0000000..e05bcc6 --- /dev/null +++ b/packages/server/src/email-verification/email-verification.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EmailVerificationController } from './email-verification.controller'; + +describe('EmailVerificationController', () => { + let controller: EmailVerificationController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [EmailVerificationController], + }).compile(); + + controller = module.get(EmailVerificationController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/packages/server/src/email-verification/email-verification.controller.ts b/packages/server/src/email-verification/email-verification.controller.ts new file mode 100644 index 0000000..4700394 --- /dev/null +++ b/packages/server/src/email-verification/email-verification.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('email-verification') +export class EmailVerificationController {} diff --git a/packages/server/src/email-verification/email-verification.module.ts b/packages/server/src/email-verification/email-verification.module.ts new file mode 100644 index 0000000..8c0b50a --- /dev/null +++ b/packages/server/src/email-verification/email-verification.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { EmailVerificationController } from './email-verification.controller'; +import { EmailVerificationService } from './email-verification.service'; + +@Module({ + controllers: [EmailVerificationController], + providers: [EmailVerificationService] +}) +export class EmailVerificationModule {} diff --git a/packages/server/src/email-verification/email-verification.service.spec.ts b/packages/server/src/email-verification/email-verification.service.spec.ts new file mode 100644 index 0000000..acf9450 --- /dev/null +++ b/packages/server/src/email-verification/email-verification.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EmailVerificationService } from './email-verification.service'; + +describe('EmailVerificationService', () => { + let service: EmailVerificationService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [EmailVerificationService], + }).compile(); + + service = module.get(EmailVerificationService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/server/src/email-verification/email-verification.service.ts b/packages/server/src/email-verification/email-verification.service.ts new file mode 100644 index 0000000..4e1d9fb --- /dev/null +++ b/packages/server/src/email-verification/email-verification.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class EmailVerificationService {} From c0469f32d166e2e25ac06146c72f71ca59855d65 Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Sat, 24 Jun 2023 02:12:39 -0400 Subject: [PATCH 6/7] Serverside endpoint for email verification check --- .../email-verification.graphql | 3 ++ .../email-verification/email-verification.ts | 46 +++++++++++++++++++ packages/client/src/graphql/graphql.ts | 9 ++++ .../dto/email-verification.dto.ts | 26 +++++++++++ .../email-verification.controller.ts | 14 +++++- .../email-verification.module.ts | 11 ++++- .../email-verification.resolver.ts | 21 +++++++++ .../email-verification.service.ts | 30 +++++++++++- 8 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 packages/client/src/graphql/email-verification/email-verification.graphql create mode 100644 packages/client/src/graphql/email-verification/email-verification.ts create mode 100644 packages/server/src/email-verification/dto/email-verification.dto.ts create mode 100644 packages/server/src/email-verification/email-verification.resolver.ts diff --git a/packages/client/src/graphql/email-verification/email-verification.graphql b/packages/client/src/graphql/email-verification/email-verification.graphql new file mode 100644 index 0000000..ce5ebeb --- /dev/null +++ b/packages/client/src/graphql/email-verification/email-verification.graphql @@ -0,0 +1,3 @@ +query getEmailVerificationStatus($accessToken: String!) { + getEmailVerificationStatus(user: {accessToken: $accessToken}) +} \ No newline at end of file diff --git a/packages/client/src/graphql/email-verification/email-verification.ts b/packages/client/src/graphql/email-verification/email-verification.ts new file mode 100644 index 0000000..e0f53db --- /dev/null +++ b/packages/client/src/graphql/email-verification/email-verification.ts @@ -0,0 +1,46 @@ +/* Generated File DO NOT EDIT. */ +/* tslint:disable */ +import * as Types from '../graphql'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type GetEmailVerificationStatusQueryVariables = Types.Exact<{ + accessToken: Types.Scalars['String']; +}>; + +export type GetEmailVerificationStatusQuery = { __typename?: 'Query'; getEmailVerificationStatus: boolean }; + +export const GetEmailVerificationStatusDocument = gql` + query getEmailVerificationStatus($accessToken: String!) { + getEmailVerificationStatus(user: { accessToken: $accessToken }) + } +`; + +/** + * __useGetEmailVerificationStatusQuery__ + * + * To run a query within a React component, call `useGetEmailVerificationStatusQuery` and pass it any options that fit your needs. + * When your component renders, `useGetEmailVerificationStatusQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetEmailVerificationStatusQuery({ + * variables: { + * accessToken: // value for 'accessToken' + * }, + * }); + */ +export function useGetEmailVerificationStatusQuery(baseOptions: Apollo.QueryHookOptions) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery(GetEmailVerificationStatusDocument, options); +} +export function useGetEmailVerificationStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery(GetEmailVerificationStatusDocument, options); +} +export type GetEmailVerificationStatusQueryHookResult = ReturnType; +export type GetEmailVerificationStatusLazyQueryHookResult = ReturnType; +export type GetEmailVerificationStatusQueryResult = Apollo.QueryResult; diff --git a/packages/client/src/graphql/graphql.ts b/packages/client/src/graphql/graphql.ts index 3e18dd6..80e14e9 100644 --- a/packages/client/src/graphql/graphql.ts +++ b/packages/client/src/graphql/graphql.ts @@ -53,6 +53,10 @@ export type EmailLoginDto = { projectId: Scalars['String']; }; +export type EmailVerificationDto = { + accessToken: Scalars['String']; +}; + export type ForgotDto = { email: Scalars['String']; projectId: Scalars['String']; @@ -230,6 +234,7 @@ export type Query = { __typename?: 'Query'; _entities: Array>; _service: _Service; + getEmailVerificationStatus: Scalars['Boolean']; getProject: ProjectModel; getUser: UserModel; invite: InviteModel; @@ -244,6 +249,10 @@ export type Query_EntitiesArgs = { representations: Array; }; +export type QueryGetEmailVerificationStatusArgs = { + user: EmailVerificationDto; +}; + export type QueryGetProjectArgs = { id: Scalars['String']; }; diff --git a/packages/server/src/email-verification/dto/email-verification.dto.ts b/packages/server/src/email-verification/dto/email-verification.dto.ts new file mode 100644 index 0000000..da85e97 --- /dev/null +++ b/packages/server/src/email-verification/dto/email-verification.dto.ts @@ -0,0 +1,26 @@ +import { Type } from 'class-transformer'; +import { IsBoolean, IsDefined, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { PipeTransform } from '@nestjs/common'; +import { Field, InputType } from '@nestjs/graphql'; +import { JwtService } from '@nestjs/jwt'; + +@InputType() +export class EmailVerificationDto { + @IsNotEmpty() + @IsString() + @IsDefined() + @Type(() => String) + @Field() + accessToken: string; +} + +export class EmailVerificationTransformPipe implements PipeTransform { + constructor () {} + transform(body: any): EmailVerificationDto { + const user = new EmailVerificationDto(); + + user.accessToken = body.accessToken; + + return user; + } +} \ No newline at end of file diff --git a/packages/server/src/email-verification/email-verification.controller.ts b/packages/server/src/email-verification/email-verification.controller.ts index 4700394..3c34c94 100644 --- a/packages/server/src/email-verification/email-verification.controller.ts +++ b/packages/server/src/email-verification/email-verification.controller.ts @@ -1,4 +1,14 @@ -import { Controller } from '@nestjs/common'; +import { Controller, ParseUUIDPipe, Get, HttpException, HttpStatus, Param, UsePipes, Body} from '@nestjs/common'; +import { EmailVerificationService } from './email-verification.service'; +import { EmailVerificationDto, EmailVerificationTransformPipe } from './dto/email-verification.dto'; @Controller('email-verification') -export class EmailVerificationController {} +export class EmailVerificationController { + constructor(private readonly emailVerificationService: EmailVerificationService) {} + + @Get('status/?') + @UsePipes(new EmailVerificationTransformPipe()) + async getVerificationStatus(@Body() user: EmailVerificationDto): Promise { + return this.emailVerificationService.getVerificationStatus(user.accessToken) + } +} diff --git a/packages/server/src/email-verification/email-verification.module.ts b/packages/server/src/email-verification/email-verification.module.ts index 8c0b50a..f1d8853 100644 --- a/packages/server/src/email-verification/email-verification.module.ts +++ b/packages/server/src/email-verification/email-verification.module.ts @@ -1,9 +1,18 @@ import { Module } from '@nestjs/common'; import { EmailVerificationController } from './email-verification.controller'; import { EmailVerificationService } from './email-verification.service'; +import { UserModule } from '../user/user.module'; +import { ProjectModule } from '../project/project.module'; +import { NotificationModule } from '../notification/notification.module'; +import { JwtModule } from '../jwt/jwt.module'; +import { HttpModule } from '@nestjs/axios'; +import { EmailVerificationResolver } from './email-verification.resolver'; +import { PrismaService } from 'src/prisma/prisma.service'; @Module({ + imports: [UserModule, ProjectModule, NotificationModule, JwtModule, HttpModule], controllers: [EmailVerificationController], - providers: [EmailVerificationService] + providers: [EmailVerificationService, EmailVerificationResolver, PrismaService], + exports: [EmailVerificationService] }) export class EmailVerificationModule {} diff --git a/packages/server/src/email-verification/email-verification.resolver.ts b/packages/server/src/email-verification/email-verification.resolver.ts new file mode 100644 index 0000000..91ea2cf --- /dev/null +++ b/packages/server/src/email-verification/email-verification.resolver.ts @@ -0,0 +1,21 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { EmailVerificationService } from './email-verification.service'; + +import { + EmailVerificationDto, + EmailVerificationTransformPipe +} from './dto/email-verification.dto' +import { UsePipes } from '@nestjs/common'; + +@Resolver() +export class EmailVerificationResolver { + constructor(private readonly emailVerificationService: EmailVerificationService) {} + + /**Get User Email Verification Status */ + @Query(() => Boolean) + @UsePipes(new EmailVerificationTransformPipe()) + async getEmailVerificationStatus(@Args('user') user: EmailVerificationDto): Promise { + return this.emailVerificationService.getVerificationStatus(user.accessToken); + } + +} \ No newline at end of file diff --git a/packages/server/src/email-verification/email-verification.service.ts b/packages/server/src/email-verification/email-verification.service.ts index 4e1d9fb..a3ccd04 100644 --- a/packages/server/src/email-verification/email-verification.service.ts +++ b/packages/server/src/email-verification/email-verification.service.ts @@ -1,4 +1,30 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import { UserService } from '../user/user.service'; +import { ProjectService } from '../project/project.service'; +import { NotificationService } from '../notification/notification.service'; +import { HttpService } from '@nestjs/axios'; @Injectable() -export class EmailVerificationService {} +export class EmailVerificationService { + constructor( + private readonly userService: UserService, + private readonly jwtService: JwtService, + private readonly projectService: ProjectService, + private readonly configService: ConfigService, + private readonly notification: NotificationService, + private readonly http: HttpService + + ) {} + + async getVerificationStatus(accessToken: string): Promise { + const decoded = this.jwtService.decode(accessToken) + + const user = await this.userService.findUserById(decoded['id']); + console.log(user.emailVerified) + return user.emailVerified; + + } + +} From f40e4d82e1c9902016c9d06fbf6093c51adfaecf Mon Sep 17 00:00:00 2001 From: Shashank Karthikeyan Date: Wed, 26 Jul 2023 14:59:28 -0400 Subject: [PATCH 7/7] Email Auth Backend --- .../dto/email-verification.dto.ts | 32 +++++++++++++++++-- .../email-verification.controller.ts | 18 +++++++++-- .../email-verification.resolver.ts | 16 +++++++++- .../email-verification.service.ts | 19 ++++++++++- packages/server/src/user/user.service.ts | 20 ++++++++++++ 5 files changed, 98 insertions(+), 7 deletions(-) diff --git a/packages/server/src/email-verification/dto/email-verification.dto.ts b/packages/server/src/email-verification/dto/email-verification.dto.ts index da85e97..862c611 100644 --- a/packages/server/src/email-verification/dto/email-verification.dto.ts +++ b/packages/server/src/email-verification/dto/email-verification.dto.ts @@ -14,8 +14,25 @@ export class EmailVerificationDto { accessToken: string; } +@InputType() +export class GenerateLinkDto { + @IsNotEmpty() + @IsString() + @IsDefined() + @Type(() => String) + @Field() + baseUrl: string; + + @IsNotEmpty() + @IsString() + @IsDefined() + @Type(() => String) + @Field() + accessToken: string; +} + + export class EmailVerificationTransformPipe implements PipeTransform { - constructor () {} transform(body: any): EmailVerificationDto { const user = new EmailVerificationDto(); @@ -23,4 +40,15 @@ export class EmailVerificationTransformPipe implements PipeTransform { return user; } -} \ No newline at end of file +} + +export class SendLinkTransformPipe implements PipeTransform { + transform(body: any): GenerateLinkDto { + const user = new GenerateLinkDto(); + + user.baseUrl = body.baseUrl; + user.accessToken = body.accessToken; + + return user; + } +} diff --git a/packages/server/src/email-verification/email-verification.controller.ts b/packages/server/src/email-verification/email-verification.controller.ts index 3c34c94..2d6c6e2 100644 --- a/packages/server/src/email-verification/email-verification.controller.ts +++ b/packages/server/src/email-verification/email-verification.controller.ts @@ -1,14 +1,26 @@ -import { Controller, ParseUUIDPipe, Get, HttpException, HttpStatus, Param, UsePipes, Body} from '@nestjs/common'; +import { Controller, ParseUUIDPipe, Get, HttpException, HttpStatus, Param, UsePipes, Body, Put} from '@nestjs/common'; import { EmailVerificationService } from './email-verification.service'; -import { EmailVerificationDto, EmailVerificationTransformPipe } from './dto/email-verification.dto'; +import { EmailVerificationDto, EmailVerificationTransformPipe, GenerateLinkDto, SendLinkTransformPipe } from './dto/email-verification.dto'; @Controller('email-verification') export class EmailVerificationController { constructor(private readonly emailVerificationService: EmailVerificationService) {} - @Get('status/?') + @Get('status') @UsePipes(new EmailVerificationTransformPipe()) async getVerificationStatus(@Body() user: EmailVerificationDto): Promise { return this.emailVerificationService.getVerificationStatus(user.accessToken) } + + @Get('verificationLink') + @UsePipes(new SendLinkTransformPipe()) + async generateVerificationLink(@Body() user: GenerateLinkDto): Promise { + return this.emailVerificationService.generateVerificationLink(user.baseUrl, user.accessToken); + } + + @Put('verify') + @UsePipes(new EmailVerificationTransformPipe()) + async verifyEmail(@Body() user: EmailVerificationDto): Promise { + return this.emailVerificationService.verifyEmail(user.accessToken) + } } diff --git a/packages/server/src/email-verification/email-verification.resolver.ts b/packages/server/src/email-verification/email-verification.resolver.ts index 91ea2cf..9279b41 100644 --- a/packages/server/src/email-verification/email-verification.resolver.ts +++ b/packages/server/src/email-verification/email-verification.resolver.ts @@ -3,7 +3,9 @@ import { EmailVerificationService } from './email-verification.service'; import { EmailVerificationDto, - EmailVerificationTransformPipe + EmailVerificationTransformPipe, + GenerateLinkDto, + SendLinkTransformPipe } from './dto/email-verification.dto' import { UsePipes } from '@nestjs/common'; @@ -17,5 +19,17 @@ export class EmailVerificationResolver { async getEmailVerificationStatus(@Args('user') user: EmailVerificationDto): Promise { return this.emailVerificationService.getVerificationStatus(user.accessToken); } + + @Mutation(() => String) + @UsePipes(new SendLinkTransformPipe()) + async generateVerificationLink(@Args('user') user: GenerateLinkDto): Promise { + return this.emailVerificationService.generateVerificationLink(user.baseUrl, user.accessToken); + } + + @Mutation(() => Boolean) + @UsePipes(new EmailVerificationTransformPipe()) + async verifyEmail(@Args('user') user: EmailVerificationDto): Promise { + return this.emailVerificationService.verifyEmail(user.accessToken); + } } \ No newline at end of file diff --git a/packages/server/src/email-verification/email-verification.service.ts b/packages/server/src/email-verification/email-verification.service.ts index a3ccd04..7b729a0 100644 --- a/packages/server/src/email-verification/email-verification.service.ts +++ b/packages/server/src/email-verification/email-verification.service.ts @@ -22,8 +22,25 @@ export class EmailVerificationService { const decoded = this.jwtService.decode(accessToken) const user = await this.userService.findUserById(decoded['id']); - console.log(user.emailVerified) + console.log(user.emailVerified); return user.emailVerified; + } + + async generateVerificationLink(baseUrl: string, accessToken: string): Promise { + const decoded = this.jwtService.decode(accessToken); + const user = await this.userService.findUserById(decoded['id']); + + const verificationLink = `${baseUrl}?token=${accessToken}`; + return verificationLink; + + } + + async verifyEmail(accessToken: string): Promise { + const decoded = this.jwtService.decode(accessToken) + const user = await this.userService.findUserById(decoded['id']); + + this.userService.updateUserEmailVerificationStatus(user.id, true); + return this.getVerificationStatus(accessToken); } diff --git a/packages/server/src/user/user.service.ts b/packages/server/src/user/user.service.ts index 9ae74cd..4c35296 100644 --- a/packages/server/src/user/user.service.ts +++ b/packages/server/src/user/user.service.ts @@ -247,4 +247,24 @@ export class UserService { } }); } + + /** + * Update user's email verification status + * + * @param id ID of the user + * @param emailVerified `true` to verify email. `false` to un-verify email + */ + + async updateUserEmailVerificationStatus(id: string, emailVerified: boolean): Promise { + const userToUpdate = await this.findUserById(id); + + await this.prisma.user.update({ + where: { + id: id + }, + data: { + emailVerified: emailVerified + } + }); + } }