diff --git a/.env.production b/.env.production index bcc0aaed2..00e97a34a 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,6 @@ NODE_ENV=production NX_API_URL=https://connect-api.redi-school.org/api +NEST_API_URL=https://connect-nestjs-api.redi-school.org/api NX_S3_UPLOAD_SIGN_URL=https://connect-api.redi-school.org/s3/sign NX_SENTRY_TRACES_SAMPLE_RATE=1.0 diff --git a/.vscode/settings.json b/.vscode/settings.json index ddc9ac195..fdba4738c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,9 @@ } ] }, - "editor.codeActionsOnSave": { "source.organizeImports": true }, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, "explorer.fileNesting.patterns": { "*.ts": "${capture}.js, ${capture}.typegen.ts, ${capture}.graphql, ${capture}.generated.ts", "*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts", @@ -23,5 +25,9 @@ "tsconfig.json": "tsconfig.*.json", "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml", "*.graphql": "${capture}.generated.ts" - } + }, + "cSpell.words": [ + "entra", + "msal" + ] } diff --git a/apps/nestjs-api/src/app/app.module.ts b/apps/nestjs-api/src/app/app.module.ts index 91f9256fc..28cb1d28f 100644 --- a/apps/nestjs-api/src/app/app.module.ts +++ b/apps/nestjs-api/src/app/app.module.ts @@ -2,6 +2,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo' import { CacheModule, Module } from '@nestjs/common' import { EventEmitterModule } from '@nestjs/event-emitter' import { GraphQLModule } from '@nestjs/graphql' +import { EntraIdModule } from '../auth-entra-id/entra-id.module' import { AuthModule } from '../auth/auth.module' import { ConMenteeFavoritedMentorsModule } from '../con-mentee-favorited-mentors/con-mentee-favorited-mentors.module' import { ConMentoringSessionsModule } from '../con-mentoring-sessions/con-mentoring-sessions.module' @@ -41,6 +42,7 @@ import { AppService } from './app.service' SfApiModule, SalesforceRecordEventsListenerModule, AuthModule, + EntraIdModule, ConProfilesModule, ConMentoringSessionsModule, ConMentorshipMatchesModule, diff --git a/apps/nestjs-api/src/auth-entra-id/entra-id-config.provider.ts b/apps/nestjs-api/src/auth-entra-id/entra-id-config.provider.ts new file mode 100644 index 000000000..2f5d8a589 --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/entra-id-config.provider.ts @@ -0,0 +1,39 @@ +import msal, { LogLevel } from '@azure/msal-node' +import { Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { NEST_API_URL } from '@talent-connect/shared-config' +import { EntraIdLoginOptions } from './entra-id-login-options.interface' + +@Injectable() +export class EntraIdConfigProvider { + readonly options: EntraIdLoginOptions + readonly msalConfig: msal.Configuration + + constructor(configService: ConfigService) { + this.options = { + scopes: [], + redirectUri: `${NEST_API_URL}/api/auth/entra-redirect`, // to backend + successRedirect: configService.get('NX_FRONTEND_URI') + '/front/login/entra-login', // to frontend + cloudInstance: configService.get('NX_ENTRA_ID_CLOUD_INSTANCE'), + } + this.msalConfig = { + auth: { + clientId: configService.get('NX_ENTRA_ID_CLIENT_ID'), // 'Application (client) ID' of app registration in Azure portal - this value is a GUID + authority: configService.get('NX_ENTRA_ID_CLOUD_INSTANCE') + '/consumers', // Full directory URL, in the form of https://login.microsoftonline.com/ + clientSecret: configService.get('NX_ENTRA_ID_CLIENT_SECRET'), // Client secret generated from the app registration in Azure portal + }, + system: { + loggerOptions: { + loggerCallback(logLevel, message, containsPii) { + if (!containsPii) { + if (logLevel === LogLevel.Error) console.error(message) + else console.log(message) + } + }, + piiLoggingEnabled: false, + logLevel: LogLevel.Verbose, + }, + }, + } + } +} diff --git a/apps/nestjs-api/src/auth-entra-id/entra-id-login-options.interface.ts b/apps/nestjs-api/src/auth-entra-id/entra-id-login-options.interface.ts new file mode 100644 index 000000000..759088b8e --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/entra-id-login-options.interface.ts @@ -0,0 +1,6 @@ +export interface EntraIdLoginOptions { + scopes: string[] + redirectUri: string + successRedirect: string + cloudInstance: string +} \ No newline at end of file diff --git a/apps/nestjs-api/src/auth-entra-id/entra-id-login.middleware.ts b/apps/nestjs-api/src/auth-entra-id/entra-id-login.middleware.ts new file mode 100644 index 000000000..415879988 --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/entra-id-login.middleware.ts @@ -0,0 +1,85 @@ +import { AuthorizationUrlRequest, ResponseMode } from '@azure/msal-node' +import { Injectable, NestMiddleware } from '@nestjs/common' +import { NextFunction, Request, Response } from 'express' +import { EntraIdConfigProvider } from './entra-id-config.provider' +import { EntraIdService } from './entra-id.service' +import { VerificationData } from './verification-data.interface' + +@Injectable() +export class EntraIdLoginMiddleware implements NestMiddleware { + private authorizationUrlRequestParams: AuthorizationUrlRequest + + constructor( + private readonly idService: EntraIdService, + private readonly configProvider: EntraIdConfigProvider + ) { + this.authorizationUrlRequestParams = this.prepareAuthCodeRequestParams() + } + + use(_: Request, res: Response, next: NextFunction) { + this.redirectToAuthCodeUrl(res, next) + return null + } + + private async redirectToAuthCodeUrl(res: Response, next: NextFunction) { + const { verifier, challenge } = await this.idService.generatePkceCodes() + + this.storeVerificationCookie(verifier, res) + + const clientApplication = await this.idService.getClientApplication() + clientApplication + .getAuthCodeUrl(this.prepareAuthCodeUrlRequest(challenge)) + .then((url) => res.redirect(url)) + .catch((err) => { + console.error(err) + next(err) + }) + } + + private storeVerificationCookie(verifier: string, res: Response) { + const verificationData = { + ...this.authorizationUrlRequestParams, + code: '', + codeVerifier: verifier, + } as VerificationData + + res.cookie( + this.idService.verifierCookieName, + this.idService.encodeObject(verificationData), + { maxAge: 2 * 60 * 60, httpOnly: true } + ) + } + + private prepareAuthCodeUrlRequest( + challenge: string + ): AuthorizationUrlRequest { + return { + ...this.authorizationUrlRequestParams, + responseMode: ResponseMode.FORM_POST, // recommended for confidential clients + codeChallenge: challenge, + codeChallengeMethod: 'S256', + } + } + + private prepareAuthCodeRequestParams(): AuthorizationUrlRequest { + /** + * MSAL Node library allows you to pass your custom state as state parameter in the Request object. + * The state parameter can also be used to encode information of the app's state before redirect. + * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter. + */ + const state: string = this.idService.encodeObject({ + successRedirect: this.configProvider.options.successRedirect || '/', + }) + + return { + state: state, + /** + * In future we could use this to set more specific auth scopes for different user types. + * By default, MSAL Node will add OIDC scopes to the auth code url request. For more information, visit: + * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes + */ + scopes: this.configProvider.options.scopes || [], + redirectUri: this.configProvider.options.redirectUri, + } + } +} diff --git a/apps/nestjs-api/src/auth-entra-id/entra-id.controller.ts b/apps/nestjs-api/src/auth-entra-id/entra-id.controller.ts new file mode 100644 index 000000000..337ff9963 --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/entra-id.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Next, Post, Req, Res } from '@nestjs/common'; +import { NextFunction, Request, Response } from 'express'; +import { EntraIdService } from './entra-id.service'; + +@Controller('auth') +export class EntraIdController { + constructor(private readonly entraIdService: EntraIdService) {} + + // empty route to trigger the entra-id auth middleware + @Get('entra-id') + entraId() { + return '' + } + + @Post('entra-redirect') + redirectPost(@Req() req: Request, @Res() res: Response, @Next() next: NextFunction) { + this.entraIdService.handleAuthRedirect(req, res, next) + return '' + } +} diff --git a/apps/nestjs-api/src/auth-entra-id/entra-id.module.ts b/apps/nestjs-api/src/auth-entra-id/entra-id.module.ts new file mode 100644 index 000000000..28e254d14 --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/entra-id.module.ts @@ -0,0 +1,20 @@ +import { HttpModule } from '@nestjs/axios' +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common' +import { ConfigModule } from '@nestjs/config' +import { EntraIdConfigProvider } from './entra-id-config.provider' +import { EntraIdLoginMiddleware } from './entra-id-login.middleware' +import { EntraIdController } from './entra-id.controller' +import { EntraIdService } from './entra-id.service' + +@Module({ + imports: [ConfigModule, HttpModule], + controllers: [EntraIdController], + providers: [EntraIdConfigProvider, EntraIdService, EntraIdLoginMiddleware], +}) +export class EntraIdModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply(EntraIdLoginMiddleware) + .forRoutes('auth/entra-id'); + } +} diff --git a/apps/nestjs-api/src/auth-entra-id/entra-id.service.ts b/apps/nestjs-api/src/auth-entra-id/entra-id.service.ts new file mode 100644 index 000000000..552408822 --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/entra-id.service.ts @@ -0,0 +1,147 @@ +import { + AuthenticationResult, + AuthorizationCodeRequest, + ClientApplication, + ConfidentialClientApplication, + Configuration, + CryptoProvider, +} from '@azure/msal-node' +import { HttpService } from '@nestjs/axios' +import { Injectable } from '@nestjs/common' +import { AxiosError, AxiosRequestConfig } from 'axios' +import { NextFunction, Request, Response } from 'express' +import { firstValueFrom } from 'rxjs' +import { catchError } from 'rxjs/operators' +import { EntraIdConfigProvider } from './entra-id-config.provider' +import { VerificationData } from './verification-data.interface' + +@Injectable() +export class EntraIdService { + readonly verifierCookieName = 'entra_id_verifier' + private readonly cryptoProvider = new CryptoProvider() + + constructor( + private readonly configProvider: EntraIdConfigProvider, + private readonly httpService: HttpService + ) {} + + async getClientApplication(): Promise { + const config = await this.prepareConfig() + + return new ConfidentialClientApplication(config) + } + + async handleAuthRedirect(req: Request, res: Response, next: NextFunction) { + const body = req.body + if (!body.state || !body.code) { + console.error('malformed request body from entra id') + throw 'could not log in' + } + + if (!(this.verifierCookieName in req.cookies)) { + throw 'missing verification data' + } + + try { + const tokenResponse = await this.verifyToken(req, res) + const decryptedState = this.decodeObject(body.state) + + /** + * TODO - do legacy salesforce validation and add these values to token + * for now, we'll just return to the success redirect page + */ + res.redirect(this.configProvider.options.successRedirect) + } catch (error) { + next(error) + } + } + + encodeObject(o: object): string { + return this.cryptoProvider.base64Encode(JSON.stringify(o)) + } + + decodeObject(s: string): object { + return JSON.parse(this.cryptoProvider.base64Decode(s)) + } + + async generatePkceCodes() { + return await this.cryptoProvider.generatePkceCodes() + } + + private async verifyToken(req: Request, res: Response): Promise { + const verificationData = this.decodeObject( + req.cookies[this.verifierCookieName] + ) as VerificationData + + res.clearCookie(this.verifierCookieName) + + const authCodeRequest: AuthorizationCodeRequest = { + scopes: verificationData.scopes, + redirectUri: verificationData.redirectUri, + state: verificationData.state, + codeVerifier: verificationData.codeVerifier, + code: req.body.code, + } + + const clientApplication = await this.getClientApplication() + + // throws error if verification is false + return clientApplication.acquireTokenByCode(authCodeRequest, req.body) + } + + private async prepareConfig(): Promise { + const config = this.configProvider.msalConfig + + /** + * If the current msal configuration does not have cloudDiscoveryMetadata or authorityMetadata, we will + * make a request to the relevant endpoints to retrieve the metadata. This allows MSAL to avoid making + * metadata discovery calls, thereby improving performance of token acquisition process. For more, see: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/performance.md + */ + if (!config.auth.cloudDiscoveryMetadata || !config.auth.authorityMetadata) { + const [cloudDiscoveryMetadata, authorityMetadata] = await Promise.all([ + this.getCloudDiscoveryMetadata(), + this.getAuthorityMetadata(), + ]) + + config.auth.cloudDiscoveryMetadata = JSON.stringify( + cloudDiscoveryMetadata + ) + config.auth.authorityMetadata = JSON.stringify(authorityMetadata) + } + + return config + } + + // Retrieves cloud discovery metadata from the /discovery/instance endpoint + private async getCloudDiscoveryMetadata() { + const endpoint = `${this.configProvider.options.cloudInstance}/common/discovery/instance` + + return this.queryEndpoint(endpoint, { + params: { + 'api-version': '1.1', + authorization_endpoint: `${this.configProvider.msalConfig.auth.authority}/oauth2/v2.0/authorize`, + }, + }) + } + + // Retrieves oidc metadata from the openid endpoint + private async getAuthorityMetadata() { + const endpoint = `${this.configProvider.msalConfig.auth.authority}/v2.0/.well-known/openid-configuration` + + return this.queryEndpoint(endpoint) + } + + private async queryEndpoint(endpoint: string, params?: AxiosRequestConfig) { + const { data } = await firstValueFrom( + this.httpService.get(endpoint, params).pipe( + catchError((error: AxiosError) => { + console.error(error.response.data) + throw 'Could not setup login service' + }) + ) + ) + + return data + } +} diff --git a/apps/nestjs-api/src/auth-entra-id/verification-data.interface.ts b/apps/nestjs-api/src/auth-entra-id/verification-data.interface.ts new file mode 100644 index 000000000..b695280f3 --- /dev/null +++ b/apps/nestjs-api/src/auth-entra-id/verification-data.interface.ts @@ -0,0 +1,7 @@ +export interface VerificationData { + state: string + scopes: string[] + redirectUri: string + code: string + codeVerifier: string +} diff --git a/apps/nestjs-api/src/main.ts b/apps/nestjs-api/src/main.ts index 50fecc6b0..9543010fa 100644 --- a/apps/nestjs-api/src/main.ts +++ b/apps/nestjs-api/src/main.ts @@ -7,13 +7,17 @@ import { Logger } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import { json, urlencoded } from 'express' import { AppModule } from './app/app.module' +import cookieParser from 'cookie-parser' async function bootstrap() { const app = await NestFactory.create(AppModule) + if (process.env.NODE_ENV !== 'production') app.enableCors() + const globalPrefix = 'api' app.setGlobalPrefix(globalPrefix) app.use(json({ limit: '1mb' })) app.use(urlencoded({ extended: true, limit: '1mb' })) + app.use(cookieParser()) // TODO! Re-enable this? If we can set to a higher debug log level // app.useLogger(SentryService.SentryServiceInstance()) const port = process.env.PORT || 3333 diff --git a/apps/redi-connect/src/pages/front/login/EntraLogin.tsx b/apps/redi-connect/src/pages/front/login/EntraLogin.tsx new file mode 100644 index 000000000..a8aa845ac --- /dev/null +++ b/apps/redi-connect/src/pages/front/login/EntraLogin.tsx @@ -0,0 +1,19 @@ +import { Heading } from '@talent-connect/shared-atomic-design-components' +import { useState } from 'react' +import { Columns, Content } from 'react-bulma-components' +import Landing from '../../../components/templates/Landing' + +export default function EntraLogin() { + const [loginError] = useState('') + + return ( + + + + Entra ID Login - still WIP + {loginError} + + + + ) +} diff --git a/apps/redi-connect/src/pages/front/login/Login.scss b/apps/redi-connect/src/pages/front/login/Login.scss new file mode 100644 index 000000000..73d24e11a --- /dev/null +++ b/apps/redi-connect/src/pages/front/login/Login.scss @@ -0,0 +1,6 @@ +@import '~bulma/sass/utilities/_all'; +@import '_variables.scss'; + +.entra-id-login-button { + margin-top: 10px +} diff --git a/apps/redi-connect/src/pages/front/login/Login.tsx b/apps/redi-connect/src/pages/front/login/Login.tsx index ed7e1267d..c5fdbaa90 100644 --- a/apps/redi-connect/src/pages/front/login/Login.tsx +++ b/apps/redi-connect/src/pages/front/login/Login.tsx @@ -32,6 +32,7 @@ import { purgeAllSessionData, } from '../../../services/auth/auth' import { envRediLocation } from '../../../utils/env-redi-location' +import './Login.scss' interface LoginFormValues { username: string @@ -76,6 +77,16 @@ export default function Login() { const [tpProfileLocation, setTpProfileLocation] = useState(null) + const entraIdLoginEnabled = process.env.NX_ENTRA_ID_ENABLED + const loginWithEntraId = () => { + try { + history.push('/front/login/entra-redirect') + } catch (err) { + console.error(err) + setLoginError('Could not log in with microsoft') + } + } + const submitForm = async () => { // LOG THE USER IN VIA LOOPBACK try { @@ -303,6 +314,14 @@ export default function Login() { + {entraIdLoginEnabled && ( + + )} diff --git a/apps/redi-connect/src/routes/routes__logged-out.tsx b/apps/redi-connect/src/routes/routes__logged-out.tsx index bbd94caed..6bc85f2bb 100644 --- a/apps/redi-connect/src/routes/routes__logged-out.tsx +++ b/apps/redi-connect/src/routes/routes__logged-out.tsx @@ -1,3 +1,4 @@ +import { NEST_API_URL } from '@talent-connect/shared-config' import { lazy } from 'react' import Faqs from '../pages/front/Faqs' import Home from '../pages/front/landing/Home' @@ -12,6 +13,12 @@ const Login = lazy( /* webpackChunkName: "Login", webpackPreload: true */ '../pages/front/login/Login' ) ) +const EntraLogin = lazy( + () => + import( + /* webpackChunkName: "EntraLogin", webpackPreload: true */ '../pages/front/login/EntraLogin' + ) +) const SignUpLanding = lazy( () => import( @@ -69,6 +76,19 @@ export const routes__loggedOut: RouteDefinition[] = [ component: Login, exact: true, }, + { + path: '/front/login/entra-login', + component: EntraLogin, + exact: true, + }, + { + path: '/front/login/entra-redirect', + component: () => { + window.location.href = `${NEST_API_URL}/api/auth/entra-id` + return null + }, + exact: true, + }, { path: '/front/signup-landing', component: SignUpLanding, diff --git a/apps/redi-connect/src/services/api/api.tsx b/apps/redi-connect/src/services/api/api.tsx index e11a56326..ef5bc588d 100644 --- a/apps/redi-connect/src/services/api/api.tsx +++ b/apps/redi-connect/src/services/api/api.tsx @@ -5,7 +5,7 @@ import { getAccessTokenFromLocalStorage, purgeAllSessionData, saveAccessTokenToLocalStorage, - setGraphQlClientAuthHeader, + setGraphQlClientAuthHeader } from '../auth/auth' import { history } from '../history/history' import { http } from '../http/http' diff --git a/libs/data-access/src/lib/graphql-client.ts b/libs/data-access/src/lib/graphql-client.ts index ad5f5da16..69f486216 100644 --- a/libs/data-access/src/lib/graphql-client.ts +++ b/libs/data-access/src/lib/graphql-client.ts @@ -1,11 +1,7 @@ +import { NEST_API_URL } from '@talent-connect/shared-config' import { GraphQLClient } from 'graphql-request' -const endpoint = - process.env.NODE_ENV === 'production' - ? 'https://connect-nestjs-api.redi-school.org/graphql' - : 'http://localhost:3333/graphql' - -export const graphqlClient = new GraphQLClient(endpoint, { +export const graphqlClient = new GraphQLClient(`${NEST_API_URL}/graphql`, { // headers: { // authorization: 'Bearer MY_TOKEN', // }, diff --git a/libs/shared-config/src/lib/config.ts b/libs/shared-config/src/lib/config.ts index 69d618899..393aaf28f 100644 --- a/libs/shared-config/src/lib/config.ts +++ b/libs/shared-config/src/lib/config.ts @@ -368,6 +368,9 @@ export const MENTORSHIP_MATCH_STATUS_LABELS: any = { INVALIDATED_AS_OTHER_MENTOR_ACCEPTED: 'Cancelled', } +export const NEST_API_URL = process.env.NODE_ENV === 'production' + ? 'https://connect-nestjs-api.redi-school.org' + : 'http://localhost:3333' export const API_URL = process.env.NX_API_URL ? process.env.NX_API_URL : 'http://localhost:3003/api' diff --git a/package.json b/package.json index c2313fb9a..accaa132d 100644 --- a/package.json +++ b/package.json @@ -85,11 +85,14 @@ "@nestjs/jwt": "^8.0.0", "@nestjs/passport": "^8.2.1", "@nestjs/platform-express": "^8.0.0", + "@nestjs/axios": "^3.0.1", "@react-pdf/renderer": "2.0.15", "@sentry/node": "^7.43.0", "@sentry/react": "^7.47.0", "@travelerdev/nestjs-sentry-graphql": "^4.1.1", + "cookie-parser": "^1.4.6", "apollo-server-express": "^3.6.7", + "@azure/msal-node": "^1.18.4", "async": "^3.2.3", "aws-sdk": "^2.418.0", "axios": "^0.21.1", @@ -196,6 +199,7 @@ "@types/react-router": "5.1.1", "@types/react-router-dom": "5.3.3", "@types/yup": "0.26.24", + "@types/cookie-parser": "^1.4.6", "@typescript-eslint/eslint-plugin": "5.10.2", "@typescript-eslint/parser": "5.10.2", "babel-jest": "27.2.3", diff --git a/yarn.lock b/yarn.lock index 0f36691fc..1d4923088 100644 --- a/yarn.lock +++ b/yarn.lock @@ -63,6 +63,20 @@ dependencies: xss "^1.0.8" +"@azure/msal-common@13.3.1": + version "13.3.1" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-13.3.1.tgz#012465bf940d12375dc47387b754ccf9d6b92180" + integrity sha512-Lrk1ozoAtaP/cp53May3v6HtcFSVxdFrg2Pa/1xu5oIvsIwhxW6zSPibKefCOVgd5osgykMi5jjcZHv8XkzZEQ== + +"@azure/msal-node@^1.18.4": + version "1.18.4" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-1.18.4.tgz#c921b0447c92fb3b0cb1ebf5a9a76fcad2ec7c21" + integrity sha512-Kc/dRvhZ9Q4+1FSfsTFDME/v6+R2Y1fuMty/TfwqE5p9GTPw08BPbKgeWinE8JRHRp+LemjQbUZsn4Q4l6Lszg== + dependencies: + "@azure/msal-common" "13.3.1" + jsonwebtoken "^9.0.0" + uuid "^8.3.0" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -2527,6 +2541,11 @@ lodash.omit "4.5.0" tslib "2.3.1" +"@nestjs/axios@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@nestjs/axios/-/axios-3.0.1.tgz#b006f81dd54a49def92cfaf9a8970434567e75ce" + integrity sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ== + "@nestjs/common@^8.0.0": version "8.4.4" resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-8.4.4.tgz#0914c6c0540b5a344c7c8fd6072faa1a49af1158" @@ -4750,6 +4769,13 @@ dependencies: "@types/node" "*" +"@types/cookie-parser@^1.4.6": + version "1.4.6" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.6.tgz#002643c514cccf883a65cbe044dbdc38c0b92ade" + integrity sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w== + dependencies: + "@types/express" "*" + "@types/cors@2.8.12": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" @@ -8130,11 +8156,24 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, dependencies: safe-buffer "~5.1.1" +cookie-parser@^1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594" + integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA== + dependencies: + cookie "0.4.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= +cookie@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookie@0.4.2, cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -13534,6 +13573,22 @@ jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: ms "^2.1.1" semver "^5.6.0" +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + 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.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -18659,7 +18714,7 @@ semver@7.3.4: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -20746,7 +20801,7 @@ uuid@3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.2: +uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==