Skip to content

Commit 298ab9f

Browse files
committed
complete send verification email
1 parent 7fb4cfc commit 298ab9f

File tree

10 files changed

+137
-13
lines changed

10 files changed

+137
-13
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ After that you will need to select the following scopes:
8484
- `read:users`
8585
- `read:roles`
8686
- `delete:users`
87+
- `update:users` (Sending email verification)
8788

8889
These are the only scopes we need, but you can select all if you want.
8990

docs/cc-api-spec.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@types/express-jwt": "^0.0.42",
5656
"@types/jest": "^26.0.19",
5757
"@types/node": "^10.12.18",
58+
"@types/node-fetch": "^2.6.1",
5859
"@types/supertest": "^2.0.7",
5960
"concurrently": "^4.1.0",
6061
"faker": "^5.1.0",

src/modules/common/auth0.service.ts renamed to src/modules/common/auth0/auth0.service.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { Injectable } from '@nestjs/common';
1+
import { HttpException, Injectable } from '@nestjs/common';
22
import fetch from 'node-fetch';
3-
import Config from '../../config';
3+
import * as Sentry from '@sentry/node';
4+
import Config from '../../../config';
5+
import type { Auth0Response } from './auth0.types';
46

57
@Injectable()
68
export class Auth0Service {
79
// Get an access token for the Auth0 Admin API
8-
async getAdminAccessToken() {
10+
async getAdminAccessToken(): Promise<{ access_token: string }> {
911
const options = {
1012
method: 'POST',
1113
headers: { 'content-type': 'application/json' },
@@ -27,7 +29,7 @@ export class Auth0Service {
2729
}
2830

2931
// Get the user's profile from auth0
30-
async getUserProfile(accessToken, userID) {
32+
async getUserProfile(accessToken: string, userID: string) {
3133
const options = {
3234
headers: {
3335
Authorization: `Bearer ${accessToken}`,
@@ -59,4 +61,41 @@ export class Auth0Service {
5961

6062
return response;
6163
}
64+
65+
async sendVerificationEmail(
66+
accessToken: string,
67+
auth0UserId: string,
68+
): Promise<Auth0Response> {
69+
try {
70+
const [provider, userId] = auth0UserId.split('|');
71+
const payload = {
72+
user_id: auth0UserId,
73+
identity: { user_id: userId, provider },
74+
};
75+
const options = {
76+
method: 'POST',
77+
headers: {
78+
Authorization: `Bearer ${accessToken}`,
79+
'content-type': 'application/json',
80+
},
81+
body: JSON.stringify(payload),
82+
};
83+
84+
const response: Auth0Response = await (
85+
await fetch(
86+
`https://${Config.auth0.backend.DOMAIN}/api/v2/jobs/verification-email`,
87+
options,
88+
)
89+
).json();
90+
91+
if ('statusCode' in response) {
92+
throw new HttpException(response, response.statusCode);
93+
}
94+
95+
return response;
96+
} catch (error) {
97+
Sentry.captureException(error);
98+
throw error;
99+
}
100+
}
62101
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
interface Auth0ResponseSuccess {} // tslint:disable-line
2+
3+
interface Auth0ResponseError {
4+
statusCode: number;
5+
error: string;
6+
message: string;
7+
errorCode: string;
8+
}
9+
10+
export type Auth0Response = Auth0ResponseSuccess | Auth0ResponseError;
11+
12+
interface Auth0UserIdentity {
13+
connection: string;
14+
provider: string;
15+
user_id: string;
16+
isSocial: boolean;
17+
}
18+
19+
export interface Auth0User {
20+
created_at: string;
21+
email: string;
22+
email_verified: boolean;
23+
identities: Auth0UserIdentity[];
24+
name: string;
25+
nickname: string;
26+
picture: string;
27+
updated_at: string;
28+
user_id: string;
29+
last_ip: string;
30+
last_login: string;
31+
logins_count: number;
32+
}

src/modules/common/common.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Module } from '@nestjs/common';
2-
import { Auth0Service } from './auth0.service';
2+
import { Auth0Service } from './auth0/auth0.service';
33
import { UsersService } from './users.service';
44
import { commonProviders } from './common.providers';
55
import { DatabaseModule } from '../../database/database.module';

src/modules/mentorships/__tests__/mentorships.controller.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('modules/mentorships/MentorshipsController', () => {
132132

133133
await expect(
134134
mentorshipsController.applyForMentorship(
135-
<Request>request,
135+
request as Request,
136136
mentorId,
137137
mentorship,
138138
),

src/modules/users/users.controller.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import Config from '../../config';
2828
import { UserDto } from '../common/dto/user.dto';
2929
import { UserRecordDto } from '../common/dto/user-record.dto';
3030
import { UsersService } from '../common/users.service';
31-
import { Auth0Service } from '../common/auth0.service';
31+
import { Auth0Service } from '../common/auth0/auth0.service';
3232
import { FileService } from '../common/file.service';
3333
import { MentorsService } from '../common/mentors.service';
3434
import { Role, User } from '../common/interfaces/user.interface';
@@ -87,7 +87,7 @@ export class UsersController {
8787

8888
if (!currentUser) {
8989
try {
90-
const data: any = await this.auth0Service.getAdminAccessToken();
90+
const data = await this.auth0Service.getAdminAccessToken();
9191
const user: User = await this.auth0Service.getUserProfile(
9292
data.access_token,
9393
userId,
@@ -159,7 +159,7 @@ export class UsersController {
159159
return response;
160160
}
161161

162-
private enrichUserResponse(user: User, auth0User: User): User {
162+
private enrichUserResponse(user: User, auth0User: AccessTokenUser): User {
163163
return {
164164
...user,
165165
email_verified: Boolean(auth0User.email_verified),
@@ -388,6 +388,30 @@ export class UsersController {
388388
};
389389
}
390390

391+
@ApiOperation({ title: 'Send a verification email' })
392+
@ApiImplicitParam({ name: 'id', description: 'The user _id' })
393+
@Post('verify')
394+
async resendVerificationEmail(@Req() request: Request) {
395+
const user: User = await this.usersService.findByAuth0Id(
396+
request.user.auth0Id,
397+
);
398+
399+
if (!user) {
400+
throw new BadRequestException('User not found');
401+
}
402+
403+
const data = await this.auth0Service.getAdminAccessToken();
404+
const response = await this.auth0Service.sendVerificationEmail(
405+
data.access_token,
406+
user.auth0Id,
407+
);
408+
409+
return {
410+
success: true,
411+
data: response,
412+
};
413+
}
414+
391415
//#region admin
392416
@ApiOperation({ title: 'Add a record to user' })
393417
@ApiImplicitParam({ name: 'id', description: 'The user _id' })

src/utils/request.d.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
import { User } from '../modules/common/interfaces/user.interface';
1+
interface AccessTokenUser {
2+
iss: string;
3+
sub: string;
4+
aud: string;
5+
iat: number;
6+
exp: number;
7+
at_hash: string;
8+
nonce: string;
9+
auth0Id: string;
10+
email_verified: boolean;
11+
}
212

313
declare module 'express-serve-static-core' {
414
interface Request {
5-
user?: User;
15+
user?: AccessTokenUser;
616
}
717
}

yarn.lock

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,14 @@
819819
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
820820
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
821821

822+
"@types/node-fetch@^2.6.1":
823+
version "2.6.1"
824+
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975"
825+
integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==
826+
dependencies:
827+
"@types/node" "*"
828+
form-data "^3.0.0"
829+
822830
"@types/node@*":
823831
version "12.0.2"
824832
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40"
@@ -1615,7 +1623,7 @@ color@^3.1.2:
16151623
color-convert "^1.9.1"
16161624
color-string "^1.5.2"
16171625

1618-
combined-stream@^1.0.6, combined-stream@~1.0.6:
1626+
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
16191627
version "1.0.8"
16201628
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
16211629
dependencies:
@@ -2518,6 +2526,15 @@ form-data@^2.5.0:
25182526
combined-stream "^1.0.6"
25192527
mime-types "^2.1.12"
25202528

2529+
form-data@^3.0.0:
2530+
version "3.0.1"
2531+
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
2532+
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
2533+
dependencies:
2534+
asynckit "^0.4.0"
2535+
combined-stream "^1.0.8"
2536+
mime-types "^2.1.12"
2537+
25212538
formidable@^1.2.0:
25222539
version "1.2.1"
25232540
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659"

0 commit comments

Comments
 (0)