Skip to content

Commit bca3419

Browse files
committed
Add sign in mutation
1 parent 6eca0e3 commit bca3419

File tree

7 files changed

+154
-37
lines changed

7 files changed

+154
-37
lines changed

apps/main-service/src/app/@common/modules/global.module.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
99
import { join } from 'path';
1010
import type { ClientOpts } from 'redis';
1111
import * as redisStore from 'cache-manager-redis-store';
12-
import { ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core';
12+
import {
13+
ApolloServerPluginSchemaReporting,
14+
ApolloServerPluginLandingPageLocalDefault,
15+
} from 'apollo-server-core';
1316
import { MailerModule } from '@nestjs-modules/mailer';
1417
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
1518

@@ -64,7 +67,10 @@ import { Match } from '../validators/match.validator';
6467
driver: ApolloDriver,
6568
autoSchemaFile: true,
6669
playground: false,
67-
plugins: [ApolloServerPluginLandingPageLocalDefault()],
70+
plugins: [
71+
ApolloServerPluginSchemaReporting(),
72+
ApolloServerPluginLandingPageLocalDefault(),
73+
],
6874
}),
6975
MailerModule.forRoot({
7076
// transport: 'smtps://user@example.com:topsecret@smtp.example.com',

apps/main-service/src/app/auth/auth.service.ts

+50-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
BadRequestException,
33
Injectable,
4+
Logger,
45
NotFoundException,
56
UnauthorizedException,
67
} from '@nestjs/common';
@@ -15,7 +16,9 @@ import { Profile } from '../profile/entities/profile.entity';
1516
import { UserVerification } from '../verification/entities/user-verification.entity';
1617

1718
import { SignUpViaEmailInput } from './dto/sign-up-via-email.input';
19+
import { SignInViaEmailInput } from './dto/sign-in-via-email.input';
1820
import { SignedUpViaEmailOutput } from './dto/signed-up-via-email.output';
21+
import { SignedInViaEmailOutput } from './dto/signed-in-via-email.output';
1922

2023
import { JwtPayload } from './interfaces/jwt-payload.interface';
2124

@@ -25,6 +28,8 @@ import { UserSignedUpEvent } from './events/user-signed-up.event';
2528

2629
@Injectable()
2730
export class AuthService {
31+
private readonly logger: Logger;
32+
2833
constructor(
2934
private readonly eventEmitter: EventEmitter2,
3035
private readonly jwtService: JwtService,
@@ -34,7 +39,9 @@ export class AuthService {
3439
private readonly profileRepository: Repository<Profile>,
3540
@InjectRepository(UserVerification)
3641
private readonly userVerificationRepository: Repository<UserVerification>
37-
) {}
42+
) {
43+
this.logger = new Logger(AuthService.name);
44+
}
3845

3946
async signUpViaEmail(signUpViaEmailInput: SignUpViaEmailInput) {
4047
const queryRunner = this.dataSource.createQueryRunner();
@@ -77,20 +84,55 @@ export class AuthService {
7784
} catch (e) {
7885
await queryRunner.rollbackTransaction();
7986

87+
this.logger.error(e.message, e);
88+
8089
throw new BadRequestException('Failed to sign up via email');
8190
} finally {
8291
await queryRunner.release();
8392
}
8493
}
8594

95+
async signInViaEmail(signInViaEmailInput: SignInViaEmailInput) {
96+
try {
97+
const { email, password } = signInViaEmailInput;
98+
99+
const user = await this.userRepository.findOne({
100+
where: {
101+
email,
102+
},
103+
});
104+
105+
if (!user || !(await user?.validatePassword(password))) {
106+
throw new UnauthorizedException('Invalid user credentials');
107+
}
108+
109+
const accessToken = await this.generateJwtToken(user);
110+
111+
return {
112+
user,
113+
auth: {
114+
accessToken,
115+
},
116+
} as SignedInViaEmailOutput;
117+
} catch (e) {
118+
this.logger.error(e.message, e);
119+
120+
throw e instanceof UnauthorizedException
121+
? e
122+
: new BadRequestException('Failed to sign in via email');
123+
}
124+
}
125+
86126
async findUserById(id: string) {
87127
try {
88128
return await this.userRepository.findOneOrFail({
89129
where: {
90130
id,
91131
},
92132
});
93-
} catch (error) {
133+
} catch (e) {
134+
this.logger.error(e.message, e);
135+
94136
throw new NotFoundException('User not found!');
95137
}
96138
}
@@ -102,7 +144,9 @@ export class AuthService {
102144
id: payload.id,
103145
},
104146
});
105-
} catch (error) {
147+
} catch (e) {
148+
this.logger.error(e.message, e);
149+
106150
throw new NotFoundException('User not found!');
107151
}
108152
}
@@ -112,7 +156,9 @@ export class AuthService {
112156
const payload = this.jwtService.decode(jwt) as JwtPayload;
113157

114158
return await this.findUserById(payload.id);
115-
} catch (error) {
159+
} catch (e) {
160+
this.logger.error(e.message, e);
161+
116162
throw new UnauthorizedException();
117163
}
118164
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { InputType, Field } from '@nestjs/graphql';
2+
3+
import {
4+
IsEmail,
5+
IsNotEmpty,
6+
IsString,
7+
Matches,
8+
MaxLength,
9+
MinLength,
10+
} from 'class-validator';
11+
12+
@InputType()
13+
export class SignInViaEmailInput {
14+
@Field({ description: 'Email address to sign into account with' })
15+
@IsNotEmpty()
16+
@MaxLength(100)
17+
@IsEmail()
18+
readonly email: string;
19+
20+
@Field({ description: 'Password to sign into account with' })
21+
@IsNotEmpty()
22+
@IsString()
23+
@MinLength(8)
24+
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
25+
message: 'Password too weak',
26+
})
27+
readonly password: string;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Field, ObjectType } from '@nestjs/graphql';
2+
3+
import { User } from '../../users/entities/user.entity';
4+
5+
import { AuthOutput } from './auth.output';
6+
7+
@ObjectType()
8+
export class SignedInViaEmailOutput {
9+
@Field(() => User, { description: 'User signed in' })
10+
public user: User;
11+
12+
@Field(() => AuthOutput, { description: 'Auth options' })
13+
public auth: AuthOutput;
14+
}

apps/main-service/src/app/auth/resolvers/auth.resolver.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Resolver, Mutation, Args } from '@nestjs/graphql';
22

33
import { SignUpViaEmailInput } from '../dto/sign-up-via-email.input';
4+
import { SignInViaEmailInput } from '../dto/sign-in-via-email.input';
45
import { SignedUpViaEmailOutput } from '../dto/signed-up-via-email.output';
6+
import { SignedInViaEmailOutput } from '../dto/signed-in-via-email.output';
57

68
import { AuthService } from '../auth.service';
79

@@ -16,4 +18,10 @@ export class AuthResolver {
1618
return this.authService.signUpViaEmail(signUpViaEmailInput);
1719
}
1820

21+
@Mutation(() => SignedInViaEmailOutput, { name: 'signInViaEmail' })
22+
signInViaEmail(
23+
@Args('signInViaEmailInput') signInViaEmailInput: SignInViaEmailInput
24+
) {
25+
return this.authService.signInViaEmail(signInViaEmailInput);
26+
}
1927
}

apps/main-service/src/app/verification/verification.resolver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { User } from '../users/entities/user.entity';
55

66
import { VerifyEmailViaOtpInput } from './dto/verify-email-via-otp.input';
77
import { VerifyPhoneViaOtpInput } from './dto/verify-phone-via-otp.input';
8+
import { SendOtpInput } from './dto/send-otp.input';
89

910
import { CurrentUser } from '../@common/decorators/current-user.decorator';
1011

1112
import { GqlAuthGuard } from '../@common/guards/gql-auth.guard';
1213

1314
import { VerificationService } from './verification.service';
14-
import { SendOtpInput } from './dto/send-otp.input';
1515

1616
@Resolver()
1717
@UseGuards(GqlAuthGuard)

apps/main-service/src/app/verification/verification.service.ts

+45-30
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,19 @@ export class VerificationService {
8787
verifyPhoneViaOtpInput: VerifyPhoneViaOtpInput,
8888
user: User
8989
) {
90-
const { code } = verifyPhoneViaOtpInput;
90+
try {
91+
const { code } = verifyPhoneViaOtpInput;
9192

92-
const res = await this.phoneVerificationService.verifyCode({
93-
code,
94-
number: user.phone,
95-
});
93+
const res = await this.phoneVerificationService.verifyCode({
94+
code,
95+
number: user.phone,
96+
});
9697

97-
if (!res.success) {
98-
this.logger.error(res);
99-
throw new BadRequestException('OTP Code is not valid!');
100-
}
98+
if (!res.success) {
99+
this.logger.error(res);
100+
throw new BadRequestException('OTP Code is not valid!');
101+
}
101102

102-
try {
103103
const fullUser = await this.userRepository.findOneOrFail({
104104
where: {
105105
id: user.id,
@@ -118,32 +118,44 @@ export class VerificationService {
118118
} catch (e) {
119119
this.logger.error(e.message, e);
120120

121-
throw new BadRequestException('Failed to verify phone via otp!');
121+
throw e instanceof BadRequestException
122+
? e
123+
: new BadRequestException('Failed to verify phone via otp!');
122124
}
123125
}
124126

125127
async sendOtpForPhoneVerification(sendOtpInput: SendOtpInput, user: User) {
126-
const { type } = sendOtpInput;
128+
try {
129+
const { type } = sendOtpInput;
127130

128-
const res = await this.phoneVerificationService.sendCode({
129-
number: user.phone,
130-
medium: type === OtpType.SMS ? 'sms' : 'voice',
131-
message: `Your ${environment.app.name} phone number verification code is %otp_code%. Do not share this code with anyone. Visit your ${environment.app.name} account now to verify.`,
132-
});
131+
const res = await this.phoneVerificationService.sendCode({
132+
number: user.phone,
133+
medium: type === OtpType.SMS ? 'sms' : 'voice',
134+
message: `Your ${environment.app.name} phone number verification code is %otp_code%. Do not share this code with anyone. Visit your ${environment.app.name} account now to verify.`,
135+
});
133136

134-
if (!res.success) {
135-
this.logger.error(res);
136-
throw new BadRequestException(
137-
'Failed to send OTP code. Please try again later!'
138-
);
139-
}
137+
if (!res.success) {
138+
this.logger.error(res);
139+
throw new BadRequestException(
140+
'Failed to send OTP code. Please try again later!'
141+
);
142+
}
140143

141-
return await this.userRepository.findOneOrFail({
142-
where: {
143-
id: user.id,
144-
},
145-
relations: ['profile', 'verification'],
146-
});
144+
return await this.userRepository.findOneOrFail({
145+
where: {
146+
id: user.id,
147+
},
148+
relations: ['profile', 'verification'],
149+
});
150+
} catch (e) {
151+
this.logger.error(e.message, e);
152+
153+
throw e instanceof BadRequestException
154+
? e
155+
: new BadRequestException(
156+
'Failed to send OTP code. Please try again later!'
157+
);
158+
}
147159
}
148160

149161
async sendOtpForEmailVerification(sendOtpInput: SendOtpInput, user: User) {
@@ -176,7 +188,8 @@ export class VerificationService {
176188

177189
return fullUser;
178190
} catch (e) {
179-
this.logger.error(e);
191+
this.logger.error(e.message, e);
192+
180193
throw new BadRequestException(
181194
'Failed to send OTP code. Please try again later!'
182195
);
@@ -191,6 +204,8 @@ export class VerificationService {
191204
},
192205
});
193206
} catch (e) {
207+
this.logger.error(e.message, e);
208+
194209
throw new NotFoundException('User Verification not found!');
195210
}
196211
}

0 commit comments

Comments
 (0)