diff --git a/api/.env.development b/api/.env.development deleted file mode 100644 index 35c73302..00000000 --- a/api/.env.development +++ /dev/null @@ -1 +0,0 @@ -JWT_SECRET=thisisasecretthatshouldntbeusedinprod diff --git a/api/package.json b/api/package.json index c8990697..11c1faab 100644 --- a/api/package.json +++ b/api/package.json @@ -23,12 +23,17 @@ "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.1", + "@types/passport-jwt": "^3.0.13", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "generate-password": "^1.7.1", + "jwks-rsa": "^3.1.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "postgres": "^3.4.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 1d0aac4f..65f145b4 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -8,7 +8,7 @@ import { UsersModule } from './users/users.module'; // 3rd Party Modules const LIBRARY_IMPORTS = [ - ConfigModule.forRoot({ cache: true, isGlobal: true, envFilePath: [".env.development", ".env"] }), + ConfigModule.forRoot({ cache: true, isGlobal: true, envFilePath: [".env", ".env.development"] }), TypeOrmModule.forRoot(getDatabaseConfig()), ]; @@ -24,6 +24,8 @@ const FEATURE_IMPORTS = [ ...FEATURE_IMPORTS, ], controllers: [AppController], - providers: [AppService], + providers: [ + AppService + ], }) export class AppModule {} diff --git a/api/src/users/jwt.strategy.ts b/api/src/users/jwt.strategy.ts new file mode 100644 index 00000000..5332cd68 --- /dev/null +++ b/api/src/users/jwt.strategy.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { passportJwtSecret } from 'jwks-rsa'; +import { ExtractJwt, Strategy } from 'passport-jwt'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private configService: ConfigService) { + super({ + secretOrKeyProvider: passportJwtSecret({ + cache: true, + rateLimit: true, + jwksRequestsPerMinute: 5, + jwksUri: 'https://id.realliance.net/application/o/profile/jwks/', + }), + + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + audience: 'LNXdUuOZUue5HPlw8Vyglo83sIYndFaGUCIdQrSZ', + issuer: 'https://id.realliance.net/application/o/profile/', + algorithms: ['RS256'], + }); + } + + validate(payload: unknown): unknown { + return payload; + } +} \ No newline at end of file diff --git a/api/src/users/user.controller.ts b/api/src/users/user.controller.ts new file mode 100644 index 00000000..d183e805 --- /dev/null +++ b/api/src/users/user.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Controller() +export class UserController { + constructor() {} + + @Get('profile') + @UseGuards(AuthGuard('jwt')) + getProfile(): string { + return 'OK'; + } +} diff --git a/api/src/users/users.module.ts b/api/src/users/users.module.ts index 3f16c4be..fa421ea8 100644 --- a/api/src/users/users.module.ts +++ b/api/src/users/users.module.ts @@ -2,12 +2,21 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; +import { UserController } from './user.controller'; +import { JwtStrategy } from './jwt.strategy'; +import { PassportModule } from '@nestjs/passport'; @Module({ // forFeature is what triggers the entity to be loaded into the database management system as a persistant object type - imports: [TypeOrmModule.forFeature([User])], - providers: [UsersService], - controllers: [], + imports: [ + TypeOrmModule.forFeature([User]), + PassportModule.register({ defaultStrategy: 'jwt' }) + ], + providers: [ + UsersService, + JwtStrategy + ], + controllers: [UserController], exports: [UsersService], }) export class UsersModule {} diff --git a/frontend/package.json b/frontend/package.json index f2d33d94..045795f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,16 +4,19 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite --open", + "dev": "vite --open --port 8080", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { + "@types/js-cookie": "^3.0.6", "autoprefixer": "^10.4.16", "flowbite": "^2.2.0", "flowbite-react": "^0.7.0", "jose": "^5.1.3", + "js-cookie": "^3.0.5", + "oauth4webapi": "^2.4.0", "postcss": "^8.4.32", "react": "^18.2.0", "react-cookie": "^6.1.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2307dcc1..93d2d8f2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,20 @@ +import { Button } from "flowbite-react" +import { beginAuthFlow, onRedirect } from "./util/oauth" +import { useEffect } from "react" function App() { + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + + if (urlParams.get('code') !== null) { + onRedirect() + } + }, []); return (
Nothing to see here yet. +
) } diff --git a/frontend/src/util/oauth.ts b/frontend/src/util/oauth.ts new file mode 100644 index 00000000..2741bb2f --- /dev/null +++ b/frontend/src/util/oauth.ts @@ -0,0 +1,87 @@ +import * as oauth from 'oauth4webapi'; +import Cookies from 'js-cookie'; + +const ISSUER = new URL('https://id.realliance.net/application/o/profile/'); +const REDIRECT_URI = import.meta.env.PROD ? "https://profile.realliance.net" : "http://localhost:8080"; + +const as = await oauth + .discoveryRequest(ISSUER) + .then((response) => oauth.processDiscoveryResponse(ISSUER, response)); + +const client: oauth.Client = { + client_id: 'LNXdUuOZUue5HPlw8Vyglo83sIYndFaGUCIdQrSZ', + token_endpoint_auth_method: 'none', + } + +export async function beginAuthFlow() { + + + if (as.code_challenge_methods_supported?.includes('S256') !== true) { + // This example assumes S256 PKCE support is signalled + // If it isn't supported, random `nonce` must be used for CSRF protection. + throw new Error("S256 PKCE not supported"); + } + + const code_verifier = oauth.generateRandomCodeVerifier() + const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier) + const code_challenge_method = 'S256' + + Cookies.remove('codeVerifier'); + Cookies.set('codeVerifier', code_verifier); + + const authorizationUrl = new URL(as.authorization_endpoint!) + authorizationUrl.searchParams.set('client_id', client.client_id) + authorizationUrl.searchParams.set('code_challenge', code_challenge) + authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method) + authorizationUrl.searchParams.set('redirect_uri', REDIRECT_URI) + authorizationUrl.searchParams.set('response_type', 'code') + authorizationUrl.searchParams.set('scope', 'openid profile') + + window.location.href = authorizationUrl.toString(); +} + +export async function onRedirect() { + const currentUrl = new URL(window.location.href); + const params = oauth.validateAuthResponse(as, client, currentUrl, oauth.skipStateCheck) + if (oauth.isOAuth2Error(params)) { + console.log('error', params) + throw new Error("Oauth2 Failed") // Handle OAuth 2.0 redirect error + } + + const code_verifier = Cookies.get('codeVerifier') ?? ""; + Cookies.remove('codeVerifier'); + + const response = await oauth.authorizationCodeGrantRequest( + as, + client, + params, + REDIRECT_URI, + code_verifier, + ) + + let challenges: oauth.WWWAuthenticateChallenge[] | undefined + if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) { + for (const challenge of challenges) { + console.log('challenge', challenge) + } + throw new Error() // Handle www-authenticate challenges as needed + } + + const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response) + if (oauth.isOAuth2Error(result)) { + console.log('error', result) + throw new Error() // Handle OAuth 2.0 response body error + } + + console.log('result', result) + const claims = oauth.getValidatedIdTokenClaims(result) + console.log('ID Token Claims', claims) + + const res = await fetch('http://localhost:3000/api/profile', { + headers: { + "Authorization": `Bearer ${result.access_token}`, + } + }); + + console.log(await res.text()); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8df0e7c5..874ffe43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1234,6 +1234,11 @@ "@types/jsonwebtoken" "9.0.5" jsonwebtoken "9.0.2" +"@nestjs/passport@^10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-10.0.3.tgz#26ec5b2167d364e04962c115fcef80d10e185367" + integrity sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ== + "@nestjs/platform-express@^10.0.0": version "10.2.10" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.2.10.tgz#6ff94e5a3f3a15ff39d8044e966fd4ceecd8d068" @@ -1959,12 +1964,17 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/js-cookie@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95" + integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== + "@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/jsonwebtoken@9.0.5": +"@types/jsonwebtoken@*", "@types/jsonwebtoken@9.0.5", "@types/jsonwebtoken@^9.0.2": version "9.0.5" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588" integrity sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA== @@ -2024,6 +2034,30 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== +"@types/passport-jwt@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-3.0.13.tgz#119267d2fc1af7d274a512731146183de5f2b53f" + integrity sha512-fjHaC6Bv8EpMMqzTnHP32SXlZGaNfBPC/Po5dmRGYi2Ky7ljXPbGnOy+SxZqa6iZvFgVhoJ1915Re3m93zmcfA== + dependencies: + "@types/express" "*" + "@types/jsonwebtoken" "*" + "@types/passport-strategy" "*" + +"@types/passport-strategy@*": + version "0.2.38" + resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz#482abba0b165cd4553ec8b748f30b022bd6c04d3" + integrity sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.16" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.16.tgz#5a2918b180a16924c4d75c31254c31cdca5ce6cf" + integrity sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A== + dependencies: + "@types/express" "*" + "@types/prop-types@*": version "15.7.11" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" @@ -5837,11 +5871,21 @@ jiti@^1.19.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +jose@^4.14.6: + version "4.15.4" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.4.tgz#02a9a763803e3872cf55f29ecef0dfdcc218cc03" + integrity sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ== + jose@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/jose/-/jose-5.1.3.tgz#303959d85c51b5cb14725f930270b72be56abdca" integrity sha512-GPExOkcMsCLBTi1YetY2LmkoY559fss0+0KVa6kOfb2YFe84nAM7Nm/XzuZozah4iHgmBGrCOHL5/cy670SBRw== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -5916,7 +5960,7 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@9.0.2: +jsonwebtoken@9.0.2, 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== @@ -5941,6 +5985,18 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jwks-rsa@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-3.1.0.tgz#50406f23e38c9b2682cd437f824d7d61aa983171" + integrity sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg== + dependencies: + "@types/express" "^4.17.17" + "@types/jsonwebtoken" "^9.0.2" + debug "^4.3.4" + jose "^4.14.6" + limiter "^1.1.5" + lru-memoizer "^2.2.0" + jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -6013,6 +6069,11 @@ lilconfig@^3.0.0: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc" integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== +limiter@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" + integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -6057,6 +6118,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -6186,6 +6252,22 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== +lru-cache@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" + integrity sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw== + dependencies: + pseudomap "^1.0.1" + yallist "^2.0.0" + +lru-memoizer@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.2.0.tgz#b9d90c91637b4b1a423ef76f3156566691293df8" + integrity sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw== + dependencies: + lodash.clonedeep "^4.5.0" + lru-cache "~4.0.0" + macos-release@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9" @@ -7464,6 +7546,11 @@ npmlog@^6.0.0: gauge "^4.0.3" set-blocking "^2.0.0" +oauth4webapi@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-2.4.0.tgz#2a5e75996a33878cced3f367753aa42f21d36e27" + integrity sha512-ZWl8ov8HeGVyc9Icl1cag76HvIcDAp23eIIT+UVGir+dEu8BMgMlvZeZwqLVd0P8DqaumH4N+QLQXN69G1QjSA== + object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -7670,6 +7757,28 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" +passport-jwt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" + integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ== + dependencies: + jsonwebtoken "^9.0.0" + passport-strategy "^1.0.0" + +passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.7.0.tgz#3688415a59a48cf8068417a8a8092d4492ca3a05" + integrity sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -7718,6 +7827,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + peek-readable@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" @@ -7923,7 +8037,7 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -pseudomap@^1.0.2: +pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== @@ -9543,7 +9657,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== @@ -9814,7 +9928,7 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: +yallist@^2.0.0, yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==