From 224c7f40b51b193998ef8f78238319571c8ea69e Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 09:33:40 +0100 Subject: [PATCH 01/20] Add IP pinning flag to the session options --- src/module.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/module.ts b/src/module.ts index f99d885..a78af62 100644 --- a/src/module.ts +++ b/src/module.ts @@ -44,6 +44,13 @@ declare interface SessionOptions { * @docs https://github.com/unjs/unstorage */ storageOptions: CreateStorageOptions, + /** + * Whether to pin sessions to the user's IP (i.e. Different IP means a different session) + * @default false + * @example true + * @type boolean + */ + ipPinning: boolean, } declare interface ApiOptions { From 919bceb495e361b3964cc27c597964a962187dcf Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 09:38:03 +0100 Subject: [PATCH 02/20] Document IP pinning in the README --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index efe630b..1ffb8e2 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,9 @@ Here's what the full _default_ module configuration looks like: // The session cookie same site policy is `lax` cookieSameSite: 'lax', // In-memory storage is used (these are `unjs/unstorage` options) - storageOptions: {} + storageOptions: {}, + // Sessions aren't pinned to the user's IP address + ipPinning: false, }, api: { // The API is enabled @@ -254,10 +256,11 @@ Without further ado, here's some attack cases you can consider and take action a - possible mitigations: - disable reading of data on the client side by disabling the api or setting `api: { methods: [] }` - increase the default sessionId length (although with `64` characters it already is quite long, in 2022) + - use the `ipPinning` flag (although this means that everytime the user changes IP address, they'll lose their current session) 4. stealing session id(s) of client(s) - problem: session data can leak - possible mitigations: - - increase cookie protection, e.g., by setting `session.cookieSameSite: 'stric'` (default: `lax`) + - increase cookie protection, e.g., by setting `session.cookieSameSite: 'strict'` (default: `lax`) - use very short-lived sessions - don't allow session renewal From 469b24cae7b6e9123a7df4fa0561351b4b4255b9 Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 10:11:21 +0100 Subject: [PATCH 03/20] Add initial draft for IP pinning logic --- src/module.ts | 3 +- .../server/middleware/session/index.ts | 58 ++++++++++++++++--- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/module.ts b/src/module.ts index a78af62..58b07b4 100644 --- a/src/module.ts +++ b/src/module.ts @@ -107,7 +107,8 @@ const defaults: ModuleOptions = { idLength: 64, storePrefix: 'sessions', cookieSameSite: 'lax', - storageOptions: {} + storageOptions: {}, + ipPinning: false }, api: { isEnabled: true, diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index b61d121..c058ea0 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -20,9 +20,24 @@ const safeSetCookie = (event: H3Event, name: string, value: string) => setCookie export declare interface Session { id: string createdAt: Date + ip?: string [key: string]: any } +/** + * Get a hashed representation of the given IP address (for GDPR compliance) + * @param ip string|undefined The IP address to hash + */ +const hashIpAddress = (ip: string|undefined) => ip + +const compareIpAddresses = (ip: string|undefined, ipHash: string|undefined) => ip === ipHash + +/** + * Get the IP address corresponding to the user's request + * @param event H3Event Event passing through middleware + */ +const getRequestIpAddress = (event: H3Event): string|undefined => event.req.socket.remoteAddress + /** * Get the current session id. * @@ -57,12 +72,19 @@ const newSession = async (event: H3Event) => { // Cleanup old session data to avoid leaks await deleteSession(event) + const runtimeConfig = useRuntimeConfig() + const sessionOptions = runtimeConfig.session.session + // (Re-)Set cookie - const sessionId = nanoid(useRuntimeConfig().session.session.idLength) + const sessionId = nanoid(sessionOptions.idLength) safeSetCookie(event, SESSION_COOKIE_NAME, sessionId) // Store session data in storage - const session: Session = { id: sessionId, createdAt: new Date() } + const session: Session = { + id: sessionId, + createdAt: new Date(), + ip: sessionOptions.ipPinning ? hashIpAddress(getRequestIpAddress(event)) : undefined + } await setStorageSession(sessionId, session) return session @@ -81,8 +103,11 @@ const getSession = async (event: H3Event): Promise => { return null } + const runtimeConfig = useRuntimeConfig() + const sessionOptions = runtimeConfig.session.session + // 3. Is the session not expired? - const sessionExpiryInSeconds = useRuntimeConfig().session.session.expiryInSeconds + const sessionExpiryInSeconds = sessionOptions.expiryInSeconds if (sessionExpiryInSeconds !== null) { const now = dayjs() if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { @@ -90,15 +115,32 @@ const getSession = async (event: H3Event): Promise => { } } + // 4. Check for IP pinning logic + if (sessionOptions.ipPinning) { + const hashedIP = session.ip + + // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled + if (!hashedIP) { + await deleteSession(event) // Cleanup + return null + } + + // 4.2. Get request's IP + const requestIP = getRequestIpAddress(event) + + // 4.3. Ensure pinning + if (!compareIpAddresses(requestIP, hashedIP)) { + // 4.4. Report session-jacking attempt + // TODO: Report session-jacking attempt from requestIP + return null + } + } + return session } function isSession (shape: unknown): shape is Session { - if (typeof shape === 'object' && !!shape && 'id' in shape && 'createdAt' in shape) { - return true - } - - return false + return typeof shape === 'object' && !!shape && 'id' in shape && 'createdAt' in shape } const ensureSession = async (event: H3Event) => { From 0e9ddfa2636d495a68a634850b5a884944e7a9fa Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 10:12:55 +0100 Subject: [PATCH 04/20] Add argon2 dependency for secure hashing of IP addresses stored in the session (GDPR compliance) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3af307f..a38728b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@nuxt/kit": "^3.0.0-rc.12", + "argon2": "^0.30.1", "dayjs": "^1.11.5", "defu": "^6.1.0", "h3": "^0.8.5", From 7be03104bc3d2661e356a650a7d55328b8d60e10 Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 10:29:31 +0100 Subject: [PATCH 05/20] Replace placeholder hashing logic with argon2 implementation --- .../server/middleware/session/index.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index c058ea0..1df0da1 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -1,6 +1,7 @@ import { H3Event, eventHandler, setCookie, parseCookies, deleteCookie } from 'h3' import { nanoid } from 'nanoid' import dayjs from 'dayjs' +import { hash, argon2id, verify } from 'argon2' import type { SameSiteOptions } from '../../../../module' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' import { useRuntimeConfig } from '#imports' @@ -24,13 +25,27 @@ export declare interface Session { [key: string]: any } +const argon2Options = { + // cryptographically-secure salt is generated automatically + type: argon2id, // resistant against GPU & tradeoff attacks + hashLength: 60 +} + /** * Get a hashed representation of the given IP address (for GDPR compliance) * @param ip string|undefined The IP address to hash */ -const hashIpAddress = (ip: string|undefined) => ip +const hashIpAddress = (ip: string|undefined) => + !ip + ? Promise.resolve(undefined) + : hash(ip, argon2Options) -const compareIpAddresses = (ip: string|undefined, ipHash: string|undefined) => ip === ipHash +/** + * Check that the given (raw) IP address and the hashed IP address match + * @param ip string|undefined The IP address to verify + * @param ipHash string|undefined The (hashed) IP address to test against + */ +const ipAddressesMatch = (ip: string|undefined, ipHash: string|undefined) => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) /** * Get the IP address corresponding to the user's request @@ -83,7 +98,7 @@ const newSession = async (event: H3Event) => { const session: Session = { id: sessionId, createdAt: new Date(), - ip: sessionOptions.ipPinning ? hashIpAddress(getRequestIpAddress(event)) : undefined + ip: sessionOptions.ipPinning ? await hashIpAddress(getRequestIpAddress(event)) : undefined } await setStorageSession(sessionId, session) @@ -121,7 +136,6 @@ const getSession = async (event: H3Event): Promise => { // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled if (!hashedIP) { - await deleteSession(event) // Cleanup return null } @@ -129,7 +143,8 @@ const getSession = async (event: H3Event): Promise => { const requestIP = getRequestIpAddress(event) // 4.3. Ensure pinning - if (!compareIpAddresses(requestIP, hashedIP)) { + const matches = await ipAddressesMatch(requestIP, hashedIP) + if (!matches) { // 4.4. Report session-jacking attempt // TODO: Report session-jacking attempt from requestIP return null From 1320b82d89d5893fde16396e8520dfaee5ce2629 Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 10:33:05 +0100 Subject: [PATCH 06/20] Change the way sessions are deleted to avoid deleting on storage-jacking attempts --- src/runtime/server/middleware/session/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index 1df0da1..a3be3a5 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -84,9 +84,6 @@ export const deleteSession = async (event: H3Event) => { } const newSession = async (event: H3Event) => { - // Cleanup old session data to avoid leaks - await deleteSession(event) - const runtimeConfig = useRuntimeConfig() const sessionOptions = runtimeConfig.session.session @@ -126,6 +123,7 @@ const getSession = async (event: H3Event): Promise => { if (sessionExpiryInSeconds !== null) { const now = dayjs() if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { + await deleteSession(event) // Cleanup old session data to avoid leaks return null } } @@ -136,6 +134,7 @@ const getSession = async (event: H3Event): Promise => { // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled if (!hashedIP) { + await deleteSession(event) // Cleanup to avoid leaks (and properly recreate a session) return null } @@ -147,6 +146,7 @@ const getSession = async (event: H3Event): Promise => { if (!matches) { // 4.4. Report session-jacking attempt // TODO: Report session-jacking attempt from requestIP + // NOTE: DO NOT DELETE SESSION HERE, this would mean we eliminate session-jacking, but users could delete others' sessions return null } } From 885fe83df64caee8c8f41455c79af949b2b4a9c0 Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 11:24:34 +0100 Subject: [PATCH 07/20] Improve IP address resolution, add more documentation in README --- README.md | 17 ++++++++++- .../server/middleware/session/index.ts | 30 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1ffb8e2..e4a90c7 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,21 @@ You can configure the storage backend using the `session.session.storageOptions` Check out here what storage backends are supported and how to configure them: https://github.com/unjs/unstorage#drivers +### IP Pinning + +For increased security, you can enable the `ipPinning` flag in the session options. This will make it so sessions are bound by both the session's ID and the user's IP, which means no [session-jacking](https://owasp.org/www-community/attacks/Session_hijacking_attack). + +For this feature to work, we rely on [`Socket#remoteAddress`](https://nodejs.org/api/net.html#socketremoteaddress) which means you might have to configure your (reverse) proxy to properly forward IP addresses (Usually via the `X-Forwarded-For` header): + +* [Using Apache 2](https://httpd.apache.org/docs/current/mod/mod_proxy.html#x-headers) +* [Using NGINX](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) +* [Using CloudFlare](https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/) + +Supported headers (in order): +* `X-Forwarded-For` +* `True-Client-Ip` +* `CF-Connecting-Ip` + ### Configuration Here's what the full _default_ module configuration looks like: @@ -220,7 +235,7 @@ Here's what the full _default_ module configuration looks like: // In-memory storage is used (these are `unjs/unstorage` options) storageOptions: {}, // Sessions aren't pinned to the user's IP address - ipPinning: false, + ipPinning: false }, api: { // The API is enabled diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index a3be3a5..b411a54 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -35,7 +35,7 @@ const argon2Options = { * Get a hashed representation of the given IP address (for GDPR compliance) * @param ip string|undefined The IP address to hash */ -const hashIpAddress = (ip: string|undefined) => +const hashIpAddress = (ip: string|undefined): Promise => !ip ? Promise.resolve(undefined) : hash(ip, argon2Options) @@ -45,13 +45,37 @@ const hashIpAddress = (ip: string|undefined) => * @param ip string|undefined The IP address to verify * @param ipHash string|undefined The (hashed) IP address to test against */ -const ipAddressesMatch = (ip: string|undefined, ipHash: string|undefined) => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) +const ipAddressesMatch = (ip: string|undefined, ipHash: string|undefined): Promise => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) + +/** + * Extract the IP address from an HTTP header + * @param header string|string[]|undefined The header value to inspect and extract the IP from + */ +const extractIpFromHeader = (header?: string|string[]): string|undefined => { + if (Array.isArray(header)) { + return header[0].split(',')[0] + } + + if (typeof header === 'string') { + return header.split(',')[0] + } + + return undefined +} /** * Get the IP address corresponding to the user's request * @param event H3Event Event passing through middleware */ -const getRequestIpAddress = (event: H3Event): string|undefined => event.req.socket.remoteAddress +const getRequestIpAddress = ({ req }: H3Event): string|undefined => { + const foundIp = [ + 'x-forwarded-for', + 'true-client-ip', + 'cf-connecting-ip' + ].find(headerName => extractIpFromHeader(req.headers[headerName])) + + return foundIp ?? req.connection?.remoteAddress ?? req.socket.remoteAddress +} /** * Get the current session id. From 4037d5a927832ebd8ead68fb9381b2752b151e43 Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 11:47:06 +0100 Subject: [PATCH 08/20] Update illegal keys detection in PATCH & POST endpoints, fix session merging --- src/runtime/server/api/session.patch.ts | 2 +- src/runtime/server/api/session.post.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/runtime/server/api/session.patch.ts b/src/runtime/server/api/session.patch.ts index 62f4e7e..55a367c 100644 --- a/src/runtime/server/api/session.patch.ts +++ b/src/runtime/server/api/session.patch.ts @@ -6,7 +6,7 @@ export const checkIfObjectAndContainsIllegalKeys = (shape: unknown): shape is Ob } // see https://stackoverflow.com/a/39283005 for this usage - return Object.prototype.hasOwnProperty.call(shape, 'id') || Object.prototype.hasOwnProperty.call(shape, 'createdAt') + return !!['id', 'createdAt', 'ip'].find(key => Object.prototype.hasOwnProperty.call(shape, key)) } export default eventHandler(async (event) => { diff --git a/src/runtime/server/api/session.post.ts b/src/runtime/server/api/session.post.ts index e665b69..72acc30 100644 --- a/src/runtime/server/api/session.post.ts +++ b/src/runtime/server/api/session.post.ts @@ -1,4 +1,4 @@ -import { eventHandler, readBody } from 'h3' +import { createError, eventHandler, readBody } from 'h3' import { checkIfObjectAndContainsIllegalKeys } from './session.patch' export default eventHandler(async (event) => { @@ -9,9 +9,10 @@ export default eventHandler(async (event) => { // Fully overwrite the session with body data, only keep sessions own properties (id, createdAt) event.context.session = { + ...body, id: event.context.session.id, createdAt: event.context.session.createdAt, - ...body + ip: event.context.session.ip } return event.context.session From e402eb496a080d3c758bca34cf00415b6be22e9f Mon Sep 17 00:00:00 2001 From: Voltra Date: Tue, 1 Nov 2022 12:28:35 +0100 Subject: [PATCH 09/20] Add warning about trusting IP-forwarding headers --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e4a90c7..c8b39dc 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,8 @@ Supported headers (in order): * `True-Client-Ip` * `CF-Connecting-Ip` +***WARNING:*** Only trust these headers when sent directly by your webserver or (reverse) proxy + ### Configuration Here's what the full _default_ module configuration looks like: From e19160fc2fd07616c9c3bee4bedc9752c5fdf803 Mon Sep 17 00:00:00 2001 From: Voltra Date: Wed, 2 Nov 2022 20:52:21 +0100 Subject: [PATCH 10/20] Publish changes in lockfile --- package-lock.json | 224 ++++++++++++++++++---------------------------- 1 file changed, 87 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index def4115..1620feb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "@sidebase/nuxt-session", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sidebase/nuxt-session", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "dependencies": { "@nuxt/kit": "^3.0.0-rc.12", + "argon2": "^0.30.1", "dayjs": "^1.11.5", "defu": "^6.1.0", "h3": "^0.8.5", @@ -724,7 +725,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", - "dev": true, "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -744,7 +744,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, "engines": { "node": ">=10" } @@ -753,7 +752,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -765,7 +763,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -777,7 +774,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -790,7 +786,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -802,7 +797,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -822,7 +816,6 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -1094,6 +1087,14 @@ "eslint": "^8.23.0" } }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgr/utils": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", @@ -1923,8 +1924,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/acorn": { "version": "8.8.0", @@ -1950,7 +1950,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -2005,7 +2004,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2040,8 +2038,7 @@ "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "node_modules/arch": { "version": "2.2.0", @@ -2155,7 +2152,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -2164,6 +2160,20 @@ "node": ">=10" } }, + "node_modules/argon2": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.30.1.tgz", + "integrity": "sha512-wJYS8ebn6zHZwv4/iOOdlW1KUuLdbyIyhGsT51j6d9l5cRrHQU8pl817ubAxNgnS1jrbW/NGwegN3YrV0d5jRg==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "@phc/format": "^1.0.0", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2264,8 +2274,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2325,7 +2334,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2749,7 +2757,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, "bin": { "color-support": "bin.js" } @@ -2798,8 +2805,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/consola": { "version": "2.15.3", @@ -2809,8 +2815,7 @@ "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/convert-source-map": { "version": "1.9.0", @@ -3134,8 +3139,7 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "node_modules/denque": { "version": "2.1.0", @@ -3173,7 +3177,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", - "dev": true, "engines": { "node": ">=8" } @@ -4746,8 +4749,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -4799,7 +4801,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -4818,14 +4819,12 @@ "node_modules/gauge/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/gauge/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5156,8 +5155,7 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "node_modules/hash-sum": { "version": "2.0.0", @@ -5231,7 +5229,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -5326,7 +5323,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5582,7 +5578,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -6185,7 +6180,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -6200,7 +6194,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -6308,7 +6301,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6971,6 +6963,11 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "node_modules/node-addon-api": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", + "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -7041,7 +7038,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, "dependencies": { "abbrev": "1" }, @@ -7117,7 +7113,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -7224,7 +7219,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7315,7 +7309,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -7576,7 +7569,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8419,7 +8411,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -8597,7 +8588,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -8612,7 +8602,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8898,8 +8887,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -9068,7 +9056,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -9149,7 +9136,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -9469,8 +9455,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tsconfig-paths": { "version": "3.14.1", @@ -10236,8 +10221,7 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack-sources": { "version": "3.2.3", @@ -10256,7 +10240,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -10296,7 +10279,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -10304,14 +10286,12 @@ "node_modules/wide-align/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/wide-align/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10389,8 +10369,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.9.0", @@ -11047,7 +11026,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", - "dev": true, "requires": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -11063,14 +11041,12 @@ "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "requires": { "minipass": "^3.0.0" } @@ -11079,7 +11055,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -11088,7 +11063,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -11097,14 +11071,12 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, "requires": { "whatwg-url": "^5.0.0" } @@ -11113,7 +11085,6 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -11339,6 +11310,11 @@ "eslint-plugin-import": "^2.26.0" } }, + "@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==" + }, "@pkgr/utils": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", @@ -11932,8 +11908,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "acorn": { "version": "8.8.0", @@ -11951,7 +11926,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "requires": { "debug": "4" } @@ -11988,8 +11962,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -12012,8 +11985,7 @@ "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "arch": { "version": "2.2.0", @@ -12103,12 +12075,21 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, + "argon2": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.30.1.tgz", + "integrity": "sha512-wJYS8ebn6zHZwv4/iOOdlW1KUuLdbyIyhGsT51j6d9l5cRrHQU8pl817ubAxNgnS1jrbW/NGwegN3YrV0d5jRg==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "@phc/format": "^1.0.0", + "node-addon-api": "^5.0.0" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -12175,8 +12156,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -12219,7 +12199,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12513,8 +12492,7 @@ "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, "colord": { "version": "2.9.3", @@ -12554,8 +12532,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "consola": { "version": "2.15.3", @@ -12565,8 +12542,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "convert-source-map": { "version": "1.9.0", @@ -12804,8 +12780,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "denque": { "version": "2.1.0", @@ -12832,8 +12807,7 @@ "detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", - "dev": true + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" }, "dir-glob": { "version": "3.0.1", @@ -13891,8 +13865,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", @@ -13928,7 +13901,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, "requires": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -13944,14 +13916,12 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14199,8 +14169,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "hash-sum": { "version": "2.0.0", @@ -14258,7 +14227,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -14315,7 +14283,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -14490,8 +14457,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.3", @@ -14957,7 +14923,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "requires": { "semver": "^6.0.0" }, @@ -14965,8 +14930,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -15054,7 +15018,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -15456,6 +15419,11 @@ } } }, + "node-addon-api": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", + "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + }, "node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -15498,7 +15466,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, "requires": { "abbrev": "1" } @@ -15552,7 +15519,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, "requires": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -15638,8 +15604,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { "version": "1.12.2", @@ -15706,7 +15671,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -15886,8 +15850,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -16457,7 +16420,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16585,7 +16547,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" }, @@ -16594,7 +16555,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -16807,8 +16767,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "setprototypeof": { "version": "1.2.0", @@ -16948,7 +16907,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "requires": { "safe-buffer": "~5.2.0" } @@ -17007,7 +16965,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -17244,8 +17201,7 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tsconfig-paths": { "version": "3.14.1", @@ -17813,8 +17769,7 @@ "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "webpack-sources": { "version": "3.2.3", @@ -17830,7 +17785,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -17861,7 +17815,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2 || 3 || 4" }, @@ -17869,14 +17822,12 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -17928,8 +17879,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { "version": "8.9.0", From 5d9a9b6bad59db18d1b2b770e2e7ff79e7602a0b Mon Sep 17 00:00:00 2001 From: Voltra Date: Sat, 5 Nov 2022 08:53:42 +0100 Subject: [PATCH 11/20] Move IP pinning logic to a separate file --- .../server/middleware/session/index.ts | 56 +------------------ .../server/middleware/session/ipPinning.ts | 50 +++++++++++++++++ 2 files changed, 52 insertions(+), 54 deletions(-) create mode 100644 src/runtime/server/middleware/session/ipPinning.ts diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index b411a54..a3a804b 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -1,9 +1,9 @@ -import { H3Event, eventHandler, setCookie, parseCookies, deleteCookie } from 'h3' +import { deleteCookie, eventHandler, H3Event, parseCookies, setCookie } from 'h3' import { nanoid } from 'nanoid' import dayjs from 'dayjs' -import { hash, argon2id, verify } from 'argon2' import type { SameSiteOptions } from '../../../../module' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' +import { getRequestIpAddress, hashIpAddress, ipAddressesMatch } from './ipPinning' import { useRuntimeConfig } from '#imports' const SESSION_COOKIE_NAME = 'sessionId' @@ -25,58 +25,6 @@ export declare interface Session { [key: string]: any } -const argon2Options = { - // cryptographically-secure salt is generated automatically - type: argon2id, // resistant against GPU & tradeoff attacks - hashLength: 60 -} - -/** - * Get a hashed representation of the given IP address (for GDPR compliance) - * @param ip string|undefined The IP address to hash - */ -const hashIpAddress = (ip: string|undefined): Promise => - !ip - ? Promise.resolve(undefined) - : hash(ip, argon2Options) - -/** - * Check that the given (raw) IP address and the hashed IP address match - * @param ip string|undefined The IP address to verify - * @param ipHash string|undefined The (hashed) IP address to test against - */ -const ipAddressesMatch = (ip: string|undefined, ipHash: string|undefined): Promise => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) - -/** - * Extract the IP address from an HTTP header - * @param header string|string[]|undefined The header value to inspect and extract the IP from - */ -const extractIpFromHeader = (header?: string|string[]): string|undefined => { - if (Array.isArray(header)) { - return header[0].split(',')[0] - } - - if (typeof header === 'string') { - return header.split(',')[0] - } - - return undefined -} - -/** - * Get the IP address corresponding to the user's request - * @param event H3Event Event passing through middleware - */ -const getRequestIpAddress = ({ req }: H3Event): string|undefined => { - const foundIp = [ - 'x-forwarded-for', - 'true-client-ip', - 'cf-connecting-ip' - ].find(headerName => extractIpFromHeader(req.headers[headerName])) - - return foundIp ?? req.connection?.remoteAddress ?? req.socket.remoteAddress -} - /** * Get the current session id. * diff --git a/src/runtime/server/middleware/session/ipPinning.ts b/src/runtime/server/middleware/session/ipPinning.ts new file mode 100644 index 0000000..0bfde78 --- /dev/null +++ b/src/runtime/server/middleware/session/ipPinning.ts @@ -0,0 +1,50 @@ +import { argon2id, hash, verify } from 'argon2' +import { H3Event } from 'h3' + +const argon2Options = { + // cryptographically-secure salt is generated automatically + type: argon2id, // resistant against GPU & tradeoff attacks + hashLength: 60 +} +/** + * Get a hashed representation of the given IP address (for GDPR compliance) + * @param ip string|undefined The IP address to hash + */ +export const hashIpAddress = (ip: string | undefined): Promise => + !ip + ? Promise.resolve(undefined) + : hash(ip, argon2Options) +/** + * Check that the given (raw) IP address and the hashed IP address match + * @param ip string|undefined The IP address to verify + * @param ipHash string|undefined The (hashed) IP address to test against + */ +export const ipAddressesMatch = (ip: string | undefined, ipHash: string | undefined): Promise => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) +/** + * Extract the IP address from an HTTP header + * @param header string|string[]|undefined The header value to inspect and extract the IP from + */ +const extractIpFromHeader = (header?: string | string[]): string | undefined => { + if (Array.isArray(header)) { + return header[0].split(',')[0] + } + + if (typeof header === 'string') { + return header.split(',')[0] + } + + return undefined +} +/** + * Get the IP address corresponding to the user's request + * @param event H3Event Event passing through middleware + */ +export const getRequestIpAddress = ({ req }: H3Event): string | undefined => { + const foundIp = [ + 'x-forwarded-for', + 'true-client-ip', + 'cf-connecting-ip' + ].find(headerName => extractIpFromHeader(req.headers[headerName])) + + return foundIp ?? req.connection?.remoteAddress ?? req.socket.remoteAddress +} From 067e11ef25a933bb0ea5bf86ce28f5475fca68c9 Mon Sep 17 00:00:00 2001 From: Voltra Date: Sat, 5 Nov 2022 08:58:32 +0100 Subject: [PATCH 12/20] Change way of getting the IP hash when creating a new session --- src/runtime/server/middleware/session/index.ts | 4 ++-- src/runtime/server/middleware/session/ipPinning.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index a3a804b..da0adff 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -3,7 +3,7 @@ import { nanoid } from 'nanoid' import dayjs from 'dayjs' import type { SameSiteOptions } from '../../../../module' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' -import { getRequestIpAddress, hashIpAddress, ipAddressesMatch } from './ipPinning' +import { getHashedIpAddress, getRequestIpAddress, hashIpAddress, ipAddressesMatch } from "./ipPinning"; import { useRuntimeConfig } from '#imports' const SESSION_COOKIE_NAME = 'sessionId' @@ -67,7 +67,7 @@ const newSession = async (event: H3Event) => { const session: Session = { id: sessionId, createdAt: new Date(), - ip: sessionOptions.ipPinning ? await hashIpAddress(getRequestIpAddress(event)) : undefined + ip: sessionOptions.ipPinning ? await getHashedIpAddress(event) : undefined } await setStorageSession(sessionId, session) diff --git a/src/runtime/server/middleware/session/ipPinning.ts b/src/runtime/server/middleware/session/ipPinning.ts index 0bfde78..355087b 100644 --- a/src/runtime/server/middleware/session/ipPinning.ts +++ b/src/runtime/server/middleware/session/ipPinning.ts @@ -48,3 +48,7 @@ export const getRequestIpAddress = ({ req }: H3Event): string | undefined => { return foundIp ?? req.connection?.remoteAddress ?? req.socket.remoteAddress } + +export const getHashedIpAddress = (event: H3Event): Promise => { + return hashIpAddress(getRequestIpAddress(event)) +} From 70d593607f5c5dabace99b629956bb178b8a93db Mon Sep 17 00:00:00 2001 From: Voltra Date: Sat, 5 Nov 2022 09:24:24 +0100 Subject: [PATCH 13/20] Refactor session checks into seperate functions that may throw on error --- .../server/middleware/session/exceptions.ts | 17 +++++ .../server/middleware/session/index.ts | 72 ++++++++++++------- 2 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 src/runtime/server/middleware/session/exceptions.ts diff --git a/src/runtime/server/middleware/session/exceptions.ts b/src/runtime/server/middleware/session/exceptions.ts new file mode 100644 index 0000000..8352a55 --- /dev/null +++ b/src/runtime/server/middleware/session/exceptions.ts @@ -0,0 +1,17 @@ +export class SessionHijackAttempt extends Error { + constructor (message = 'session-jacking attempt') { + super(message) + } +} + +export class IpMissingFromSession extends Error { + constructor (message = 'no IP in session even though ipPinning is enabled') { + super(message) + } +} + +export class SessionExpired extends Error { + constructor (message = 'session expired') { + super(message) + } +} diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index da0adff..71d6e7d 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -3,7 +3,8 @@ import { nanoid } from 'nanoid' import dayjs from 'dayjs' import type { SameSiteOptions } from '../../../../module' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' -import { getHashedIpAddress, getRequestIpAddress, hashIpAddress, ipAddressesMatch } from "./ipPinning"; +import { getHashedIpAddress, getRequestIpAddress, ipAddressesMatch } from './ipPinning' +import { IpMissingFromSession, SessionExpired, SessionHijackAttempt } from './exceptions' import { useRuntimeConfig } from '#imports' const SESSION_COOKIE_NAME = 'sessionId' @@ -25,6 +26,34 @@ export declare interface Session { [key: string]: any } +const checkSessionExpirationTime = (session: Session, sessionExpiryInSeconds: number) => { + const now = dayjs() + if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { + throw new SessionExpired() + } +} + +const checkSessionIp = async (event: H3Event, session) => { + const hashedIP = session.ip + + // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled + if (!hashedIP) { + throw new IpMissingFromSession() + } + + // 4.2. Get request's IP + const requestIP = getRequestIpAddress(event) + + // 4.3. Ensure pinning + const matches = await ipAddressesMatch(requestIP, hashedIP) + if (!matches) { + // 4.4. Report session-jacking attempt + // TODO: Report session-jacking attempt from requestIP + // NOTE: DO NOT DELETE SESSION HERE, this would mean we eliminate session-jacking, but users could delete others' sessions + throw new SessionHijackAttempt() + } +} + /** * Get the current session id. * @@ -90,37 +119,26 @@ const getSession = async (event: H3Event): Promise => { const runtimeConfig = useRuntimeConfig() const sessionOptions = runtimeConfig.session.session - // 3. Is the session not expired? - const sessionExpiryInSeconds = sessionOptions.expiryInSeconds - if (sessionExpiryInSeconds !== null) { - const now = dayjs() - if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { - await deleteSession(event) // Cleanup old session data to avoid leaks - return null + try { + // 3. Is the session not expired? + const sessionExpiryInSeconds = sessionOptions.expiryInSeconds + if (sessionExpiryInSeconds !== null) { + checkSessionExpirationTime(session, sessionExpiryInSeconds) } - } - - // 4. Check for IP pinning logic - if (sessionOptions.ipPinning) { - const hashedIP = session.ip - // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled - if (!hashedIP) { - await deleteSession(event) // Cleanup to avoid leaks (and properly recreate a session) - return null + // 4. Check for IP pinning logic + if (sessionOptions.ipPinning) { + await checkSessionIp(event, session) } + } catch (e) { + // NOTE: DO NOT DELETE SESSION ON HIJACK ATTEMPTS, this would mean we eliminate session-jacking, but users could delete others' sessions + const shouldCleanup = !(e instanceof SessionHijackAttempt) - // 4.2. Get request's IP - const requestIP = getRequestIpAddress(event) - - // 4.3. Ensure pinning - const matches = await ipAddressesMatch(requestIP, hashedIP) - if (!matches) { - // 4.4. Report session-jacking attempt - // TODO: Report session-jacking attempt from requestIP - // NOTE: DO NOT DELETE SESSION HERE, this would mean we eliminate session-jacking, but users could delete others' sessions - return null + if (shouldCleanup) { + await deleteSession(event) // Cleanup old session data to avoid leaks } + + return null } return session From 91cd1c574689175d4d15863f6b2289454ca8967f Mon Sep 17 00:00:00 2001 From: Voltra Date: Sat, 5 Nov 2022 10:07:52 +0100 Subject: [PATCH 14/20] Move conditional inside the checks themselves --- src/module.ts | 20 +++++++++++--- .../server/middleware/session/index.ts | 26 +++++++++++-------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/module.ts b/src/module.ts index 58b07b4..a7259e0 100644 --- a/src/module.ts +++ b/src/module.ts @@ -5,7 +5,11 @@ import { defu } from 'defu' export type SameSiteOptions = 'lax' | 'strict' | 'none' export type SupportedSessionApiMethods = 'patch' | 'delete' | 'get' | 'post' -declare interface SessionOptions { +type DeepRequired = T extends object ? Required<{ + [Key in keyof T]: DeepRequired +}> : Required; + +export interface SessionOptions { /** * Set the session duration in seconds. Once the session expires, a new one with a new id will be created. Set to `null` for infinite sessions * @default 600 @@ -53,7 +57,7 @@ declare interface SessionOptions { ipPinning: boolean, } -declare interface ApiOptions { +export interface ApiOptions { /** * Whether to enable the session API endpoints that allow read, update and delete operations from the client side. Use `/api/session` to access the endpoints. * @default true @@ -117,6 +121,14 @@ const defaults: ModuleOptions = { } } +export interface ModuleRuntimeConfig { + session: ModuleOptions; +} + +export interface ModulePublicRuntimeConfig { + api: ApiOptions; +} + export default defineNuxtModule({ meta: { name: `@sidebase/${PACKAGE_NAME}`, @@ -139,9 +151,9 @@ export default defineNuxtModule({ logger.info('Setting up sessions...') // 2. Set public and private runtime configuration - const options = defu(moduleOptions, defaults) + const options = defu(moduleOptions, defaults) as DeepRequired options.api.methods = moduleOptions.api.methods.length > 0 ? moduleOptions.api.methods : ['patch', 'delete', 'get', 'post'] - nuxt.options.runtimeConfig.session = defu(nuxt.options.runtimeConfig.session, options) + nuxt.options.runtimeConfig.session = defu(nuxt.options.runtimeConfig.session, options) as DeepRequired nuxt.options.runtimeConfig.public = defu(nuxt.options.runtimeConfig.public, { session: { api: options.api } }) // 3. Locate runtime directory and transpile module diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index 71d6e7d..ddb372b 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -1,7 +1,7 @@ import { deleteCookie, eventHandler, H3Event, parseCookies, setCookie } from 'h3' import { nanoid } from 'nanoid' import dayjs from 'dayjs' -import type { SameSiteOptions } from '../../../../module' +import type { SameSiteOptions, SessionOptions } from '../../../../module' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' import { getHashedIpAddress, getRequestIpAddress, ipAddressesMatch } from './ipPinning' import { IpMissingFromSession, SessionExpired, SessionHijackAttempt } from './exceptions' @@ -26,14 +26,23 @@ export declare interface Session { [key: string]: any } -const checkSessionExpirationTime = (session: Session, sessionExpiryInSeconds: number) => { +const checkSessionExpirationTime = (session: Session, sessionOptions: SessionOptions) => { + const sessionExpiryInSeconds = sessionOptions.expiryInSeconds + if (sessionExpiryInSeconds === null) { + return + } + const now = dayjs() if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { throw new SessionExpired() } } -const checkSessionIp = async (event: H3Event, session) => { +const checkSessionIp = async (event: H3Event, session: Session, sessionOptions: SessionOptions) => { + if (!sessionOptions.ipPinning) { + return + } + const hashedIP = session.ip // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled @@ -117,19 +126,14 @@ const getSession = async (event: H3Event): Promise => { } const runtimeConfig = useRuntimeConfig() - const sessionOptions = runtimeConfig.session.session + const sessionOptions = runtimeConfig.session.session as SessionOptions try { // 3. Is the session not expired? - const sessionExpiryInSeconds = sessionOptions.expiryInSeconds - if (sessionExpiryInSeconds !== null) { - checkSessionExpirationTime(session, sessionExpiryInSeconds) - } + checkSessionExpirationTime(session, sessionOptions) // 4. Check for IP pinning logic - if (sessionOptions.ipPinning) { - await checkSessionIp(event, session) - } + await checkSessionIp(event, session, sessionOptions) } catch (e) { // NOTE: DO NOT DELETE SESSION ON HIJACK ATTEMPTS, this would mean we eliminate session-jacking, but users could delete others' sessions const shouldCleanup = !(e instanceof SessionHijackAttempt) From ab85ea440fa7f448e65f2b16eb0f408b2d83ee5e Mon Sep 17 00:00:00 2001 From: Voltra Date: Sat, 5 Nov 2022 14:14:40 +0100 Subject: [PATCH 15/20] Refactor ipPinning API to allow users to configure trusted headers --- README.md | 35 ++++- src/module.ts | 136 +++------------- src/runtime/composables/useSession.ts | 3 +- .../server/middleware/session/index.ts | 9 +- .../server/middleware/session/ipPinning.ts | 21 ++- src/types.ts | 147 ++++++++++++++++++ 6 files changed, 212 insertions(+), 139 deletions(-) create mode 100644 src/types.ts diff --git a/README.md b/README.md index c8b39dc..5927ebe 100644 --- a/README.md +++ b/README.md @@ -203,20 +203,41 @@ Check out here what storage backends are supported and how to configure them: ht ### IP Pinning -For increased security, you can enable the `ipPinning` flag in the session options. This will make it so sessions are bound by both the session's ID and the user's IP, which means no [session-jacking](https://owasp.org/www-community/attacks/Session_hijacking_attack). +For increased security, you can enable the `ipPinning` flag in the session options by setting it to `true`. -For this feature to work, we rely on [`Socket#remoteAddress`](https://nodejs.org/api/net.html#socketremoteaddress) which means you might have to configure your (reverse) proxy to properly forward IP addresses (Usually via the `X-Forwarded-For` header): +This will make it so sessions are bound by both the session's ID and the user's IP, which means no [session-jacking](https://owasp.org/www-community/attacks/Session_hijacking_attack). + +For this feature to work, we rely on [`Socket#remoteAddress`](https://nodejs.org/api/net.html#socketremoteaddress). + +If you want, you can also configure your (reverse) proxy to properly forward IP addresses (Usually via the `X-Forwarded-For` header): * [Using Apache 2](https://httpd.apache.org/docs/current/mod/mod_proxy.html#x-headers) * [Using NGINX](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) * [Using CloudFlare](https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/) +* [Using Tomcat](https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/valves/RemoteIpValve.html) +* [Using LiteSpeed](https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:config:show-real-ip-behind-a-proxy) +* [Using Caddy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#defaults) +* [Using Lighttpd](https://redmine.lighttpd.net/projects/1/wiki/Docs_ModExtForward) +* [Using Microsoft IIS](https://techcommunity.microsoft.com/t5/iis-support-blog/how-to-use-x-forwarded-for-header-to-log-actual-client-ip/ba-p/873115) + +***WARNING:*** Only trust these headers when sent directly by your webserver or (reverse) proxy. You should also clean up undesired/disallowed headers to avoid interpreting untrusted data from the client. -Supported headers (in order): -* `X-Forwarded-For` -* `True-Client-Ip` -* `CF-Connecting-Ip` +To configure which header to read from, just set the `ipPinning` like this: +```typescript +{ + // [...], + session: { + // [...] + ipPinning: { + headerName: "My-Custom-Ip-Header" + } + // [...] + }, + // [...] +} +``` -***WARNING:*** Only trust these headers when sent directly by your webserver or (reverse) proxy +Note that header names are case-insensitive and will be transformed to lowercase before inspection. ### Configuration diff --git a/src/module.ts b/src/module.ts index a7259e0..a35bbff 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,133 +1,32 @@ import { addImportsDir, addServerHandler, createResolver, defineNuxtModule, useLogger } from '@nuxt/kit' import { CreateStorageOptions } from 'unstorage' import { defu } from 'defu' - -export type SameSiteOptions = 'lax' | 'strict' | 'none' -export type SupportedSessionApiMethods = 'patch' | 'delete' | 'get' | 'post' - -type DeepRequired = T extends object ? Required<{ - [Key in keyof T]: DeepRequired -}> : Required; - -export interface SessionOptions { - /** - * Set the session duration in seconds. Once the session expires, a new one with a new id will be created. Set to `null` for infinite sessions - * @default 600 - * @example 30 - * @type number | null - */ - expiryInSeconds: number | null - /** - * How many characters the random session id should be long - * @default 64 - * @example 128 - * @type number - */ - idLength: number - /** - * What prefix to use to store session information via `unstorage` - * @default 64 - * @example 128 - * @type number - * @docs https://github.com/unjs/unstorage - */ - storePrefix: string - /** - * When to attach session cookie to requests - * @default 'lax' - * @example 'strict' - * @type SameSiteOptions - * @docs https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite - */ - cookieSameSite: SameSiteOptions - /** - * Driver configuration for session-storage. Per default in-memory storage is used - * @default {} - * @example { driver: redisDriver({ base: 'storage:' }) } - * @type CreateStorageOptions - * @docs https://github.com/unjs/unstorage - */ - storageOptions: CreateStorageOptions, - /** - * Whether to pin sessions to the user's IP (i.e. Different IP means a different session) - * @default false - * @example true - * @type boolean - */ - ipPinning: boolean, -} - -export interface ApiOptions { - /** - * Whether to enable the session API endpoints that allow read, update and delete operations from the client side. Use `/api/session` to access the endpoints. - * @default true - * @example false - * @type boolean - */ - isEnabled: boolean - /** - * Configure which session API methods are enabled. All api methods are enabled by default. Restricting the enabled methods can be useful if you want to allow the client to read session-data but not modify it. Passing - * an empty array will result in all API methods being registered. Disable the api via the `api.isEnabled` option. - * @default [] - * @example ['get'] - * @type SupportedSessionApiMethods[] - */ - methods: SupportedSessionApiMethods[] - /** - * Base path of the session api. - * @default /api/session - * @example /_session - * @type string - */ - basePath: string -} - -export interface ModuleOptions { - /** - * Whether to enable the module - * @default true - * @example true - * @type boolean - */ - isEnabled: boolean, - /** - * Configure session-behvaior - * @type SessionOptions - */ - session: Partial - /** - * Configure session-api and composable-behavior - * @type ApiOptions - */ - api: Partial -} +import type { + FilledModuleOptions, + ModuleOptions, + ModulePublicRuntimeConfig, + SessionIpPinningOptions, + SupportedSessionApiMethods +} from './types' const PACKAGE_NAME = 'nuxt-session' -const defaults: ModuleOptions = { +const defaults: FilledModuleOptions = { isEnabled: true, session: { expiryInSeconds: 60 * 10, idLength: 64, storePrefix: 'sessions', cookieSameSite: 'lax', - storageOptions: {}, - ipPinning: false + storageOptions: {} as CreateStorageOptions, + ipPinning: false as boolean|SessionIpPinningOptions }, api: { isEnabled: true, - methods: [], + methods: [] as SupportedSessionApiMethods[], basePath: '/api/session' } -} - -export interface ModuleRuntimeConfig { - session: ModuleOptions; -} - -export interface ModulePublicRuntimeConfig { - api: ApiOptions; -} +} as const export default defineNuxtModule({ meta: { @@ -151,10 +50,13 @@ export default defineNuxtModule({ logger.info('Setting up sessions...') // 2. Set public and private runtime configuration - const options = defu(moduleOptions, defaults) as DeepRequired + const options: FilledModuleOptions = defu(moduleOptions, defaults) options.api.methods = moduleOptions.api.methods.length > 0 ? moduleOptions.api.methods : ['patch', 'delete', 'get', 'post'] - nuxt.options.runtimeConfig.session = defu(nuxt.options.runtimeConfig.session, options) as DeepRequired - nuxt.options.runtimeConfig.public = defu(nuxt.options.runtimeConfig.public, { session: { api: options.api } }) + // @ts-ignore TODO: Fix this `nuxi prepare` bug + nuxt.options.runtimeConfig.session = defu(nuxt.options.runtimeConfig.session, options) + + const publicConfig: ModulePublicRuntimeConfig = { session: { api: options.api } } + nuxt.options.runtimeConfig.public = defu(nuxt.options.runtimeConfig.public, publicConfig) // 3. Locate runtime directory and transpile module const { resolve } = createResolver(import.meta.url) @@ -185,3 +87,5 @@ export default defineNuxtModule({ logger.success('Session setup complete') } }) + +export * from './types' diff --git a/src/runtime/composables/useSession.ts b/src/runtime/composables/useSession.ts index ba8689c..c131d21 100644 --- a/src/runtime/composables/useSession.ts +++ b/src/runtime/composables/useSession.ts @@ -1,8 +1,7 @@ import { useFetch, createError } from '#app' import { nanoid } from 'nanoid' import { Ref, ref } from 'vue' -import type { SupportedSessionApiMethods } from '../../module' -import type { Session } from '../server/middleware/session' +import type { Session, SupportedSessionApiMethods } from '../../types' import { useRuntimeConfig } from '#imports' type SessionData = Record diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index ddb372b..e63e82e 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -1,7 +1,7 @@ import { deleteCookie, eventHandler, H3Event, parseCookies, setCookie } from 'h3' import { nanoid } from 'nanoid' import dayjs from 'dayjs' -import type { SameSiteOptions, SessionOptions } from '../../../../module' +import { SameSiteOptions, Session, SessionOptions } from '../../../../types' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' import { getHashedIpAddress, getRequestIpAddress, ipAddressesMatch } from './ipPinning' import { IpMissingFromSession, SessionExpired, SessionHijackAttempt } from './exceptions' @@ -19,13 +19,6 @@ const safeSetCookie = (event: H3Event, name: string, value: string) => setCookie sameSite: useRuntimeConfig().session.session.cookieSameSite as SameSiteOptions }) -export declare interface Session { - id: string - createdAt: Date - ip?: string - [key: string]: any -} - const checkSessionExpirationTime = (session: Session, sessionOptions: SessionOptions) => { const sessionExpiryInSeconds = sessionOptions.expiryInSeconds if (sessionExpiryInSeconds === null) { diff --git a/src/runtime/server/middleware/session/ipPinning.ts b/src/runtime/server/middleware/session/ipPinning.ts index 355087b..721387a 100644 --- a/src/runtime/server/middleware/session/ipPinning.ts +++ b/src/runtime/server/middleware/session/ipPinning.ts @@ -1,11 +1,13 @@ import { argon2id, hash, verify } from 'argon2' import { H3Event } from 'h3' +import { useRuntimeConfig } from '#app' const argon2Options = { // cryptographically-secure salt is generated automatically type: argon2id, // resistant against GPU & tradeoff attacks hashLength: 60 } + /** * Get a hashed representation of the given IP address (for GDPR compliance) * @param ip string|undefined The IP address to hash @@ -14,12 +16,14 @@ export const hashIpAddress = (ip: string | undefined): Promise => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) + /** * Extract the IP address from an HTTP header * @param header string|string[]|undefined The header value to inspect and extract the IP from @@ -35,18 +39,23 @@ const extractIpFromHeader = (header?: string | string[]): string | undefined => return undefined } + /** * Get the IP address corresponding to the user's request * @param event H3Event Event passing through middleware */ export const getRequestIpAddress = ({ req }: H3Event): string | undefined => { - const foundIp = [ - 'x-forwarded-for', - 'true-client-ip', - 'cf-connecting-ip' - ].find(headerName => extractIpFromHeader(req.headers[headerName])) + const sessionOptions = useRuntimeConfig().session.session + + // @ts-ignore TODO: Fix this `nuxi prepare` bug + const headerName = sessionOptions.ipPinning?.headerName + + // @ts-ignore TODO: Fix this `nuxi prepare` bug + if (typeof sessionOptions.ipPinning === 'object' && 'headerName' in sessionOptions.ipPinning.headerName) { + return extractIpFromHeader(req.headers[headerName.toLowerCase()]) + } - return foundIp ?? req.connection?.remoteAddress ?? req.socket.remoteAddress + return req.connection?.remoteAddress ?? req.socket.remoteAddress } export const getHashedIpAddress = (event: H3Event): Promise => { diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..bcbb3fd --- /dev/null +++ b/src/types.ts @@ -0,0 +1,147 @@ +import type { CreateStorageOptions } from 'unstorage' + +export type SameSiteOptions = 'lax' | 'strict' | 'none' +export type SupportedSessionApiMethods = 'patch' | 'delete' | 'get' | 'post' + +export interface SessionIpPinningOptions { + /** + * The name of the HTTP header used to retrieve the forwarded (real) IP address of the user + * @example 'X-Forwarded-For' + * @type string + */ + headerName: string; +} + +export interface SessionOptions { + /** + * Set the session duration in seconds. Once the session expires, a new one with a new id will be created. Set to `null` for infinite sessions + * @default 600 + * @example 30 + * @type number | null + */ + expiryInSeconds: number | null + /** + * How many characters the random session id should be long + * @default 64 + * @example 128 + * @type number + */ + idLength: number + /** + * What prefix to use to store session information via `unstorage` + * @default 64 + * @example 128 + * @type number + * @docs https://github.com/unjs/unstorage + */ + storePrefix: string + /** + * When to attach session cookie to requests + * @default 'lax' + * @example 'strict' + * @type SameSiteOptions + * @docs https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + */ + cookieSameSite: SameSiteOptions + /** + * Driver configuration for session-storage. Per default in-memory storage is used + * @default {} + * @example { driver: redisDriver({ base: 'storage:' }) } + * @type CreateStorageOptions + * @docs https://github.com/unjs/unstorage + */ + storageOptions: CreateStorageOptions, + /** + * Whether to pin sessions to the user's IP (i.e. Different IP means a different session) + * @default false + * @example + * { + * headerName: "X-Forwarded-For" + * } + * @type {SessionIpPinningOptions|boolean} + */ + ipPinning: SessionIpPinningOptions|boolean, +} + +export interface ApiOptions { + /** + * Whether to enable the session API endpoints that allow read, update and delete operations from the client side. Use `/api/session` to access the endpoints. + * @default true + * @example false + * @type boolean + */ + isEnabled: boolean; + /** + * Configure which session API methods are enabled. All api methods are enabled by default. Restricting the enabled methods can be useful if you want to allow the client to read session-data but not modify it. Passing + * an empty array will result in all API methods being registered. Disable the api via the `api.isEnabled` option. + * @default [] + * @example ['get'] + * @type SupportedSessionApiMethods[] + */ + methods: SupportedSessionApiMethods[]; + /** + * Base path of the session api. + * @default /api/session + * @example /_session + * @type string + */ + basePath: string; +} + +export interface ModuleOptions { + /** + * Whether to enable the module + * @default true + * @example true + * @type boolean + */ + isEnabled: boolean, + /** + * Configure session-behavior + * @type SessionOptions + */ + session: Partial + /** + * Configure session-api and composable-behavior + * @type ApiOptions + */ + api: Partial +} + +export interface FilledModuleOptions { + /** + * Whether the module is enabled + * @type boolean + */ + isEnabled: boolean, + + /** + * Session configuration + * @type SessionOptions + */ + session: SessionOptions, + + /** + * Session-api and composable-behavior configuration + * @type ApiOptions + */ + api: ApiOptions +} + +export interface ModuleRuntimeConfig { + session: FilledModuleOptions; +} + +export interface ModulePublicRuntimeConfig { + session: { + api: ModuleRuntimeConfig['session']['api']; + }; +} + +export declare interface Session { + id: string; + createdAt: Date; + ip?: string; + + [key: string]: any; +} From c13b01cad33bf31b8d0284655e33dd26b4436aa1 Mon Sep 17 00:00:00 2001 From: Voltra Date: Wed, 9 Nov 2022 19:42:06 +0100 Subject: [PATCH 16/20] Rename IP mismatch error class --- src/runtime/server/middleware/session/exceptions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/server/middleware/session/exceptions.ts b/src/runtime/server/middleware/session/exceptions.ts index 8352a55..65c43e9 100644 --- a/src/runtime/server/middleware/session/exceptions.ts +++ b/src/runtime/server/middleware/session/exceptions.ts @@ -1,17 +1,17 @@ -export class SessionHijackAttempt extends Error { - constructor (message = 'session-jacking attempt') { +export class IpMismatch extends Error { + constructor (message = 'User IP doesn\'t match the one in session') { super(message) } } export class IpMissingFromSession extends Error { - constructor (message = 'no IP in session even though ipPinning is enabled') { + constructor (message = 'No IP in session even though ipPinning is enabled') { super(message) } } export class SessionExpired extends Error { - constructor (message = 'session expired') { + constructor (message = 'Session expired') { super(message) } } From cc4f588af1f18b087eda628e48fe030ac5c48b2e Mon Sep 17 00:00:00 2001 From: Voltra Date: Wed, 9 Nov 2022 19:43:52 +0100 Subject: [PATCH 17/20] Move session IP processing in the IP pinning file --- .../server/middleware/session/ipPinning.ts | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/runtime/server/middleware/session/ipPinning.ts b/src/runtime/server/middleware/session/ipPinning.ts index 721387a..efc7881 100644 --- a/src/runtime/server/middleware/session/ipPinning.ts +++ b/src/runtime/server/middleware/session/ipPinning.ts @@ -1,6 +1,8 @@ import { argon2id, hash, verify } from 'argon2' import { H3Event } from 'h3' import { useRuntimeConfig } from '#app' +import { Session } from '../../../../types' +import { IpMissingFromSession, IpMismatch } from './exceptions' const argon2Options = { // cryptographically-secure salt is generated automatically @@ -22,7 +24,7 @@ export const hashIpAddress = (ip: string | undefined): Promise => !(ip && ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) +export const ipAddressesMatch = (ip: string | undefined, ipHash: string | undefined): Promise => (!ip && !ipHash) ? Promise.resolve(false) : verify(ipHash, ip, argon2Options) /** * Extract the IP address from an HTTP header @@ -55,9 +57,30 @@ export const getRequestIpAddress = ({ req }: H3Event): string | undefined => { return extractIpFromHeader(req.headers[headerName.toLowerCase()]) } - return req.connection?.remoteAddress ?? req.socket.remoteAddress + return req.socket.remoteAddress } export const getHashedIpAddress = (event: H3Event): Promise => { return hashIpAddress(getRequestIpAddress(event)) } + +export const processSessionIp = async (event: H3Event, session: Session) => { + const hashedIP = session.ip + + // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled + if (!hashedIP) { + throw new IpMissingFromSession() + } + + // 4.2. Get request's IP + const requestIP = getRequestIpAddress(event) + + // 4.3. Ensure pinning + const matches = await ipAddressesMatch(requestIP, hashedIP) + if (!matches) { + // 4.4. Report session-jacking attempt + // TODO: Report session-jacking attempt from requestIP + // NOTE: DO NOT DELETE SESSION HERE, this would mean we eliminate session-jacking, but users could delete others' sessions + throw new IpMismatch() + } +} From 6c23fc6497606e20c81aae6019ac505cc517881f Mon Sep 17 00:00:00 2001 From: Voltra Date: Wed, 9 Nov 2022 19:44:33 +0100 Subject: [PATCH 18/20] Move conditionals outside of checks --- .../server/middleware/session/index.ts | 47 +++++-------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index e63e82e..04dc160 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -3,8 +3,8 @@ import { nanoid } from 'nanoid' import dayjs from 'dayjs' import { SameSiteOptions, Session, SessionOptions } from '../../../../types' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' -import { getHashedIpAddress, getRequestIpAddress, ipAddressesMatch } from './ipPinning' -import { IpMissingFromSession, SessionExpired, SessionHijackAttempt } from './exceptions' +import { processSessionIp, getHashedIpAddress } from './ipPinning' +import { SessionExpired, IpMismatch } from './exceptions' import { useRuntimeConfig } from '#imports' const SESSION_COOKIE_NAME = 'sessionId' @@ -19,43 +19,13 @@ const safeSetCookie = (event: H3Event, name: string, value: string) => setCookie sameSite: useRuntimeConfig().session.session.cookieSameSite as SameSiteOptions }) -const checkSessionExpirationTime = (session: Session, sessionOptions: SessionOptions) => { - const sessionExpiryInSeconds = sessionOptions.expiryInSeconds - if (sessionExpiryInSeconds === null) { - return - } - +const checkSessionExpirationTime = (session: Session, sessionExpiryInSeconds: number) => { const now = dayjs() if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { throw new SessionExpired() } } -const checkSessionIp = async (event: H3Event, session: Session, sessionOptions: SessionOptions) => { - if (!sessionOptions.ipPinning) { - return - } - - const hashedIP = session.ip - - // 4.1. (Should not happen) No IP address present in the session even though the flag is enabled - if (!hashedIP) { - throw new IpMissingFromSession() - } - - // 4.2. Get request's IP - const requestIP = getRequestIpAddress(event) - - // 4.3. Ensure pinning - const matches = await ipAddressesMatch(requestIP, hashedIP) - if (!matches) { - // 4.4. Report session-jacking attempt - // TODO: Report session-jacking attempt from requestIP - // NOTE: DO NOT DELETE SESSION HERE, this would mean we eliminate session-jacking, but users could delete others' sessions - throw new SessionHijackAttempt() - } -} - /** * Get the current session id. * @@ -120,16 +90,21 @@ const getSession = async (event: H3Event): Promise => { const runtimeConfig = useRuntimeConfig() const sessionOptions = runtimeConfig.session.session as SessionOptions + const sessionExpiryInSeconds = sessionOptions.expiryInSeconds try { // 3. Is the session not expired? - checkSessionExpirationTime(session, sessionOptions) + if (sessionExpiryInSeconds !== null) { + checkSessionExpirationTime(session, sessionExpiryInSeconds) + } // 4. Check for IP pinning logic - await checkSessionIp(event, session, sessionOptions) + if (sessionOptions.ipPinning) { + await processSessionIp(event, session) + } } catch (e) { // NOTE: DO NOT DELETE SESSION ON HIJACK ATTEMPTS, this would mean we eliminate session-jacking, but users could delete others' sessions - const shouldCleanup = !(e instanceof SessionHijackAttempt) + const shouldCleanup = !(e instanceof IpMismatch) if (shouldCleanup) { await deleteSession(event) // Cleanup old session data to avoid leaks From 7c01cfd25c36905e7a95401953606bf81560c3d7 Mon Sep 17 00:00:00 2001 From: Voltra Date: Thu, 10 Nov 2022 23:03:57 +0100 Subject: [PATCH 19/20] Change way session are cleaned up Sessions are now always cleaned up on error. Previous behavior was to ignore cleanup on IP mismatch. --- src/runtime/server/middleware/session/index.ts | 11 +++-------- src/runtime/server/middleware/session/ipPinning.ts | 1 - 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index 54725f1..5ccb4e7 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -4,7 +4,7 @@ import dayjs from 'dayjs' import { SameSiteOptions, Session, SessionOptions } from '../../../../types' import { dropStorageSession, getStorageSession, setStorageSession } from './storage' import { processSessionIp, getHashedIpAddress } from './ipPinning' -import { SessionExpired, IpMismatch } from './exceptions' +import { SessionExpired } from './exceptions' import { useRuntimeConfig } from '#imports' const SESSION_COOKIE_NAME = 'sessionId' @@ -104,13 +104,8 @@ const getSession = async (event: H3Event): Promise => { if (sessionOptions.ipPinning) { await processSessionIp(event, session) } - } catch (e) { - // NOTE: DO NOT DELETE SESSION ON HIJACK ATTEMPTS, this would mean we eliminate session-jacking, but users could delete others' sessions - const shouldCleanup = !(e instanceof IpMismatch) - - if (shouldCleanup) { - await deleteSession(event) // Cleanup old session data to avoid leaks - } + } catch { + await deleteSession(event) // Cleanup old session data to avoid leaks return null } diff --git a/src/runtime/server/middleware/session/ipPinning.ts b/src/runtime/server/middleware/session/ipPinning.ts index efc7881..84a2372 100644 --- a/src/runtime/server/middleware/session/ipPinning.ts +++ b/src/runtime/server/middleware/session/ipPinning.ts @@ -80,7 +80,6 @@ export const processSessionIp = async (event: H3Event, session: Session) => { if (!matches) { // 4.4. Report session-jacking attempt // TODO: Report session-jacking attempt from requestIP - // NOTE: DO NOT DELETE SESSION HERE, this would mean we eliminate session-jacking, but users could delete others' sessions throw new IpMismatch() } } From 107c69930a8c7f581e9f8c251c9e028f548875d7 Mon Sep 17 00:00:00 2001 From: Voltra Date: Thu, 10 Nov 2022 23:10:30 +0100 Subject: [PATCH 20/20] Remove unnecessary ts-ignore comments --- src/module.ts | 4 ++-- src/runtime/server/middleware/session/ipPinning.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/module.ts b/src/module.ts index be23413..3f6355f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -53,8 +53,8 @@ export default defineNuxtModule({ // 2. Set public and private runtime configuration const options: FilledModuleOptions = defu(moduleOptions, defaults) options.api.methods = moduleOptions.api.methods.length > 0 ? moduleOptions.api.methods : ['patch', 'delete', 'get', 'post'] - // @ts-ignore TODO: Fix this `nuxi prepare` bug - nuxt.options.runtimeConfig.session = defu(nuxt.options.runtimeConfig.session, options) + // @ts-ignore TODO: Fix this `nuxi prepare` bug (see https://github.com/nuxt/framework/issues/8728) + nuxt.options.runtimeConfig.session = defu(nuxt.options.runtimeConfig.session, options) as FilledModuleOptions const publicConfig: ModulePublicRuntimeConfig = { session: { api: options.api } } nuxt.options.runtimeConfig.public = defu(nuxt.options.runtimeConfig.public, publicConfig) diff --git a/src/runtime/server/middleware/session/ipPinning.ts b/src/runtime/server/middleware/session/ipPinning.ts index 84a2372..0a8bb1a 100644 --- a/src/runtime/server/middleware/session/ipPinning.ts +++ b/src/runtime/server/middleware/session/ipPinning.ts @@ -49,10 +49,8 @@ const extractIpFromHeader = (header?: string | string[]): string | undefined => export const getRequestIpAddress = ({ req }: H3Event): string | undefined => { const sessionOptions = useRuntimeConfig().session.session - // @ts-ignore TODO: Fix this `nuxi prepare` bug const headerName = sessionOptions.ipPinning?.headerName - // @ts-ignore TODO: Fix this `nuxi prepare` bug if (typeof sessionOptions.ipPinning === 'object' && 'headerName' in sessionOptions.ipPinning.headerName) { return extractIpFromHeader(req.headers[headerName.toLowerCase()]) }