From 477c2c4bc335e503cd94a4f57dd7b4f66955c37f Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:35:29 +0100 Subject: [PATCH 01/12] feat: login dto --- brints-estate-api/src/auth/dto/login.dto.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 brints-estate-api/src/auth/dto/login.dto.ts diff --git a/brints-estate-api/src/auth/dto/login.dto.ts b/brints-estate-api/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..0e642b8 --- /dev/null +++ b/brints-estate-api/src/auth/dto/login.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; + +export class LoginUserDto { + @ApiProperty() + @IsNotEmpty() + @IsEmail() + @IsString() + email: string; + + @ApiProperty() + @IsNotEmpty() + @IsString() + password: string; +} From 0df922ab1bafed3bde4fdd6d58dc97772323d77b Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:36:07 +0100 Subject: [PATCH 02/12] testing endpoint to login --- .../src/auth/http/auth.post.endpoint.http | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 brints-estate-api/src/auth/http/auth.post.endpoint.http diff --git a/brints-estate-api/src/auth/http/auth.post.endpoint.http b/brints-estate-api/src/auth/http/auth.post.endpoint.http new file mode 100644 index 0000000..151a202 --- /dev/null +++ b/brints-estate-api/src/auth/http/auth.post.endpoint.http @@ -0,0 +1,21 @@ +POST http://localhost:3001/auth/register +Content-Type: application/json + +{ + "first_name": "aniebiet", + "last_name": "afia", + "email": "aniebietafia@gmail.com", + "password": "Test1234$", + "confirm_password": "Test1234$", + "phone_number": "08012345678", + "gender": "male", + "country_code": "+234" +} + +POST http://localhost:3001/auth/login +Content-Type: application/json + +{ + "email": "aniebietafia@gmail.com", + "password": "Test1234$" +} \ No newline at end of file From d8fdf5f0c11ac3a1e41e56d3c610337659984c44 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:36:41 +0100 Subject: [PATCH 03/12] feat: create new user provider --- .../auth/providers/create-user.provider.ts | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 brints-estate-api/src/auth/providers/create-user.provider.ts diff --git a/brints-estate-api/src/auth/providers/create-user.provider.ts b/brints-estate-api/src/auth/providers/create-user.provider.ts new file mode 100644 index 0000000..43ec3da --- /dev/null +++ b/brints-estate-api/src/auth/providers/create-user.provider.ts @@ -0,0 +1,154 @@ +import { forwardRef, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { User } from 'src/users/entities/user.entity'; +import { UserAuth } from 'src/users/entities/userAuth.entity'; +import { CreateUserDto } from 'src/users/dto/create-user.dto'; +import { CreateUserAuthDto } from 'src/users/dto/create-userauth.dto'; +import { CustomException } from 'src/exceptions/custom.exception'; +import { UserHelper } from 'src/utils/userHelper.lib'; +import { HashingProvider } from './hashing.provider'; +import { VerificationStatus } from 'src/enums/roles.model'; +import { GenerateTokenHelper } from 'src/utils/generate-token.lib'; + +@Injectable() +export class CreateUserProvider { + constructor( + @InjectRepository(User) + private readonly userRepository: Repository, + + @InjectRepository(UserAuth) + private readonly userAuthRepository: Repository, + + @Inject(forwardRef(() => HashingProvider)) + private readonly hashingProvider: HashingProvider, + + @Inject(forwardRef(() => UserHelper)) + private readonly userHelper: UserHelper, + + @Inject(forwardRef(() => GenerateTokenHelper)) + private readonly generateTokenHelper: GenerateTokenHelper, + ) {} + + public async createUser( + createUserDto: CreateUserDto, + createUserAuthDto: CreateUserAuthDto, + ): Promise { + const { + first_name, + last_name, + email, + password, + confirm_password, + phone_number, + gender, + country_code, + } = createUserDto; + + if (gender.toLowerCase() !== 'female' && gender.toLowerCase() !== 'male') { + throw new CustomException( + HttpStatus.BAD_REQUEST, + `${gender} is not a valid gender`, + ); + } + + if (!country_code.startsWith('+')) { + throw new CustomException( + HttpStatus.BAD_REQUEST, + 'Country code must start with a + followed by a number', + ); + } + + const fullPhoneNumber = this.userHelper.formatPhoneNumber( + country_code, + phone_number, + ); + + if (password !== confirm_password) { + throw new CustomException( + HttpStatus.BAD_REQUEST, + 'Passwords do not match. Please try again', + ); + } + + if (password === email) { + throw new CustomException( + HttpStatus.BAD_REQUEST, + 'Password cannot be the same as email', + ); + } + + const formattedFirstName = + this.userHelper.capitalizeFirstLetter(first_name); + const formattedLastName = this.userHelper.capitalizeFirstLetter(last_name); + + const userExists = await this.userRepository.findOne({ + where: { email: email.toLowerCase() }, + }); + if (userExists) { + throw new CustomException( + HttpStatus.CONFLICT, + 'User Exists already. Please login', + ); + } + + const phoneNumberExists = await this.userRepository.findOne({ + where: { phone_number: fullPhoneNumber }, + }); + if (phoneNumberExists) { + throw new CustomException( + HttpStatus.CONFLICT, + 'Phone number Exists already. Use another phone number', + ); + } + + const user = this.userRepository.create({ + ...CreateUserDto, + first_name: formattedFirstName, + last_name: formattedLastName, + email: email.toLowerCase(), + phone_number: fullPhoneNumber, + password: await this.hashingProvider.hashPassword(password), + gender, + }); + + const verificationToken = + this.generateTokenHelper.generateVerificationToken(); + const verificationTokenExpiry = new Date(); + verificationTokenExpiry.setHours(verificationTokenExpiry.getHours() + 1); + + const newOtp = this.generateTokenHelper.generateOTP(6); + const otpExpiry = new Date(); + otpExpiry.setMinutes(otpExpiry.getMinutes() + 20); + + const emailVerificationToken = verificationToken; + const emailVerificationTokenExpiresIn = verificationTokenExpiry; + + const otp = parseInt(newOtp); + const otpExpiresIn = otpExpiry; + + const isEmailVerified = false; + const isPhoneNumberVerified = false; + const status = VerificationStatus.PENDING; + + const userAuth = this.userAuthRepository.create({ + ...createUserAuthDto, + emailVerificationToken, + emailVerificationTokenExpiresIn, + otp, + otpExpiresIn, + isEmailVerified, + isPhoneNumberVerified, + status, + user, + }); + + user.user_auth = userAuth; + + await this.userAuthRepository.save(userAuth); + await this.userRepository.save(user); + + return user; + } +} From ef42cd35fde451d0afbdea12e149424a4ed843ad Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:37:10 +0100 Subject: [PATCH 04/12] feat: login user provider --- .../src/auth/providers/login-user.provider.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 brints-estate-api/src/auth/providers/login-user.provider.ts diff --git a/brints-estate-api/src/auth/providers/login-user.provider.ts b/brints-estate-api/src/auth/providers/login-user.provider.ts new file mode 100644 index 0000000..0c48ce6 --- /dev/null +++ b/brints-estate-api/src/auth/providers/login-user.provider.ts @@ -0,0 +1,43 @@ +import { forwardRef, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { User } from 'src/users/entities/user.entity'; +import { HashingProvider } from './hashing.provider'; +import { LoginUserDto } from '../dto/login.dto'; +import { CustomException } from 'src/exceptions/custom.exception'; + +@Injectable() +export class LoginUserProvider { + constructor( + @InjectRepository(User) + private readonly userRepository: Repository, + + @Inject(forwardRef(() => HashingProvider)) + private readonly hashingProvider: HashingProvider, + ) {} + + public async loginUser(loginUserDto: LoginUserDto): Promise { + const user = await this.userRepository.findOne({ + where: { email: loginUserDto.email }, + }); + + if (!user) { + throw new CustomException(HttpStatus.NOT_FOUND, 'User not found'); + } + + const passwordMatch = await this.hashingProvider.comparePassword( + loginUserDto.password, + user.password, + ); + + if (!passwordMatch) { + throw new CustomException( + HttpStatus.BAD_REQUEST, + 'Invalid login credentials', + ); + } + + return user; + } +} From 49f456272ee275d2a2ccb2ca9376474f82bbf627 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:37:51 +0100 Subject: [PATCH 05/12] feat: updated controller with login controller --- brints-estate-api/src/auth/auth.controller.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/brints-estate-api/src/auth/auth.controller.ts b/brints-estate-api/src/auth/auth.controller.ts index 7b427bc..b3b94b5 100644 --- a/brints-estate-api/src/auth/auth.controller.ts +++ b/brints-estate-api/src/auth/auth.controller.ts @@ -1,11 +1,13 @@ -import { Controller, Post, Body, HttpStatus } from '@nestjs/common'; +import { Controller, Post, Body, HttpStatus, HttpCode } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + import { AuthService } from './providers/auth.service'; import { CreateUserDto } from 'src/users/dto/create-user.dto'; import { CreateUserAuthDto } from 'src/users/dto/create-userauth.dto'; -import { ApiTags } from '@nestjs/swagger'; +import { LoginUserDto } from './dto/login.dto'; @Controller('auth') -@ApiTags('Auth') +@ApiTags('Authentication') export class AuthController { constructor(private readonly authService: AuthService) {} @@ -25,4 +27,15 @@ export class AuthController { data: user, }; } + + @Post('login') + @HttpCode(HttpStatus.OK) + async loginUser(@Body() loginUserDto: LoginUserDto) { + const user = await this.authService.loginUser(loginUserDto); + return { + message: 'Login successful', + status_code: HttpStatus.OK, + data: user, + }; + } } From 9843b9b8d021b99c7930b84ddcd016412f7a8c40 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:38:43 +0100 Subject: [PATCH 06/12] feat: injected providers for login and creating user --- brints-estate-api/src/auth/auth.module.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/brints-estate-api/src/auth/auth.module.ts b/brints-estate-api/src/auth/auth.module.ts index aeb79ca..4dd887e 100644 --- a/brints-estate-api/src/auth/auth.module.ts +++ b/brints-estate-api/src/auth/auth.module.ts @@ -4,11 +4,19 @@ import { AuthService } from './providers/auth.service'; import { UsersModule } from 'src/users/users.module'; import { HashingProvider } from './providers/hashing.provider'; import { BcryptProvider } from './providers/bcrypt.provider'; +import { CreateUserProvider } from './providers/create-user.provider'; +import { UserHelper } from 'src/utils/userHelper.lib'; +import { GenerateTokenHelper } from 'src/utils/generate-token.lib'; +import { LoginUserProvider } from './providers/login-user.provider'; @Module({ controllers: [AuthController], providers: [ AuthService, + CreateUserProvider, + LoginUserProvider, + UserHelper, + GenerateTokenHelper, { provide: HashingProvider, useClass: BcryptProvider, From 883439535cb8c5864e00a96cf794ac6c5e297c2e Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:39:34 +0100 Subject: [PATCH 07/12] refactor: modified custom exception --- brints-estate-api/src/exceptions/custom.exception.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brints-estate-api/src/exceptions/custom.exception.ts b/brints-estate-api/src/exceptions/custom.exception.ts index d096727..8bdd0dd 100644 --- a/brints-estate-api/src/exceptions/custom.exception.ts +++ b/brints-estate-api/src/exceptions/custom.exception.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus } from '@nestjs/common'; -export class CustomConflictException extends HttpException { +export class CustomException extends HttpException { constructor(status_code: HttpStatus, message: string) { super(message, status_code); } From 28bb4e86e38f8b66d1b2bca20e82d0cf22c04360 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:40:52 +0100 Subject: [PATCH 08/12] refactor: modified create user dto to include country code --- brints-estate-api/src/users/dto/create-user.dto.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/brints-estate-api/src/users/dto/create-user.dto.ts b/brints-estate-api/src/users/dto/create-user.dto.ts index 1aa6c2b..ab1c979 100644 --- a/brints-estate-api/src/users/dto/create-user.dto.ts +++ b/brints-estate-api/src/users/dto/create-user.dto.ts @@ -52,7 +52,15 @@ export class CreateUserDto { @ApiProperty() @IsNotEmpty() @IsString() - @MaxLength(15) + @Matches(/^\+[0-9]{1,3}$/, { + message: 'Country code must start with a + followed by a number', + }) + country_code: string; + + @ApiProperty() + @IsNotEmpty() + @IsString() + @MaxLength(50) phone_number: string; @ApiProperty() From efca4b04f4dd6657af5a509763a5cbc756b7f051 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:42:15 +0100 Subject: [PATCH 09/12] refactor: modified length of phone number in user entity --- brints-estate-api/src/users/entities/user.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brints-estate-api/src/users/entities/user.entity.ts b/brints-estate-api/src/users/entities/user.entity.ts index 47222fd..7dee0d1 100644 --- a/brints-estate-api/src/users/entities/user.entity.ts +++ b/brints-estate-api/src/users/entities/user.entity.ts @@ -17,10 +17,10 @@ export class User extends AbstractBaseEntity { @Column({ type: 'varchar', length: 255, unique: true }) email: string; - @Column({ type: 'varchar', length: 16 }) + @Column({ type: 'varchar', length: 255 }) password: string; - @Column({ type: 'varchar', length: 15 }) + @Column({ type: 'varchar', length: 50, unique: true }) phone_number: string; @Column({ type: 'enum', enum: UserGender }) From e0875595c4bd6dfa735dc30a0e445323655eb13b Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:43:08 +0100 Subject: [PATCH 10/12] feat: building generation token class --- .../src/utils/generate-token.lib.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/brints-estate-api/src/utils/generate-token.lib.ts b/brints-estate-api/src/utils/generate-token.lib.ts index 38071ae..a54196c 100644 --- a/brints-estate-api/src/utils/generate-token.lib.ts +++ b/brints-estate-api/src/utils/generate-token.lib.ts @@ -1,11 +1,25 @@ import * as crypto from 'crypto'; -export function generateVerificationToken() { - return crypto.randomBytes(40).toString('hex'); -} +// export function generateVerificationToken() { +// return crypto.randomBytes(40).toString('hex'); +// } + +// export function generateOTP(length: number) { +// return Math.floor(100000 + Math.random() * 900000) +// .toString() +// .slice(0, length); +// } + +export class GenerateTokenHelper { + constructor() {} + + public generateVerificationToken() { + return crypto.randomBytes(40).toString('hex'); + } -export function generateOTP(length: number) { - return Math.floor(100000 + Math.random() * 900000) - .toString() - .slice(0, length); + public generateOTP(length: number) { + return Math.floor(100000 + Math.random() * 900000) + .toString() + .slice(0, length); + } } From 80e0855f3153ef3b90b2bfd8a75baafcd0e6ae64 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:43:48 +0100 Subject: [PATCH 11/12] feat: user helper class --- brints-estate-api/src/utils/userHelper.lib.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 brints-estate-api/src/utils/userHelper.lib.ts diff --git a/brints-estate-api/src/utils/userHelper.lib.ts b/brints-estate-api/src/utils/userHelper.lib.ts new file mode 100644 index 0000000..20f1ec2 --- /dev/null +++ b/brints-estate-api/src/utils/userHelper.lib.ts @@ -0,0 +1,18 @@ +export class UserHelper { + constructor() {} + + public capitalizeFirstLetter(name: string): string { + return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(); + } + + public validateCountryCode(countryCode: string): boolean { + return countryCode.startsWith('+'); + } + + public formatPhoneNumber(countryCode: string, phoneNumber: string): string { + if (phoneNumber.startsWith('0')) { + phoneNumber = phoneNumber.slice(1); + } + return `${countryCode}${phoneNumber}`; + } +} From fd06a420aa720b07c18d2e08c348f875ce83b41e Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Mon, 2 Sep 2024 17:44:34 +0100 Subject: [PATCH 12/12] feat: installing jwt package --- brints-estate-api/package.json | 1 + .../src/auth/http/auth.endpoint.http | 12 -- brints-estate-api/yarn.lock | 128 +++++++++++++++++- 3 files changed, 128 insertions(+), 13 deletions(-) delete mode 100644 brints-estate-api/src/auth/http/auth.endpoint.http diff --git a/brints-estate-api/package.json b/brints-estate-api/package.json index 1ace16a..a6ee0fc 100644 --- a/brints-estate-api/package.json +++ b/brints-estate-api/package.json @@ -24,6 +24,7 @@ "@nestjs/common": "^10.4.1", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.4.1", + "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.4.1", "@nestjs/swagger": "^7.4.0", diff --git a/brints-estate-api/src/auth/http/auth.endpoint.http b/brints-estate-api/src/auth/http/auth.endpoint.http deleted file mode 100644 index bf9442a..0000000 --- a/brints-estate-api/src/auth/http/auth.endpoint.http +++ /dev/null @@ -1,12 +0,0 @@ -POST http://localhost:3001/auth/register -Content-Type: application/json - -{ - "first_name": "John", - "last_name": "Doe", - "email": "johndoe@test.com", - "password": "Test1234$", - "confirm_password": "Test1234$", - "phone_number": "08012345678", - "gender": "male" -} \ No newline at end of file diff --git a/brints-estate-api/yarn.lock b/brints-estate-api/yarn.lock index 1db229a..9bc8e8c 100644 --- a/brints-estate-api/yarn.lock +++ b/brints-estate-api/yarn.lock @@ -1064,6 +1064,18 @@ __metadata: languageName: node linkType: hard +"@nestjs/jwt@npm:^10.2.0": + version: 10.2.0 + resolution: "@nestjs/jwt@npm:10.2.0" + dependencies: + "@types/jsonwebtoken": "npm:9.0.5" + jsonwebtoken: "npm:9.0.2" + peerDependencies: + "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/81c5cbcb459122b175ad6b50dad83aab7d5dc3beb6122a56c7f985cc1c7838cd1c5eae9d630e95550b95a03e183502a183029e36ba51879c638bd0bad086c056 + languageName: node + linkType: hard + "@nestjs/mapped-types@npm:*, @nestjs/mapped-types@npm:2.0.5": version: 2.0.5 resolution: "@nestjs/mapped-types@npm:2.0.5" @@ -1525,6 +1537,15 @@ __metadata: languageName: node linkType: hard +"@types/jsonwebtoken@npm:9.0.5": + version: 9.0.5 + resolution: "@types/jsonwebtoken@npm:9.0.5" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/c582b8420586f3b9550f7e34992cb32be300bc953636f3b087ed9c180ce7ea5c2e4b35090be2d57f0d3168cc3ca1074932907caa2afe09f4e9c84cf5c0daefa8 + languageName: node + linkType: hard + "@types/methods@npm:^1.1.4": version: 1.1.4 resolution: "@types/methods@npm:1.1.4" @@ -2429,6 +2450,7 @@ __metadata: "@nestjs/common": "npm:^10.4.1" "@nestjs/config": "npm:^3.2.3" "@nestjs/core": "npm:^10.4.1" + "@nestjs/jwt": "npm:^10.2.0" "@nestjs/mapped-types": "npm:*" "@nestjs/platform-express": "npm:^10.4.1" "@nestjs/schematics": "npm:^10.1.4" @@ -2500,6 +2522,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -3220,6 +3249,15 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -5224,6 +5262,45 @@ __metadata: languageName: node linkType: hard +"jsonwebtoken@npm:9.0.2": + version: 9.0.2 + resolution: "jsonwebtoken@npm:9.0.2" + dependencies: + jws: "npm:^3.2.2" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10c0/d287a29814895e866db2e5a0209ce730cbc158441a0e5a70d5e940eb0d28ab7498c6bf45029cc8b479639bca94056e9a7f254e2cdb92a2f5750c7f358657a131 + languageName: node + linkType: hard + +"jwa@npm:^1.4.1": + version: 1.4.1 + resolution: "jwa@npm:1.4.1" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/5c533540bf38702e73cf14765805a94027c66a0aa8b16bc3e89d8d905e61a4ce2791e87e21be97d1293a5ee9d4f3e5e47737e671768265ca4f25706db551d5e9 + languageName: node + linkType: hard + +"jws@npm:^3.2.2": + version: 3.2.2 + resolution: "jws@npm:3.2.2" + dependencies: + jwa: "npm:^1.4.1" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/e770704533d92df358adad7d1261fdecad4d7b66fa153ba80d047e03ca0f1f73007ce5ed3fbc04d2eba09ba6e7e6e645f351e08e5ab51614df1b0aa4f384dfff + languageName: node + linkType: hard + "keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -5296,6 +5373,48 @@ __metadata: languageName: node linkType: hard +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 10c0/7ca498b9b75bf602d04e48c0adb842dfc7d90f77bcb2a91a2b2be34a723ad24bc1c8b3683ec6b2552a90f216c723cdea530ddb11a3320e08fa38265703978f4b + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 + languageName: node + linkType: hard + +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 10c0/4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 10c0/2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: 10c0/09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 + languageName: node + linkType: hard + "lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -5310,6 +5429,13 @@ __metadata: languageName: node linkType: hard +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 10c0/46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 + languageName: node + linkType: hard + "lodash@npm:4.17.21, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -5662,7 +5788,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3": +"ms@npm:2.1.3, ms@npm:^2.1.1": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48