Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/email verifiction #91

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query getEmailVerificationStatus($accessToken: String!) {
getEmailVerificationStatus(user: {accessToken: $accessToken})
}
Original file line number Diff line number Diff line change
@@ -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<GetEmailVerificationStatusQuery, GetEmailVerificationStatusQueryVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetEmailVerificationStatusQuery, GetEmailVerificationStatusQueryVariables>(GetEmailVerificationStatusDocument, options);
}
export function useGetEmailVerificationStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetEmailVerificationStatusQuery, GetEmailVerificationStatusQueryVariables>) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<GetEmailVerificationStatusQuery, GetEmailVerificationStatusQueryVariables>(GetEmailVerificationStatusDocument, options);
}
export type GetEmailVerificationStatusQueryHookResult = ReturnType<typeof useGetEmailVerificationStatusQuery>;
export type GetEmailVerificationStatusLazyQueryHookResult = ReturnType<typeof useGetEmailVerificationStatusLazyQuery>;
export type GetEmailVerificationStatusQueryResult = Apollo.QueryResult<GetEmailVerificationStatusQuery, GetEmailVerificationStatusQueryVariables>;
17 changes: 13 additions & 4 deletions packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -193,6 +197,7 @@ export type ProjectCreateInput = {
muiTheme?: InputMaybe<Scalars['JSON']>;
name: Scalars['String'];
redirectUrl?: InputMaybe<Scalars['String']>;
verifyEmail: Scalars['Boolean'];
};

export type ProjectModel = {
Expand All @@ -215,18 +220,21 @@ export type ProjectModel = {
export type ProjectSettingsInput = {
allowSignup?: InputMaybe<Scalars['Boolean']>;
displayProjectName?: InputMaybe<Scalars['Boolean']>;
verifyEmail: Scalars['Boolean'];
};

export type ProjectSettingsModel = {
__typename?: 'ProjectSettingsModel';
allowSignup: Scalars['Boolean'];
displayProjectName: Scalars['Boolean'];
verifyEmail?: Maybe<Scalars['Boolean']>;
};

export type Query = {
__typename?: 'Query';
_entities: Array<Maybe<_Entity>>;
_service: _Service;
getEmailVerificationStatus: Scalars['Boolean'];
getProject: ProjectModel;
getUser: UserModel;
invite: InviteModel;
Expand All @@ -241,6 +249,10 @@ export type Query_EntitiesArgs = {
representations: Array<Scalars['_Any']>;
};

export type QueryGetEmailVerificationStatusArgs = {
user: EmailVerificationDto;
};

export type QueryGetProjectArgs = {
id: Scalars['String'];
};
Expand All @@ -261,10 +273,6 @@ export type QueryProjectUsersArgs = {
projectId: Scalars['String'];
};

export type QueryUsersArgs = {
projectId: Scalars['ID'];
};

export type ResetDto = {
code: Scalars['String'];
email: Scalars['String'];
Expand All @@ -277,6 +285,7 @@ export type UserModel = {
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
email?: Maybe<Scalars['String']>;
emailVerified?: Maybe<Scalars['Boolean']>;
fullname?: Maybe<Scalars['String']>;
id: Scalars['ID'];
projectId: Scalars['String'];
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/graphql/project/project.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ query getProject($id: String!) {
settings {
displayProjectName
allowSignup
verifyEmail
}
authMethods {
googleAuth
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/graphql/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
};
};
Expand All @@ -34,6 +34,7 @@ export const GetProjectDocument = gql`
settings {
displayProjectName
allowSignup
verifyEmail
}
authMethods {
googleAuth
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/graphql/user/user.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
query getUser($id: ID!) {
getUser(id: $id) {
id
emailVerified
}
}
3 changes: 2 additions & 1 deletion packages/client/src/graphql/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "verifyEmail" BOOLEAN DEFAULT false;

-- AlterTable
ALTER TABLE "User" ADD COLUMN "emailVerified" BOOLEAN DEFAULT false;
2 changes: 2 additions & 0 deletions packages/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -46,6 +47,7 @@ model Project {
emailAuth Boolean? @default(true)
allowSignup Boolean? @default(true)
Invite Invite[]
verifyEmail Boolean? @default(false)
}

model Invite {
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -30,7 +31,8 @@ import { TelemetryModule } from './telemetry/telemetry.module';
InviteModule,
NotificationModule,
JwtModule,
TelemetryModule
TelemetryModule,
EmailVerificationModule
]
})
export class AppModule implements NestModule {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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;
}

@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 {
transform(body: any): EmailVerificationDto {
const user = new EmailVerificationDto();

user.accessToken = body.accessToken;

return user;
}
}

export class SendLinkTransformPipe implements PipeTransform {
transform(body: any): GenerateLinkDto {
const user = new GenerateLinkDto();

user.baseUrl = body.baseUrl;
user.accessToken = body.accessToken;

return user;
}
}
Original file line number Diff line number Diff line change
@@ -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>(EmailVerificationController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Controller, ParseUUIDPipe, Get, HttpException, HttpStatus, Param, UsePipes, Body, Put} from '@nestjs/common';
import { EmailVerificationService } from './email-verification.service';
import { EmailVerificationDto, EmailVerificationTransformPipe, GenerateLinkDto, SendLinkTransformPipe } from './dto/email-verification.dto';

@Controller('email-verification')
export class EmailVerificationController {
constructor(private readonly emailVerificationService: EmailVerificationService) {}

@Get('status')
@UsePipes(new EmailVerificationTransformPipe())
async getVerificationStatus(@Body() user: EmailVerificationDto): Promise<Boolean> {
return this.emailVerificationService.getVerificationStatus(user.accessToken)
}

@Get('verificationLink')
@UsePipes(new SendLinkTransformPipe())
async generateVerificationLink(@Body() user: GenerateLinkDto): Promise<String> {
return this.emailVerificationService.generateVerificationLink(user.baseUrl, user.accessToken);
}

@Put('verify')
@UsePipes(new EmailVerificationTransformPipe())
async verifyEmail(@Body() user: EmailVerificationDto): Promise<Boolean> {
return this.emailVerificationService.verifyEmail(user.accessToken)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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, EmailVerificationResolver, PrismaService],
exports: [EmailVerificationService]
})
export class EmailVerificationModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { EmailVerificationService } from './email-verification.service';

import {
EmailVerificationDto,
EmailVerificationTransformPipe,
GenerateLinkDto,
SendLinkTransformPipe
} 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<Boolean> {
return this.emailVerificationService.getVerificationStatus(user.accessToken);
}

@Mutation(() => String)
@UsePipes(new SendLinkTransformPipe())
async generateVerificationLink(@Args('user') user: GenerateLinkDto): Promise<String> {
return this.emailVerificationService.generateVerificationLink(user.baseUrl, user.accessToken);
}

@Mutation(() => Boolean)
@UsePipes(new EmailVerificationTransformPipe())
async verifyEmail(@Args('user') user: EmailVerificationDto): Promise<Boolean> {
return this.emailVerificationService.verifyEmail(user.accessToken);
}

}
Original file line number Diff line number Diff line change
@@ -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>(EmailVerificationService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
Loading