From b52448058bc8c91d842f409d96547dc2ab558c4e Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 Nov 2023 20:24:41 +0100 Subject: [PATCH 1/2] feat: api auth --- packages/api/src/app.controller.ts | 23 +++++++++++++-- packages/api/src/app.module.ts | 6 ++-- packages/api/src/auth/auth.module.ts | 29 +++++++++++++++++++ packages/api/src/auth/auth.service.spec.ts | 18 ++++++++++++ packages/api/src/auth/auth.service.ts | 26 +++++++++++++++++ .../api/src/auth/guards/jwt-auth.guard.ts | 5 ++++ .../api/src/auth/guards/local-auth.guard.ts | 5 ++++ .../api/src/auth/strategies/jwt.strategy.ts | 19 ++++++++++++ .../api/src/auth/strategies/local.strategy.ts | 19 ++++++++++++ packages/api/src/auth/users/users.module.ts | 8 +++++ .../api/src/auth/users/users.service.spec.ts | 18 ++++++++++++ packages/api/src/auth/users/users.service.ts | 24 +++++++++++++++ packages/api/src/auth/utils/constants.ts | 3 ++ 13 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 packages/api/src/auth/auth.module.ts create mode 100644 packages/api/src/auth/auth.service.spec.ts create mode 100644 packages/api/src/auth/auth.service.ts create mode 100644 packages/api/src/auth/guards/jwt-auth.guard.ts create mode 100644 packages/api/src/auth/guards/local-auth.guard.ts create mode 100644 packages/api/src/auth/strategies/jwt.strategy.ts create mode 100644 packages/api/src/auth/strategies/local.strategy.ts create mode 100644 packages/api/src/auth/users/users.module.ts create mode 100644 packages/api/src/auth/users/users.service.spec.ts create mode 100644 packages/api/src/auth/users/users.service.ts create mode 100644 packages/api/src/auth/utils/constants.ts diff --git a/packages/api/src/app.controller.ts b/packages/api/src/app.controller.ts index cce879ee6..3a277d3d0 100644 --- a/packages/api/src/app.controller.ts +++ b/packages/api/src/app.controller.ts @@ -1,12 +1,31 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Request, Post, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; +import { AuthGuard } from '@nestjs/passport'; +import { LocalAuthGuard } from './auth/guards/local-auth.guard'; +import { AuthService } from './auth/auth.service'; +import { JwtAuthGuard } from './auth/guards/jwt-auth.guard'; @Controller() export class AppController { - constructor(private readonly appService: AppService) {} + constructor( + private readonly appService: AppService, + private authService: AuthService, + ) {} @Get() getHello(): string { return this.appService.getHello(); } + + @UseGuards(JwtAuthGuard) + @Get('profile') + getProfile(@Request() req) { + return req.user; + } + + @UseGuards(LocalAuthGuard) + @Post('auth/login') + async login(@Request() req) { + return this.authService.login(req.user); + } } diff --git a/packages/api/src/app.module.ts b/packages/api/src/app.module.ts index b997af3d7..819452f4e 100644 --- a/packages/api/src/app.module.ts +++ b/packages/api/src/app.module.ts @@ -3,10 +3,12 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ContactModule } from './crm/contact/contact.module'; import { CrmModule } from './crm/crm.module'; +import { AuthModule } from './auth/auth.module'; +import { AuthService } from './auth/auth.service'; @Module({ - imports: [CrmModule], + imports: [CrmModule, AuthModule], controllers: [AppController], - providers: [AppService], + providers: [AppService, AuthService], }) export class AppModule {} diff --git a/packages/api/src/auth/auth.module.ts b/packages/api/src/auth/auth.module.ts new file mode 100644 index 000000000..b904aa993 --- /dev/null +++ b/packages/api/src/auth/auth.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { UsersModule } from './users/users.module'; +import { LocalStrategy } from './strategies/local.strategy'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule, JwtService } from '@nestjs/jwt'; +import { jwtConstants } from './utils/constants'; +import { JwtStrategy } from './strategies/jwt.strategy'; +import { UsersService } from './users/users.service'; + +@Module({ + providers: [ + AuthService, + LocalStrategy, + JwtStrategy, + UsersService, + JwtService, + ], + imports: [ + UsersModule, + PassportModule, + JwtModule.register({ + secret: jwtConstants.secret, + signOptions: { expiresIn: '60s' }, + }), + ], + exports: [UsersService, JwtService], +}) +export class AuthModule {} diff --git a/packages/api/src/auth/auth.service.spec.ts b/packages/api/src/auth/auth.service.spec.ts new file mode 100644 index 000000000..800ab6626 --- /dev/null +++ b/packages/api/src/auth/auth.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AuthService], + }).compile(); + + service = module.get(AuthService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/api/src/auth/auth.service.ts b/packages/api/src/auth/auth.service.ts new file mode 100644 index 000000000..d9367c11a --- /dev/null +++ b/packages/api/src/auth/auth.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { UsersService } from './users/users.service'; +import { JwtService } from '@nestjs/jwt'; + +@Injectable() +export class AuthService { + constructor( + private usersService: UsersService, + private jwtService: JwtService, + ) {} + + async validateUser(username: string, pass: string): Promise { + const user = await this.usersService.findOne(username); + if (user && user.password === pass) { + const { password, ...result } = user; + return result; + } + return null; + } + async login(user: any) { + const payload = { username: user.username, sub: user.userId }; + return { + access_token: this.jwtService.sign(payload), + }; + } +} diff --git a/packages/api/src/auth/guards/jwt-auth.guard.ts b/packages/api/src/auth/guards/jwt-auth.guard.ts new file mode 100644 index 000000000..2155290ed --- /dev/null +++ b/packages/api/src/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/packages/api/src/auth/guards/local-auth.guard.ts b/packages/api/src/auth/guards/local-auth.guard.ts new file mode 100644 index 000000000..ccf962b67 --- /dev/null +++ b/packages/api/src/auth/guards/local-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class LocalAuthGuard extends AuthGuard('local') {} diff --git a/packages/api/src/auth/strategies/jwt.strategy.ts b/packages/api/src/auth/strategies/jwt.strategy.ts new file mode 100644 index 000000000..601f94a7b --- /dev/null +++ b/packages/api/src/auth/strategies/jwt.strategy.ts @@ -0,0 +1,19 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { jwtConstants } from '../utils/constants'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwtConstants.secret, + }); + } + + async validate(payload: any) { + return { userId: payload.sub, username: payload.username }; + } +} diff --git a/packages/api/src/auth/strategies/local.strategy.ts b/packages/api/src/auth/strategies/local.strategy.ts new file mode 100644 index 000000000..beaa26dae --- /dev/null +++ b/packages/api/src/auth/strategies/local.strategy.ts @@ -0,0 +1,19 @@ +import { Strategy } from 'passport-local'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { AuthService } from '../auth.service'; + +@Injectable() +export class LocalStrategy extends PassportStrategy(Strategy) { + constructor(private authService: AuthService) { + super(); + } + + async validate(username: string, password: string): Promise { + const user = await this.authService.validateUser(username, password); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} diff --git a/packages/api/src/auth/users/users.module.ts b/packages/api/src/auth/users/users.module.ts new file mode 100644 index 000000000..8fa904f17 --- /dev/null +++ b/packages/api/src/auth/users/users.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; + +@Module({ + providers: [UsersService], + exports: [UsersService], +}) +export class UsersModule {} diff --git a/packages/api/src/auth/users/users.service.spec.ts b/packages/api/src/auth/users/users.service.spec.ts new file mode 100644 index 000000000..62815ba64 --- /dev/null +++ b/packages/api/src/auth/users/users.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersService } from './users.service'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/api/src/auth/users/users.service.ts b/packages/api/src/auth/users/users.service.ts new file mode 100644 index 000000000..e8e4595fb --- /dev/null +++ b/packages/api/src/auth/users/users.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; + +// This should be a real class/interface representing a user entity +export type User = any; + +@Injectable() +export class UsersService { + private readonly users = [ + { + userId: 1, + username: 'john', + password: 'changeme', + }, + { + userId: 2, + username: 'maria', + password: 'guess', + }, + ]; + + async findOne(username: string): Promise { + return this.users.find((user) => user.username === username); + } +} diff --git a/packages/api/src/auth/utils/constants.ts b/packages/api/src/auth/utils/constants.ts new file mode 100644 index 000000000..633a88263 --- /dev/null +++ b/packages/api/src/auth/utils/constants.ts @@ -0,0 +1,3 @@ +export const jwtConstants = { + secret: process.env.JWT_SECRET, +}; From 7af35d23adde0a80ad5fcbd10031f6993a8c4570 Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 Nov 2023 23:36:17 +0100 Subject: [PATCH 2/2] feat: add package.json in api --- packages/api/package.json | 3 + packages/api/prisma/schema.prisma | 23 ++++-- pnpm-lock.yaml | 118 ++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 7 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 6896d36a9..f1759d534 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,6 +23,7 @@ "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.1.1", "@nestjs/mapped-types": "*", "@nestjs/passport": "^10.0.2", "@nestjs/platform-express": "^10.0.0", @@ -30,6 +31,7 @@ "@prisma/client": "^5.4.2", "axios": "^1.5.1", "passport": "^0.6.0", + "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", @@ -42,6 +44,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-jwt": "^3.0.12", "@types/passport-local": "^1.0.37", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index ef317f765..ee81aef80 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -48,12 +48,12 @@ model jobs { } model jobs_status_history { - id Int @id(map: "pk_1") @default(autoincrement()) - timestamp DateTime @default(now()) @db.Timestamp(6) - previous_status String - new_status String - id_job Int @default(autoincrement()) - jobs jobs @relation(fields: [id_job], references: [id_job], onDelete: NoAction, onUpdate: NoAction, map: "fk_4") + id_jobs_status_history Int @id(map: "pk_1") @default(autoincrement()) + timestamp DateTime @default(now()) @db.Timestamp(6) + previous_status String + new_status String + id_job Int @default(autoincrement()) + jobs jobs @relation(fields: [id_job], references: [id_job], onDelete: NoAction, onUpdate: NoAction, map: "fk_4") @@index([id_job], map: "id_job_jobs_status_history") } @@ -63,7 +63,6 @@ model organizations { name String stripe_customer_id String timezone String - logo_url String projects projects[] users users[] } @@ -86,7 +85,17 @@ model users { created_at DateTime @default(now()) @db.Timestamp(6) modified_at DateTime @default(now()) @db.Timestamp(6) id_organization BigInt + api_keys api_keys[] organizations organizations @relation(fields: [id_organization], references: [id_organization], onDelete: NoAction, onUpdate: NoAction, map: "fk_5") @@index([id_organization], map: "fk_1_users") } + +model api_keys { + id_api_key BigInt @id(map: "id_") @default(autoincrement()) + api_key String @unique(map: "unique_api_keys") + id_user Int + users users @relation(fields: [id_user], references: [id_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_7") + + @@index([id_user], map: "fk_1") +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1029454ba..a352f7d44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: '@nestjs/core': specifier: ^10.0.0 version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.1.1 + version: 10.1.1(@nestjs/common@10.0.0) '@nestjs/mapped-types': specifier: '*' version: 0.0.1(class-transformer@0.2.3)(class-validator@0.11.1)(reflect-metadata@0.1.13) @@ -93,6 +96,9 @@ importers: passport: specifier: ^0.6.0 version: 0.6.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 passport-local: specifier: ^1.0.0 version: 1.0.0 @@ -124,6 +130,9 @@ importers: '@types/node': specifier: ^20.3.1 version: 20.3.1 + '@types/passport-jwt': + specifier: ^3.0.12 + version: 3.0.12 '@types/passport-local': specifier: ^1.0.37 version: 1.0.37 @@ -3461,6 +3470,16 @@ packages: transitivePeerDependencies: - encoding + /@nestjs/jwt@10.1.1(@nestjs/common@10.0.0): + resolution: {integrity: sha512-sISYylg8y1Mb7saxPx5Zh11i7v9JOh70CEC/rN6g43MrbFlJ57c1eYFrffxip1YAx3DmV4K67yXob3syKZMOew==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.0.0(class-transformer@0.2.3)(class-validator@0.11.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.2 + jsonwebtoken: 9.0.0 + dev: false + /@nestjs/mapped-types@0.0.1(class-transformer@0.2.3)(class-validator@0.11.1)(reflect-metadata@0.1.13): resolution: {integrity: sha512-4G4Ui7Sj0UqXiZsUFk/6cPD3K7uZEFSElzkOftaJ3/lXW+HUi1/vfWXabF53qrzO1enTRQDxt1plDbP6SsqXEg==} peerDependencies: @@ -4006,6 +4025,11 @@ packages: /@types/json-schema@7.0.14: resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} + /@types/jsonwebtoken@9.0.2: + resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} + dependencies: + '@types/node': 20.3.1 + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -4056,6 +4080,14 @@ packages: resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==} dev: false + /@types/passport-jwt@3.0.12: + resolution: {integrity: sha512-nXCd1lu20rw//nZ5AnK1FnlVZdSC4R5xksquev9oAJlXwJw0irMdZ7dRAE4KDlalptKObiaoam6BQ8lpujeZog==} + dependencies: + '@types/express': 4.17.17 + '@types/jsonwebtoken': 9.0.2 + '@types/passport-strategy': 0.2.37 + dev: true + /@types/passport-local@1.0.37: resolution: {integrity: sha512-c57CwMHhMP2BBiOLyQZGRP43F8JtC84H976YVJdiU4EIWvqRCZ3F7QtsEgksOEIgMOk1Kz3EEKGA93OiDPQtRQ==} dependencies: @@ -5338,6 +5370,10 @@ packages: node-int64: 0.4.0 dev: true + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -6477,6 +6513,12 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -8952,6 +8994,32 @@ packages: optionalDependencies: graceful-fs: 4.2.11 + /jsonwebtoken@9.0.0: + resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash: 4.17.21 + ms: 2.1.3 + semver: 7.5.4 + dev: false + + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.5.4 + dev: false + /jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -8962,6 +9030,21 @@ packages: object.values: 1.1.7 dev: true + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + /keyv@3.1.0: resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} dependencies: @@ -9122,16 +9205,44 @@ packages: resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==} dev: false + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + /lodash.invokemap@4.6.0: resolution: {integrity: sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==} dev: false + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + /lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + /lodash.pullall@4.2.0: resolution: {integrity: sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==} dev: false @@ -9906,6 +10017,13 @@ packages: tslib: 2.6.2 dev: false + /passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + dev: false + /passport-local@1.0.0: resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} engines: {node: '>= 0.4.0'}