From: Justin Wind Date: Mon, 26 Dec 2022 23:17:45 +0000 (-0800) Subject: initial commit X-Git-Tag: v1.0.0 X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=b0103b0d496262c438b40bc20304081dbfe41e73;p=squeep-indie-auther initial commit --- diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..b3ffbe2 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,89 @@ +{ + "env": { + "browser": false, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:node/recommended", + "plugin:security/recommended", + "plugin:sonarjs/recommended" + ], + "parserOptions": { + "ecmaVersion": "latest" + }, + "plugins": [ + "node", + "security", + "sonarjs" + ], + "rules": { + "array-element-newline": [ + "error", + "consistent" + ], + "arrow-parens": [ + "error", + "always" + ], + "arrow-spacing": [ + "error", + { + "after": true, + "before": true + } + ], + "block-scoped-var": "error", + "block-spacing": "error", + "brace-style": "error", + "callback-return": "error", + "camelcase": "error", + "class-methods-use-this": "error", + "comma-dangle": [ + "error", + "always-multiline" + ], + "comma-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "indent": [ + "warn", + 2, + { + "SwitchCase": 1 + } + ], + "sonarjs/cognitive-complexity": "warn", + "sonarjs/no-duplicate-string": "warn", + "keyword-spacing": "error", + "linebreak-style": [ + "error", + "unix" + ], + "no-unused-vars": [ + "error", { + "varsIgnorePattern": "^_" + } + ], + "object-curly-spacing": [ + "error", + "always" + ], + "prefer-const": "error", + "quotes": [ + "error", + "single" + ], + "strict": "error", + "vars-on-top": "error" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88f74fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +.nyc_output +coverage +.vscode +*.sqlite* +static/*.gz +static/*.br diff --git a/.nycrc.json b/.nycrc.json new file mode 100644 index 0000000..497d8af --- /dev/null +++ b/.nycrc.json @@ -0,0 +1,6 @@ +{ + "reporter": [ + "lcov", + "text" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..80cf52d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +Releases and notable changes to this project are documented here. + +## [v1.0.0] - TBD + +### Added + +- Everything. MVP first stable release. + +--- + +[Unreleased]: https://git.squeep.com/?p=squeep-indie-auther;a=commitdiff;h=HEAD;hp=v1.0.0 +[v1.0.0]: https://git.squeep.com/?p=squeep-indie-auther;a=commitdiff;h=v1.0.0;hp=v0.0.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e37aea1 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# Welcome to my IndieAuth-er + +## What + +[IndieAuth](https://indieweb.org/IndieAuth) is a [protocol](https://indieauth.spec.indieweb.org/) which facilitates identifying users uniquely by the URLs they control to third-party applications. It is an extension of [Oauth 2](https://indieauth.spec.indieweb.org). + +This service implements the functionality required to negotiate that identity authentication and validation. + +## Let's Do Some Auth + +A ```user``` is an entity known to this service, with a credential (currently a password) used to authenticate and login to this service. +Authentication of a ```user``` is handled by either a [hashed password](https://en.wikipedia.org/wiki/Argon2) stored securely in one of the available database engines, or by optionally delegating to the host machine's [PAM subsystem](https://en.wikipedia.org/wiki/Pluggable_Authentication_Modules). +PAM can be used to leverage, exempli gratia, LDAP integration for user authentication. + +A ```profile``` is a URL (under control of a ```user```) which contents includes the necessary meta-data informing an application to contact this server for identification validation. Each ```user``` may have one or more ```profile```s. + +Each ```profile``` may also be associated with a customizable list of additional [scopes](https://www.oauth.com/oauth2-servers/scope/) which may be added to any application client grant for convenience. + +An example of the user-interface when granting consent to a client application: +![Consent page](./documentation/media/consent-page.png) + +A rudimentary ticket-sending UI is also available: +![Ticket Offer page](./documentation/media/ticket-page.png) + +## Resource Service Integration + +Other services (resources) may make calls to validate token grants by configuring a pre-shared secret, and authenticating to this server using [an HMAC-style bearer token scheme](https://git.squeep.com/?p=squeep-resource-authentication-module;a=blob_plain;f=README.md;hb=HEAD). + +## Ticket Auth + +This service can accept proffered [authentication tickets](https://indieweb.org/IndieAuth_Ticket_Auth). It will simply publish any proffered tickets for valid profiles to a configured AMQP/RabbitMQ queue for some other service to redeem and make use of. + +## Architecture + +A granted token is an encrypted identifier (specifically a UUID assigned to the initial authentication request) which references the user/client relationship stored in the database. Details such as granted scopes, token expiration, refreshability, and revocation status are stored there. + +Uh, more later. + +![Entity relationship diagram for Postgres engine](./documentation/media/postgres-er.svg) + +### Quirks + +This implementation is built atop an in-house API framework, for Reasons. Limiting the footprint of external dependencies as much as is reasonable is a design goal. + +### File Tour + +- bin/ - utility scripts +- config/ + - default.js - defines all configuration parameters' default values + - index.js - merges an environment's values over defaults + - *.js - environment specific values, edit these as needed +- server.js - launches the application server +- src/ + - chores.js - recurring maintenance tasks + - common.js - utility functions + - db/ + - abstract.js - base database class that any engine will implement + - errors.js - database Error types + - index.js - database factory + - schema-version-helper.js - schema migrations aide + - postgres/ + - index.js - PostgreSQL engine implementation + - sql/ - statements and schemas + - sqlite/ + - index.js - SQLite engine implementation + - sql - statements and schemas + - enum.js - invariants + - errors.js - local Error types + - logger/ + - data-sanitizers.js - logger helpers to scrub sensitive and verbose data + - index.js - a very simple logging class + - manager.js - process incoming requests, most of application logic resides here + - service.js - defines incoming endpoints, linking the API server framework to the manager methods + - template/ - HTML content +- static/ - static web assets, CSS, images, et cetera +- test/ - unit and coverage tests diff --git a/bin/addProfile.js b/bin/addProfile.js new file mode 100644 index 0000000..6a47b3c --- /dev/null +++ b/bin/addProfile.js @@ -0,0 +1,38 @@ +'use strict'; + +const DB = require('../src/db'); +const Logger = require('../src/logger'); +const Config = require('../config'); +const config = new Config(process.env.NODE_ENV); + +const logger = new Logger(config); +const db = new DB(logger, config); + + +const identifier = process.argv[2]; +const profile = process.argv[3]; + +if (!identifier) { + console.log('missing user'); + throw new Error('missing argument'); +} +if (!profile) { + console.log('missing profile'); + throw new Error('missing argument'); +} + +(async () => { + await db.initialize(); + await db.context(async (dbCtx) => { + const user = await db.authenticationGet(dbCtx, identifier); + if (!user) { + console.log('user does not exist'); + throw new Error('invalid identifier'); + } + const profileURL = new URL(profile); // Validate and normalize + const result = await db.profileIdentifierInsert(dbCtx, profileURL.href, identifier); + console.log(result); + }); + console.log('done'); + await db._closeConnection(); +})(); diff --git a/bin/addProfileScope.js b/bin/addProfileScope.js new file mode 100644 index 0000000..dfeaca0 --- /dev/null +++ b/bin/addProfileScope.js @@ -0,0 +1,32 @@ +'use strict'; + +const DB = require('../src/db'); +const Logger = require('../src/logger'); +const Config = require('../config'); +const config = new Config(process.env.NODE_ENV); + +const logger = new Logger(config); +const db = new DB(logger, config); + + +const profile = process.argv[2]; +const scope = process.argv[3]; + +if (!scope) { + console.log('missing scope'); + throw new Error('missing argument'); +} +if (!profile) { + console.log('missing profile'); + throw new Error('missing argument'); +} + +(async () => { + await db.initialize(); + await db.context(async (dbCtx) => { + const result = await db.profileScopeInsert(dbCtx, profile, scope); + console.log(result); + }); + console.log('done'); + await db._closeConnection(); +})(); diff --git a/bin/addScope.js b/bin/addScope.js new file mode 100644 index 0000000..809ee5f --- /dev/null +++ b/bin/addScope.js @@ -0,0 +1,32 @@ +'use strict'; + +const DB = require('../src/db'); +const Logger = require('../src/logger'); +const Config = require('../config'); +const config = new Config(process.env.NODE_ENV); + +const logger = new Logger(config); +const db = new DB(logger, config); + + +const scope = process.argv[2]; +const description = process.argv[3]; + +if (!scope) { + console.log('missing scope'); + throw new Error('missing argument'); +} +if (!description) { + console.log('missing description'); + throw new Error('missing argument'); +} + +(async () => { + await db.initialize(); + await db.context(async (dbCtx) => { + const result = await db.scopeUpsert(dbCtx, scope, description); + console.log(result); + }); + console.log('done'); + await db._closeConnection(); +})(); diff --git a/bin/authUserAdd.js b/bin/authUserAdd.js new file mode 100644 index 0000000..cc71552 --- /dev/null +++ b/bin/authUserAdd.js @@ -0,0 +1,70 @@ +'use strict'; + +const argon2 = require('argon2'); +const readline = require('readline'); +const stream = require('stream'); +const DB = require('../src/db'); +const Logger = require('../src/logger'); +const Config = require('../config'); +const config = new Config(process.env.NODE_ENV); + +const logger = new Logger(config); +const db = new DB(logger, config); + +const flags = { + isPAM: false, +}; +if (process.argv.includes('-P')) { + flags.isPAM = true; + process.argv.splice(process.argv.indexOf('-P'), 1); +} + +const identifier = process.argv[2]; + +if (!identifier) { + console.log('missing user to add'); + throw new Error('missing argument'); +} + +async function readPassword(prompt) { + const input = process.stdin; + const output = new stream.Writable({ + write: function (chunk, encoding, callback) { + if (!this.muted) { + process.stdout.write(chunk, encoding); + } + callback(); + }, + }); + const rl = readline.createInterface({ input, output, terminal: !!process.stdin.isTTY }); + rl.setPrompt(prompt); + rl.prompt(); + output.muted = true; + + return new Promise((resolve) => { + rl.question('', (answer) => { + output.muted = false; + rl.close(); + output.write('\n'); + resolve(answer); + }); + }); +} + +(async () => { + await db.initialize(); + let credential; + if (flags.isPAM) { + credential = '$PAM$'; + } else { + const password = await readPassword('password: '); + credential = await argon2.hash(password, { type: argon2.argon2id }); + } + console.log(`\t${identifier}:${credential}`); + await db.context(async (dbCtx) => { + const result = await db.authenticationUpsert(dbCtx, identifier, credential); + console.log(result); + }); + console.log('done'); + await db._closeConnection(); +})(); diff --git a/bin/cli-helper.js b/bin/cli-helper.js new file mode 100644 index 0000000..5f1a1a3 --- /dev/null +++ b/bin/cli-helper.js @@ -0,0 +1,38 @@ +'use strict'; + + +/** + * + * @param {String} option + * @returns {*} + */ +function getOption(option) { + let value; + while (process.argv.includes(option)) { + const optionIndex = process.argv.indexOf(option); + value = process.argv.splice(optionIndex, 2)[1]; + } + return value; +} + + +/** + * + * @param {String} flag + * @returns {Number} + */ +function getFlag(flag) { + let value = 0; + while (process.argv.includes(flag)) { + const flagIndex = process.argv.indexOf(flag); + process.argv.splice(flagIndex, 1); + value += 1; + } + return value; +} + + +module.exports = { + getFlag, + getOption, +}; \ No newline at end of file diff --git a/bin/dumpProfiles.js b/bin/dumpProfiles.js new file mode 100644 index 0000000..5b249fd --- /dev/null +++ b/bin/dumpProfiles.js @@ -0,0 +1,32 @@ +'use strict'; + +const DB = require('../src/db'); +const Logger = require('../src/logger'); +const Config = require('../config'); +const config = new Config(process.env.NODE_ENV); + +const logger = new Logger(config); +const db = new DB(logger, config); + + +const identifier = process.argv[2]; + +if (!identifier) { + console.log('missing user'); + throw new Error('missing argument'); +} + +(async () => { + await db.initialize(); + await db.context(async (dbCtx) => { + const user = await db.authenticationGet(dbCtx, identifier); + + const profiles = await db.profilesByIdentifier(dbCtx, identifier); + console.log(profiles); + if (!user) { + console.log('(user does not exist)'); + } + }); // dbCtx + console.log('done'); + await db._closeConnection(); +})(); diff --git a/bin/generate-engine-entity-relation-diagram.sh b/bin/generate-engine-entity-relation-diagram.sh new file mode 100755 index 0000000..cd54251 --- /dev/null +++ b/bin/generate-engine-entity-relation-diagram.sh @@ -0,0 +1,18 @@ +#!/bin/bash +if [[ $# -ne 2 ]] +then + echo "Usage: $(basename "$0") engine schema-version" + exit 64 # EX_USAGE +fi +engine="$1" +schema="$2" +base="$(dirname "$0")/.." +pwd="$(pwd)" +src=$(realpath --relative-to "${pwd}" "${base}/src/db/${engine}/sql/schema/${schema}/er.dot") +dst=$(realpath --relative-to "${pwd}" "${base}/documentation/media/${engine}-er.svg") +if [[ ! -e "${src}" ]] +then + echo "Missing: ${src}" 1>&2 + exit 65 # EX_DATAERR +fi +dot -Tsvg -o"${dst}" "${src}" diff --git a/bin/resourceCreate.js b/bin/resourceCreate.js new file mode 100644 index 0000000..ae7390a --- /dev/null +++ b/bin/resourceCreate.js @@ -0,0 +1,46 @@ +'use strict'; + +const cli = require('./cli-helper'); +const DB = require('../src/db'); +const Logger = require('../src/logger'); +const Config = require('../config'); +const { newSecret } = require('../src/common'); +const config = new Config(process.env.NODE_ENV, false); +const verbose = cli.getFlag('-v'); +if (!verbose) { + config.logger.ignoreBelowLevel = 'info'; +} +const logger = new Logger(config); +const db = new DB(logger, config); + + +const resourceId = cli.getOption('-i'); +let secret = cli.getOption('-s'); +const rest = process.argv.slice(2); +const description = rest.length ? rest.join(' ') : undefined; + +(async () => { + await db.initialize(); + try { + if (!resourceId) { + if (!description || !description.length) { + console.log('ERROR: description is required when creating a new resource.'); + throw new Error('Invalid parameters'); + } + if (!secret) { + secret = await newSecret(); + } + } + + await db.context(async (dbCtx) => { + const result = await db.resourceUpsert(dbCtx, resourceId, secret, description); + console.log(result); + }); + } catch (e) { + console.log(e); + } finally { + await db._closeConnection(); + } +})().then(() => { + console.log('done'); +}); diff --git a/config/default.js b/config/default.js new file mode 100644 index 0000000..55945fd --- /dev/null +++ b/config/default.js @@ -0,0 +1,99 @@ +'use strict'; + +// Provide default values for all configuration. + +const { name: packageName, version: packageVersion } = require('../package.json'); +const common = require('../src/common'); +const Enum = require('../src/enum'); +const roman = require('@squeep/roman'); + +const currentYear = (new Date()).getFullYear(); +const romanYearHTML = roman.toRoman(currentYear, true); + +const defaultOptions = { + // Uniquely identify this instance. + nodeId: common.requestId(), // Default to ephemeral ID: easiest for clustered deployments. + + encryptionSecret: '', // No default; set this to a long passphrase or randomness. + // This may also be set to an array, if secret needs to be rolled. This needs more documentation. + + // Dingus API Server Framework options. + dingus: { + // This needs to be the full externally accessible root URL, including any proxyPrefix component. + selfBaseUrl: '', + + // trustProxy: true, // If true, trust values of some headers regarding client IP address and protocol. + proxyPrefix: '', // Leading path parts to ignore when parsing routes, and include when constructing links, e.g. /indieauth + }, + + // The terminal portions of API route path endpoints. + route: { + authorization: 'auth', + consent: 'consent', + healthcheck: 'healthcheck', + introspection: 'introspect', + metadata: 'meta', + revocation: 'revoke', + ticket: 'ticket', + token: 'token', + userinfo: 'userinfo', + }, + + // Database options + db: { + connectionString: '', // e.g. sqlite://path/to/dbfile.sqlite + queryLogLevel: undefined, // Set to log queries + + // SQLite specific options + sqliteOptimizeAfterChanges: 0, // Number of changes before running pragma optimize, 0 for never + }, + + // Queue options, currently only for handing off ticket offers + queues: { + amqp: { + url: undefined, // AMQP endpoint, e.g. 'amqp://user:pass@rmq.host:5672' If not specified, ticket endpoint will be disabled + prefix: undefined, + }, + ticketPublishName: 'indieauth.ticket.proffered', // exchange to publish proffered tickets to + }, + + // Logging options + logger: { + ignoreBelowLevel: 'info', + }, + + manager: { + codeValidityTimeoutMs: 10 * 60 * 1000, + ticketLifespanSeconds: 300, + pageTitle: packageName, // title on html pages + logoUrl: 'static/logo.svg', // image to go with title + footerEntries: [ // common footers on all html pages + 'Development Repository', + `©`, + ], + allowLegacyNonPKCE: false, // Whether to process auth requests lacking code challenges + }, + + chores: { + scopeCleanupMs: 0, // how often to clean up unreferenced scopes, 0 for never + tokenCleanupMs: 0, // how often to clean up no-longer-valid scopes, 0 for never + }, + + // Outgoing request UA header. Setting these here to override helper defaults. + userAgent: { + product: packageName, + version: packageVersion, + implementation: Enum.Specification, + }, + + authenticator: { + authnEnabled: ['argon2', 'pam'], // Types of authentication to attempt. + secureAuthOnly: true, // Require secure transport for authentication. + forbiddenPAMIdentifiers: [ + 'root', + ], + }, + +}; + +module.exports = defaultOptions; diff --git a/config/development.js b/config/development.js new file mode 100644 index 0000000..2f69e39 --- /dev/null +++ b/config/development.js @@ -0,0 +1,36 @@ +'use strict'; +module.exports = [ + { + authenticator: { + authnEnabled: undefined, // remove all, then set one below + }, + }, + { + encryptionSecret: 'this is not a very good secret', + db: { + connectionString: `postgresql://${encodeURIComponent('/var/lib/postgresql/14/data')}/indieauther`, // local pg socket + queryLogLevel: 'debug', + }, + dingus: { + selfBaseUrl: 'https://ia.squeep.com/', + }, + queues: { + amqp: { + url: 'amqp://guest:guest@rmq.int:5672', + }, + }, + logger: { + ignoreBelowLevel: 'debug', + }, + authenticator: { + authnEnabled: ['argon2'], + }, + chores: { + scopeCleanupMs: 86400000, + tokenCleanupMs: 86400000, + }, + manager: { + allowLegacyNonPKCE: true, + }, + }, +]; diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..79410a6 --- /dev/null +++ b/config/index.js @@ -0,0 +1,24 @@ +'use strict'; + +const common = require('../src/common'); + +const defaultEnvironment = 'development'; +const testEnvironment = 'test'; + +function Config(environment, freeze = true) { + environment = environment || defaultEnvironment; + const defaultConfig = require('./default'); + let envConfig = require(`./${environment}`); // eslint-disable-line security/detect-non-literal-require + if (!Array.isArray(envConfig)) { + envConfig = Array(envConfig); + } + // We support arrays of config options in env to allow e.g. resetting an existing array + const combinedConfig = common.mergeDeep(defaultConfig, ...envConfig, { environment }); + if (freeze && !environment.includes(testEnvironment)) { + /* istanbul ignore next */ + common.freezeDeep(combinedConfig); + } + return combinedConfig; +} + +module.exports = Config; \ No newline at end of file diff --git a/config/test.js b/config/test.js new file mode 100644 index 0000000..b92272f --- /dev/null +++ b/config/test.js @@ -0,0 +1,16 @@ +'use strict'; +// Configuration used by test suites. +module.exports = { + encryptionSecret: 'not a great secret', + dingus: { + selfBaseUrl: 'https://example.com/indieauthie/', + }, + db: { + queryLogLevel: 'debug', + }, + queues: { + amqp: { + url: 'ampq://localhost:5432', + }, + }, +}; diff --git a/documentation/media/consent-page.png b/documentation/media/consent-page.png new file mode 100644 index 0000000..fab0b42 Binary files /dev/null and b/documentation/media/consent-page.png differ diff --git a/documentation/media/postgres-er.svg b/documentation/media/postgres-er.svg new file mode 100644 index 0000000..670582a --- /dev/null +++ b/documentation/media/postgres-er.svg @@ -0,0 +1,184 @@ + + + + + + +indieAutherERD + +IndieAuther Entity-Relations +Postgres +Schema 1.0.0 + + +token + + +TOKEN + +code_id + +profile_id + +created + +expires + +refresh_expires + +refreshed + +duration + +refresh_duration + +refresh_count + +is_revoked + +is_token + +client_id + +resource + +profile_data + + + +token_scope + + +TOKEN_SCOPE + +code_id + +scope_id + + + +token:pk_code_id->token_scope:fk_code_id + + + + + +profile + + +PROFILE + +profile_id + +identifier_id + +profile + + + +profile:pk_profile_id->token:fk_profile_id + + + + + +profile_scope + + +PROFILE_SCOPE + +profile_id + +scope_id + + + +profile:pk_profile_id->profile_scope:fk_profile_id + + + + + +scope + + +SCOPE + +scope_id + +scope + +description + +application + +is_permanent + +is_manually_added + + + +scope:pk_scope_id->token_scope:fk_scope_id + + + + + +scope:pk_scope_id->profile_scope:fk_scope_id + + + + + +authentication + + +AUTHENTICATION + +identifier_id + +created + +last_authenticated + +identifier + +credential + + + +authentication:pk_identifier_id->profile:fk_identifier_id + + + + + +resource + + +RESOURCE + +resource_id + +description + +created + +secret + + + +almanac + + +ALMANAC + +event + +date + + + diff --git a/documentation/media/sqlite-er.svg b/documentation/media/sqlite-er.svg new file mode 100644 index 0000000..8d036ed --- /dev/null +++ b/documentation/media/sqlite-er.svg @@ -0,0 +1,183 @@ + + + + + + +indieAutherERD + +IndieAuther Entity-RelationsSQLite +Schema 1.0.0 + + +token + + +TOKEN + +code_id + +profile_id + +created + +expires + +refresh_expires + +refreshed + +duration + +refresh_duration + +refresh_count + +is_revoked + +is_token + +client_id + +resource + +profile_data + + + +token_scope + + +TOKEN_SCOPE + +code_id + +scope_id + + + +token:pk_code_id->token_scope:fk_code_id + + + + + +profile + + +PROFILE + +profile_id + +identifier_id + +profile + + + +profile:pk_profile_id->token:fk_profile_id + + + + + +profile_scope + + +PROFILE_SCOPE + +profile_id + +scope_id + + + +profile:pk_profile_id->profile_scope:fk_profile_id + + + + + +scope + + +SCOPE + +scope_id + +scope + +description + +application + +is_permanent + +is_manually_added + + + +scope:pk_scope_id->token_scope:fk_scope_id + + + + + +scope:pk_scope_id->profile_scope:fk_scope_id + + + + + +authentication + + +AUTHENTICATION + +identifier_id + +created + +last_authenticated + +identifier + +credential + + + +authentication:pk_identifier_id->profile:fk_identifier_id + + + + + +resource + + +RESOURCE + +resource_id + +description + +created + +secret + + + +almanac + + +ALMANAC + +event + +epoch + + + diff --git a/documentation/media/ticket-page.png b/documentation/media/ticket-page.png new file mode 100644 index 0000000..5865180 Binary files /dev/null and b/documentation/media/ticket-page.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..79c2b74 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9787 @@ +{ + "name": "@squeep/indie-auther", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@squeep/indie-auther", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@squeep/amqp-helper": "git+https://git.squeep.com/squeep-amqp-helper#v1.0.0", + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.9", + "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.2.12", + "@squeep/chores": "git+https://git.squeep.com/squeep-chores/#v1.0.0", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.4.0", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.2.2", + "@squeep/logger-json-console": "git+https://git.squeep.com/squeep-logger-json-console#v1.0.2", + "@squeep/mystery-box": "^1.2.0", + "@squeep/resource-authentication-module": "git+https://git.squeep.com/squeep-resource-authentication-module#v1.0.0", + "@squeep/roman": "^1.0.0", + "@squeep/web-linking": "^1.0.7", + "better-sqlite3": "^8.0.1", + "pg-promise": "^10.15.4", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@squeep/test-helper": "git+https://git.squeep.com/squeep-test-helper#v1.0.0", + "eslint": "^8.30.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-security": "^1.5.0", + "eslint-plugin-sonarjs": "^0.17.0", + "html-minifier-lint": "^2.0.0", + "mocha": "^10.2.0", + "mocha-steps": "^1.3.0", + "nyc": "^15.1.0", + "pre-commit": "^1.2.2", + "sinon": "^15.0.1" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", + "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.7", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "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" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "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" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz", + "integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "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==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@squeep/amqp-helper": { + "version": "1.0.0", + "resolved": "git+https://git.squeep.com/squeep-amqp-helper#174280d3f44ba13dac0b26d42d968189a4f4fa93", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/api-dingus": { + "version": "1.2.9", + "resolved": "git+https://git.squeep.com/squeep-api-dingus/#3b15b5ff792fc5d61be8337989058c297460cd99", + "license": "ISC", + "dependencies": { + "mime-db": "^1.52.0", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/authentication-module": { + "version": "1.2.12", + "resolved": "git+https://git.squeep.com/squeep-authentication-module/#9a7d5352698481c0857ba8827e31c7cb97625133", + "license": "ISC", + "dependencies": { + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.9", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.4.0", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.1.7", + "@squeep/mystery-box": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "argon2": "^0.30.1", + "node-linux-pam": "^0.2.1" + } + }, + "node_modules/@squeep/base64url": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@squeep/base64url/-/base64url-1.0.5.tgz", + "integrity": "sha512-J1UBXYQ4tBuHGnFfg0MdmxCP3oEti7jJWK/qBsg520d8tZd10sL6TXiMDGdBiH9GD3OtlfSYf6wWIN4QfdW21A==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/chores": { + "version": "1.0.0", + "resolved": "git+https://git.squeep.com/squeep-chores/#d98a3c114eb33bd68477c0ca750e6f82b4d02964", + "license": "ISC" + }, + "node_modules/@squeep/html-template-helper": { + "version": "1.4.0", + "resolved": "git+https://git.squeep.com/squeep-html-template-helper#100046316a87631fb8814f80b35647709e6c7319", + "license": "ISC", + "dependencies": { + "@squeep/lazy-property": "^1.1.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/indieauth-helper": { + "version": "1.2.2", + "resolved": "git+https://git.squeep.com/squeep-indieauth-helper/#9d77dc15cee59356a4a4f7935db6a450b04a083c", + "license": "ISC", + "dependencies": { + "@squeep/base64url": "^1.0.5", + "@squeep/web-linking": "^1.0.7", + "axios": "^1.2.1", + "iconv": "^3.0.1", + "ip-address": "^8.1.0", + "microformats-parser": "^1.4.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/lazy-property": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@squeep/lazy-property/-/lazy-property-1.1.2.tgz", + "integrity": "sha512-wRdR4IOqWXoDMArx0HPo5MtM2Wk5wemAULbZ6PabVw1ylSQekkzKfoAUuupxsKuzjcRPjZvbpGDv+i04hBMnQw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/logger-json-console": { + "version": "1.0.2", + "resolved": "git+https://git.squeep.com/squeep-logger-json-console#dbff0fa5f018f7a302f73250e55f761c0ccf24b1", + "license": "ISC", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@squeep/mystery-box": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@squeep/mystery-box/-/mystery-box-1.2.0.tgz", + "integrity": "sha512-HYxsF+mv2yrOsgQO2am2i07SRgp5L4o0CySmlM3rIsVWJXlywVPGk9xZFpKtk49BXCGxopWfvtPhpM8U0roaRg==", + "dependencies": { + "@squeep/base64url": "^1.0.5" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/resource-authentication-module": { + "version": "1.0.0", + "resolved": "git+https://git.squeep.com/squeep-resource-authentication-module#69a2f5e7d73dd3f58e07b652c306daa8b253245d", + "license": "ISC", + "dependencies": { + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.8", + "@squeep/base64url": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@squeep/roman": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@squeep/roman/-/roman-1.0.0.tgz", + "integrity": "sha512-D02jDw/we8tc6QiCPor7tWVviY8MLITyp/egqp3XqrrKtsFMYdguAhaFKUmIBu1ZL1uPKgoLBOy8hIptmh8cWA==" + }, + "node_modules/@squeep/test-helper": { + "version": "1.0.0", + "resolved": "git+https://git.squeep.com/squeep-test-helper#7a5a384abb99757b53c8898c508023f0ba9e94b1", + "dev": true, + "license": "ISC", + "dependencies": { + "eslint": "^8.23.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-security": "^1.5.0", + "eslint-plugin-sonarjs": "^0.15.0", + "mocha": "^10.0.0", + "nyc": "^15.1.0", + "pre-commit": "^1.2.2", + "sinon": "^14.0.0" + } + }, + "node_modules/@squeep/test-helper/node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@squeep/test-helper/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@squeep/test-helper/node_modules/eslint-plugin-sonarjs": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.15.0.tgz", + "integrity": "sha512-LuxHdAe6VqSbi1phsUvNjbmXLuvlobmryQJJNyQYbdubCfz6K8tmgoqNiJPnz0pP2AbYDbtuPm0ajOMgMrC+dQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@squeep/test-helper/node_modules/sinon": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", + "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.0.0", + "nise": "^5.1.2", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/@squeep/web-linking": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@squeep/web-linking/-/web-linking-1.0.7.tgz", + "integrity": "sha512-9d3QijrWc/WNE7p/K7NLUHbmPf92CURRqfzDLV0cGqYNA4QWAPfzwC8hxWpdUkUnep3KakvLKK60l0kEBMM3ag==", + "engines": { + "node": ">=14.0" + } + }, + "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==", + "optional": true + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "optional": true + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "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==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/are-we-there-yet/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/are-we-there-yet/node_modules/string_decoder": { + "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==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/argon2": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.30.2.tgz", + "integrity": "sha512-RBbXTUsrJUQH259/72CCJxQa0hV961pV4PyZ7R1czGkArSsQP4DToCS2axmNfHywXaBNEMPWMW6rM82EArulYA==", + "hasInstallScript": true, + "optional": 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", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assert-options": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.0.tgz", + "integrity": "sha512-qSELrEaEz4sGwTs4Qh+swQkjiHAysC4rot21+jzXU86dJzNG+FDqBzyS3ohSoTRf4ZLA3FSwxQdiuNl5NXUtvA==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "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==", + "devOptional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-sqlite3": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.0.1.tgz", + "integrity": "sha512-JhTZjpyapA1icCEjIZB4TSSgkGdFgpWZA2Wszg7Cf4JwJwKQmbvuNnJBeR+EYG/Z29OXvR4G//Rbg31BW/Z7Yg==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bl/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl/node_modules/string_decoder": { + "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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001441", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz", + "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "node_modules/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==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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==", + "optional": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "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==", + "devOptional": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "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" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-security": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.5.0.tgz", + "integrity": "sha512-hAFVwLZ/UeXrlyVD2TDarv/x00CoFVpaY0IUZhKjPjiFxqkuQVixsK4f2rxngeQOqSxi6OUjzJM/jMwKEVjJ8g==", + "dev": true, + "dependencies": { + "safe-regex": "^2.1.1" + } + }, + "node_modules/eslint-plugin-sonarjs": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.17.0.tgz", + "integrity": "sha512-jtGtxI49UbJJeJj7CVRLI3+LLH+y+hkR3GOOwM7vBbci9DEFIRGCWvEd2BJScrzltZ6D6iubukTAfc9cyG7sdw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "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==", + "devOptional": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "devOptional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "optional": true + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "dependencies": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-minifier-lint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-minifier-lint/-/html-minifier-lint-2.0.0.tgz", + "integrity": "sha512-halWZUg/us7Y16irVM90DTdyAUP3ksFthWfFPJTG1jpBaYYyGHt9azTW9H++hZ8LWRArzQm9oIcrfM/o/CO+4A==", + "dev": true, + "dependencies": { + "html-minifier": "3.x" + }, + "bin": { + "html-minifier-lint": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/https-proxy-agent": { + "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==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/iconv/-/iconv-3.0.1.tgz", + "integrity": "sha512-lJnFLxVc0d82R7GfU7a9RujKVUQ3Eee19tPKWZWBJtAEGRHVEyFzCtbNl3GPKuDnHBBRT4/nDS4Ru9AIDT72qA==", + "hasInstallScript": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "devOptional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "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" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "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==", + "devOptional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "devOptional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/microformats-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/microformats-parser/-/microformats-parser-1.4.1.tgz", + "integrity": "sha512-BSg9Y/Aik8hvvme/fkxnXMRvTKuVwOeTapeZdaPQ+92DEubyM31iMtwbgFZ1383om643UvfYY5G23E9s1FY2KQ==", + "dependencies": { + "parse5": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha-steps": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-steps/-/mocha-steps-1.3.0.tgz", + "integrity": "sha512-KZvpMJTqzLZw3mOb+EEuYi4YZS41C9iTnb7skVFRxHjUd1OYbl64tCMSmpdIRM9LnwIrSOaRfPtNpF5msgv6Eg==", + "dev": true + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-abi": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", + "integrity": "sha512-qWO5l3SCqbwQavymOmtTVuCWZE23++S+rxyoHjXqUmPyzRcaoI4lA2gO55/drddGnedAyjA7sk76SfQ5lfUMnw==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-linux-pam": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-linux-pam/-/node-linux-pam-0.2.1.tgz", + "integrity": "sha512-OeMZW0Bs1bffsvXI/bJQbU0rkiWTOo0ceT6+mrbU84TJ33vAKykIZrLI+ApfRqkBQW5jzW5rJ7x+NSyToafqig==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "1.0.5", + "bindings": "1.5.0", + "node-addon-api": "3.1.0", + "string-template": "1.0.0", + "yargs": "15.4.1" + }, + "bin": { + "nlp": "cli.js" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/node-linux-pam/node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/node-linux-pam/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-linux-pam/node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "optional": true + }, + "node_modules/node-linux-pam/node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/node-linux-pam/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/node-linux-pam/node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/node-linux-pam/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "optional": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/node-linux-pam/node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-linux-pam/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-linux-pam/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "optional": true + }, + "node_modules/node-linux-pam/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "optional": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/node-addon-api": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz", + "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==", + "optional": true + }, + "node_modules/node-linux-pam/node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/node-linux-pam/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "optional": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/node-linux-pam/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "optional": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/node-linux-pam/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/node-linux-pam/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-linux-pam/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "optional": true + }, + "node_modules/node-linux-pam/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "optional": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-linux-pam/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "optional": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "dev": true + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-minify": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.2.tgz", + "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-promise": { + "version": "10.15.4", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.15.4.tgz", + "integrity": "sha512-BKlHCMCdNUmF6gagVbehRWSEiVcZzPVltEx14OJExR9Iz9/1R6KETDWLLGv2l6yRqYFnEZZy1VDjRhArzeIGrw==", + "dependencies": { + "assert-options": "0.8.0", + "pg": "8.8.0", + "pg-minify": "1.6.2", + "spex": "3.2.0" + }, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + } + }, + "node_modules/pre-commit/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/pre-commit/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/pre-commit/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit/node_modules/which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/pre-commit/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "devOptional": true + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", + "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "devOptional": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "devOptional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "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==", + "devOptional": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sinon": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.1.tgz", + "integrity": "sha512-PZXKc08f/wcA/BMRGBze2Wmw50CWPiAH3E21EOi4B49vJ616vW4DQh4fQrqsYox2aNR/N3kCqLuB0PwwOucQrg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "10.0.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.0.0", + "nise": "^5.1.2", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz", + "integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg==", + "engines": { + "node": ">=4.5" + } + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/string-template": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-1.0.0.tgz", + "integrity": "sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg==", + "optional": true + }, + "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==", + "devOptional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/tar-stream/node_modules/string_decoder": { + "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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "dependencies": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-js/node_modules/commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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==", + "optional": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "devOptional": true + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "requires": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + } + }, + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "dev": true + }, + "@babel/core": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", + "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.7", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "dev": true + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz", + "integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@eslint/eslintrc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@mapbox/node-pre-gyp": { + "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==", + "optional": true, + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.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==", + "optional": true + }, + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0" + } + }, + "@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "@squeep/amqp-helper": { + "version": "git+https://git.squeep.com/squeep-amqp-helper#174280d3f44ba13dac0b26d42d968189a4f4fa93", + "from": "@squeep/amqp-helper@git+https://git.squeep.com/squeep-amqp-helper#v1.0.0", + "requires": { + "amqplib": "^0.10.3" + } + }, + "@squeep/api-dingus": { + "version": "git+https://git.squeep.com/squeep-api-dingus/#3b15b5ff792fc5d61be8337989058c297460cd99", + "from": "@squeep/api-dingus@git+https://git.squeep.com/squeep-api-dingus/#v1.2.9", + "requires": { + "mime-db": "^1.52.0", + "uuid": "^9.0.0" + } + }, + "@squeep/authentication-module": { + "version": "git+https://git.squeep.com/squeep-authentication-module/#9a7d5352698481c0857ba8827e31c7cb97625133", + "from": "@squeep/authentication-module@git+https://git.squeep.com/squeep-authentication-module/#v1.2.12", + "requires": { + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.9", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.4.0", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.1.7", + "@squeep/mystery-box": "^1.2.0", + "argon2": "^0.30.1", + "node-linux-pam": "^0.2.1" + } + }, + "@squeep/base64url": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@squeep/base64url/-/base64url-1.0.5.tgz", + "integrity": "sha512-J1UBXYQ4tBuHGnFfg0MdmxCP3oEti7jJWK/qBsg520d8tZd10sL6TXiMDGdBiH9GD3OtlfSYf6wWIN4QfdW21A==" + }, + "@squeep/chores": { + "version": "git+https://git.squeep.com/squeep-chores/#d98a3c114eb33bd68477c0ca750e6f82b4d02964", + "from": "@squeep/chores@git+https://git.squeep.com/squeep-chores/#v1.0.0" + }, + "@squeep/html-template-helper": { + "version": "git+https://git.squeep.com/squeep-html-template-helper#100046316a87631fb8814f80b35647709e6c7319", + "from": "@squeep/html-template-helper@git+https://git.squeep.com/squeep-html-template-helper#v1.4.0", + "requires": { + "@squeep/lazy-property": "^1.1.2" + } + }, + "@squeep/indieauth-helper": { + "version": "git+https://git.squeep.com/squeep-indieauth-helper/#9d77dc15cee59356a4a4f7935db6a450b04a083c", + "from": "@squeep/indieauth-helper@git+https://git.squeep.com/squeep-indieauth-helper/#v1.2.2", + "requires": { + "@squeep/base64url": "^1.0.5", + "@squeep/web-linking": "^1.0.7", + "axios": "^1.2.1", + "iconv": "^3.0.1", + "ip-address": "^8.1.0", + "microformats-parser": "^1.4.1" + } + }, + "@squeep/lazy-property": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@squeep/lazy-property/-/lazy-property-1.1.2.tgz", + "integrity": "sha512-wRdR4IOqWXoDMArx0HPo5MtM2Wk5wemAULbZ6PabVw1ylSQekkzKfoAUuupxsKuzjcRPjZvbpGDv+i04hBMnQw==" + }, + "@squeep/logger-json-console": { + "version": "git+https://git.squeep.com/squeep-logger-json-console#dbff0fa5f018f7a302f73250e55f761c0ccf24b1", + "from": "@squeep/logger-json-console@git+https://git.squeep.com/squeep-logger-json-console#v1.0.2" + }, + "@squeep/mystery-box": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@squeep/mystery-box/-/mystery-box-1.2.0.tgz", + "integrity": "sha512-HYxsF+mv2yrOsgQO2am2i07SRgp5L4o0CySmlM3rIsVWJXlywVPGk9xZFpKtk49BXCGxopWfvtPhpM8U0roaRg==", + "requires": { + "@squeep/base64url": "^1.0.5" + } + }, + "@squeep/resource-authentication-module": { + "version": "git+https://git.squeep.com/squeep-resource-authentication-module#69a2f5e7d73dd3f58e07b652c306daa8b253245d", + "from": "@squeep/resource-authentication-module@git+https://git.squeep.com/squeep-resource-authentication-module#v1.0.0", + "requires": { + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.8", + "@squeep/base64url": "^1.0.5", + "uuid": "^9.0.0" + } + }, + "@squeep/roman": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@squeep/roman/-/roman-1.0.0.tgz", + "integrity": "sha512-D02jDw/we8tc6QiCPor7tWVviY8MLITyp/egqp3XqrrKtsFMYdguAhaFKUmIBu1ZL1uPKgoLBOy8hIptmh8cWA==" + }, + "@squeep/test-helper": { + "version": "git+https://git.squeep.com/squeep-test-helper#7a5a384abb99757b53c8898c508023f0ba9e94b1", + "dev": true, + "from": "@squeep/test-helper@git+https://git.squeep.com/squeep-test-helper#v1.0.0", + "requires": { + "eslint": "^8.23.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-security": "^1.5.0", + "eslint-plugin-sonarjs": "^0.15.0", + "mocha": "^10.0.0", + "nyc": "^15.1.0", + "pre-commit": "^1.2.2", + "sinon": "^14.0.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "eslint-plugin-sonarjs": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.15.0.tgz", + "integrity": "sha512-LuxHdAe6VqSbi1phsUvNjbmXLuvlobmryQJJNyQYbdubCfz6K8tmgoqNiJPnz0pP2AbYDbtuPm0ajOMgMrC+dQ==", + "dev": true, + "requires": {} + }, + "sinon": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", + "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.0.0", + "nise": "^5.1.2", + "supports-color": "^7.2.0" + } + } + } + }, + "@squeep/web-linking": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@squeep/web-linking/-/web-linking-1.0.7.tgz", + "integrity": "sha512-9d3QijrWc/WNE7p/K7NLUHbmPf92CURRqfzDLV0cGqYNA4QWAPfzwC8hxWpdUkUnep3KakvLKK60l0kEBMM3ag==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "requires": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "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==", + "optional": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "are-we-there-yet": { + "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==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "optional": true + }, + "string_decoder": { + "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==", + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "argon2": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.30.2.tgz", + "integrity": "sha512-RBbXTUsrJUQH259/72CCJxQa0hV961pV4PyZ7R1czGkArSsQP4DToCS2axmNfHywXaBNEMPWMW6rM82EArulYA==", + "optional": true, + "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", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assert-options": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.0.tgz", + "integrity": "sha512-qSELrEaEz4sGwTs4Qh+swQkjiHAysC4rot21+jzXU86dJzNG+FDqBzyS3ohSoTRf4ZLA3FSwxQdiuNl5NXUtvA==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "better-sqlite3": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.0.1.tgz", + "integrity": "sha512-JhTZjpyapA1icCEjIZB4TSSgkGdFgpWZA2Wszg7Cf4JwJwKQmbvuNnJBeR+EYG/Z29OXvR4G//Rbg31BW/Z7Yg==", + "requires": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "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==", + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "devOptional": true + }, + "caniuse-lite": { + "version": "1.0.30001441", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz", + "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true + }, + "clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "optional": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "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==", + "optional": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "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==", + "optional": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "devOptional": true + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "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==", + "devOptional": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.4.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "requires": {} + }, + "eslint-plugin-security": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.5.0.tgz", + "integrity": "sha512-hAFVwLZ/UeXrlyVD2TDarv/x00CoFVpaY0IUZhKjPjiFxqkuQVixsK4f2rxngeQOqSxi6OUjzJM/jMwKEVjJ8g==", + "dev": true, + "requires": { + "safe-regex": "^2.1.1" + } + }, + "eslint-plugin-sonarjs": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.17.0.tgz", + "integrity": "sha512-jtGtxI49UbJJeJj7CVRLI3+LLH+y+hkR3GOOwM7vBbci9DEFIRGCWvEd2BJScrzltZ6D6iubukTAfc9cyG7sdw==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "devOptional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "html-minifier-lint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-minifier-lint/-/html-minifier-lint-2.0.0.tgz", + "integrity": "sha512-halWZUg/us7Y16irVM90DTdyAUP3ksFthWfFPJTG1jpBaYYyGHt9azTW9H++hZ8LWRArzQm9oIcrfM/o/CO+4A==", + "dev": true, + "requires": { + "html-minifier": "3.x" + } + }, + "https-proxy-agent": { + "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==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/iconv/-/iconv-3.0.1.tgz", + "integrity": "sha512-lJnFLxVc0d82R7GfU7a9RujKVUQ3Eee19tPKWZWBJtAEGRHVEyFzCtbNl3GPKuDnHBBRT4/nDS4Ru9AIDT72qA==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "devOptional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "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==", + "devOptional": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "dev": true + }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "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==", + "devOptional": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "devOptional": true + } + } + }, + "microformats-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/microformats-parser/-/microformats-parser-1.4.1.tgz", + "integrity": "sha512-BSg9Y/Aik8hvvme/fkxnXMRvTKuVwOeTapeZdaPQ+92DEubyM31iMtwbgFZ1383om643UvfYY5G23E9s1FY2KQ==", + "requires": { + "parse5": "^6.0.0" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + }, + "minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "mocha-steps": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-steps/-/mocha-steps-1.3.0.tgz", + "integrity": "sha512-KZvpMJTqzLZw3mOb+EEuYi4YZS41C9iTnb7skVFRxHjUd1OYbl64tCMSmpdIRM9LnwIrSOaRfPtNpF5msgv6Eg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-abi": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", + "integrity": "sha512-qWO5l3SCqbwQavymOmtTVuCWZE23++S+rxyoHjXqUmPyzRcaoI4lA2gO55/drddGnedAyjA7sk76SfQ5lfUMnw==", + "requires": { + "semver": "^7.3.5" + } + }, + "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==", + "optional": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-linux-pam": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-linux-pam/-/node-linux-pam-0.2.1.tgz", + "integrity": "sha512-OeMZW0Bs1bffsvXI/bJQbU0rkiWTOo0ceT6+mrbU84TJ33vAKykIZrLI+ApfRqkBQW5jzW5rJ7x+NSyToafqig==", + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "1.0.5", + "bindings": "1.5.0", + "node-addon-api": "3.1.0", + "string-template": "1.0.0", + "yargs": "15.4.1" + }, + "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "optional": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "optional": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "optional": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "optional": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "optional": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "node-addon-api": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz", + "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==", + "optional": true + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "optional": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "optional": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "optional": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "optional": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "optional": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "optional": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, + "node-releases": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "dev": true + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "optional": true + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "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==", + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "devOptional": true + }, + "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==", + "devOptional": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-minify": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.2.tgz", + "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==" + }, + "pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "requires": {} + }, + "pg-promise": { + "version": "10.15.4", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.15.4.tgz", + "integrity": "sha512-BKlHCMCdNUmF6gagVbehRWSEiVcZzPVltEx14OJExR9Iz9/1R6KETDWLLGv2l6yRqYFnEZZy1VDjRhArzeIGrw==", + "requires": { + "assert-options": "0.8.0", + "pg": "8.8.0", + "pg-minify": "1.6.2", + "spex": "3.2.0" + } + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, + "prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "devOptional": true + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + } + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexp-tree": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", + "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "devOptional": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "devOptional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "requires": { + "regexp-tree": "~0.1.1" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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==", + "devOptional": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "sinon": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.1.tgz", + "integrity": "sha512-PZXKc08f/wcA/BMRGBze2Wmw50CWPiAH3E21EOi4B49vJ616vW4DQh4fQrqsYox2aNR/N3kCqLuB0PwwOucQrg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "10.0.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.0.0", + "nise": "^5.1.2", + "supports-color": "^7.2.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } + }, + "spex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz", + "integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg==" + }, + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "string-template": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-1.0.0.tgz", + "integrity": "sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg==", + "optional": true + }, + "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==", + "devOptional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "optional": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "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==", + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + } + } + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, + "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==", + "optional": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "devOptional": true + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..24e8e44 --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "@squeep/indie-auther", + "version": "1.0.0", + "description": "A stand-alone IndieAuth identity-provider service, for most of your IndieAuth endpoint needs.", + "keywords": [ + "IdP", + "Identity Provider", + "IndieAuth", + "IndieWeb", + "OAuth2", + "Authentication" + ], + "main": "server.js", + "scripts": { + "coverage": "nyc npm test", + "coverage-check": "nyc check-coverage", + "eslint": "eslint *.js src", + "test": "mocha --recursive" + }, + "pre-commit": [ + "eslint", + "coverage", + "coverage-check" + ], + "engines": { + "node": ">=14.0" + }, + "repository": { + "type": "git", + "url": "https://git.squeep.com/squeep-indie-auther/" + }, + "author": "Justin Wind ", + "license": "ISC", + "dependencies": { + "@squeep/amqp-helper": "git+https://git.squeep.com/squeep-amqp-helper#v1.0.0", + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.9", + "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.2.12", + "@squeep/chores": "git+https://git.squeep.com/squeep-chores/#v1.0.0", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.4.0", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.2.2", + "@squeep/logger-json-console": "git+https://git.squeep.com/squeep-logger-json-console#v1.0.2", + "@squeep/mystery-box": "^1.2.0", + "@squeep/resource-authentication-module": "git+https://git.squeep.com/squeep-resource-authentication-module#v1.0.0", + "@squeep/roman": "^1.0.0", + "@squeep/web-linking": "^1.0.7", + "better-sqlite3": "^8.0.1", + "pg-promise": "^10.15.4", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@squeep/test-helper": "git+https://git.squeep.com/squeep-test-helper#v1.0.0", + "eslint": "^8.30.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-security": "^1.5.0", + "eslint-plugin-sonarjs": "^0.17.0", + "html-minifier-lint": "^2.0.0", + "mocha": "^10.2.0", + "mocha-steps": "^1.3.0", + "nyc": "^15.1.0", + "pre-commit": "^1.2.2", + "sinon": "^15.0.1" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..1e9c089 --- /dev/null +++ b/server.js @@ -0,0 +1,39 @@ +'use strict'; + +const http = require('http'); + +const Config = require('./config'); +const DB = require('./src/db'); +const Service = require('./src/service'); +const Logger = require('./src/logger'); +const { fileScope } = require('./src/common'); +const _fileScope = fileScope(__filename); +const { version } = require('./package.json'); + +const PORT = process.env.PORT || 3002; +const ADDR = process.env.LISTEN_ADDR || '127.0.0.1'; + +(async function main () { + const _scope = _fileScope('main'); + let config, logger, db, service; + try { + config = new Config(process.env.NODE_ENV); + logger = new Logger(config); + db = new DB(logger, config); + await db.initialize(); + service = new Service(logger, db, config); + await service.initialize(); + + http.createServer((req, res) => { + service.dispatch(req, res); + }).listen(PORT, ADDR, (err) => { + if (err) { + logger.error(_scope, 'error starting server', err); + throw err; + } + logger.info(_scope, 'server started', { version, listenAddress: ADDR, listenPort: PORT }); + }); + } catch (e) { + (logger || console).error(_scope, 'error starting server', e); + } +})(); \ No newline at end of file diff --git a/src/chores.js b/src/chores.js new file mode 100644 index 0000000..0f3d379 --- /dev/null +++ b/src/chores.js @@ -0,0 +1,70 @@ +'use strict'; + +const common = require('./common'); +const Enum = require('./enum'); +const { Chores: BaseChores } = require('@squeep/chores'); +const _fileScope = common.fileScope(__filename); + +/** + * Wrangle periodic tasks what need doing. + */ + +class Chores extends BaseChores { + constructor(logger, db, options) { + super(logger); + this.options = options; + this.db = db; + + this.establishChore(Enum.Chore.CleanTokens, this.cleanTokens.bind(this), options?.chores?.tokenCleanupMs); + this.establishChore(Enum.Chore.CleanScopes, this.cleanScopes.bind(this), options?.chores?.scopeCleanupMs); + } + + /** + * Attempt to remove tokens which are expired or otherwise no longer valid. + * @param {Number} atLeastMsSinceLast + */ + async cleanTokens(atLeastMsSinceLast = this.options?.chores?.tokenCleanupMs || 0) { + const _scope = _fileScope('cleanTokens'); + this.logger.debug(_scope, 'called', atLeastMsSinceLast); + + let tokensCleaned; + try { + await this.db.context(async (dbCtx) => { + const codeValidityTimeoutSeconds = Math.ceil(this.options.manager.codeValidityTimeoutMs / 1000); + tokensCleaned = await this.db.tokenCleanup(dbCtx, codeValidityTimeoutSeconds, atLeastMsSinceLast); + }); // dbCtx + if (tokensCleaned) { + this.logger.info(_scope, 'finished', { tokensCleaned }); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; + } + } + + + /** + * Attempt to remove ephemeral scopes which are no longer referenced by tokens. + * @param {Number} atLeastMsSinceLast + */ + async cleanScopes(atLeastMsSinceLast = this.options?.chores?.scopeCleanupMs || 0) { + const _scope = _fileScope('cleanScopes'); + this.logger.debug(_scope, 'called', atLeastMsSinceLast); + + let scopesCleaned; + try { + await this.db.context(async (dbCtx) => { + scopesCleaned = await this.db.scopeCleanup(dbCtx, atLeastMsSinceLast); + }); // dbCtx + if (scopesCleaned) { + this.logger.info(_scope, 'finished', { scopesCleaned }); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; + } + } + +} // IAChores + +module.exports = Chores; \ No newline at end of file diff --git a/src/common.js b/src/common.js new file mode 100644 index 0000000..d58f535 --- /dev/null +++ b/src/common.js @@ -0,0 +1,167 @@ +'use strict'; + +const { common } = require('@squeep/api-dingus'); + +const { randomBytes } = require('crypto'); +const { promisify } = require('util'); +const randomBytesAsync = promisify(randomBytes); + +/** + * Pick out useful axios response fields. + * @param {*} res + * @returns + */ +const axiosResponseLogData = (res) => { + const data = common.pick(res, [ + 'status', + 'statusText', + 'headers', + 'elapsedTimeMs', + 'data', + ]); + if (data.data) { + data.data = logTruncate(data.data, 100); + } + return data; +}; + +/** + * Limit length of string to keep logs sane + * @param {String} str + * @param {Number} len + * @returns {String} + */ +const logTruncate = (str, len) => { + if (typeof str !== 'string' || str.toString().length <= len) { + return str; + } + return str.toString().slice(0, len) + `... (${str.toString().length} bytes)`; +}; + +/** + * Turn a snake into a camel. + * @param {String} snakeCase + * @param {String|RegExp} delimiter + * @returns {String} + */ +const camelfy = (snakeCase, delimiter = '_') => { + if (!snakeCase || typeof snakeCase.split !== 'function') { + return undefined; + } + const words = snakeCase.split(delimiter); + return [ + words.shift(), + ...words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)), + ].join(''); +}; + +/** + * Return an array containing x if x is not an array. + * @param {*} x + */ +const ensureArray = (x) => { + if (x === undefined) { + return []; + } + if (!Array.isArray(x)) { + return Array(x); + } + return x; +}; + +/** + * Recursively freeze an object. + * @param {Object} o + * @returns {Object} + */ +const freezeDeep = (o) => { + Object.freeze(o); + Object.getOwnPropertyNames(o).forEach((prop) => { + if (Object.hasOwnProperty.call(o, prop) + && ['object', 'function'].includes(typeof o[prop]) // eslint-disable-line security/detect-object-injection + && !Object.isFrozen(o[prop])) { // eslint-disable-line security/detect-object-injection + return freezeDeep(o[prop]); // eslint-disable-line security/detect-object-injection + } + }); + return o; +}; + + +/** Oauth2.1 §3.2.3.1 + * %x20-21 / %x23-5B / %x5D-7E + * @param {String} char + */ +const validErrorChar = (char) => { + const value = char.charCodeAt(0); + return value === 0x20 || value === 0x21 + || (value >= 0x23 && value <= 0x5b) + || (value >= 0x5d && value <= 0x7e); +}; + + +/** + * Determine if an OAuth error message is valid. + * @param {String} error + * @returns {Boolean} + */ +const validError = (error) => { + return error && error.split('').filter((c) => !validErrorChar(c)).length === 0 || false; +}; + + +/** + * OAuth2.1 §3.2.2.1 + * scope-token = 1*( %x21 / %x23-5B / %x5D-7E ) + * @param {String} char + */ +const validScopeChar = (char) => { + const value = char.charCodeAt(0); + return value === 0x21 + || (value >= 0x23 && value <= 0x5b) + || (value >= 0x5d && value <= 0x7e); +}; + + +/** + * Determine if a scope has a valid name. + * @param {String} scope + * @returns {Boolean} + */ +const validScope = (scope) => { + return scope && scope.split('').filter((c) => !validScopeChar(c)).length === 0 || false; +}; + + +/** + * + * @param {Number} bytes + */ +const newSecret = async (bytes = 64) => { + return (await randomBytesAsync(bytes * 3 / 4)).toString('base64'); +}; + + +/** + * Convert a Date object to epoch seconds. + * @param {Date=} date + * @returns {Number} + */ +const dateToEpoch = (date) => { + const dateMs = date ? date.getTime() : Date.now(); + return Math.ceil(dateMs / 1000); +}; + +module.exports = { + ...common, + axiosResponseLogData, + camelfy, + dateToEpoch, + ensureArray, + freezeDeep, + logTruncate, + newSecret, + randomBytesAsync, + validScope, + validError, +}; + diff --git a/src/db/abstract.js b/src/db/abstract.js new file mode 100644 index 0000000..26867e4 --- /dev/null +++ b/src/db/abstract.js @@ -0,0 +1,563 @@ +/* eslint-disable no-unused-vars */ +'use strict'; + +const common = require('../common'); +const DatabaseErrors = require('./errors'); +const svh = require('./schema-version-helper'); +const uuid = require('uuid'); + +const _fileScope = common.fileScope(__filename); + +class Database { + constructor(logger, options) { + this.logger = logger; + this.options = options; + } + + + /** + * Perform tasks needed to prepare database for use. Ensure this is called + * after construction, and before any other database activity. + * At the minimum, this will validate a compatible schema is present and usable. + * Some engines will also perform other initializations or async actions which + * are easier handled outside the constructor. + */ + async initialize() { + const _scope = _fileScope('initialize'); + + const currentSchema = await this._currentSchema(); + const current = svh.schemaVersionObjectToNumber(currentSchema); + const min = svh.schemaVersionObjectToNumber(this.schemaVersionsSupported.min); + const max = svh.schemaVersionObjectToNumber(this.schemaVersionsSupported.max); + if (current >= min && current <= max) { + this.logger.debug(_scope, 'schema supported', { currentSchema, schemaVersionsSupported: this.schemaVersionsSupported }); + } else { + this.logger.error(_scope, 'schema not supported', { currentSchema, schemaVersionsSupported: this.schemaVersionsSupported }); + throw new DatabaseErrors.MigrationNeeded(); + } + } + + + /** + * Query the current schema version. + * This is a standalone query function, as it is called before statements are loaded. + * @returns {Object} version + * @returns {Number} version.major + * @returns {Number} version.minor + * @returns {Number} version.patch + */ + async _currentSchema() { + this._notImplemented('_currentSchema', arguments); + } + + + /** + * Perform db connection health-check, if applicable. + * Throw something if a database situation should pull us out of a load-balancer. + */ + async healthCheck() { + this._notImplemented('healthCheck', arguments); + } + + + /** + * Wrap a function call in a database context. + * @param {Function} fn fn(ctx) + */ + async context(fn) { + this._notImplemented('context', arguments); + } + + /** + * Wrap a function call in a transaction context. + * @param {*} dbCtx + * @param {Function} fn fn(txCtx) + */ + async transaction(dbCtx, fn) { + this._notImplemented('transaction', arguments); + } + + + /** + * @param {*} x + * @returns {Boolean} + */ + static _isUUID(x) { + try { + uuid.parse(x); + return true; + } catch (e) { + return false; + } + } + + + /** + * @param {*} x + * @returns {Boolean} + */ + static _isInfinites(x) { + return typeof(x) === 'number' + && Math.abs(x) === Infinity; + } + + /** + * Basic type checking of object properties. + * @param {Object} object + * @param {String[]} properties + * @param {String[]} types + */ + _ensureTypes(object, properties, types) { + const _scope = _fileScope('_ensureTypes'); + + if (!(object && properties && types)) { + this.logger.error(_scope, 'undefined argument', { object, properties, types }); + throw new DatabaseErrors.DataValidation(); + } + properties.forEach((p) => { + // eslint-disable-next-line security/detect-object-injection + const pObj = object[p]; + const pType = typeof pObj; + if (!types.includes(pType) + && !(types.includes('array') && Array.isArray(pObj)) + && !(types.includes('buffer') && pObj instanceof Buffer) + && !(types.includes('date') && pObj instanceof Date) + && !(types.includes('infinites')) + && !(types.includes('null') && pObj === null) + && !(types.includes('number') && pType === 'bigint') + && !(types.includes('uuid') && Database._isUUID(pObj))) { + const reason = `'${p}' is '${pType}', but must be ${types.length > 1 ? 'one of ' : ''}'${types}'`; + this.logger.error(_scope, reason, {}); + throw new DatabaseErrors.DataValidation(reason); + } + }); + } + + + /** + * @typedef {Object} Authentication + * @property {String} identifier + * @property {String=} credential + * @property {Date} created + * @property {Date=} lastAuthenticated + */ + /** + * @param {Authentication} authentication + */ + _validateAuthentication(authentication) { + [ + [['identifier'], ['string']], + [['credential'], ['string', 'null']], + [['created'], ['date']], + [['lastAuthenticated'], ['date', 'infinites']], + ].forEach(([properties, types]) => this._ensureTypes(authentication, properties, types)); + } + + + /** + * @typedef {Object} Resource + * @property {String} resourceId - uuid + * @property {String} secret + * @property {String} description + * @property {Date} created + */ + /** + * @param {Resource} resource + */ + _validateResource(resource) { + [ + [['resourceId', 'secret', 'description'], ['string']], + [['resourceId'], ['uuid']], + [['created'], ['date']], + ].forEach(([properties, types]) => this._ensureTypes(resource, properties, types)); + } + + + /** + * @typedef {Object} Token + * @property {String} codeId - uuid + * @property {String} profile + * @property {Date} created + * @property {Date=} expires + * @property {Date=} refreshExpires + * @property {Date=} refreshed + * @property {*=} duration + * @property {*=} refreshDuration + * @property {Number|BigInt=} refresh_count + * @property {Boolean} is_revoked + * @property {Boolean} is_token + * @property {String} client_id + * @property {String[]} scopes + * @property {Object=} profileData + */ + /** + * @param {Token} token + */ + _validateToken(token) { + [ + [['codeId', 'profile', 'clientId'], ['string']], + [['codeId'], ['uuid']], + [['created'], ['date']], + [['expires', 'refreshExpires', 'refreshed'], ['date', 'null']], + [['isToken', 'isRevoked'], ['boolean']], + [['scopes'], ['array']], + [['profileData'], ['object', 'null']], + ].forEach(([properties, types]) => this._ensureTypes(token, properties, types)); + this._ensureTypes(token.scopes, Object.keys(token.scopes), ['string']); + } + + + /** + * Interface methods need implementations. Ensure the db-interaction + * methods on the base class call this, so they may be overridden by + * implementation classes. + * @param {String} method + * @param {arguments} args + */ + _notImplemented(method, args) { + this.logger.error(_fileScope(method), 'abstract method called', Array.from(args)); + throw new DatabaseErrors.NotImplemented(method); + } + + + /** + * Get all the almanac entries. + * @param {*} dbCtx + */ + async almanacGetAll(dbCtx) { + this._notImplemented('almanacGetAll', arguments); + } + + + /** + * Fetch the authentication record for an identifier. + * @param {*} dbCtx + * @param {String} identifier + * @returns {Promise} + */ + async authenticationGet(dbCtx, identifier) { + this._notImplemented('authenticationGet', arguments); + } + + + /** + * Update the authentication record for the identifier that + * correct credentials have been supplied. + * @param {*} dbCtx + * @param {String} identifier + * @returns {Promise} + */ + async authenticationSuccess(dbCtx, identifier) { + this._notImplemented('authenticationSuccess', arguments); + } + + + /** + * Insert or update the credential for an identifier. + * @param {*} dbCtx + * @param {String} identifier + * @param {String} credential + * @returns {Promise} + */ + async authenticationUpsert(dbCtx, identifier, credential) { + this._notImplemented('authenticationUpsert', arguments); + } + + + /** + * Determine if profile url is known to this service. + * @param {*} dbCtx + * @param {String} profile + * @returns {Promise} + */ + async profileIsValid(dbCtx, profile) { + this._notImplemented('profileGet', arguments); + } + + + /** + * Insert a new relationship between a profile endpoint and + * an authenticated identifier. + * @param {*} dbCtx + * @param {String} profile + * @param {String} identifier + * @returns {Promise} + */ + async profileIdentifierInsert(dbCtx, profile, identifier) { + this._notImplemented('profileIdentifierInsert', arguments); + } + + + /** + * Adds a scope to be available for a profile to include on any authorization request. + * @param {*} dbCtx + * @param {String} profile + * @param {String} scope + * @returns {Promise} + */ + async profileScopeInsert(dbCtx, profile, scope) { + this._notImplemented('profileScopeInsert', arguments); + } + + + /** + * @typedef {Object} ScopeDetails + * @property {String} description + * @property {String[]=} profiles + */ + /** + * @typedef {Object.} ProfileScopes + * @property {Object.} profile + * @property {Object.} profile.scope + */ + /** + * @typedef {Object.} ScopeIndex + * @property {ScopeDetails} scope + */ + /** + * @typedef {Object} ProfilesScopesReturn + * @property {ProfileScopes} profileScopes + * @property {ScopeIndex} scopeIndex + * @property {String[]} profiles + */ + /** + * Returns an object containing: + * - an object with profiles as keys to objects with scopes as keys to scope objects, + * which each contain a description of the scope and a list of profiles offering it + * - an object with scopes as keys to the same scope objects + * - a list of profiles + * @param {*} dbCtx + * @param {String} identifier + * @returns {Promise} + */ + async profilesScopesByIdentifier(dbCtx, identifier) { + this._notImplemented('profilesScopesByIdentifier', arguments); + } + + + /** + * @typedef ProfileScopesRow + * @property profile + * @property scope + * @property description + * @property application + * @property isPermanent + * @property isManuallyAdded + */ + /** + * Convert db row data into associative structures. + * Same behavior is shared by multiple engines. + * @param {ProfileScopesRow[]} profileScopesRows + * @returns {ProfileScopesReturn} + */ + static _profilesScopesBuilder(profileScopesRows) { + const scopeIndex = {}; + const profileScopes = {}; + const profileSet = new Set(); + + (profileScopesRows || []).forEach(({ profile, scope, description, application, isPermanent, isManuallyAdded }) => { + if (scope && !(scope in scopeIndex)) { + scopeIndex[scope] = { // eslint-disable-line security/detect-object-injection + description, + application, + isPermanent, + isManuallyAdded, + profiles: [], + }; + } + if (profile) { + profileSet.add(profile); + if (!(profile in profileScopes)) { + profileScopes[profile] = {}; // eslint-disable-line security/detect-object-injection + } + } + if (profile && scope) { + scopeIndex[scope].profiles.push(profile); // eslint-disable-line security/detect-object-injection + profileScopes[profile][scope] = scopeIndex[scope]; // eslint-disable-line security/detect-object-injection + } + }); + + return { + profiles: [...profileSet], + profileScopes, + scopeIndex, + }; + } + + + /** + * Sets list of additional scopes available to profile. + * @param {*} dbCtx + * @param {String} profile + * @param {String[]} scopes + * @returns {Promise} + */ + async profileScopesSetAll(dbCtx, profile, scopes) { + this._notImplemented('profileScopesSetAll', arguments); + } + + + /** + * Create (or revoke a duplicate) code as a token entry. + * @param {*} dbCtx + * @param {Object} data + * @param {String} data.codeId + * @param {Date} data.created + * @param {Boolean} data.isToken + * @param {String} data.clientId + * @param {String} data.profile - profile uri + * @param {String} data.identifier + * @param {String[]} data.scopes + * @param {Number|Null} data.lifespanSeconds - null sets expiration to Infinity + * @param {Number|Null} data.refreshLifespanSeconds - null sets refresh to none + * @param {String|Null} data.resource + * @param {Object|Null} data.profileData - profile data from profile uri + * @returns {Promise} whether redemption was successful + */ + async redeemCode(dbCtx, { codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, profileData } = {}) { + this._notImplemented('redeemCode', arguments); + } + + + /** + * @typedef {Object} RefreshedToken + * @property {Date} expires + * @property {Date} refreshExpires + * @property {String[]=} scopes if scopes were reduced + */ + /** + * Redeem a refresh token to renew token codeId. + * @param {*} dbCtx + * @param {String} codeId + * @param {Date} refreshed + * @param {String[]} removeScopes + * @returns {Promise} + */ + async refreshCode(dbCtx, codeId, refreshed, removeScopes) { + this._notImplemented('refreshCode', arguments); + } + + + /** + * Fetch a resource server record. + * @param {*} dbCtx + * @param {String} identifier uuid + * @returns {Promise} + */ + async resourceGet(dbCtx, resourceId) { + this._notImplemented('resourceGet', arguments); + } + + + /** + * Create, or update description of, a resourceId. + * @param {*} dbCtx + * @param {String=} resourceId uuid + * @param {String=} secret + * @param {String=} description + * @returns {Promise} + */ + async resourceUpsert(dbCtx, resourceId, secret, description) { + this._notImplemented('resourceUpsert', arguments); + } + + + /** + * Register a scope and its description. + * @param {*} dbCtx + * @param {String} scope + * @param {String} application + * @param {String} description + * @returns {Promise} + */ + async scopeUpsert(dbCtx, scope, application, description, manuallyAdded = false) { + this._notImplemented('scopeUpsert', arguments); + } + + + /** + * Remove a non-permanent scope if it is not currently in use. + * @param {*} dbCtx + * @param {String} scope + * @returns {Promise} + */ + async scopeDelete(dbCtx, scope) { + this._notImplemented('scopeDelete', arguments); + } + + + /** + * @typedef {Number|BigInt} CleanupResult + */ + /** + * @typedef {Object} CleanupResult + */ + /** + * Remove any non-permanent and non-manually-created scopes not currently in use. + * @param {*} dbCtx + * @param {Number} atLeastMsSinceLast skip cleanup if already executed this recently + * @returns {Promise} + */ + async scopeCleanup(dbCtx, atLeastMsSinceLast) { + this._notImplemented('scopeClean', arguments); + } + + + /** + * Forget tokens after they have expired, and redeemed codes after they have expired. + * @param {*} dbCtx + * @param {Number} codeLifespanSeconds + * @param {Number} atLeastMsSinceLast skip cleanup if already executed this recently + * @returns {Promise} + */ + async tokenCleanup(dbCtx, codeLifespanSeconds, atLeastMsSinceLast) { + this._notImplemented('tokenCleanup', arguments); + } + + + /** + * Look up a redeemed token by code_id. + * @param {*} dbCtx + * @param {String} codeId + * @returns {Promise} + */ + async tokenGetByCodeId(dbCtx, codeId) { + this._notImplemented('tokenGetByCodeId', arguments); + } + + + /** + * Sets a redeemed token as revoked. + * @param {*} dbCtx + * @param {String} codeId - uuid + * @returns {Promise} + */ + async tokenRevokeByCodeId(dbCtx, codeId) { + this._notImplemented('tokenRevokeByCodeId', arguments); + } + + + /** + * Revoke the refreshability of a codeId. + * @param {*} dbCtx + * @param {String} codeId - uuid + * @returns {Promise} + */ + async tokenRefreshRevokeByCodeId(dbCtx, codeId) { + this._notImplemented('tokenRefreshRevokeByCodeId', arguments); + } + + + /** + * Get all tokens assigned to identifier. + * @param {*} dbCtx + * @param {String} identifier + * @returns {Promise} + */ + async tokensGetByIdentifier(dbCtx, identifier) { + this._notImplemented('tokensGetByIdentifier', arguments); + } + +} + +module.exports = Database; \ No newline at end of file diff --git a/src/db/errors.js b/src/db/errors.js new file mode 100644 index 0000000..cd43239 --- /dev/null +++ b/src/db/errors.js @@ -0,0 +1,56 @@ +'use strict'; + +class DatabaseError extends Error { + constructor(...args) { + super(...args); + Error.captureStackTrace(DatabaseError); + } + + get name() { + /* istanbul ignore next */ + return this.constructor.name; + } +} + +class DataValidation extends DatabaseError { + constructor(...args) { + super(...args); + Error.captureStackTrace(DataValidation); + } +} + +class NotImplemented extends DatabaseError { + constructor(...args) { + super(...args); + Error.captureStackTrace(NotImplemented); + } +} + +class UnexpectedResult extends DatabaseError { + constructor(...args) { + super(...args); + Error.captureStackTrace(UnexpectedResult); + } +} + +class UnsupportedEngine extends DatabaseError { + constructor(...args) { + super(...args); + Error.captureStackTrace(UnsupportedEngine); + } +} + +class MigrationNeeded extends DatabaseError { + constructor(...args) { + super(...args); + } +} + +module.exports = { + DatabaseError, + DataValidation, + MigrationNeeded, + NotImplemented, + UnexpectedResult, + UnsupportedEngine, +}; diff --git a/src/db/index.js b/src/db/index.js new file mode 100644 index 0000000..0d5ef16 --- /dev/null +++ b/src/db/index.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common'); +const DatabaseErrors = require('./errors'); + +const _fileScope = common.fileScope(__filename); + +class DatabaseFactory { + constructor(logger, options, ...rest) { + const _scope = _fileScope('constructor'); + + const connectionString = options.db.connectionString || ''; + const protocol = connectionString.slice(0, connectionString.indexOf('://')).toLowerCase(); + + let Engine; + switch (protocol) { + case DatabaseFactory.Engines.PostgreSQL: + Engine = require('./postgres'); + break; + + case DatabaseFactory.Engines.SQLite: + Engine = require('./sqlite'); + break; + + default: + logger.error(_scope, 'unsupported connectionString', { protocol, options }); + throw new DatabaseErrors.UnsupportedEngine(protocol); + } + + return new Engine(logger, options, ...rest); + } + + static get Engines() { + return { + PostgreSQL: 'postgresql', + SQLite: 'sqlite', + }; + } + +} + +module.exports = DatabaseFactory; diff --git a/src/db/postgres/index.js b/src/db/postgres/index.js new file mode 100644 index 0000000..54c6e9b --- /dev/null +++ b/src/db/postgres/index.js @@ -0,0 +1,638 @@ +/* eslint-disable security/detect-object-injection */ +'use strict'; + +const pgpInitOptions = { + capSQL: true, +}; + +const path = require('path'); +const pgp = require('pg-promise')(pgpInitOptions); +const svh = require('../schema-version-helper'); +const Database = require('../abstract'); +const DBErrors = require('../errors'); +const common = require('../../common'); + +const _fileScope = common.fileScope(__filename); + +const PGTypeIdINT8 = 20; // Type Id 20 == INT8 (BIGINT) +const PGTYpeIdINT8Array = 1016; //Type Id 1016 == INT8[] (BIGINT[]) +pgp.pg.types.setTypeParser(PGTypeIdINT8, BigInt); // Type Id 20 = INT8 (BIGINT) +const parseBigIntArray = pgp.pg.types.getTypeParser(PGTYpeIdINT8Array); // Type Id 1016 = INT8[] (BIGINT[]) +pgp.pg.types.setTypeParser(PGTYpeIdINT8Array, (a) => parseBigIntArray(a).map(BigInt)); + +const schemaVersionsSupported = { + min: { + major: 1, + minor: 0, + patch: 0, + }, + max: { + major: 1, + minor: 0, + patch: 0, + }, +}; + +class DatabasePostgres extends Database { + constructor(logger, options, _pgp = pgp) { + super(logger, options); + + this.db = _pgp(options.db.connectionString); + this.schemaVersionsSupported = schemaVersionsSupported; + + // Suppress QF warnings when running tests + this.noWarnings = options.db.noWarnings; + + // Log queries + const queryLogLevel = options.db.queryLogLevel; + if (queryLogLevel) { + const queryScope = _fileScope('pgp:query'); + pgpInitOptions.query = (event) => { + this.logger[queryLogLevel](queryScope, '', { ...common.pick(event, ['query', 'params']) }); + }; + } + + // Log errors + const errorScope = _fileScope('pgp:error'); + pgpInitOptions.error = (err, event) => { + this.logger.error(errorScope, '', { err, event }); + }; + + // Deophidiate column names in-place, log results + pgpInitOptions.receive = (data, result, event) => { + const exemplaryRow = data[0]; + for (const prop in exemplaryRow) { + const camel = common.camelfy(prop); + if (!(camel in exemplaryRow)) { + for (const d of data) { + d[camel] = d[prop]; + delete d[prop]; + } + } + } + if (queryLogLevel) { + // Omitting .rows + const resultLog = common.pick(result, ['command', 'rowCount', 'duration']); + this.logger[queryLogLevel](_fileScope('pgp:result'), '', { query: event.query, ...resultLog }); + } + }; + + // Expose these for test coverage + this.pgpInitOptions = pgpInitOptions; + this._pgp = _pgp; + + this._initStatements(_pgp); + } + + + _queryFileHelper(_pgp) { + return (file) => { + const _scope = _fileScope('_queryFile'); + /* istanbul ignore next */ + const qfParams = { + minify: true, + ...(this.noWarnings && { noWarnings: this.noWarnings }), + }; + const qf = new _pgp.QueryFile(file, qfParams); + if (qf.error) { + this.logger.error(_scope, 'failed to create SQL statement', { error: qf.error, file }); + throw qf.error; + } + return qf; + }; + } + + + async initialize(applyMigrations = true) { + const _scope = _fileScope('initialize'); + this.logger.debug(_scope, 'called', { applyMigrations }); + if (applyMigrations) { + await this._initTables(); + } + await super.initialize(); + if (this.listener) { + await this.listener.start(); + } + } + + + async _initTables(_pgp) { + const _scope = _fileScope('_initTables'); + this.logger.debug(_scope, 'called', {}); + + const _queryFile = this._queryFileHelper(_pgp || this._pgp); + + // Migrations rely upon this table, ensure it exists. + const metaVersionTable = '_meta_schema_version'; + + const tableExists = async (name) => this.db.oneOrNone('SELECT table_name FROM information_schema.tables WHERE table_name=$(name)', { name }); + let metaExists = await tableExists(metaVersionTable); + if (!metaExists) { + const fPath = path.join(__dirname, 'sql', 'schema', 'init.sql'); + const initSql = _queryFile(fPath); + const results = await this.db.multiResult(initSql); + this.logger.debug(_scope, 'executed init sql', { results }); + metaExists = await tableExists(metaVersionTable); + /* istanbul ignore if */ + if (!metaExists) { + throw new DBErrors.UnexpectedResult(`did not create ${metaVersionTable} table`); + } + this.logger.info(_scope, 'created schema version table', { metaVersionTable }); + } + + // Apply migrations + const currentSchema = await this._currentSchema(); + const migrationsWanted = svh.unappliedSchemaVersions(__dirname, currentSchema, this.schemaVersionsSupported); + this.logger.debug(_scope, 'schema migrations wanted', { migrationsWanted }); + for (const v of migrationsWanted) { + const fPath = path.join(__dirname, 'sql', 'schema', v, 'apply.sql'); + const migrationSql = _queryFile(fPath); + const results = await this.db.multiResult(migrationSql); + this.logger.debug(_scope, 'executed migration sql', { version: v, results }); + this.logger.info(_scope, 'applied migration', { version: v }); + } + } + + + _initStatements(_pgp) { + const _scope = _fileScope('_initStatements'); + const _queryFile = this._queryFileHelper(_pgp); + this.statement = _pgp.utils.enumSql(path.join(__dirname, 'sql'), {}, _queryFile); + this.logger.debug(_scope, 'statements initialized', { statements: Object.keys(this.statement).length }); + } + + + async healthCheck() { + const _scope = _fileScope('healthCheck'); + this.logger.debug(_scope, 'called', {}); + const c = await this.db.connect(); + c.done(); + return { serverVersion: c.client.serverVersion }; + } + + + async _currentSchema() { + return this.db.one('SELECT major, minor, patch FROM _meta_schema_version ORDER BY major DESC, minor DESC, patch DESC LIMIT 1'); + } + + + async _closeConnection() { + const _scope = _fileScope('_closeConnection'); + try { + if (this.listener) { + await this.listener.stop(); + } + await this._pgp.end(); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; + } + } + + + /* istanbul ignore next */ + async _purgeTables(really = false) { + const _scope = _fileScope('_purgeTables'); + try { + if (really) { + await this.db.tx(async (t) => { + await t.batch([ + 'authentication', + 'resource', + 'profile', + 'token', + ].map(async (table) => t.query('TRUNCATE TABLE $(table:name) CASCADE', { table }))); + }); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; + } + } + + + async context(fn) { + return this.db.task(async (t) => fn(t)); + } + + + // eslint-disable-next-line class-methods-use-this + async transaction(dbCtx, fn) { + return dbCtx.txIf(async (t) => fn(t)); + } + + + async almanacGetAll(dbCtx) { + const _scope = _fileScope('almanacGetAll'); + this.logger.debug(_scope, 'called'); + + try { + return await dbCtx.manyOrNone(this.statement.almanacGetAll); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; + } + } + + + async authenticationGet(dbCtx, identifier) { + const _scope = _fileScope('authenticationGet'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + return await dbCtx.oneOrNone(this.statement.authenticationGet, { identifier }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + + + async authenticationSuccess(dbCtx, identifier) { + const _scope = _fileScope('authenticationSuccess'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + const result = await dbCtx.result(this.statement.authenticationSuccess, { identifier }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not update authentication success event'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + + + async authenticationUpsert(dbCtx, identifier, credential) { + const _scope = _fileScope('authenticationUpsert'); + const scrubbedCredential = '*'.repeat((credential || '').length); + this.logger.debug(_scope, 'called', { identifier, scrubbedCredential }); + + try { + const result = await dbCtx.result(this.statement.authenticationUpsert, { identifier, credential }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not upsert authentication'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier, scrubbedCredential }); + throw e; + } + } + + + async profileIdentifierInsert(dbCtx, profile, identifier) { + const _scope = _fileScope('profileIdentifierInsert'); + this.logger.debug(_scope, 'called', { profile, identifier }); + + try { + const result = await dbCtx.result(this.statement.profileIdentifierInsert, { profile, identifier }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not insert identifier'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile, identifier }); + throw e; + } + } + + + async profileIsValid(dbCtx, profile) { + const _scope = _fileScope('profileIsValid'); + this.logger.debug(_scope, 'called', { profile }); + + try { + const profileResponse = await dbCtx.oneOrNone(this.statement.profileGet, { profile }); + return !!profileResponse; + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile }); + throw e; + } + } + + + async profileScopeInsert(dbCtx, profile, scope) { + const _scope = _fileScope('profileScopeInsert'); + this.logger.debug(_scope, 'called', { profile, scope }); + + try { + const result = await dbCtx.result(this.statement.profileScopeInsert, { profile, scope }); + // Duplicate inserts get ignored + if (result.rowCount != 1 && result.rowCount != 0) { + throw new DBErrors.UnexpectedResult('did not insert profile scope'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile, scope }); + throw e; + } + } + + + async profileScopesSetAll(dbCtx, profile, scopes) { + const _scope = _fileScope('profileScopesSetAll'); + this.logger.debug(_scope, 'called', { profile, scopes }); + + try { + await this.transaction(dbCtx, async (txCtx) => { + await txCtx.result(this.statement.profileScopesClear, { profile }); + if (scopes.length) { + await txCtx.result(this.statement.profileScopesSetAll, { profile, scopes }); + } + }); // transaction + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile, scopes }); + throw e; + } + } + + + async profilesScopesByIdentifier(dbCtx, identifier) { + const _scope = _fileScope('profilesScopesByIdentifier'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + const profileScopesRows = await dbCtx.manyOrNone(this.statement.profilesScopesByIdentifier, { identifier }); + return Database._profilesScopesBuilder(profileScopesRows); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + + + async redeemCode(dbCtx, { codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, resource, profileData }) { + const _scope = _fileScope('redeemCode'); + this.logger.debug(_scope, 'called', { codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, resource, profileData }); + + let result, ret = false; + try { + await this.transaction(dbCtx, async (txCtx) => { + result = await txCtx.result(this.statement.redeemCode, { codeId, created, isToken, clientId, profile, identifier, lifespanSeconds, refreshLifespanSeconds, resource, profileData }); + if (result.rowCount != 1) { + this.logger.error(_scope, 'failed', { result }); + throw new DBErrors.UnexpectedResult('did not redeem code'); + } + // Abort and return false if redemption resulted in revocation. + if (result.rows[0].isRevoked) { + return; + } + this.logger.debug(_scope, 'code redeemed', { redeemed: result.rows[0] }); + + // Ensure there are entries for all scopes. + if (scopes.length !== 0) { + await txCtx.result(this.statement.scopesInsert, { scopes }); + } + + // Record accepted scopes for this token. + result = await txCtx.result(this.statement.tokenScopesSet, { codeId, scopes }); + if (result.rowCount != scopes.length) { + this.logger.error(_scope, 'token scope count mismatch', { codeId, scopes, result }); + throw new DBErrors.UnexpectedResult('did not set all scopes on token'); + } + ret = true; + }); // txCtx + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, profileData }); + throw e; + } + + return ret; + } + + + async refreshCode(dbCtx, codeId, refreshed, removeScopes) { + const _scope = _fileScope('refreshCode'); + this.logger.debug(_scope, 'called', { codeId, refreshed, removeScopes }); + + try { + return await this.transaction(dbCtx, async (txCtx) => { + const refreshedToken = await txCtx.oneOrNone(this.statement.refreshCode, { codeId, refreshed }); + if (refreshedToken) { + if (removeScopes.length) { + const removeResult = await txCtx.result(this.statement.tokenScopesRemove, { codeId, removeScopes }); + if (removeResult.rowCount != removeScopes.length) { + this.logger.error(_scope, 'failed to remove token scopes', { actual: removeResult.rowCount, expected: removeScopes.length }); + throw new DBErrors.UnexpectedResult('did not remove scopes from token'); + } + } else { + delete refreshedToken.scopes; // Not updated, remove from response. + } + } else { + this.logger.debug(_scope, 'did not refresh token', {}); + } + return refreshedToken; + }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + async resourceGet(dbCtx, resourceId) { + const _scope = _fileScope('resourceGet'); + this.logger.debug(_scope, 'called', { resourceId }); + + try { + return await dbCtx.oneOrNone(this.statement.resourceGet, { resourceId }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, resourceId }); + throw e; + } + } + + + async resourceUpsert(dbCtx, resourceId, secret, description) { + const _scope = _fileScope('resourceUpsert'); + const logSecret = secret?.length && common.logTruncate('*'.repeat(secret.length), 3) || undefined; + this.logger.debug(_scope, 'called', { resourceId, secret: logSecret, description }); + + try { + const result = await dbCtx.result(this.statement.resourceUpsert, { resourceId, secret, description }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not upsert resource'); + } + return result.rows[0]; + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, resourceId, secret: logSecret, description }); + throw e; + } + } + + + async scopeCleanup(dbCtx, atLeastMsSinceLast) { + const _scope = _fileScope('scopeCleanup'); + this.logger.debug(_scope, 'called', { atLeastMsSinceLast }); + + const almanacEvent = 'scopeCleanup'; + try { + return await this.transaction(dbCtx, async (txCtx) => { + + // Check that enough time has passed since last cleanup + const now = new Date(); + const cleanupNotAfter = new Date(now.getTime() - atLeastMsSinceLast); + const { date: lastCleanupDate } = await txCtx.oneOrNone(this.statement.almanacGet, { event: almanacEvent }) || { date: new Date(0) }; + if (lastCleanupDate >= cleanupNotAfter) { + this.logger.debug(_scope, 'skipping token cleanup, too soon', { lastCleanupDate, cleanupNotAfter, atLeastMsSinceLast }); + return; + } + + // Do the cleanup + const { rowCount: scopesRemoved } = await txCtx.result(this.statement.scopeCleanup); + + // Update the last cleanup time + const result = await txCtx.result(this.statement.almanacUpsert, { event: almanacEvent, date: now }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not update almanac'); + } + + this.logger.debug(_scope, 'completed', { scopesRemoved, atLeastMsSinceLast }); + return scopesRemoved; + }); // tx + + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, atLeastMsSinceLast }); + throw e; + } + } + + + async scopeDelete(dbCtx, scope) { + const _scope = _fileScope('scopeDelete'); + this.logger.debug(_scope, 'called', { scope }); + + try { + return await this.transaction(dbCtx, async (txCtx) => { + const { inUse } = await txCtx.one(this.statement.scopeInUse, { scope }); + if (inUse) { + this.logger.debug(_scope, 'not deleted, in use', { scope }); + return false; + } + const result = await txCtx.result(this.statement.scopeDelete, { scope }); + if (result.rowCount == 0) { + this.logger.debug(_scope, 'no such scope', { scope }); + } else { + this.logger.debug(_scope, 'deleted', { scope }); + } + return true; + }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, scope }); + throw e; + } + } + + + async scopeUpsert(dbCtx, scope, application, description, manuallyAdded = false) { + const _scope = _fileScope('scopeUpsert'); + this.logger.debug(_scope, 'called', { scope, description }); + + try { + const result = await dbCtx.result(this.statement.scopeUpsert, { scope, application, description, manuallyAdded }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not upsert scope'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, scope, application, description }); + throw e; + } + } + + + async tokenCleanup(dbCtx, codeLifespanSeconds, atLeastMsSinceLast) { + const _scope = _fileScope('tokenCleanup'); + this.logger.debug(_scope, 'called', { codeLifespanSeconds, atLeastMsSinceLast }); + + const almanacEvent = 'tokenCleanup'; + try { + return await this.transaction(dbCtx, async (txCtx) => { + + // Check that enough time has passed since last cleanup + const now = new Date(); + const cleanupNotAfter = new Date(now.getTime() - atLeastMsSinceLast); + const { date: lastCleanupDate } = await txCtx.oneOrNone(this.statement.almanacGet, { event: almanacEvent }) || { date: new Date(0) }; + if (lastCleanupDate >= cleanupNotAfter) { + this.logger.debug(_scope, 'skipping token cleanup, too soon', { lastCleanupDate, cleanupNotAfter, codeLifespanSeconds, atLeastMsSinceLast }); + return; + } + + // Do the cleanup + const { rowCount: tokensRemoved } = await txCtx.result(this.statement.tokenCleanup, { codeLifespanSeconds }); + + // Update the last cleanup time + const result = await txCtx.result(this.statement.almanacUpsert, { event: almanacEvent, date: now }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not update almanac'); + } + + this.logger.debug(_scope, 'completed', { tokensRemoved, codeLifespanSeconds, atLeastMsSinceLast }); + return tokensRemoved; + }); // tx + + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, atLeastMsSinceLast }); + throw e; + } + } + + + async tokenGetByCodeId(dbCtx, codeId) { + const _scope = _fileScope('tokenGetByCodeId'); + this.logger.debug(_scope, 'called', { codeId }); + + try { + return await dbCtx.oneOrNone(this.statement.tokenGetByCodeId, { codeId }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + async tokenRevokeByCodeId(dbCtx, codeId) { + const _scope = _fileScope('tokenRevokeByCodeId'); + this.logger.debug(_scope, 'called', { codeId }); + + try { + const result = await dbCtx.result(this.statement.tokenRevokeByCodeId, { codeId }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not revoke token'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + async tokenRefreshRevokeByCodeId(dbCtx, codeId) { + const _scope = _fileScope('tokenRefreshRevokeByCodeId'); + this.logger.debug(_scope, 'called', { codeId }); + + try { + const result = await dbCtx.result(this.statement.tokenRefreshRevokeByCodeId, { codeId }); + if (result.rowCount != 1) { + throw new DBErrors.UnexpectedResult('did not revoke token'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + async tokensGetByIdentifier(dbCtx, identifier) { + const _scope = _fileScope('tokensGetByIdentifier'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + return await dbCtx.manyOrNone(this.statement.tokensGetByIdentifier, { identifier }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + +} + +module.exports = DatabasePostgres; diff --git a/src/db/postgres/sql/almanac-get-all.sql b/src/db/postgres/sql/almanac-get-all.sql new file mode 100644 index 0000000..b534d20 --- /dev/null +++ b/src/db/postgres/sql/almanac-get-all.sql @@ -0,0 +1,3 @@ +-- +SELECT * FROM almanac + diff --git a/src/db/postgres/sql/almanac-get.sql b/src/db/postgres/sql/almanac-get.sql new file mode 100644 index 0000000..077dfe2 --- /dev/null +++ b/src/db/postgres/sql/almanac-get.sql @@ -0,0 +1,6 @@ +-- +SELECT date +FROM almanac +WHERE event = $(event) +FOR UPDATE + diff --git a/src/db/postgres/sql/almanac-upsert.sql b/src/db/postgres/sql/almanac-upsert.sql new file mode 100644 index 0000000..4bda1bc --- /dev/null +++ b/src/db/postgres/sql/almanac-upsert.sql @@ -0,0 +1,9 @@ +-- +INSERT INTO almanac + (event, date) +VALUES + ($(event), $(date)) +ON CONFLICT (event) DO UPDATE +SET + date = $(date) + diff --git a/src/db/postgres/sql/authentication-get.sql b/src/db/postgres/sql/authentication-get.sql new file mode 100644 index 0000000..f75533e --- /dev/null +++ b/src/db/postgres/sql/authentication-get.sql @@ -0,0 +1,4 @@ +-- +SELECT * +FROM authentication +WHERE identifier = $(identifier) diff --git a/src/db/postgres/sql/authentication-success.sql b/src/db/postgres/sql/authentication-success.sql new file mode 100644 index 0000000..81983ea --- /dev/null +++ b/src/db/postgres/sql/authentication-success.sql @@ -0,0 +1,4 @@ +-- +UPDATE authentication + SET last_authentication = now() + WHERE identifier = $(identifier) diff --git a/src/db/postgres/sql/authentication-upsert.sql b/src/db/postgres/sql/authentication-upsert.sql new file mode 100644 index 0000000..e86d4fb --- /dev/null +++ b/src/db/postgres/sql/authentication-upsert.sql @@ -0,0 +1,9 @@ +-- +INSERT INTO authentication + (identifier, credential) +VALUES + ($(identifier), $(credential)) +ON CONFLICT (identifier) DO UPDATE +SET + identifier = $(identifier), + credential = $(credential) diff --git a/src/db/postgres/sql/profile-get.sql b/src/db/postgres/sql/profile-get.sql new file mode 100644 index 0000000..e4172d1 --- /dev/null +++ b/src/db/postgres/sql/profile-get.sql @@ -0,0 +1,3 @@ +-- +SELECT * FROM profile +WHERE profile = $(profile) diff --git a/src/db/postgres/sql/profile-identifier-insert.sql b/src/db/postgres/sql/profile-identifier-insert.sql new file mode 100644 index 0000000..2c42fac --- /dev/null +++ b/src/db/postgres/sql/profile-identifier-insert.sql @@ -0,0 +1,4 @@ +-- +INSERT INTO profile (profile, identifier_id) + SELECT $(profile), identifier_id FROM authentication WHERE identifier = $(identifier) +ON CONFLICT (identifier_id, profile) DO NOTHING diff --git a/src/db/postgres/sql/profile-scope-insert.sql b/src/db/postgres/sql/profile-scope-insert.sql new file mode 100644 index 0000000..26cc6bb --- /dev/null +++ b/src/db/postgres/sql/profile-scope-insert.sql @@ -0,0 +1,5 @@ +-- +INSERT INTO profile_scope (profile_id, scope_id) + SELECT p.profile_id, s.scope_id FROM profile p, scope s + WHERE p.profile = $(profile) AND s.scope = $(scope) +ON CONFLICT (profile_id, scope_id) DO NOTHING diff --git a/src/db/postgres/sql/profile-scopes-clear.sql b/src/db/postgres/sql/profile-scopes-clear.sql new file mode 100644 index 0000000..bd7827b --- /dev/null +++ b/src/db/postgres/sql/profile-scopes-clear.sql @@ -0,0 +1,6 @@ +-- +DELETE FROM profile_scope +WHERE profile_id IN ( + SELECT profile_id FROM profile WHERE profile = $(profile) +) + diff --git a/src/db/postgres/sql/profile-scopes-set-all.sql b/src/db/postgres/sql/profile-scopes-set-all.sql new file mode 100644 index 0000000..a7e74cf --- /dev/null +++ b/src/db/postgres/sql/profile-scopes-set-all.sql @@ -0,0 +1,5 @@ +-- +INSERT INTO profile_scope (profile_id, scope_id) + SELECT p.profile_id, s.scope_id FROM profile p, scope s + WHERE p.profile = $(profile) AND s.scope = ANY ($(scopes)) + diff --git a/src/db/postgres/sql/profiles-scopes-by-identifier.sql b/src/db/postgres/sql/profiles-scopes-by-identifier.sql new file mode 100644 index 0000000..525ced6 --- /dev/null +++ b/src/db/postgres/sql/profiles-scopes-by-identifier.sql @@ -0,0 +1,10 @@ +-- +SELECT p.profile, s.* + FROM profile p + INNER JOIN authentication a USING (identifier_id) + FULL JOIN profile_scope ps USING (profile_id) + FULL JOIN scope s USING (scope_id) + WHERE a.identifier = $(identifier) +UNION ALL SELECT NULL AS profile, * + FROM scope + WHERE is_manually_added OR is_permanent diff --git a/src/db/postgres/sql/redeem-code.sql b/src/db/postgres/sql/redeem-code.sql new file mode 100644 index 0000000..e61d503 --- /dev/null +++ b/src/db/postgres/sql/redeem-code.sql @@ -0,0 +1,30 @@ +-- +INSERT INTO token ( + created, + code_id, + is_token, + client_id, + profile_id, + duration, + expires, + refresh_duration, + refresh_expires, + resource, + profile_data +) SELECT + $(created)::timestamptz, + $(codeId), + $(isToken), + $(clientId), + p.profile_id, + $(lifespanSeconds)::text::interval, + CASE WHEN $(lifespanSeconds) IS NULL THEN NULL ELSE $(created)::timestamptz + $(lifespanSeconds)::text::interval END, + $(refreshLifespanSeconds)::text::interval, + CASE WHEN $(refreshLifespanSeconds) IS NULL THEN NULL ELSE $(created)::timestamptz + $(refreshLifespanSeconds)::text::interval END, + $(resource), + $(profileData) +FROM profile p INNER JOIN authentication a USING (identifier_id) +WHERE p.profile = $(profile) AND a.identifier = $(identifier) +ON CONFLICT (code_id) DO UPDATE -- repeated redemption attempt invalidates existing token + SET is_revoked = true +RETURNING * diff --git a/src/db/postgres/sql/refresh-code.sql b/src/db/postgres/sql/refresh-code.sql new file mode 100644 index 0000000..339e488 --- /dev/null +++ b/src/db/postgres/sql/refresh-code.sql @@ -0,0 +1,19 @@ +-- +UPDATE token SET + expires = $(refreshed)::timestamptz + duration, + refreshed = $(refreshed)::timestamptz, + refresh_expires = $(refreshed)::timestamptz + refresh_duration, + refresh_count = refresh_count + 1 +WHERE + code_id = $(codeId) +AND + NOT is_revoked +AND + (refresh_expires IS NOT NULL AND refresh_expires > $(refreshed)::timestamptz) +RETURNING + expires, + refresh_expires, + ARRAY( + SELECT s.scope FROM token_scope ts INNER JOIN scope s USING (scope_id) + WHERE ts.code_id = code_id + ) AS scopes diff --git a/src/db/postgres/sql/resource-get.sql b/src/db/postgres/sql/resource-get.sql new file mode 100644 index 0000000..53100f1 --- /dev/null +++ b/src/db/postgres/sql/resource-get.sql @@ -0,0 +1,4 @@ +-- +SELECT * +FROM resource +WHERE resource_id = $(resourceId) diff --git a/src/db/postgres/sql/resource-upsert.sql b/src/db/postgres/sql/resource-upsert.sql new file mode 100644 index 0000000..51d4e94 --- /dev/null +++ b/src/db/postgres/sql/resource-upsert.sql @@ -0,0 +1,10 @@ +-- +INSERT INTO resource + (resource_id, secret, description) +VALUES + (COALESCE($(resourceId)::UUID, uuid_generate_v4()), $(secret), $(description)) +ON CONFLICT (resource_id) DO UPDATE +SET + secret = COALESCE(EXCLUDED.secret, resource.secret), + description = COALESCE(EXCLUDED.description, resource.description) +RETURNING * diff --git a/src/db/postgres/sql/schema/1.0.0/apply.sql b/src/db/postgres/sql/schema/1.0.0/apply.sql new file mode 100644 index 0000000..274dd7a --- /dev/null +++ b/src/db/postgres/sql/schema/1.0.0/apply.sql @@ -0,0 +1,147 @@ +BEGIN; + + CREATE TABLE almanac ( + event TEXT NOT NULL PRIMARY KEY, + date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT '-infinity'::timestamptz + ); + COMMENT ON TABLE almanac IS $docstring$ +Notable events for service administration. + $docstring$; + + CREATE TABLE authentication ( + identifier_id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + last_authentication TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT '-infinity'::timestamptz, + identifier TEXT NOT NULL UNIQUE, + credential TEXT + ); + COMMENT ON TABLE authentication IS $docstring$ +Users and their credentials. + $docstring$; + + CREATE TABLE resource ( + resource_id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(), -- few insertions, v4 preferred over v1 + description TEXT NOT NULL DEFAULT '', + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + secret TEXT NOT NULL CHECK(length(secret) > 0) + ); + COMMENT ON TABLE resource IS $docstring$ +External resource servers and their credentials. + $docstring$; + + CREATE TABLE profile ( + profile_id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + identifier_id BIGINT NOT NULL REFERENCES authentication(identifier_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + profile TEXT NOT NULL, + CONSTRAINT unique_identifier_id_profile UNIQUE (identifier_id, profile) + ); + CREATE INDEX profile_identifier_id_idx ON profile(identifier_id); + CREATE INDEX profile_profile_idx ON profile(profile); + COMMENT ON TABLE profile IS $docstring$ +Profile URIs and the users they are associated with. +$docstring$; + + CREATE TABLE scope ( + scope_id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + scope TEXT NOT NULL UNIQUE, + description TEXT NOT NULL DEFAULT '', + application TEXT NOT NULL DEFAULT '', + is_permanent BOOLEAN NOT NULL DEFAULT false, + is_manually_added BOOLEAN NOT NULL DEFAULT false + ); + COMMENT ON TABLE scope IS $docstring$ +All and any scopes encountered. +$docstring$; + COMMENT ON COLUMN scope.is_permanent IS $docstring$ +Prevents deletion of scope, set on seeded scope rows. +$docstring$; + COMMENT ON COLUMN scope.is_manually_added IS $docstring$ +Prevents deletion of scope when no longer referenced, set on user-added scopes. +User can still delete manually. +$docstring$; + CREATE INDEX scope_garbage_collectable_idx ON scope(scope_id) WHERE NOT (is_permanent OR is_manually_added); + COMMENT ON INDEX scope_garbage_collectable_idx IS $docstring$ +Shadow the primary index with a partial to help GC cleanup of client-provided scopes. +$docstring$; + CREATE INDEX scope_is_permanent_idx ON scope(scope_id) WHERE is_permanent; + CREATE INDEX scope_is_manual_idx ON scope(scope_id) WHERE is_manually_added; + + CREATE TABLE profile_scope ( + profile_id BIGINT NOT NULL REFERENCES profile(profile_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + scope_id BIGINT NOT NULL REFERENCES scope(scope_id) ON DELETE NO ACTION ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + PRIMARY KEY (profile_id, scope_id) + ); + COMMENT ON TABLE profile_scope IS $docstring$ +Convenience bindings of available scopes for a profile. +$docstring$; + CREATE INDEX profile_scope_scope_id_idx ON profile_scope(scope_id); + + CREATE TABLE token ( + code_id UUID NOT NULL PRIMARY KEY, + profile_id BIGINT NOT NULL REFERENCES profile(profile_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + expires TIMESTAMP WITH TIME ZONE, + refresh_expires TIMESTAMP WITH TIME ZONE, + refreshed TIMESTAMP WITH TIME ZONE, + duration INTERVAL, + CONSTRAINT expires_needs_duration CHECK (expires IS NULL OR duration IS NOT NULL), + refresh_duration INTERVAL, + CONSTRAINT refresh_expires_needs_refresh_duration CHECK (refresh_expires IS NULL OR refresh_duration IS NOT NULL), + refresh_count BIGINT NOT NULL DEFAULT 0, + is_revoked BOOLEAN NOT NULL DEFAULT false, + is_token BOOLEAN NOT NULL, + client_id TEXT NOT NULL, + resource TEXT, + profile_data JSONB + ); + CREATE INDEX token_profile_id_idx ON token(profile_id); + CREATE INDEX token_created_idx ON token(created); + CREATE INDEX token_expires_idx ON token(expires); + CREATE INDEX token_refresh_expires_idx ON token(refresh_expires); + CREATE INDEX token_is_revoked_idx ON token(is_revoked) WHERE is_revoked; + COMMENT ON TABLE token IS $docstring$ +Redeemed codes and their current states. +$docstring$; + COMMENT ON COLUMN token.is_token IS $docstring$ +Whether code was redeemed for a token, or only a profile. +We track non-token redemptions to prevent re-redemption while +code is still valid. +$docstring$; + COMMENT ON COLUMN token.resource IS $docstring$ +Tokens granted by ticket redemption are associated with a +resource url for which they are valid. +$docstring$; + COMMENT ON INDEX token_is_revoked_idx IS $docstring$ +A partial index on revoked tokens, to ease cleanup query. +$docstring$; + + CREATE TABLE token_scope ( + code_id UUID NOT NULL REFERENCES token(code_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + scope_id BIGINT NOT NULL REFERENCES scope(scope_id) ON DELETE NO ACTION ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + PRIMARY KEY (code_id, scope_id) + ); + COMMENT ON TABLE token_scope IS $docstring$ +Scopes associated with a token. +$docstring$; + CREATE INDEX token_scope_scope_id_idx ON token_scope(scope_id); + + -- Update schema version + INSERT INTO _meta_schema_version (major, minor, patch) VALUES (1, 0, 0); + + -- Seed scope data + INSERT INTO scope (scope, description, application, is_permanent) VALUES + ('profile', 'Access detailed profile information, including name, image, and url.', 'IndieAuth', true), + ('email', 'Include email address with detailed profile information.', 'IndieAuth', true), + ('create', 'Create new items.', 'MicroPub', true), + ('draft', 'All created items are drafts.', 'MicroPub', true), + ('update', 'Edit existing items.', 'MicroPub', true), + ('delete', 'Remove items.', 'MicroPub', true), + ('media', 'Allow file uploads.', 'MicroPub', true), + ('read', 'Read access to channels.', 'MicroSub', true), + ('follow', 'Management of following list.', 'MicroSub', true), + ('mute', 'Management of user muting.', 'MicroSub', true), + ('block', 'Management of user blocking.', 'MicroSub', true), + ('channels', 'Management of channels.', 'MicroSub', true) + ; + +COMMIT; diff --git a/src/db/postgres/sql/schema/1.0.0/er.dot b/src/db/postgres/sql/schema/1.0.0/er.dot new file mode 100644 index 0000000..147f50b --- /dev/null +++ b/src/db/postgres/sql/schema/1.0.0/er.dot @@ -0,0 +1,105 @@ +digraph indieAutherERD { + graph[ + rankdir=LR, + overlap=false, + splines=true, + label="IndieAuther Entity-Relations\nPostgres\nSchema 1.0.0", + labelloc="t", + fontsize=26, + ]; + // layout=neato; + node[shape=plain]; + edge[arrowhead=crow]; + + token [label=< + + + + + + + + + + + + + + + + +
TOKEN
code_id
profile_id
created
expires
refresh_expires
refreshed
duration
refresh_duration
refresh_count
is_revoked
is_token
client_id
resource
profile_data
+ >]; + profile:pk_profile_id -> token:fk_profile_id; + + scope [label=< + + + + + + + + +
SCOPE
scope_id
scope
description
application
is_permanent
is_manually_added
+ >]; + + token_scope [label=< + + + + +
TOKEN_SCOPE
code_id
scope_id
+ >]; + token:pk_code_id -> token_scope:fk_code_id; + scope:pk_scope_id -> token_scope:fk_scope_id; + + profile [label=< + + + + + +
PROFILE
profile_id
identifier_id
profile
+ >]; + authentication:pk_identifier_id -> profile:fk_identifier_id; + + profile_scope [label=< + + + + +
PROFILE_SCOPE
profile_id
scope_id
+ >]; + profile:pk_profile_id -> profile_scope:fk_profile_id; + scope:pk_scope_id -> profile_scope:fk_scope_id; + + authentication [label=< + + + + + + + +
AUTHENTICATION
identifier_id
created
last_authenticated
identifier
credential
+ >]; + + resource [label=< + + + + + + +
RESOURCE
resource_id
description
created
secret
+ >]; + + almanac [label=< + + + + +
ALMANAC
event
date
+ >]; +} diff --git a/src/db/postgres/sql/schema/1.0.0/revert.sql b/src/db/postgres/sql/schema/1.0.0/revert.sql new file mode 100644 index 0000000..af4665a --- /dev/null +++ b/src/db/postgres/sql/schema/1.0.0/revert.sql @@ -0,0 +1,9 @@ +BEGIN; + DROP TABLE authentication CASCADE; + DROP TABLE profile CASCADE; + DROP TABLE token CASCADE; + DROP TABLE scope CASCADE; + DROP TABLE profile_scope CASCADE; + + DELETE FROM _meta_schema_version WHERE major = 1 AND minor = 0 AND patch = 0; +COMMIT; diff --git a/src/db/postgres/sql/schema/init.sql b/src/db/postgres/sql/schema/init.sql new file mode 100644 index 0000000..618781f --- /dev/null +++ b/src/db/postgres/sql/schema/init.sql @@ -0,0 +1,15 @@ +-- +CREATE EXTENSION IF NOT EXISTS pg_stat_statements; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- +BEGIN; +CREATE TABLE IF NOT EXISTS _meta_schema_version ( + major BIGINT NOT NULL, + minor BIGINT NOT NULL, + patch BIGINT NOT NULL, + applied TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + PRIMARY KEY (major, minor, patch) +); +INSERT INTO _meta_schema_version (major, minor, patch) VALUES (0, 0, 0); +COMMIT; diff --git a/src/db/postgres/sql/scope-cleanup.sql b/src/db/postgres/sql/scope-cleanup.sql new file mode 100644 index 0000000..120e3d6 --- /dev/null +++ b/src/db/postgres/sql/scope-cleanup.sql @@ -0,0 +1,8 @@ +-- Remove any un-used, non-permament, non-manually-created scopes. +DELETE FROM scope WHERE scope_id NOT IN ( + SELECT scope_id FROM scope s + INNER JOIN profile_scope ps USING (scope_id) + UNION All + SELECT scope_id FROM scope s + INNER JOIN token_scope ts USING (scope_id) +) AND NOT (is_permanent OR is_manually_added) diff --git a/src/db/postgres/sql/scope-delete.sql b/src/db/postgres/sql/scope-delete.sql new file mode 100644 index 0000000..9fd42a2 --- /dev/null +++ b/src/db/postgres/sql/scope-delete.sql @@ -0,0 +1,3 @@ +-- remove an inpermanent scope +DELETE FROM scope + WHERE scope = $(scope) AND is_permanent = false diff --git a/src/db/postgres/sql/scope-in-use.sql b/src/db/postgres/sql/scope-in-use.sql new file mode 100644 index 0000000..a09c540 --- /dev/null +++ b/src/db/postgres/sql/scope-in-use.sql @@ -0,0 +1,13 @@ +-- return whether a scope is currently in use, either a profile setting or on a token +SELECT EXISTS ( + SELECT 1 FROM scope s + INNER JOIN profile_scope ps USING (scope_id) + WHERE s.scope = $(scope) + UNION All + SELECT 1 FROM scope s + INNER JOIN token_scope ts USING (scope_id) + WHERE s.scope = $(scope) + UNION All + SELECT 1 FROM scope s + WHERE s.scope = $(scope) AND s.is_permanent +) AS in_use diff --git a/src/db/postgres/sql/scope-upsert.sql b/src/db/postgres/sql/scope-upsert.sql new file mode 100644 index 0000000..543d89d --- /dev/null +++ b/src/db/postgres/sql/scope-upsert.sql @@ -0,0 +1,14 @@ +-- +-- N.B. weirdness with postgres empty string null equivalence and coalesce +INSERT INTO scope ( + scope, application, description, is_manually_added +) VALUES ( + $(scope), + CASE WHEN $(application) IS NULL THEN '' ELSE $(application) END, + CASE WHEN $(description) IS NULL THEN '' ELSE $(description) END, + COALESCE($(manuallyAdded), false) +) ON CONFLICT (scope) DO UPDATE +SET + application = CASE WHEN $(application) IS NULL THEN EXCLUDED.application ELSE $(application) END, + description = CASE WHEN $(description) IS NULL THEN EXCLUDED.description ELSE $(description) END, + is_manually_added = EXCLUDED.is_manually_added OR COALESCE($(manuallyAdded), false) diff --git a/src/db/postgres/sql/scopes-insert.sql b/src/db/postgres/sql/scopes-insert.sql new file mode 100644 index 0000000..a933e28 --- /dev/null +++ b/src/db/postgres/sql/scopes-insert.sql @@ -0,0 +1,6 @@ +-- Insert an externally-provided scope, or ignore. +INSERT INTO scope + (scope) +SELECT + unnest(${scopes}) +ON CONFLICT (scope) DO NOTHING diff --git a/src/db/postgres/sql/token-cleanup.sql b/src/db/postgres/sql/token-cleanup.sql new file mode 100644 index 0000000..9c2c744 --- /dev/null +++ b/src/db/postgres/sql/token-cleanup.sql @@ -0,0 +1,26 @@ +-- Remove tokens no longer in use. +-- only clean after code has expired +WITH +cleanable_codes AS ( + SELECT t.code_id, t.is_token, t.is_revoked, t.expires, t.refresh_expires FROM token t + WHERE t.created < (now() - $(codeLifespanSeconds)::text::interval) +) +DELETE FROM token WHERE code_id IN ( + SELECT code_id FROM cleanable_codes + WHERE + NOT is_token -- profile-only redemptions + OR ( + is_token AND ( + is_revoked -- revoked + OR ( + expires < now() AND ( + -- expired and unrefreshable + refresh_expires IS NULL + OR + -- expired and refresh expired + (refresh_expires IS NOT NULL AND refresh_expires < now()) + ) + ) + ) + ) +) diff --git a/src/db/postgres/sql/token-get-by-code-id.sql b/src/db/postgres/sql/token-get-by-code-id.sql new file mode 100644 index 0000000..23f0f73 --- /dev/null +++ b/src/db/postgres/sql/token-get-by-code-id.sql @@ -0,0 +1,24 @@ +-- +SELECT + t.code_id, + p.profile, + t.created, + t.expires, + t.refresh_expires, + t.refreshed, + t.duration, + t.refresh_duration, + t.refresh_count, + t.is_revoked, + t.is_token, + t.client_id, + t.profile_data, + a.identifier, + ARRAY( + SELECT s.scope FROM token_scope ts INNER JOIN scope s USING (scope_id) + WHERE ts.code_id = t.code_id + ) AS scopes +FROM token t + INNER JOIN profile p USING (profile_id) + INNER JOIN authentication a USING (identifier_id) +WHERE code_id = $(codeId) diff --git a/src/db/postgres/sql/token-refresh-revoke-by-code-id.sql b/src/db/postgres/sql/token-refresh-revoke-by-code-id.sql new file mode 100644 index 0000000..4c7033d --- /dev/null +++ b/src/db/postgres/sql/token-refresh-revoke-by-code-id.sql @@ -0,0 +1,5 @@ +-- Revoke the refresh-token for a token +UPDATE token SET + refresh_expires = NULL, + refresh_duration = NULL +WHERE code_id = $(codeId) diff --git a/src/db/postgres/sql/token-revoke-by-code-id.sql b/src/db/postgres/sql/token-revoke-by-code-id.sql new file mode 100644 index 0000000..7a57efc --- /dev/null +++ b/src/db/postgres/sql/token-revoke-by-code-id.sql @@ -0,0 +1,5 @@ +-- +UPDATE token SET + is_revoked = true, + refresh_expires = NULL +WHERE code_id = $(codeId) diff --git a/src/db/postgres/sql/token-scopes-remove.sql b/src/db/postgres/sql/token-scopes-remove.sql new file mode 100644 index 0000000..dd667f2 --- /dev/null +++ b/src/db/postgres/sql/token-scopes-remove.sql @@ -0,0 +1,7 @@ +-- +DELETE FROM token_scope +WHERE (code_id, scope_id) IN ( + SELECT code_id, scope_id FROM token_scope ts + INNER JOIN scope s USING (scope_id) + WHERE scope = ANY($(removeScopes)) AND code_id = $(codeId) +) diff --git a/src/db/postgres/sql/token-scopes-set.sql b/src/db/postgres/sql/token-scopes-set.sql new file mode 100644 index 0000000..1557179 --- /dev/null +++ b/src/db/postgres/sql/token-scopes-set.sql @@ -0,0 +1,4 @@ +-- +INSERT INTO token_scope (code_id, scope_id) + SELECT $(codeId), scope_id FROM scope WHERE scope = ANY ($(scopes)) + diff --git a/src/db/postgres/sql/tokens-get-by-identifier.sql b/src/db/postgres/sql/tokens-get-by-identifier.sql new file mode 100644 index 0000000..f8ec90e --- /dev/null +++ b/src/db/postgres/sql/tokens-get-by-identifier.sql @@ -0,0 +1,26 @@ +-- +SELECT + t.code_id, + p.profile, + t.created, + t.expires, + t.refresh_expires, + t.refreshed, + t.duration, + t.refresh_duration, + t.refresh_count, + t.is_revoked, + t.is_token, + t.client_id, + t.resource, + t.profile_data, + a.identifier, + ARRAY( + SELECT s.scope FROM token_scope ts INNER JOIN scope s USING (scope_id) + WHERE ts.code_id = t.code_id + ) AS scopes +FROM token t + INNER JOIN profile p USING (profile_id) + INNER JOIN authentication a USING (identifier_id) + WHERE a.identifier = $(identifier) + ORDER BY GREATEST(t.created, t.refreshed) DESC diff --git a/src/db/schema-version-helper.js b/src/db/schema-version-helper.js new file mode 100644 index 0000000..65a1e39 --- /dev/null +++ b/src/db/schema-version-helper.js @@ -0,0 +1,131 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/** + * Utility functions for wrangling schema migrations. + * This mostly just deals with sorting and comparing 'x.y.z' version + * strings, with some presumptions about directory layouts and whatnot. + */ + +/** + * @typedef {Object} SchemaVersionObject + * @property {Number} major + * @property {Number} minor + * @property {Number} patch + */ + + +/** + * Split a dotted version string into parts. + * @param {String} v + * @returns {SchemaVersionObject} + */ +function schemaVersionStringToObject(v) { + const [ major, minor, patch ] = v.split('.', 3).map((x) => parseInt(x, 10)); + return { major, minor, patch }; +} + + +/** + * Render a version object numerically. + * @param {SchemaVersionObject} v + * @returns {Number} + */ +function schemaVersionObjectToNumber(v) { + const vScale = 1000; + return parseInt(v.major) * vScale * vScale + parseInt(v.minor) * vScale + parseInt(v.patch); +} + + +/** + * Convert dotted version string into number. + * @param {String} v + * @returns {Number} + */ +function schemaVersionStringToNumber(v) { + return schemaVersionObjectToNumber(schemaVersionStringToObject(v)); +} + + +/** + * Version string comparison, for sorting. + * @param {String} a + * @param {String} b + * @returns {Number} + */ +function schemaVersionStringCmp(a, b) { + return schemaVersionStringToNumber(a) - schemaVersionStringToNumber(b); +} + + +/** + * Check if an entry in a directory is a directory containing a migration file. + * @param {String} schemaDir + * @param {String} name + * @returns {Boolean} + */ +function isSchemaMigrationDirectory(schemaDir, name, migrationFile = 'apply.sql') { + // eslint-disable-next-line security/detect-non-literal-fs-filename + const nameStat = fs.statSync(path.join(schemaDir, name)); + if (nameStat.isDirectory()) { + let applyStat; + try { + // eslint-disable-next-line security/detect-non-literal-fs-filename + applyStat = fs.statSync(path.join(schemaDir, name, migrationFile)); + return applyStat.isFile(); + } catch (e) { + return false; + } + } + return false; +} + + +/** + * Return an array of schema migration directory names within engineDir, + * sorted in increasing order. + * @param {String} engineDir + * @returns {String[]} + */ +function allSchemaVersions(engineDir) { + const schemaDir = path.join(engineDir, 'sql', 'schema'); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const availableVersions = fs.readdirSync(schemaDir).filter((d) => isSchemaMigrationDirectory(schemaDir, d)); + availableVersions.sort(schemaVersionStringCmp); + return availableVersions; +} + + +/** + * Return an array of schema migration directory names within engineDir, + * which are within supported range, and are greater than the current + * @param {String} engineDir + * @param {SchemaVersionObject} current + * @param {Object} supported + * @param {SchemaVersionObject} supported.min + * @param {SchemaVersionObject} supported.max + * @returns {String[]} + */ +function unappliedSchemaVersions(engineDir, current, supported) { + const min = schemaVersionObjectToNumber(supported.min); + const max = schemaVersionObjectToNumber(supported.max); + const cur = schemaVersionObjectToNumber(current); + const available = allSchemaVersions(engineDir); + return available.filter((a) => { + a = schemaVersionStringToNumber(a); + return a >= min && a <= max && a > cur; + }); +} + + +module.exports = { + schemaVersionStringToObject, + schemaVersionObjectToNumber, + schemaVersionStringToNumber, + schemaVersionStringCmp, + isSchemaMigrationDirectory, + allSchemaVersions, + unappliedSchemaVersions, +}; \ No newline at end of file diff --git a/src/db/sqlite/index.js b/src/db/sqlite/index.js new file mode 100644 index 0000000..878a004 --- /dev/null +++ b/src/db/sqlite/index.js @@ -0,0 +1,739 @@ +'use strict'; + +const common = require('../../common'); +const Database = require('../abstract'); +const DBErrors = require('../errors'); +const svh = require('../schema-version-helper'); +const SQLite = require('better-sqlite3'); +const fs = require('fs'); +const path = require('path'); +const uuid = require('uuid'); +const { performance } = require('perf_hooks'); + +const _fileScope = common.fileScope(__filename); + +const schemaVersionsSupported = { + min: { + major: 1, + minor: 0, + patch: 0, + }, + max: { + major: 1, + minor: 0, + patch: 0, + }, +}; + +// max of signed int64 (2^63 - 1), should be enough +// const EPOCH_FOREVER = BigInt('9223372036854775807'); + +class DatabaseSQLite extends Database { + constructor(logger, options) { + super(logger, options); + + const connectionString = options.db.connectionString || 'sqlite://:memory:'; + const csDelim = '://'; + const dbFilename = connectionString.slice(connectionString.indexOf(csDelim) + csDelim.length); + + const queryLogLevel = options.db.queryLogLevel; + + const sqliteOptions = { + ...(queryLogLevel && { + // eslint-disable-next-line security/detect-object-injection + verbose: (query) => this.logger[queryLogLevel](_fileScope('SQLite:verbose'), '', { query }), + }), + }; + this.db = new SQLite(dbFilename, sqliteOptions); + this.schemaVersionsSupported = schemaVersionsSupported; + this.changesSinceLastOptimize = BigInt(0); + this.optimizeAfterChanges = options.db.sqliteOptimizeAfterChanges || 0; // Default to no periodic optimization. + this.db.pragma('foreign_keys = on'); // Enforce consistency. + this.db.pragma('journal_mode = WAL'); // Be faster, expect local filesystem. + this.db.defaultSafeIntegers(true); // This probably isn't necessary, but by using these BigInts we keep weird floats out of the query logs. + + this._initTables(); + this._initStatements(); + } + + + /** + * Boolean to 0/1 representation for SQLite params. + * @param {Boolean} bool + * @returns {Number} + */ + static _booleanToNumeric(bool) { + // eslint-disable-next-line security/detect-object-injection + return { + true: 1, + false: 0, + }[bool]; + } + + + /** + * SQLite cannot prepare its statements without a schema, ensure such exists. + */ + _initTables() { + const _scope = _fileScope('_initTables'); + + // Migrations rely upon this table, ensure it exists. + const metaVersionTable = '_meta_schema_version'; + const tableExists = this.db.prepare('SELECT name FROM sqlite_master WHERE type=:type AND name=:name').pluck(true).bind({ type: 'table', name: metaVersionTable }); + let metaExists = tableExists.get(); + if (metaExists === undefined) { + const fPath = path.join(__dirname, 'sql', 'schema', 'init.sql'); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const fSql = fs.readFileSync(fPath, { encoding: 'utf8' }); + this.db.exec(fSql); + metaExists = tableExists.get(); + /* istanbul ignore if */ + if (metaExists === undefined) { + throw new DBErrors.UnexpectedResult(`did not create ${metaVersionTable} table`); + } + this.logger.info(_scope, 'created schema version table', { metaVersionTable }); + } + + // Apply migrations + const currentSchema = this._currentSchema(); + const migrationsWanted = svh.unappliedSchemaVersions(__dirname, currentSchema, this.schemaVersionsSupported); + this.logger.debug(_scope, 'schema migrations wanted', { migrationsWanted }); + migrationsWanted.forEach((v) => { + const fPath = path.join(__dirname, 'sql', 'schema', v, 'apply.sql'); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const fSql = fs.readFileSync(fPath, { encoding: 'utf8' }); + this.logger.info(_scope, 'applying migration', { version: v }); + this.db.exec(fSql); + }); + } + + + _initStatements() { + const _scope = _fileScope('_initStatements'); + const sqlDir = path.join(__dirname, 'sql'); + this.statement = {}; + + // Decorate the statement calls we use with timing and logging. + const wrapFetch = (logName, statementName, fn) => { + const _wrapScope = _fileScope(logName); + return (...args) => { + const startTimestampMs = performance.now(); + const rows = fn(...args); + DatabaseSQLite._deOphidiate(rows); + const elapsedTimeMs = performance.now() - startTimestampMs; + this.logger.debug(_wrapScope, 'complete', { statementName, elapsedTimeMs }); + return rows; + }; + }; + const wrapRun = (logName, statementName, fn) => { + const _wrapScope = _fileScope(logName); + return (...args) => { + const startTimestampMs = performance.now(); + const result = fn(...args); + const elapsedTimeMs = performance.now() - startTimestampMs; + this._updateChanges(result); + this.logger.debug(_wrapScope, 'complete', { ...result, statementName, elapsedTimeMs }); + result.duration = elapsedTimeMs; + return result; + }; + }; + + // eslint-disable-next-line security/detect-non-literal-fs-filename + for (const f of fs.readdirSync(sqlDir)) { + const fPath = path.join(sqlDir, f); + const { name: fName, ext: fExt } = path.parse(f); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const stat = fs.statSync(fPath); + if (!stat.isFile() + || fExt.toLowerCase() !== '.sql') { + continue; + } + // eslint-disable-next-line security/detect-non-literal-fs-filename + const fSql = fs.readFileSync(fPath, { encoding: 'utf8' }); + const statementName = common.camelfy(fName.toLowerCase(), '-'); + let statement; + try { + statement = this.db.prepare(fSql); + } catch (e) /* istanbul ignore next */ { + this.logger.error(_scope, 'failed to prepare statement', { error: e, file: f }); + throw e; + } + // eslint-disable-next-line security/detect-object-injection + this.statement[statementName] = statement; + const { get: origGet, all: origAll, run: origRun } = statement; + statement.get = wrapFetch('SQLite:get', statementName, origGet.bind(statement)); + statement.all = wrapFetch('SQLite:all', statementName, origAll.bind(statement)); + statement.run = wrapRun('SQLite:run', statementName, origRun.bind(statement)); + } + this.statement._optimize = this.db.prepare('SELECT * FROM pragma_optimize(0xffff)'); + + this.logger.debug(_scope, 'statements initialized', { statements: Object.keys(this.statement).length }); + } + + + static _deOphidiate(rows) { + const rowsIsArray = Array.isArray(rows); + if (!rowsIsArray) { + rows = [rows]; + } + const exemplaryRow = rows[0]; + for (const prop in exemplaryRow) { + const camel = common.camelfy(prop); + if (!(camel in exemplaryRow)) { + for (const d of rows) { + d[camel] = d[prop]; // eslint-disable-line security/detect-object-injection + delete d[prop]; // eslint-disable-line security/detect-object-injection + } + } + } + return rowsIsArray ? rows : rows[0]; + } + + + _currentSchema() { + return this.db.prepare('SELECT major, minor, patch FROM _meta_schema_version ORDER BY major DESC, minor DESC, patch DESC LIMIT 1').get(); + } + + + healthCheck() { + const _scope = _fileScope('healthCheck'); + this.logger.debug(_scope, 'called', {}); + if (!this.db.open) { + throw new DBErrors.UnexpectedResult('database is not open'); + } + return { open: this.db.open }; + } + + + _closeConnection() { + this.db.close(); + } + + + _optimize() { + const _scope = _fileScope('_optimize'); + + const optimize = this.statement._optimize.all(); + this.logger.debug(_scope, 'optimize', { optimize, changes: this.changesSinceLastOptimize }); + this.db.pragma('optimize'); + this.changesSinceLastOptimize = BigInt(0); + } + + + _updateChanges(dbResult) { + if (this.optimizeAfterChanges) { + this.changesSinceLastOptimize += BigInt(dbResult.changes); + if (this.changesSinceLastOptimize >= this.optimizeAfterChanges) { + this._optimize(); + } + } + } + + + _purgeTables(really) { + if (really) { + [ + 'authentication', + 'profile', + 'token', + ].map((table) => { + const result = this.db.prepare(`DELETE FROM ${table}`).run(); + this.logger.debug(_fileScope('_purgeTables'), 'success', { table, result }); + }); + } + } + + + context(fn) { + return fn(this.db); + } + + + transaction(dbCtx, fn) { + dbCtx = dbCtx || this.db; + return dbCtx.transaction(fn)(); + } + + + static _almanacToNative(entry) { + return { + event: entry.event, + date: new Date(Number(entry.epoch) * 1000), + }; + } + + almanacGetAll(dbCtx) { // eslint-disable-line no-unused-vars + const _scope = _fileScope('almanacGetAll'); + this.logger.debug(_scope, 'called'); + + try { + const entries = this.statement.almanacGetAll.all(); + return entries.map((entry) => DatabaseSQLite._almanacToNative(entry)); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; + } + } + + + static _authenticationToNative(authentication) { + if (authentication) { + authentication.created = new Date(Number(authentication.created) * 1000); + authentication.lastAuthentication = new Date(Number(authentication.lastAuthentication) * 1000); + } + return authentication; + } + + + authenticationGet(dbCtx, identifier) { + const _scope = _fileScope('authenticationGet'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + const authentication = this.statement.authenticationGet.get({ identifier }); + return DatabaseSQLite._authenticationToNative(authentication); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + + + authenticationSuccess(dbCtx, identifier) { + const _scope = _fileScope('authenticationSuccess'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + const result = this.statement.authenticationSuccess.run({ identifier }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not update authentication success'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + + + authenticationUpsert(dbCtx, identifier, credential) { + const _scope = _fileScope('authenticationUpsert'); + const scrubbedCredential = '*'.repeat((credential || '').length); + this.logger.debug(_scope, 'called', { identifier, scrubbedCredential }); + + let result; + try { + result = this.statement.authenticationUpsert.run({ identifier, credential }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not upsert authentication'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier, scrubbedCredential }); + throw e; + } + } + + + profileIdentifierInsert(dbCtx, profile, identifier) { + const _scope = _fileScope('profileIdentifierInsert'); + this.logger.debug(_scope, 'called', { profile, identifier }); + + try { + const result = this.statement.profileIdentifierInsert.run({ profile, identifier }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not insert profile identifier relationship'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile, identifier }); + throw e; + } + } + + + profileIsValid(dbCtx, profile) { + const _scope = _fileScope('profileIsValid'); + this.logger.debug(_scope, 'called', { profile }); + + try { + const profileResponse = this.statement.profileGet.get({ profile }); + return !!profileResponse; + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile }); + throw e; + } + } + + + profileScopeInsert(dbCtx, profile, scope) { + const _scope = _fileScope('profileScopeInsert'); + this.logger.debug(_scope, 'called', { profile, scope }); + + try { + const result = this.statement.profileScopeInsert.run({ profile, scope }); + // Duplicate inserts get ignored + if (result.changes != 1 && result.changes != 0) { + throw new DBErrors.UnexpectedResult('did not insert profile scope'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile, scope }); + throw e; + } + } + + + profileScopesSetAll(dbCtx, profile, scopes) { + const _scope = _fileScope('profileScopesSetAll'); + this.logger.debug(_scope, 'called', { profile, scopes }); + + try { + this.transaction(dbCtx, () => { + this.statement.profileScopesClear.run({ profile }); + if (scopes.length) { + scopes.forEach((scope) => { + this.statement.profileScopeInsert.run({ profile, scope }); + }); + } + }); // transaction + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, profile, scopes }); + throw e; + } + } + + + profilesScopesByIdentifier(dbCtx, identifier) { + const _scope = _fileScope('profilesScopesByIdentifier'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + const profileScopesRows = this.statement.profilesScopesByIdentifier.all({ identifier }); + return Database._profilesScopesBuilder(profileScopesRows); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + + + redeemCode(dbCtx, { codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, profileData }) { + const _scope = _fileScope('redeemCode'); + this.logger.debug(_scope, 'called', { codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, profileData }); + + let result, ret = false; + try { + if (profileData) { + profileData = JSON.stringify(profileData); + } + this.transaction(dbCtx, () => { + result = this.statement.redeemCode.get({ codeId, created: common.dateToEpoch(created), isToken: DatabaseSQLite._booleanToNumeric(isToken), clientId, profile, identifier, lifespanSeconds, refreshLifespanSeconds, profileData }); + if (!result) { + this.logger.error(_scope, 'failed', { result }); + throw new DBErrors.UnexpectedResult('did not redeem code'); + } + // Abort and return false if redemption resulted in revocation. + if (result.isRevoked) { + return; + } + + // Ensure there are entries for all scopes, and associate with token. + scopes.forEach((scope) => { + this.statement.scopeInsert.run({ scope }); + this.statement.tokenScopeSet.run({ codeId, scope }); + }); + ret = true; + }); // tx + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, profileData }); + throw e; + } + return ret; + } + + + static _refreshCodeResponseToNative(refreshResponse) { + if (refreshResponse) { + ['expires', 'refreshExpires'].forEach((epochField) => { + if (refreshResponse[epochField]) { // eslint-disable-line security/detect-object-injection + refreshResponse[epochField] = new Date(Number(refreshResponse[epochField]) * 1000); // eslint-disable-line security/detect-object-injection + } + }); + } + return refreshResponse; + } + + + refreshCode(dbCtx, codeId, refreshed, removeScopes) { + const _scope = _fileScope('refreshCode'); + this.logger.debug(_scope, 'called', { codeId, refreshed, removeScopes }); + + try { + return this.transaction(dbCtx, () => { + const refreshResponse = this.statement.refreshCode.get({ codeId, refreshed: common.dateToEpoch(refreshed) }); + if (refreshResponse) { + removeScopes.forEach((scope) => { + const result = this.statement.tokenScopeRemove.run({ codeId, scope }); + if (result?.changes != 1) { + this.logger.error(_scope, 'failed to remove token scope', { codeId, scope }); + throw new DBErrors.UnexpectedResult('did not remove scope from token'); + } + }); + if (removeScopes.length) { + refreshResponse.scopes = (this.statement.tokenScopesGetByCodeId.all({ codeId }) || []) + .map((row) => row.scope); + } + } else { + this.logger.debug(_scope, 'did not refresh token', {}); + } + return DatabaseSQLite._refreshCodeResponseToNative(refreshResponse); + }); // tx + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId, refreshed }); + throw e; + } + } + + + static _resourceToNative(resource) { + if (resource) { + resource.created = new Date(Number(resource.created) * 1000); + } + return resource; + } + + + resourceGet(dbCtx, resourceId) { + const _scope = _fileScope('resourceGet'); + this.logger.debug(_scope, 'called', { resourceId }); + + try { + const resource = this.statement.resourceGet.get({ resourceId }); + return DatabaseSQLite._resourceToNative(resource); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, resourceId }); + throw e; + } + } + + + resourceUpsert(dbCtx, resourceId, secret, description) { + const _scope = _fileScope('resourceUpsert'); + this.logger.debug(_scope, 'called', { resourceId }); + + try { + if (!resourceId) { + resourceId = uuid.v4(); + } + const result = this.statement.resourceUpsert.run({ resourceId, secret, description }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not upsert resource'); + } + const resource = this.statement.resourceGet.get({ resourceId }); + return DatabaseSQLite._resourceToNative(resource); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, resourceId, secret, description }); + throw e; + } + } + + + scopeCleanup(dbCtx, atLeastMsSinceLast) { + const _scope = _fileScope('scopeCleanup'); + this.logger.debug(_scope, 'called', { atLeastMsSinceLast }); + + const almanacEvent = 'scopeCleanup'; + try { + return this.db.transaction(() => { + + // Check that enough time has passed since last cleanup + const nowEpoch = BigInt(common.dateToEpoch()); + const { epoch: lastCleanupEpoch } = this.statement.almanacGet.get({ event: almanacEvent }) || { epoch: 0n }; + const elapsedMs = (nowEpoch - lastCleanupEpoch) * 1000n; + if (elapsedMs < atLeastMsSinceLast) { + this.logger.debug(_scope, 'skipping token cleanup, too soon', { lastCleanupEpoch, elapsedMs, atLeastMsSinceLast }); + return; + } + + // Do the cleanup + const { changes: scopesRemoved } = this.statement.scopeCleanup.run(); + + // Update the last cleanup time + const result = this.statement.almanacUpsert.run({ event: almanacEvent, epoch: nowEpoch }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not update almanac'); + } + + this.logger.debug(_scope, 'finished', { scopesRemoved, atLeastMsSinceLast }); + return scopesRemoved; + }).exclusive(); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, atLeastMsSinceLast }); + throw e; + } + } + + + scopeDelete(dbCtx, scope) { + const _scope = _fileScope('scopeDelete'); + this.logger.debug(_scope, 'called', { scope }); + + try { + return this.transaction(dbCtx, () => { + const { inUse } = this.statement.scopeInUse.get({ scope }); + if (inUse) { + this.logger.debug(_scope, 'not deleted, in use', { scope }); + return false; + } + const result = this.statement.scopeDelete.run({ scope }); + if (result.changes == 0) { + this.logger.debug(_scope, 'no such scope', { scope }); + } else { + this.logger.debug(_scope, 'deleted', { scope }); + } + return true; + }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, scope }); + throw e; + } + } + + + scopeUpsert(dbCtx, scope, application, description, manuallyAdded) { + const _scope = _fileScope('scopeUpsert'); + this.logger.debug(_scope, 'called', { scope, application, description, manuallyAdded }); + + try { + const result = this.statement.scopeUpsert.run({ scope, application, description, manuallyAdded: DatabaseSQLite._booleanToNumeric(manuallyAdded) }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not upsert scope'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, scope, application, description, manuallyAdded }); + throw e; + } + } + + + tokenCleanup(dbCtx, codeLifespanSeconds, atLeastMsSinceLast) { + const _scope = _fileScope('tokenCleanup'); + this.logger.debug(_scope, 'called', { codeLifespanSeconds, atLeastMsSinceLast }); + + const almanacEvent = 'tokenCleanup'; + try { + return this.db.transaction(() => { + + // Check that enough time has passed since last cleanup + const nowEpoch = BigInt(common.dateToEpoch()); + const { epoch: lastCleanupEpoch } = this.statement.almanacGet.get({ event: almanacEvent }) || { epoch: 0n }; + const elapsedMs = (nowEpoch - lastCleanupEpoch) * 1000n; + if (elapsedMs < atLeastMsSinceLast) { + this.logger.debug(_scope, 'skipping token cleanup, too soon', { lastCleanupEpoch, elapsedMs, atLeastMsSinceLast }); + return; + } + + // Do the cleanup + const { changes: tokensRemoved } = this.statement.tokenCleanup.run({ codeLifespanSeconds }); + + // Update the last cleanup time + const result = this.statement.almanacUpsert.run({ event: almanacEvent, epoch: nowEpoch }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not update almanac'); + } + + this.logger.debug(_scope, 'finished', { tokensRemoved, codeLifespanSeconds, atLeastMsSinceLast }); + return tokensRemoved; + }).exclusive(); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeLifespanSeconds, atLeastMsSinceLast }); + throw e; + } + } + + + static _tokenToNative(token) { + if (token) { + token.created = new Date(Number(token.created) * 1000); + if (token.expires || token.expires == 0) { + token.expires = new Date(Number(token.expires) * 1000); + } + if (token.refreshExpires || token.refreshExpires == 0) { + token.refreshExpires = new Date(Number(token.refreshExpires) * 1000); + } + if (token.refreshed || token.refreshed == 0) { + token.refreshed = new Date(Number(token.refreshed) * 1000); + } + token.isRevoked = !!token.isRevoked; + token.isToken = !!token.isToken; + if (token.profileData) { + token.profileData = JSON.parse(token.profileData); + } + } + return token; + } + + + tokenGetByCodeId(dbCtx, codeId) { + const _scope = _fileScope('tokenGetByCodeId'); + this.logger.debug(_scope, 'called', { codeId }); + + try { + return this.transaction(dbCtx, () => { + const token = this.statement.tokenGetByCodeId.get({ codeId }); + token.scopes = (this.statement.tokenScopesGetByCodeId.all({ codeId }) || []) + .map((row) => row.scope); + return DatabaseSQLite._tokenToNative(token); + }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + tokenRefreshRevokeByCodeId(dbCtx, codeId) { + const _scope = _fileScope('tokenRefreshRevokeByCodeId'); + this.logger.debug(_scope, 'called', { codeId }); + + try { + const result = this.statement.tokenRefreshRevokeByCodeId.run({ codeId }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not revoke refresh'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + tokenRevokeByCodeId(dbCtx, codeId) { + const _scope = _fileScope('tokenRevokeByCodeId'); + this.logger.debug(_scope, 'called', { codeId }); + + try { + const result = this.statement.tokenRevokeByCodeId.run({ codeId }); + if (result.changes != 1) { + throw new DBErrors.UnexpectedResult('did not revoke token'); + } + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, codeId }); + throw e; + } + } + + + tokensGetByIdentifier(dbCtx, identifier) { + const _scope = _fileScope('tokensGetByIdentifier'); + this.logger.debug(_scope, 'called', { identifier }); + + try { + const tokens = this.statement.tokensGetByIdentifier.all({ identifier }); + return tokens.map(DatabaseSQLite._tokenToNative); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, identifier }); + throw e; + } + } + +} + +module.exports = DatabaseSQLite; \ No newline at end of file diff --git a/src/db/sqlite/sql/almanac-get-all.sql b/src/db/sqlite/sql/almanac-get-all.sql new file mode 100644 index 0000000..fddd282 --- /dev/null +++ b/src/db/sqlite/sql/almanac-get-all.sql @@ -0,0 +1,2 @@ +-- +SELECT * FROM almanac diff --git a/src/db/sqlite/sql/almanac-get.sql b/src/db/sqlite/sql/almanac-get.sql new file mode 100644 index 0000000..00dc708 --- /dev/null +++ b/src/db/sqlite/sql/almanac-get.sql @@ -0,0 +1,2 @@ +-- +SELECT epoch FROM almanac WHERE event = :event diff --git a/src/db/sqlite/sql/almanac-upsert.sql b/src/db/sqlite/sql/almanac-upsert.sql new file mode 100644 index 0000000..fc8be48 --- /dev/null +++ b/src/db/sqlite/sql/almanac-upsert.sql @@ -0,0 +1,9 @@ +-- +INSERT INTO almanac + (event, epoch) +VALUES + (:event, :epoch) +ON CONFLICT (event) DO UPDATE +SET + epoch = :epoch + diff --git a/src/db/sqlite/sql/authentication-get.sql b/src/db/sqlite/sql/authentication-get.sql new file mode 100644 index 0000000..687e54a --- /dev/null +++ b/src/db/sqlite/sql/authentication-get.sql @@ -0,0 +1,8 @@ +-- +SELECT + created, + last_authentication, + identifier, + credential +FROM authentication +WHERE identifier = :identifier diff --git a/src/db/sqlite/sql/authentication-success.sql b/src/db/sqlite/sql/authentication-success.sql new file mode 100644 index 0000000..b4c45a6 --- /dev/null +++ b/src/db/sqlite/sql/authentication-success.sql @@ -0,0 +1,4 @@ +-- +UPDATE authentication + SET last_authentication = strftime('%s', 'now') + WHERE identifier = :identifier diff --git a/src/db/sqlite/sql/authentication-upsert.sql b/src/db/sqlite/sql/authentication-upsert.sql new file mode 100644 index 0000000..8a141ee --- /dev/null +++ b/src/db/sqlite/sql/authentication-upsert.sql @@ -0,0 +1,9 @@ +-- +INSERT INTO authentication + (identifier, credential) +VALUES + (:identifier, :credential) +ON CONFLICT (identifier) DO UPDATE +SET + identifier = :identifier, + credential = :credential diff --git a/src/db/sqlite/sql/profile-get.sql b/src/db/sqlite/sql/profile-get.sql new file mode 100644 index 0000000..74aa7a5 --- /dev/null +++ b/src/db/sqlite/sql/profile-get.sql @@ -0,0 +1,3 @@ +-- +SELECT * FROM profile +WHERE profile = :profile diff --git a/src/db/sqlite/sql/profile-identifier-insert.sql b/src/db/sqlite/sql/profile-identifier-insert.sql new file mode 100644 index 0000000..b357dce --- /dev/null +++ b/src/db/sqlite/sql/profile-identifier-insert.sql @@ -0,0 +1,4 @@ +-- +INSERT INTO profile (profile, identifier_id) + SELECT :profile, identifier_id FROM authentication WHERE identifier = :identifier + diff --git a/src/db/sqlite/sql/profile-scope-insert.sql b/src/db/sqlite/sql/profile-scope-insert.sql new file mode 100644 index 0000000..be4f212 --- /dev/null +++ b/src/db/sqlite/sql/profile-scope-insert.sql @@ -0,0 +1,5 @@ +-- +INSERT INTO profile_scope (profile_id, scope_id) + SELECT p.profile_id, s.scope_id FROM profile p, scope s + WHERE p.profile = :profile AND s.scope = :scope +ON CONFLICT (profile_id, scope_id) DO NOTHING diff --git a/src/db/sqlite/sql/profile-scopes-clear.sql b/src/db/sqlite/sql/profile-scopes-clear.sql new file mode 100644 index 0000000..be544fc --- /dev/null +++ b/src/db/sqlite/sql/profile-scopes-clear.sql @@ -0,0 +1,5 @@ +-- +DELETE FROM profile_scope +WHERE profile_id IN( + SELECT profile_id FROM profile WHERE profile = :profile +) diff --git a/src/db/sqlite/sql/profiles-scopes-by-identifier.sql b/src/db/sqlite/sql/profiles-scopes-by-identifier.sql new file mode 100644 index 0000000..485d043 --- /dev/null +++ b/src/db/sqlite/sql/profiles-scopes-by-identifier.sql @@ -0,0 +1,10 @@ +-- +SELECT p.profile, s.* + FROM profile p + INNER JOIN authentication a USING (identifier_id) + FULL JOIN profile_scope ps USING (profile_id) + FULL JOIN scope s USING (scope_id) + WHERE a.identifier = :identifier +UNION ALL SELECT NULL AS profile, * + FROM scope + WHERE is_manually_added OR is_permanent diff --git a/src/db/sqlite/sql/redeem-code.sql b/src/db/sqlite/sql/redeem-code.sql new file mode 100644 index 0000000..5f237e8 --- /dev/null +++ b/src/db/sqlite/sql/redeem-code.sql @@ -0,0 +1,27 @@ +-- +INSERT INTO token ( + code_id, + created, + is_token, + client_id, + profile_id, + expires, + duration, + refresh_expires, + refresh_duration +) + SELECT + :codeId, + :created, + :isToken, + :clientId, + p.profile_id, + CASE WHEN :lifespanSeconds IS NULL THEN NULL ELSE :created + :lifespanSeconds END, + :lifespanSeconds, + CASE WHEN :refreshLifespanSeconds IS NULL THEN NULL ELSE :created + :refreshLifespanSeconds END, + :refreshLifespanSeconds + FROM profile p INNER JOIN authentication a USING (identifier_id) + WHERE p.profile = :profile AND a.identifier = :identifier +ON CONFLICT (code_id) DO UPDATE + SET is_revoked = true +RETURNING is_revoked diff --git a/src/db/sqlite/sql/refresh-code.sql b/src/db/sqlite/sql/refresh-code.sql new file mode 100644 index 0000000..e2820c8 --- /dev/null +++ b/src/db/sqlite/sql/refresh-code.sql @@ -0,0 +1,15 @@ +-- +UPDATE token set + refreshed = :refreshed, + expires = :refreshed + duration, + refresh_expires = :refreshed + refresh_duration, + refresh_count = refresh_count + 1 +WHERE + code_id = :codeId +AND + NOT is_revoked +AND + (refresh_expires IS NOT NULL AND refresh_expires > :refreshed) +RETURNING + expires, + refresh_expires diff --git a/src/db/sqlite/sql/resource-get.sql b/src/db/sqlite/sql/resource-get.sql new file mode 100644 index 0000000..6e481d5 --- /dev/null +++ b/src/db/sqlite/sql/resource-get.sql @@ -0,0 +1,4 @@ +-- +SELECT * +FROM resource +WHERE resource_id = :resourceId diff --git a/src/db/sqlite/sql/resource-upsert.sql b/src/db/sqlite/sql/resource-upsert.sql new file mode 100644 index 0000000..84d333c --- /dev/null +++ b/src/db/sqlite/sql/resource-upsert.sql @@ -0,0 +1,9 @@ +-- +INSERT INTO resource + (resource_id, secret, description) +VALUES (:resourceId, :secret, :description) +ON CONFLICT (resource_id) DO UPDATE +SET + secret = COALESCE(EXCLUDED.secret, resource.secret), + description = COALESCE(EXCLUDED.description, resource.description) +RETURNING * diff --git a/src/db/sqlite/sql/schema/1.0.0/apply.sql b/src/db/sqlite/sql/schema/1.0.0/apply.sql new file mode 100644 index 0000000..2b2be31 --- /dev/null +++ b/src/db/sqlite/sql/schema/1.0.0/apply.sql @@ -0,0 +1,97 @@ +BEGIN; + + CREATE TABLE almanac ( + event TEXT NOT NULL PRIMARY KEY CHECK (typeof(event) = 'text'), + epoch INTEGER NOT NULL DEFAULT 0 CHECK (typeof(epoch) = 'integer') + ); + + CREATE TABLE authentication ( + identifier_id INTEGER NOT NULL PRIMARY KEY, + created INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK (typeof(created) = 'integer'), + last_authentication INTEGER NOT NULL DEFAULT 0 CHECK (typeof(last_authentication) = 'integer'), + identifier TEXT NOT NULL UNIQUE CHECK (typeof(identifier) = 'text'), + credential TEXT CHECK (typeof(credential) IN ('text', 'null')) + ); + + CREATE TABLE resource ( + resource_id TEXT NOT NULL PRIMARY KEY CHECK (typeof(resource_id) = 'text'), + description TEXT NOT NULL DEFAULT '' CHECK (typeof(description) = 'text'), + created INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK (typeof(created) = 'integer'), + secret TEXT NOT NULL CHECK (typeof(secret) = 'text') + ); + + CREATE TABLE profile ( + profile_id INTEGER NOT NULL PRIMARY KEY, + identifier_id INTEGER NOT NULL REFERENCES authentication(identifier_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + profile TEXT NOT NULL CHECK (typeof(profile) = 'text'), + UNIQUE (identifier_id, profile) ON CONFLICT IGNORE + ); + CREATE INDEX profile_identifier_id_idx ON profile(identifier_id); + CREATE INDEX profile_profile_idx ON profile(profile); + + CREATE TABLE scope ( + scope_id INTEGER NOT NULL PRIMARY KEY, + scope TEXT NOT NULL UNIQUE CHECK (typeof(scope) = 'text'), + description TEXT NOT NULL ON CONFLICT REPLACE DEFAULT '' CHECK (typeof(description) = 'text'), + application TEXT NOT NULL ON CONFLICT REPLACE DEFAULT '' CHECK (typeof(application) = 'text'), + is_permanent INTEGER NOT NULL DEFAULT 0 CHECK (typeof(is_permanent) = 'integer' AND is_permanent IN (0, 1)), + is_manually_added INTEGER NOT NULL DEFAULT 0 CHECK (typeof(is_manually_added) = 'integer' AND is_manually_added IN (0, 1)) + ); + + CREATE TABLE profile_scope ( + profile_id INTEGER NOT NULL REFERENCES profile(profile_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + scope_id INTEGER NOT NULL REFERENCES scope(scope_id) ON DELETE NO ACTION ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + PRIMARY KEY (profile_id, scope_id) + ); + CREATE INDEX profile_scope_scope_id_idx ON profile_scope(scope_id); + + CREATE TABLE token ( + code_id TEXT NOT NULL PRIMARY KEY CHECK (typeof(code_id) = 'text'), + profile_id INTEGER NOT NULL REFERENCES profile (profile_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + created INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK (typeof(created) = 'integer'), + expires INTEGER CHECK (typeof(expires) IN ('integer', 'null')), + refresh_expires INTEGER CHECK (typeof(refresh_expires) IN ('integer', 'null')), + refreshed INTEGER CHECK (typeof(refreshed) IN ('integer', 'null')), + duration INTEGER CHECK (typeof(duration) IN ('integer', 'null')), + refresh_duration INTEGER CHECK (typeof(refresh_duration) IN ('integer', 'null')), + refresh_count INTEGER NOT NULL DEFAULT 0 CHECK (typeof(refresh_count) = 'integer'), + is_revoked INTEGER NOT NULL DEFAULT 0 CHECK (typeof(is_revoked) = 'integer' AND is_revoked IN (0, 1)), + is_token INTEGER NOT NULL CHECK (typeof(is_token) = 'integer' AND is_token IN (0, 1)), + client_id TEXT NOT NULL CHECK (typeof(client_id) = 'text'), + resource TEXT CHECK (typeof(resource) IN ('integer', 'null')), + profile_data TEXT, + CONSTRAINT expires_needs_duration CHECK (expires IS NULL OR duration IS NOT NULL), + CONSTRAINT refresh_expires_needs_refresh_duration CHECK (refresh_expires IS NULL OR refresh_duration IS NOT NULL) + ); + CREATE INDEX token_profile_id_idx ON token(profile_id); + CREATE INDEX token_created_idx ON token(created); + CREATE INDEX token_expires_idx ON token(expires) WHERE expires IS NOT NULL; + CREATE INDEX token_refresh_expires_idx ON token(refresh_expires) WHERE refresh_expires IS NOT NULL; + + CREATE TABLE token_scope ( + code_id TEXT NOT NULL REFERENCES token(code_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + scope_id INTEGER NOT NULL REFERENCES scope(scope_id) ON DELETE NO ACTION ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + PRIMARY KEY (code_id, scope_id) + ); + CREATE INDEX token_scope_scope_id_idx ON token_scope(scope_id); + + -- Update schema version + INSERT INTO _meta_schema_version (major, minor, patch) VALUES (1, 0, 0); + + -- Seed scope data + INSERT INTO scope (scope, description, application, is_permanent) VALUES + ('profile', 'Access detailed profile information, including name, image, and url.', 'IndieAuth', 1), + ('email', 'Include email address with detailed profile information.', 'IndieAuth', 1), + ('create', 'Create new items.', 'MicroPub', 1), + ('draft', 'All created items are drafts.', 'MicroPub', 1), + ('update', 'Edit existing items.', 'MicroPub', 1), + ('delete', 'Remove items.', 'MicroPub', 1), + ('media', 'Allow file uploads.', 'MicroPub', 1), + ('read', 'Read access to channels.', 'MicroSub', 1), + ('follow', 'Management of following list.', 'MicroSub', 1), + ('mute', 'Management of user muting.', 'MicroSub', 1), + ('block', 'Management of user blocking.', 'MicroSub', 1), + ('channels', 'Management of channels.', 'MicroSub', 1) + ; + +COMMIT; diff --git a/src/db/sqlite/sql/schema/1.0.0/er.dot b/src/db/sqlite/sql/schema/1.0.0/er.dot new file mode 100644 index 0000000..2e43133 --- /dev/null +++ b/src/db/sqlite/sql/schema/1.0.0/er.dot @@ -0,0 +1,106 @@ +digraph indieAutherERD { + graph[ + rankdir=LR, + overlap=false, + splines=true, + label="IndieAuther Entity-Relations\SQLite\nSchema 1.0.0", + labelloc="t", + fontsize=26, + ]; + // layout=neato; + node[shape=plain]; + edge[arrowhead=crow]; + + token [label=< + + + + + + + + + + + + + + + + +
TOKEN
code_id
profile_id
created
expires
refresh_expires
refreshed
duration
refresh_duration
refresh_count
is_revoked
is_token
client_id
resource
profile_data
+ >]; + profile:pk_profile_id -> token:fk_profile_id; + + scope [label=< + + + + + + + + +
SCOPE
scope_id
scope
description
application
is_permanent
is_manually_added
+ >]; + + token_scope [label=< + + + + +
TOKEN_SCOPE
code_id
scope_id
+ >]; + token:pk_code_id -> token_scope:fk_code_id; + scope:pk_scope_id -> token_scope:fk_scope_id; + + profile [label=< + + + + + +
PROFILE
profile_id
identifier_id
profile
+ >]; + authentication:pk_identifier_id -> profile:fk_identifier_id; + + profile_scope [label=< + + + + +
PROFILE_SCOPE
profile_id
scope_id
+ >]; + profile:pk_profile_id -> profile_scope:fk_profile_id; + scope:pk_scope_id -> profile_scope:fk_scope_id; + + authentication [label=< + + + + + + + +
AUTHENTICATION
identifier_id
created
last_authenticated
identifier
credential
+ >]; + + resource [label=< + + + + + + +
RESOURCE
resource_id
description
created
secret
+ >]; + + almanac [label=< + + + + +
ALMANAC
event
epoch
+ >]; + +} diff --git a/src/db/sqlite/sql/schema/1.0.0/revert.sql b/src/db/sqlite/sql/schema/1.0.0/revert.sql new file mode 100644 index 0000000..20ccf68 --- /dev/null +++ b/src/db/sqlite/sql/schema/1.0.0/revert.sql @@ -0,0 +1,7 @@ +BEGIN; + DROP TABLE authentication; + DROP TABLE profile; + DROP TABLE token; + DROP TABLE scope; + DROP TABLE profile_scope; +COMMIT; \ No newline at end of file diff --git a/src/db/sqlite/sql/schema/init.sql b/src/db/sqlite/sql/schema/init.sql new file mode 100644 index 0000000..8e2a475 --- /dev/null +++ b/src/db/sqlite/sql/schema/init.sql @@ -0,0 +1,11 @@ +-- +BEGIN; + CREATE TABLE _meta_schema_version ( + major INTEGER NOT NULL CHECK (typeof(major) = 'integer'), + minor INTEGER NOT NULL CHECK (typeof(minor) = 'integer'), + patch INTEGER NOT NULL CHECK (typeof(patch) = 'integer'), + applied INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK (typeof(applied) = 'integer'), + PRIMARY KEY (major DESC, minor DESC, patch DESC) + ) WITHOUT ROWID; + INSERT INTO _meta_schema_version (major, minor, patch) VALUES (0, 0, 0); +COMMIT; diff --git a/src/db/sqlite/sql/scope-cleanup.sql b/src/db/sqlite/sql/scope-cleanup.sql new file mode 100644 index 0000000..4e9bd37 --- /dev/null +++ b/src/db/sqlite/sql/scope-cleanup.sql @@ -0,0 +1,8 @@ +-- Remove any un-used, non-permanent, non-manually-created scopes. +DELETE FROM scope WHERE scope_id NOT IN ( + SELECT scope_id FROM scope s + INNER JOIN profile_scope ps USING (scope_id) + UNION ALL + SELECT scope_id FROM scope s + INNER JOIN token_scope ts USING (scope_id) +) AND NOT (is_permanent OR is_manually_added) diff --git a/src/db/sqlite/sql/scope-delete.sql b/src/db/sqlite/sql/scope-delete.sql new file mode 100644 index 0000000..562bb58 --- /dev/null +++ b/src/db/sqlite/sql/scope-delete.sql @@ -0,0 +1,3 @@ +-- remove an impermanent scope +DELETE FROM scope + WHERE scope = :scope AND is_permanent = false diff --git a/src/db/sqlite/sql/scope-in-use.sql b/src/db/sqlite/sql/scope-in-use.sql new file mode 100644 index 0000000..1129caf --- /dev/null +++ b/src/db/sqlite/sql/scope-in-use.sql @@ -0,0 +1,13 @@ +-- return whether a scope is currently in use, either a profile setting or on a token +SELECT EXISTS ( + SELECT 1 FROM scope s + INNER JOIN profile_scope ps USING (scope_id) + WHERE s.scope = :scope + UNION All + SELECT 1 FROM scope s + INNER JOIN token_scope ts USING (scope_id) + WHERE s.scope = :scope + UNION All + SELECT 1 FROM scope s + WHERE s.scope = :scope AND s.is_permanent +) AS in_use diff --git a/src/db/sqlite/sql/scope-insert.sql b/src/db/sqlite/sql/scope-insert.sql new file mode 100644 index 0000000..78d26c0 --- /dev/null +++ b/src/db/sqlite/sql/scope-insert.sql @@ -0,0 +1,5 @@ +-- Insert an externally-provided scope, or ignore. +INSERT INTO scope + (scope) +VALUES (:scope) +ON CONFLICT (scope) DO NOTHING diff --git a/src/db/sqlite/sql/scope-upsert.sql b/src/db/sqlite/sql/scope-upsert.sql new file mode 100644 index 0000000..cb2770f --- /dev/null +++ b/src/db/sqlite/sql/scope-upsert.sql @@ -0,0 +1,16 @@ +-- +INSERT INTO scope ( + scope, + description, + application, + is_manually_added +) VALUES ( + :scope, + CASE WHEN :application IS NULL THEN '' ELSE :application END, + CASE WHEN :description IS NULL THEN '' ELSE :description END, + COALESCE(:manuallyAdded, false) +) ON CONFLICT (scope) DO UPDATE +SET + application = COALESCE(:application, EXCLUDED.application), + description = COALESCE(:description, EXCLUDED.description), + is_manually_added = EXCLUDED.is_manually_added OR COALESCE(:manuallyAdded, false) diff --git a/src/db/sqlite/sql/token-cleanup.sql b/src/db/sqlite/sql/token-cleanup.sql new file mode 100644 index 0000000..45f16b7 --- /dev/null +++ b/src/db/sqlite/sql/token-cleanup.sql @@ -0,0 +1,32 @@ +-- Remove tokens no longer in use. +WITH +-- reduce verbosity of using epoch timestamps +times AS ( + SELECT + (strftime('%s', 'now') - :codeLifespanSeconds) AS code_valid_offset, + strftime('%s', 'now') AS now +), +-- only clean after code has expired +cleanable_codes AS ( + SELECT t.code_id, t.is_token, t.is_revoked, t.expires, t.refresh_expires FROM token t, times + WHERE t.created < times.code_valid_offset +) +DELETE FROM token WHERE code_id IN ( + SELECT code_id FROM cleanable_codes c, times + WHERE + NOT c.is_token -- profile-only redemption + OR ( + c.is_token AND ( + c.is_revoked -- revoked + OR ( + c.expires < times.now AND ( + -- expired and unrefreshable + refresh_expires IS NULL + OR + -- expired and refresh expired + (refresh_expires IS NOT NULL AND refresh_expires < times.now) + ) + ) + ) + ) +) \ No newline at end of file diff --git a/src/db/sqlite/sql/token-get-by-code-id.sql b/src/db/sqlite/sql/token-get-by-code-id.sql new file mode 100644 index 0000000..c3ea5bb --- /dev/null +++ b/src/db/sqlite/sql/token-get-by-code-id.sql @@ -0,0 +1,20 @@ +-- +SELECT + t.code_id, + p.profile, + t.created, + t.expires, + t.refresh_expires, + t.refreshed, + t.duration, + t.refresh_duration, + t.refresh_count, + t.is_revoked, + t.is_token, + t.client_id, + t.profile_data, + a.identifier +FROM token t + INNER JOIN profile p USING (profile_id) + INNER JOIN authentication a USING (identifier_id) +WHERE code_id = :codeId diff --git a/src/db/sqlite/sql/token-refresh-revoke-by-code-id.sql b/src/db/sqlite/sql/token-refresh-revoke-by-code-id.sql new file mode 100644 index 0000000..52a851c --- /dev/null +++ b/src/db/sqlite/sql/token-refresh-revoke-by-code-id.sql @@ -0,0 +1,5 @@ +-- Revoke the refreshability for a token +UPDATE token SET + refresh_expires = NULL, + refresh_duration = NULL +WHERE code_id = :codeId diff --git a/src/db/sqlite/sql/token-revoke-by-code-id.sql b/src/db/sqlite/sql/token-revoke-by-code-id.sql new file mode 100644 index 0000000..27febe1 --- /dev/null +++ b/src/db/sqlite/sql/token-revoke-by-code-id.sql @@ -0,0 +1,5 @@ +-- +UPDATE token SET + is_revoked = true, + refresh_expires = NULL +WHERE code_id = :codeId diff --git a/src/db/sqlite/sql/token-scope-remove.sql b/src/db/sqlite/sql/token-scope-remove.sql new file mode 100644 index 0000000..94debac --- /dev/null +++ b/src/db/sqlite/sql/token-scope-remove.sql @@ -0,0 +1,3 @@ +-- +DELETE FROM token_scope +WHERE code_id = :codeId AND scope_id = (SELECT scope_id FROM scope WHERE scope = :scope) diff --git a/src/db/sqlite/sql/token-scope-set.sql b/src/db/sqlite/sql/token-scope-set.sql new file mode 100644 index 0000000..7b52dc6 --- /dev/null +++ b/src/db/sqlite/sql/token-scope-set.sql @@ -0,0 +1,3 @@ +-- +INSERT INTO token_scope (code_id, scope_id) + SELECT :codeId, scope_id FROM scope WHERE scope = :scope diff --git a/src/db/sqlite/sql/token-scopes-get-by-code-id.sql b/src/db/sqlite/sql/token-scopes-get-by-code-id.sql new file mode 100644 index 0000000..5bd7b30 --- /dev/null +++ b/src/db/sqlite/sql/token-scopes-get-by-code-id.sql @@ -0,0 +1,4 @@ +-- +SELECT s.scope FROM token_scope ts + INNER JOIN scope s USING (scope_id) + WHERE ts.code_id = :codeId diff --git a/src/db/sqlite/sql/tokens-get-by-identifier.sql b/src/db/sqlite/sql/tokens-get-by-identifier.sql new file mode 100644 index 0000000..63a3f2c --- /dev/null +++ b/src/db/sqlite/sql/tokens-get-by-identifier.sql @@ -0,0 +1,21 @@ +-- +SELECT + t.code_id, + p.profile, + t.created, + t.expires, + t.refresh_expires, + t.refreshed, + t.duration, + t.refresh_duration, + t.refresh_count, + t.is_revoked, + t.is_token, + t.client_id, + t.profile_data, + a.identifier +FROM token t + INNER JOIN profile p USING (profile_id) + INNER JOIN authentication a USING (identifier_id) +WHERE a.identifier = :identifier +ORDER BY max(t.created, COALESCE(t.refreshed, 0)) DESC diff --git a/src/enum.js b/src/enum.js new file mode 100644 index 0000000..8179941 --- /dev/null +++ b/src/enum.js @@ -0,0 +1,27 @@ +'use strict'; +const common = require('./common'); +const { Enum } = require('@squeep/api-dingus'); + +common.mergeEnum(Enum, { + Specification: 'living-standard-20220212', + + ContentType: { + ApplicationOctetStream: 'application/octet-stream', + }, + + Header: { + Authorization: 'Authorization', + Link: 'Link', + Location: 'Location', + Pragma: 'Pragma', + UserAgent: 'User-Agent', + WWWAuthenticate: 'WWW-Authenticate', + }, + + Chore: { + CleanTokens: 'cleanTokens', + CleanScopes: 'cleanScopes', + }, +}); + +module.exports = Enum; diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 0000000..9f4cb2b --- /dev/null +++ b/src/errors.js @@ -0,0 +1,21 @@ +'use strict'; + +const { Errors } = require('@squeep/api-dingus'); + +/** + * A stack-less exception for general data issues. + */ +class ValidationError extends Error { + constructor(...args) { + super(...args); + delete this.stack; + } + + get name() { + return this.constructor.name; + } +} +module.exports = { + ...Errors, + ValidationError, +}; \ No newline at end of file diff --git a/src/logger/data-sanitizers.js b/src/logger/data-sanitizers.js new file mode 100644 index 0000000..450842e --- /dev/null +++ b/src/logger/data-sanitizers.js @@ -0,0 +1,116 @@ +'use strict'; + +/** + * Scrub credential from POST login body data. + * @param {Object} data + * @param {Boolean} sanitize + * @returns {Boolean} + */ +function sanitizePostCredential(data, sanitize = true) { + let unclean = false; + + const credentialLength = data?.ctx?.parsedBody?.credential?.length; + if (credentialLength) { + unclean = true; + } + if (unclean && sanitize) { + data.ctx.parsedBody.credential = '*'.repeat(credentialLength); + } + + return unclean; +} + + +/** + * Reduce logged data about scopes from profilesScopes. + * For all referenced scopes, only include profiles list. + * Remove scopes without profile references from scopeIndex. + * @param {Object} data + * @param {Boolean} sanitize + */ +function reduceScopeVerbosity(data, sanitize = true) { + let unclean = false; + + const { + scopesEntries: ctxScopesEntries, + profilesEntries: ctxProfilesEntries, + needsSanitize: ctxNeedsSanitize, + } = _scopesFrom(data?.ctx?.profilesScopes); + + const { + scopesEntries: sessionScopesEntries, + profilesEntries: sessionProfilesEntries, + needsSanitize: sessionNeedsSanitize, + } = _scopesFrom(data?.ctx?.session); + + if (ctxNeedsSanitize || sessionNeedsSanitize) { + unclean = true; + } + if (unclean && sanitize) { + if (ctxNeedsSanitize) { + Object.assign(data.ctx.profilesScopes, _sanitizeProfilesScopes(ctxScopesEntries, ctxProfilesEntries)); + } + if (sessionNeedsSanitize) { + Object.assign(data.ctx.session, _sanitizeProfilesScopes(sessionScopesEntries, sessionProfilesEntries)); + } + } + + return unclean; +} + + +/** + * Return any scope entries on an object, and whether sanitization is needed. + * @param {Object=} obj + * @returns {Object} + */ +const _scopesFrom = (obj) => { + const scopesEntries = Object.entries(obj?.scopeIndex || {}); + const profilesEntries = Object.entries(obj?.profileScopes || {}); + const needsSanitize = scopesEntries.length || profilesEntries.length; + return { + scopesEntries, + profilesEntries, + needsSanitize, + }; +}; + + +/** + * @typedef {[String, Object]} ScopeEntry + */ +/** + * Return new list of entries with scrubbed scopeDetails. + * @param {ScopeEntry[]} entries + * @returns {ScopeEntry[]} + */ +const _scopeEntriesScrubber = (entries) => entries.map(([scopeName, scopeDetails]) => ([scopeName, { profiles: scopeDetails.profiles }])); + + +/** + * Create a new profilesScopes type object with scrubbed scope details. + * @param {ScopeEntry[]} scopesEntries + * @param {ScopeEntry[]} profilesEntries + * @returns {Object} + */ +const _sanitizeProfilesScopes = (scopesEntries, profilesEntries) => { + const referencedScopesEntries = scopesEntries.filter(([_scopeName, scopeDetails]) => scopeDetails?.profiles?.length); // eslint-disable-line no-unused-vars + const scrubbedScopesEntries = _scopeEntriesScrubber(referencedScopesEntries); + + const scrubbedProfilesEntries = profilesEntries.map(([profileName, profileScopes]) => { + const profileScopeEntries = Object.entries(profileScopes); + const scrubbedProfileScopeEntries = _scopeEntriesScrubber(profileScopeEntries); + const scrubbedProfileScopes = Object.fromEntries(scrubbedProfileScopeEntries); + return [profileName, scrubbedProfileScopes]; + }); + + return { + scopeIndex: Object.fromEntries(scrubbedScopesEntries), + profileScopes: Object.fromEntries(scrubbedProfilesEntries), + }; +}; + +module.exports = { + sanitizePostCredential, + reduceScopeVerbosity, +}; \ No newline at end of file diff --git a/src/logger/index.js b/src/logger/index.js new file mode 100644 index 0000000..7944cf6 --- /dev/null +++ b/src/logger/index.js @@ -0,0 +1,13 @@ +'use strict'; + +const BaseLogger = require('@squeep/logger-json-console'); +const dataSanitizers = require('./data-sanitizers'); + +class Logger extends BaseLogger { + constructor(options, ...args) { + super(options, ...args); + Array.prototype.push.apply(this.dataSanitizers, Object.values(dataSanitizers)); + } +} + +module.exports = Logger; \ No newline at end of file diff --git a/src/manager.js b/src/manager.js new file mode 100644 index 0000000..6c16414 --- /dev/null +++ b/src/manager.js @@ -0,0 +1,2015 @@ +'use strict'; + +const common = require('./common'); +const { ResponseError, ValidationError } = require('./errors'); +const Enum = require('./enum'); +const { Communication, Errors: CommunicationErrors } = require('@squeep/indieauth-helper'); +const Template = require('./template'); +const { MysteryBox } = require('@squeep/mystery-box'); +const DBErrors = require('./db/errors'); +const Chores = require('./chores'); +const { Publisher: QueuePublisher } = require('@squeep/amqp-helper'); + +const _fileScope = common.fileScope(__filename); + +// These are used during request ingestion and validation +const validBase64URLRE = /^[-A-Za-z0-9_]+$/; +const scopeSplitRE = / +/; + +const supportedCodeChallengeMethods = ['S256', 'SHA256']; + +class Manager { + constructor(logger, db, options) { + this.options = options; + this.logger = logger; + this.db = db; + this.chores = new Chores(logger, db, options); + this.communication = new Communication(logger, options); + if (options.queues.amqp.url) { + this.queuePublisher = new QueuePublisher(logger, options.queues.amqp); + } + this.mysteryBox = new MysteryBox(logger, options); + + // We need to know how the outside world sees us, to verify if a + // profile indicates us as the auth server. + // selfBaseUrl should already include proxy prefix and end with a / + this.selfAuthorizationEndpoint = options.dingus.selfBaseUrl + options.route.authorization; + } + + + /** + * Perform any async startup tasks. + */ + async initialize() { + if (this.queuePublisher) { + await this._connectQueues(); + } + } + + + async _connectQueues() { + await this.queuePublisher.connect(); + await this.queuePublisher.establishAMQPPlumbing(this.options.queues.ticketPublishName); + } + + + /** + * Add an error to a session, keeping only the most-severe code, but all descriptions. + * This error is sent along on the redirection back to client endpoint. + * @param {Object} ctx + * @param {Object} ctx.session + * @param {String[]=} ctx.session.errorDescriptions + * @param {String=} ctx.session.error + * @param {String} error + * @param {String} errorDescription + */ + static _setError(ctx, error, errorDescription) { + const errorPrecedence = [ // By increasing severity + 'invalid_scope', + 'unsupported_response_type', + 'access_denied', + 'unauthorized_client', + 'invalid_grant', + 'invalid_request', + 'temporarily_unavailable', + 'server_error', + ]; + if (!(errorPrecedence.includes(error))) { + throw new RangeError(`invalid error value '${error}'`); + } + if (!ctx.session.errorDescriptions) { + ctx.session.errorDescriptions = []; + } + if (!common.validError(errorDescription)) { + throw new RangeError(`invalid error description '${errorDescription}'`); + } + const isHigherPrecedence = errorPrecedence.indexOf(error) > errorPrecedence.indexOf(ctx.session.error); + if (!ctx.session.error || isHigherPrecedence) { + ctx.session.error = error; + } + if (isHigherPrecedence) { + ctx.session.errorDescriptions.unshift(errorDescription); + } else { + ctx.session.errorDescriptions.push(errorDescription); + } + } + + + /** + * Discourage caching of a response. + * OAuth 2.1 §3.2.3 + * The authorization server MUST include the HTTP Cache-Control response + * header field with a value of no-store in any response + * containing tokens, credentials, or other sensitive information. + * @param {http.ServerResponse} res + */ + static _sensitiveResponse(res) { + Object.entries({ + [Enum.Header.CacheControl]: 'no-store', + [Enum.Header.Pragma]: 'no-cache', + }).forEach(([k, v]) => res.setHeader(k, v)); + } + + + /** + * Sets params entries as url search parameters. + * @param {URL} url + * @param {Object} params + */ + static _setSearchParams(url, params) { + Object.entries(params).forEach((param) => url.searchParams.set(...param)); + } + + + /** + * Serve the informational root page. + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getRoot(res, ctx) { + const _scope = _fileScope('getRoot'); + this.logger.debug(_scope, 'called', { ctx }); + + res.end(Template.rootHTML(ctx, this.options)); + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Serve the metadata for this service. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getMeta(res, ctx) { + const _scope = _fileScope('getMeta'); + this.logger.debug(_scope, 'called', { ctx }); + + const base = this.options.dingus.selfBaseUrl; + const endpoint = (r) => `${base}${this.options.route[r]}`; // eslint-disable-line security/detect-object-injection + + const metadata = { + issuer: base, + 'authorization_endpoint': endpoint('authorization'), + 'token_endpoint': endpoint('token'), + ...(this.queuePublisher && { 'ticket_endpoint': endpoint('ticket') }), + 'introspection_endpoint': endpoint('introspection'), + 'introspection_endpoint_auth_methods_supported': ['Bearer'], + 'revocation_endpoint': endpoint('revocation'), + 'revocation_endpoint_auth_methods_supported': ['none'], + 'scopes_supported': ['profile', 'email'], // only advertise minimum IA scopes + 'response_types_supported': 'code', + 'grant_types_supported': [ + 'authorization_code', + 'refresh_token', + ...(this.queuePublisher && ['ticket'] || []), + ], + 'service_documentation': 'https://indieauth.spec.indieweb.org/', + 'code_challenge_methods_supported': supportedCodeChallengeMethods, + 'authorization_response_iss_parameter_supported': true, + 'userinfo_endpoint': endpoint('userinfo'), + }; + + res.end(JSON.stringify(metadata)); + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Process an authorization request from a client. + * User has authenticated, check if user matches profile, + * present user with consent form. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getAuthorization(res, ctx) { + const _scope = _fileScope('getAuthorization'); + this.logger.debug(_scope, 'called', { ctx }); + + ctx.session = Object.assign({}, ctx.session, { + errorDescriptions: [], + }); + + // Ingest and validate expected data, populating ctx.session. + await this._clientIdRequired(ctx); + Manager._redirectURIRequired(ctx); + Manager._responseTypeRequired(ctx); + Manager._stateRequired(ctx); + this._codeChallengeMethodRequired(ctx); + this._codeChallengeRequired(ctx); + this._scopeOptional(ctx); + await this._meOptional(ctx); + + if (!ctx.session.clientIdentifier || !ctx.session.redirectUri) { + // Do not redirect if either of these fields were invalid, just report error. + this.logger.debug(_scope, 'invalid request, not redirecting', { ctx }); + + // Set error response for template to render. + ctx.errors.push('Cannot redirect to client application.'); + ctx.errorContent = [ + 'There was an error in the request sent by the application attempting to authenticate you. Check with that service.', + ]; + res.statusCode = 400; + res.end(Template.authorizationErrorHTML(ctx, this.options)); + this.logger.info(_scope, 'bad request', { ctx }); + return; + } + + await this.db.context(async (dbCtx) => { + const profilesScopes = await this.db.profilesScopesByIdentifier(dbCtx, ctx.authenticationId); + Object.assign(ctx.session, { + profiles: [], + profileScopes: {}, + scopeIndex: {}, + }, profilesScopes); + }); // dbCtx + + if (!ctx.session.profiles.length) { + this.logger.error(_scope, 'identifier has no profiles', { ctx }); + Manager._setError(ctx, 'access_denied', 'Profile not valid for the authenticated user.'); + } + + if (!this._profileValidForIdentifier(ctx)) { + // if the hinted profile supplied in me does not match any known + // profile mappings for the authenticated identifier, remove the + // hint. UI will prompt to choose from available profiles. + this.logger.debug(_scope, 'removing provided me hint, not valid for identifier', { ctx }); + delete ctx.session.me; + } + + // Ugly support logic for allowing legacy non-pkce requests, for the micropub.rocks site until it is updated. + // Require both be missing to qualify as a legacy request, otherwise still fail. + const isMissingBothPKCE = (!ctx.session.codeChallengeMethod) && (!ctx.session.codeChallenge); + if (isMissingBothPKCE && this.options.manager.allowLegacyNonPKCE) { + ctx.notifications.push('
This request was submitted using an unsupported legacy format, which does not include PKCE safeguards! This is a security issue! This request should not be accepted!
'); + } else { + if (!ctx.session.codeChallenge) { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'code_challenge\''); + } + if (!ctx.session.codeChallengeMethod) { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'code_challenge_method\''); + } + } + + // If anything went wrong, redirect with error report. + if (ctx.session.error) { + // Valid redirect_url and client_id, errors hop back to them. + this.logger.debug(_scope, 'invalid request, redirecting', { ctx }); + + Manager._setSearchParams(ctx.session.redirectUri, { + 'state': ctx.session.state, + 'error': ctx.session.error, + 'error_description': ctx.session.errorDescriptions.join(', '), + }); + res.statusCode = 302; // Found + res.setHeader(Enum.Header.Location, ctx.session.redirectUri.href); + res.end(); + this.logger.info(_scope, 'bad request', { ctx }); + return; + } + + // Store the current state of this session, to be forwarded on to consent processing. + // This blob will be passed on as a form field in consent response. + ctx.session.persist = await this.mysteryBox.pack({ + id: common.requestId(), // codeId in database + clientId: ctx.session.clientId.href, + clientIdentifier: ctx.session.clientIdentifier, + redirectUri: ctx.session.redirectUri.href, + responseType: ctx.session.responseType, + state: ctx.session.state, + codeChallengeMethod: ctx.session.codeChallengeMethod, + codeChallenge: ctx.session.codeChallenge, + me: ctx.session.me, + profiles: ctx.session.profiles, + requestedScopes: ctx.session.scope, + authenticationId: ctx.authenticationId, + }); + + // Present authenticated user the option to submit consent + const content = Template.authorizationRequestHTML(ctx, this.options); + res.end(content); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Validates, fetches, and parses client_id url, populating clientIdentifier with client h-app data. + * @param {Object} ctx + */ + async _clientIdRequired(ctx) { + if (ctx.queryParams['client_id']) { + try { + ctx.session.clientId = await this.communication.validateClientIdentifier(ctx.queryParams['client_id']); + ctx.session.clientIdentifier = await this.communication.fetchClientIdentifier(ctx.session.clientId); + if (!ctx.session.clientIdentifier) { + Manager._setError(ctx, 'invalid_request', 'invalid client_id: could not fetch'); + throw new ValidationError('could not fetch'); + } + } catch (e) { + ctx.session.clientId = undefined; + if (e instanceof CommunicationErrors.ValidationError) { + Manager._setError(ctx, 'invalid_request', e.message); + } + Manager._setError(ctx, 'invalid_request', 'invalid value for parameter \'client_id\''); + } + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'client_id\''); + } + } + + + /** + * Ensure redirect_uri exists and is corroborated by clientIdentifier data. + * @param {Object} ctx + */ + static _redirectURIRequired(ctx) { + if (ctx.queryParams['redirect_uri']) { + try { + ctx.session.redirectUri = new URL(ctx.queryParams['redirect_uri']); + + if (ctx.session.clientId) { + // Either all these parts must match, or a specific alternative must be specified. + const redirectMatchesClientId = ['protocol', 'hostname', 'port'] + .map((p) => ctx.session.redirectUri[p] == ctx.session.clientId[p]) // eslint-disable-line security/detect-object-injection + .reduce((acc, match) => acc && match, true); + + // Check for alternate redirect_uri entries on client_id data if no initial match + if (!redirectMatchesClientId) { + const validRedirectUris = ctx.session?.clientIdentifier?.['rels']?.['redirect_uri'] || []; + if (!validRedirectUris.includes(ctx.session.redirectUri.href)) { + Manager._setError(ctx, 'invalid_request', 'redirect_uri not valid for that client_id'); + // Remove invalid redirect_uri from session; doing this eases error routing. + ctx.session.redirectUri = undefined; + } + } + } + } catch (e) { + Manager._setError(ctx, 'invalid_request', 'invalid value for parameter \'redirect_uri\''); + } + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'redirect_uri\''); + } + } + + + /** + * response_type must be valid + * @param {Object} ctx + */ + static _responseTypeRequired(ctx) { + ctx.session.responseType = ctx.queryParams['response_type']; + if (ctx.session.responseType) { + // Must be one of these types + if (!['code'].includes(ctx.session.responseType)) { + Manager._setError(ctx, 'unsupported_response_type', 'invalid value for parameter \'response_type\''); + } + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'response_type\''); + } + } + + + /** + * A state parameter must be present + * @param {Object} ctx + */ + static _stateRequired(ctx) { + ctx.session.state = ctx.queryParams['state']; + if (ctx.session.state) { + // No restrictions on content of this + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'state\''); + } + } + + + /** + * A code_challenge_method must be present and valid + * @param {Object} ctx + */ + _codeChallengeMethodRequired(ctx) { + ctx.session.codeChallengeMethod = ctx.queryParams['code_challenge_method']; + if (ctx.session.codeChallengeMethod) { + if (!supportedCodeChallengeMethods.includes(ctx.session.codeChallengeMethod)) { + Manager._setError(ctx, 'invalid_request', 'unsupported code_challenge_method'); + } + } else { + if (this.options.manager.allowLegacyNonPKCE) { + return; + } + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'code_challenge_method\''); + } + } + + + /** + * A code_challenge must be present + * @param {Object} ctx + */ + _codeChallengeRequired(ctx) { + ctx.session.codeChallenge = ctx.queryParams['code_challenge']; + if (ctx.session.codeChallenge) { + if (!validBase64URLRE.test(ctx.session.codeChallenge)) { + Manager._setError(ctx, 'invalid_request', 'invalid value for parameter \'code_challenge\''); + } + } else { + if (this.options.manager.allowLegacyNonPKCE) { + return; + } + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'code_challenge\''); + } + } + + + /** + * Scopes may be present, with one known combination limitation + * @param {Object} ctx + */ + _scopeOptional(ctx) { + const _scope = _fileScope('_scopeOptional'); + const scope = ctx.queryParams['scope']; + ctx.session.scope = []; + if (scope) { + const allScopes = scope.split(scopeSplitRE); + const validScopes = allScopes.filter((s) => common.validScope(s)); + ctx.session.scope.push(...validScopes); + if (allScopes.length != validScopes.length) { + const invalidScopes = allScopes.filter((s) => !common.validScope(s)); + this.logger.debug(_scope, 'client requested invalid scope', { ctx, invalidScopes }); + } + } + // If email scope is requested, profile scope must also be explicitly requested. + if (ctx.session.scope.includes('email') + && !ctx.session.scope.includes('profile')) { + Manager._setError(ctx, 'invalid_scope', 'cannot provide \'email\' scope without \'profile\' scope'); + } + } + + + /** + * Parses me, if provided + * @param {Object} ctx + */ + async _meOptional(ctx) { + const me = ctx.queryParams['me']; + if (me) { + try { + ctx.session.me = await this.communication.validateProfile(me); + } catch (e) { + ctx.session.me = undefined; + } + } + } + + + /** + * Ensure authenticated identifier matches profile. + * @param {Object} ctx + * @returns {Boolean} + */ + _profileValidForIdentifier(ctx) { + const _scope = _fileScope('_profileValidForIdentifier'); + + if (!ctx.session.me) { + this.logger.debug(_scope, 'no profile provided, cannot correlate', { ctx }); + return false; + } + + return ctx.session.profiles.includes(ctx.session.me.href); + } + + + /** + * Get numeric value from form field data. + * @param {*} ctx + * @param {String} field + * @param {String} customField + * @returns {Number=} + */ + _parseLifespan(ctx, field, customField) { + const _scope = _fileScope('_parseLifespan'); + + const presetValues = { + 'never': undefined, + '1d': 86400, + '1w': 86400 * 7, + '1m': 86400 * 31, + }; + const fieldValue = ctx.parsedBody[field]; // eslint-disable-line security/detect-object-injection + if (fieldValue in presetValues) { + return presetValues[fieldValue]; // eslint-disable-line security/detect-object-injection + } + + if (fieldValue === 'custom') { + const expiresSeconds = parseInt(ctx.parsedBody[customField], 10); // eslint-disable-line security/detect-object-injection + if (isFinite(expiresSeconds) && expiresSeconds > 0) { + return expiresSeconds; + } else { + this.logger.debug(_scope, 'invalid custom value', { ctx, field, customField }); + } + } + + this.logger.debug(_scope, 'invalid value', { ctx, field, customField }); + return undefined; + } + + + /** + * Validate any accepted scopes, ensure uniqueness, return as array. + * @param {Object} ctx + * @returns {String=} + */ + _parseConsentScopes(ctx) { + const _scope = _fileScope('_ingestConsentScopes'); + const acceptedScopesSet = new Set(); + const rejectedScopesSet = new Set(); + + const submittedScopes = common.ensureArray(ctx.parsedBody['accepted_scopes']) + .concat((ctx.parsedBody['ad_hoc_scopes'] || '').split(scopeSplitRE)); + submittedScopes.forEach((scope) => { + if (scope) { + (common.validScope(scope) ? acceptedScopesSet : rejectedScopesSet).add(scope); + } + }); + + // If email scope was accepted but profile was not, elide email scope + if (acceptedScopesSet.has('email') + && !acceptedScopesSet.has('profile')) { + acceptedScopesSet.delete('email'); + rejectedScopesSet.add('email (without profile)'); + } + + if (rejectedScopesSet.size) { + this.logger.debug(_scope, 'ignoring invalid scopes', { ctx, rejectedScopes: Array.from(rejectedScopesSet) }); + } + + return Array.from(acceptedScopesSet); + } + + + /** + * Parse and validate selected me is a valid profile option. + * @param {Object} ctx + * @returns {URL} + */ + _parseConsentMe(ctx) { + const _scope = _fileScope('_parseConsentMe'); + const selectedMe = ctx.parsedBody['me']; + try { + const me = new URL(selectedMe); + if (ctx.session.profiles.includes(me.href)) { + return me; + } else { + this.logger.debug(_scope, 'selected \'me\' profile not among available', { me, available: ctx.session.profiles, ctx }); + Manager._setError(ctx, 'invalid_request', 'invalid profile url'); + } + } catch (e) { + this.logger.debug(_scope, 'failed to parse selected \'me\' as url', { error: e, ctx }); + Manager._setError(ctx, 'invalid_request', 'invalid profile url'); + } + return undefined; + } + + + /** + * Get up-to-date profile data from selected profile endpoint. + * @param {Object} ctx + * @returns {Object} + */ + async _fetchConsentProfileData(ctx) { + const _scope = _fileScope('_fetchConsentProfileData'); + try { + const profile = await this.communication.fetchProfile(ctx.session.me); + if (!profile) { + this.logger.debug(_scope, 'no profile data at \'me\' endpoint', { ctx }); + Manager._setError(ctx, 'temporarily_unavailable', 'unable to retrieve profile'); + } else { + // Profile info gets persisted in code, only include known profile fields to help keep size down. + return common.pick(profile, [ + 'name', + 'photo', + 'url', + 'email', + ]); + } + } catch (e) { + this.logger.debug(_scope, 'failed to fetch \'me\' endpoint', { error: e, ctx }); + Manager._setError(ctx, 'temporarily_unavailable', 'could not reach profile endpoint'); + } + return undefined; + } + + + /** + * Ingest user consent response details, redirect as needed. + * Receives POST request from consent page, expecting these form fields: + * session - encrypted data collected from initial auth call + * accept - 'true' if consent was granted + * accepted_scopes - list of scopes to grant + * ad_hoc_scopes - additional scopes specified by user + * me - selected profile to identify as + * expires - optional lifespan + * expires-seconds - optional custom lifespan + * refresh - optional refresh lifespan + * refresh-seconds - optional custom refresh lifespan + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postConsent(res, ctx) { + const _scope = _fileScope('postConsent'); + this.logger.debug(_scope, 'called', { ctx }); + + // Ensure session exists, persisting any login session data. + ctx.session = Object.assign({}, ctx.session); + try { + // Recover the session established on initial auth request. + const oldSession = await this.mysteryBox.unpack(ctx.parsedBody['session']); + Object.assign(ctx.session, oldSession); + ctx.session.redirectUri = new URL(ctx.session.redirectUri); + ctx.session.clientId = new URL(ctx.session.clientId); + } catch (e) { + this.logger.debug(_scope, 'failed to unpack session', { error: e, ctx }); + Manager._setError(ctx, 'invalid_request', 'un-parsable data in authorization consent'); + } + + // If these are missing, we cannot proceed. + if (!ctx.session.clientId || !ctx.session.redirectUri) { + // Set error response for html template to render. + ctx.errors = [ + 'Cannot redirect to client application.', + ]; + ctx.errorContent = [ + 'There was an error in the request sent by the application attempting to authenticate you. Check with that service.', + ]; + res.statusCode = 400; + res.end(Template.authorizationErrorHTML(ctx, this.options)); + this.logger.info(_scope, 'bad request, cannot redirect', { ctx }); + return; + } + + // TODO: Should probably re-validate more unpacked session values, even though those should be trustable. + + // Check if we need to check anything else. + ctx.session.accept = (ctx.parsedBody['accept'] === 'true'); + if (!ctx.session.accept) { + this.logger.debug(_scope, 'consent denied', { ctx }); + Manager._setError(ctx, 'access_denied', 'authorization was not granted'); + } else { + // Ingest form data. + ctx.session.acceptedScopes = this._parseConsentScopes(ctx); + ctx.session.me = this._parseConsentMe(ctx); + ctx.session.profile = await this._fetchConsentProfileData(ctx); + ctx.session.tokenLifespan = this._parseLifespan(ctx, 'expires', 'expires-seconds'); + if (ctx.session.tokenLifespan) { + ctx.session.refreshLifespan = this._parseLifespan(ctx, 'refresh', 'refresh-seconds'); + } + } + + if (ctx.session.error) { + this.logger.debug(_scope, 'invalid request, redirecting', { ctx }); + + // Set all errors as parameters for client to interpret upon redirection. + Manager._setSearchParams(ctx.session.redirectUri, { + 'state': ctx.session.state, + 'error': ctx.session.error, + 'error_description': ctx.session.errorDescriptions.join(', '), + }); + res.statusCode = 302; // Found + res.setHeader(Enum.Header.Location, ctx.session.redirectUri.href); + res.end(); + this.logger.info(_scope, 'bad request, redirected', { ctx }); + return; + } + + // Consented, off we go. Keep all this session state as the code. + const code = await this.mysteryBox.pack({ + codeId: ctx.session.id, + codeChallengeMethod: ctx.session.codeChallengeMethod, + codeChallenge: ctx.session.codeChallenge, + clientId: ctx.session.clientId.href, + redirectUri: ctx.session.redirectUri.href, + acceptedScopes: ctx.session.acceptedScopes, + tokenLifespan: ctx.session.tokenLifespan, + refreshLifespan: ctx.session.refreshLifespan, + me: ctx.session.me.href, + profile: ctx.session.profile, + identifier: ctx.session.authenticatedIdentifier, // need this to pair with profile + minted: Date.now(), + }); + + Manager._setSearchParams(ctx.session.redirectUri, { + 'code': code, + 'state': ctx.session.state, + 'iss': this.options.dingus.selfBaseUrl, + }); + res.statusCode = 302; + res.setHeader(Enum.Header.Location, ctx.session.redirectUri.href); + res.end(); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Redeem a code for a profile url, and maybe more profile info. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postAuthorization(res, ctx) { + const _scope = _fileScope('postAuthorization'); + this.logger.debug(_scope, 'called', { ctx }); + + await this._ingestPostAuthorizationRequest(ctx); + + const response = { + me: ctx.session.me, + ...(ctx.session?.acceptedScopes?.includes('profile') && { profile: ctx.session.profile }), + scope: ctx.session.acceptedScopes, + }; + if (response.profile && !ctx.session?.acceptedScopes?.includes('email')) { + delete response.profile.email; + } + + if (!ctx.session.error) { + await this.db.context(async (dbCtx) => { + // Record code redemption without token. + const valid = await this.db.redeemCode(dbCtx, { + codeId: ctx.session.codeId, + created: new Date(), + isToken: false, + clientId: ctx.session.clientId.href, + profile: ctx.session.me, + identifier: ctx.session.identifier, + scopes: ctx.session.acceptedScopes, + lifespanSeconds: Math.ceil(this.options.manager.codeValidityTimeoutMs / 1000), + profileData: response.profile, + }); + if (!valid) { + this.logger.debug(_scope, 'code already redeemed', { ctx }); + Manager._setError(ctx, 'access_denied', 'code already redeemed'); + } + }); // dbCtx + } + + if (ctx.session.error) { + res.statusCode = 400; + res.end(JSON.stringify({ + 'error': ctx.session.error, + 'error_description': ctx.session.errorDescriptions.join(', '), + })); + this.logger.info(_scope, 'invalid request', { ctx }); + return; + } + + res.end(JSON.stringify(response)); + + this.logger.info(_scope, 'finished', { ctx, response }); + } + + + /** + * Ingest an incoming authorization redemption request, parsing fields + * onto a new session object on the context. + * @param {*} dbCtx + * @param {Object} ctx + */ + async _ingestPostAuthorizationRequest(ctx) { + const _scope = _fileScope('_ingestPostAuthorizationRequest'); + + ctx.session = Object.assign({}, ctx.session, { + errorDescriptions: [], + }); + + if (!ctx.parsedBody) { + this.logger.debug(_scope, 'no body data', { ctx }); + Manager._setError(ctx, 'invalid_request', 'missing data'); + } + + await this._restoreSessionFromCode(ctx); + this._checkSessionMatchingClientId(ctx); + this._checkSessionMatchingRedirectUri(ctx); + this._checkGrantType(ctx); + this._checkSessionMatchingCodeVerifier(ctx); + + if (!ctx.session.me || !ctx.session.minted) { + this.logger.debug(_scope, 'session missing fields', { ctx }); + Manager._setError(ctx, 'invalid_request', 'malformed code'); + return; + } + + const expires = new Date(ctx.session.minted + this.options.manager.codeValidityTimeoutMs); + const now = new Date(); + if (expires < now) { + this.logger.debug(_scope, 'code expired', { ctx }); + Manager._setError(ctx, 'invalid_request', 'code has expired'); + } + } + + + /** + * Unpack the session data from provided code overtop of context session .. + * @param {Object} ctx + */ + async _restoreSessionFromCode(ctx) { + const _scope = _fileScope('_restoreSessionFromCode'); + + const code = ctx.parsedBody['code']; + if (code) { + try { + const oldSession = await this.mysteryBox.unpack(code); + + // TODO: Validate unpacked fields better + const missingFields = [ + 'codeId', + 'codeChallengeMethod', + 'codeChallenge', + 'clientId', + 'redirectUri', + 'acceptedScopes', + 'me', + 'profile', + 'identifier', + 'minted', + ].filter((requiredField) => !(requiredField in oldSession)); + if (missingFields.length) { + if (this.options.manager.allowLegacyNonPKCE + && missingFields.length === 2 + && missingFields.includes('codeChallenge') + && missingFields.includes('codeChallengeMethod')) { + this.logger.debug(_scope, 'allowing legacy non-PKCE session', { ctx }); + } else { + this.logger.debug(_scope, 'unpacked code is missing required field', { missingFields, ctx }); + Manager._setError(ctx, 'invalid_request', 'code is not valid'); + } + } + + Object.assign(ctx.session, oldSession); + } catch (e) { + this.logger.debug(_scope, 'failed to parse code', { error: e, ctx }); + Manager._setError(ctx, 'invalid_request', 'code is not valid'); + } + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'code\''); + } + } + + + /** + * Ensure provided client_id matches session clientId. + * @param {Object} ctx + */ + _checkSessionMatchingClientId(ctx) { + const _scope = _fileScope('_checkSessionMatchingClientId'); + + let clientId = ctx.parsedBody['client_id']; + if (clientId) { + try { + clientId = new URL(clientId); + ctx.session.clientId = new URL(ctx.session.clientId); + } catch (e) { + this.logger.debug(_scope, 'un-parsable client_id url', { ctx }); + delete ctx.session.clientId; + Manager._setError(ctx, 'invalid_request', 'malformed client_id'); + return; + } + if (clientId.href !== ctx.session.clientId.href) { + this.logger.debug(_scope, 'clientId mismatched', { clientId, ctx }); + delete ctx.session.clientId; + Manager._setError(ctx, 'invalid_request', 'code does not belong to that client_id'); + } + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'client_id\''); + } + } + + + /** + * @param {Object} ctx + */ + _checkSessionMatchingRedirectUri(ctx) { + const _scope = _fileScope('_checkSessionMatchingClientId'); + + let redirectUri = ctx.parsedBody['redirect_uri']; + if (redirectUri) { + try { + redirectUri = new URL(redirectUri); + ctx.session.redirectUri = new URL(ctx.session.redirectUri); + } catch (e) { + this.logger.debug(_scope, 'un-parsable redirect_uri url', { ctx }); + delete ctx.session.redirectUri; + Manager._setError(ctx, 'invalid_request', 'malformed redirect_url'); + return; + } + if (redirectUri.href !== ctx.session.redirectUri.href) { + this.logger.debug(_scope, 'redirectUri mismatched', { redirectUri, ctx }); + delete ctx.session.redirectUri; + Manager._setError(ctx, 'invalid_request', 'code does not belong to that redirect_uri'); + } + } else { + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'redirect_uri\''); + } + } + + + /** + * Validate grant_type, either persist on session or set error. + * @param {Object} ctx + * @param {String[]} validGrantTypes + * @param {Boolean} treatEmptyAs + */ + _checkGrantType(ctx, validGrantTypes = ['authorization_code'], treatEmptyAs = 'authorization_code') { + const _scope = _fileScope('_checkGrantType'); + + const grantType = ctx.parsedBody['grant_type'] || treatEmptyAs; + if (!ctx.parsedBody['grant_type'] && treatEmptyAs) { + this.logger.debug(_scope, `missing grant_type, treating as ${treatEmptyAs}`, { ctx }); + } + if (validGrantTypes.includes(grantType)) { + ctx.session.grantType = grantType; + } else { + Manager._setError(ctx, 'invalid_request', 'grant_type not supported'); + } + } + + + /** + * @param {Object} ctx + */ + _checkSessionMatchingCodeVerifier(ctx) { + const _scope = _fileScope('_checkSessionMatchingCodeVerifier'); + + const codeVerifier = ctx.parsedBody['code_verifier']; + if (codeVerifier) { + try { + const valid = Communication.verifyChallenge(ctx.session.codeChallenge, codeVerifier, ctx.session.codeChallengeMethod); + if (!valid) { + this.logger.debug(_scope, 'challenge mismatched', { ctx }); + Manager._setError(ctx, 'invalid_request', 'challenge verification failed'); + } + } catch (e) /* istanbul ignore next */ { + this.logger.error(_scope, 'challenge validation failed', { error: e, ctx }); + Manager._setError(ctx, 'invalid_request', 'challenge verification failed'); + } + } else { + if (this.options.manager.allowLegacyNonPKCE + && !ctx.session.codeChallenge + && !ctx.session.codeChallengeMethod) { + this.logger.debug(_scope, 'allowing non-PKCE', { ctx }); + return; + } + Manager._setError(ctx, 'invalid_request', 'missing required parameter \'code_verifier\''); + } + } + + + /** + * Attempt to revoke a token. + * @param {*} dbCtx + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async _revokeToken(dbCtx, res, ctx) { + const _scope = _fileScope('_revokeToken'); + try { + const token = ctx.parsedBody['token']; + const tokenTypeHint = ctx.parsedBody['token_type_hint']; + switch (tokenTypeHint) { + case undefined: + break; + case 'access_token': + break; + case 'refresh_token': + break; + default: + this.logger.debug(_scope, 'unknown token_type_hint', { ctx }); + } + if (!token) { + throw new ValidationError('Token Missing'); + } + ctx.token = await this.mysteryBox.unpack(token); + if (!(ctx.token?.c || ctx.token?.rc)) { + throw new ValidationError('Token Invalid'); + } + } catch (e) { + this.logger.debug(_scope, 'invalid token', { error: e, ctx }); + res.statusCode = 400; + res.end(); + this.logger.info(_scope, 'finished, revoke request not valid', { error: e, ctx }); + return; + } + + try { + if (ctx.token.c) { + await this.db.tokenRevokeByCodeId(dbCtx, ctx.token.c); + } else { + await this.db.tokenRefreshRevokeByCodeId(dbCtx, ctx.token.rc); + } + } catch (e) { + if (e instanceof DBErrors.UnexpectedResult) { + res.statusCode = 404; + res.end(); + this.logger.info(_scope, 'finished, no token to revoke', { error: e, ctx }); + return; + } + this.logger.error(_scope, 'revoke token failed', { error: e, ctx }); + throw e; + } + + res.end(); + this.logger.info(_scope, 'finished, token revoked', { ctx }); + } + + + /** + * Legacy token validation flow. + * @param {*} dbCtx + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async _validateToken(dbCtx, req, res, ctx) { + const _scope = _fileScope('_validateToken'); + await this._checkTokenValidationRequest(dbCtx, req, ctx); + if (ctx.bearer.isValid) { + Manager._sensitiveResponse(res); + res.end(JSON.stringify({ + me: ctx.token.profile, + 'client_id': ctx.token.clientId, + scope: ctx.token.scopes, + })); + this.logger.info(_scope, 'finished, token validated', { ctx }); + } else { + const responseErrorParts = ['Bearer']; + const error = ctx.session.error ? `error="${ctx.session.error}"` : ''; + if (error) { + responseErrorParts.push(error); + } + const errorDescription = ctx.session.errorDescriptions ? `error_description="${ctx.session.errorDescriptions.join(', ')}"` : ''; + if (errorDescription) { + responseErrorParts.push(errorDescription); + } + res.setHeader(Enum.Header.WWWAuthenticate, responseErrorParts.join(', ')); + this.logger.info(_scope, 'finished, token not validated', { ctx }); + throw new ResponseError(Enum.ErrorResponse.Unauthorized); + } + } + + + /** + * Given a list of newly-requested scopes, return a list of scopes + * from previousScopes which are not in requestedScopes. + * @param {String[]} previousScopes + * @param {String[]} requestedScopes + * @returns {String[]} + */ + static _scopeDifference(previousScopes, requestedScopes) { + const scopesToRemove = []; + const existingScopesSet = new Set(previousScopes); + const validRequestedScopes = requestedScopes.filter((s) => common.validScope(s)); + const requestedScopesSet = new Set(validRequestedScopes); + existingScopesSet.forEach((s) => { + if (!requestedScopesSet.has(s)) { + scopesToRemove.push(s); + } + }); + return scopesToRemove; + } + + + /** + * Redeem a refresh token for a new token. + * @param {*} dbCtx + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async _refreshToken(dbCtx, req, res, ctx) { + const _scope = _fileScope('_refreshToken'); + this.logger.debug(_scope, 'called', { ctx }); + + const { + 'client_id': clientId, + scope, + } = ctx.parsedBody; + + try { + ctx.refreshToken = await this.mysteryBox.unpack(ctx.parsedBody['refresh_token']); + } catch (e) { + this.logger.debug(_scope, 'failed to unpack token', { error: e, ctx }); + } + + const now = new Date(); + const nowEpoch = common.dateToEpoch(now); + + await this.db.transaction(dbCtx, async (txCtx) => { + if (ctx.refreshToken?.rc) { + ctx.token = await this.db.tokenGetByCodeId(txCtx, ctx.refreshToken.rc); + } + + if (!ctx.token) { + this.logger.debug(_scope, 'no token to refresh', { ctx }); + throw new ResponseError(Enum.ErrorResponse.NotFound); + } + + if (!ctx.token.refreshExpires + || ctx.token.refreshExpires < now) { + this.logger.debug(_scope, 'token not refreshable or refresh expired', { ctx }); + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + const refreshExpiresEpoch = common.dateToEpoch(ctx.token.refreshExpires); + if (ctx.refreshToken.exp < refreshExpiresEpoch) { + this.logger.debug(_scope, 'token already refreshed', { ctx }); + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + if (clientId !== ctx.token.clientId) { + this.logger.debug(_scope, 'client identifier mismatch', { ctx }); + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + const scopesToRemove = scope ? Manager._scopeDifference(ctx.token.scopes, scope.split(scopeSplitRE)) : []; + if (scopesToRemove.length) { + this.logger.debug(_scope, 'scope reduction requested', { ctx, scopesToRemove }); + } + + const refreshedTokenData = await this.db.refreshCode(txCtx, ctx.refreshToken.rc, now, scopesToRemove); + if (refreshedTokenData) { + Object.assign(ctx.token, refreshedTokenData); + } else { + this.logger.debug(_scope, 'could not refresh token', { ctx }); + throw new ResponseError(Enum.ErrorResponse.NotFound); + } + }); // tx + + const [token, refreshToken] = await Promise.all([ + { + c: ctx.token.codeId, + ts: nowEpoch, + }, + { + rc: ctx.token.codeId, + ts: nowEpoch, + exp: nowEpoch + ctx.token.refreshDuration, + }, + ].map(this.mysteryBox.pack)); + + const response = { + 'access_token': token, + 'token_type': 'Bearer', + ...(ctx.token.duration && { 'expires_in': nowEpoch + ctx.token.duration }), + ...(refreshToken && { 'refresh_token': refreshToken }), + scope: ctx.token.scopes.join(' '), + me: ctx.session.me, + ...(ctx.token.scopes.includes('profile') && { profile: ctx.token.profileData }), + }; + if (ctx.token.scopes.includes('profile') && !ctx.token.scopes.includes('email')) { + delete response?.profile?.email; + } + + Manager._sensitiveResponse(res); + res.end(JSON.stringify(response)); + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Generate a new ticket for later redemption. + * @param {Object} payload + * @param {} payload.subject deliver ticket to this endpoint + * @param {} payload.resource url the redeemed ticket is valid for accessing + * @param {String[]} payload.scopes list of scopes assigned to ticket + * @param {String} payload.identifier user generating ticket + * @param {} payload.profile profile of user generating ticket + * @param {Number} payload.ticketLifespanSeconds ticket redeemable for this long + * @returns {String} + */ + async _mintTicket({ subject, resource, scopes, identifier, profile, ticketLifespanSeconds }) { + const _scope = _fileScope('_mintTicket'); + this.logger.debug(_scope, 'called', { subject, resource, scopes, identifier, profile, ticketLifespanSeconds }); + + const nowEpoch = common.dateToEpoch(); + return this.mysteryBox.pack({ + c: common.requestId(), + iss: nowEpoch, + exp: nowEpoch + ticketLifespanSeconds, + sub: subject, + res: resource, + scope: scopes, + ident: identifier, + profile: profile, + }); + } + + + /** + * @typedef Ticket + * @property {String} codeId + * @property {Date} issued + * @property {Date} expires + * @property {URL} subject + * @property {URL} resource + * @property {String[]} scopes + * @property {String} identifier + * @property {URL} profile + */ + /** + * + * @param {String} ticket + * @returns {Ticket} + */ + async _unpackTicket(ticket) { + const ticketObj = await this.mysteryBox.unpack(ticket); + return { + codeId: ticketObj.c, + issued: new Date(ticketObj.iss * 1000), + expires: new Date(ticketObj.exp * 1000), + subject: new URL(ticketObj.sub), + resource: new URL(ticketObj.res), + scopes: ticketObj.scope, + identifier: ticketObj.ident, + profile: new URL(ticketObj.profile), + }; + } + + + /** + * Redeem a ticket for a token. + * @param {*} dbCtx + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async _ticketAuthToken(dbCtx, req, res, ctx) { + const _scope = _fileScope('_ticketAuthToken'); + this.logger.debug(_scope, 'called', { ctx }); + + try { + ctx.ticket = await this._unpackTicket(ctx.parsedBody['ticket']); + } catch (e) { + this.logger.debug(_scope, 'failed to unpack ticket', { error: e, ctx }); + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + const now = new Date(); + if (now > ctx.ticket.expires) { + this.logger.debug(_scope, 'ticket has expired', { ctx }); + throw new ResponseError(Enum.ErrorResponse.Forbidden, { reason: 'Ticket has expired.', expired: ctx.ticket.expires }); + } + + const nowEpoch = common.dateToEpoch(now); + const token = await this.mysteryBox.pack({ + c: ctx.ticket.codeId, + ts: nowEpoch, + }); + + const response = { + 'access_token': token, + 'token_type': 'Bearer', + scope: ctx.ticket.scopes.join(' '), + me: ctx.ticket.profile.href, + }; + + const isValid = await this.db.redeemCode(dbCtx, { + created: now, + codeId: ctx.ticket.codeId, + isToken: true, + clientId: ctx.ticket.subject.href, + resource: ctx.ticket.resource.href, + profile: ctx.ticket.profile.href, + identifier: ctx.ticket.identifier, + scopes: ctx.ticket.scopes, + }); + if (!isValid) { + this.logger.debug(_scope, 'redemption failed, already redeemed', { ctx }); + throw new ResponseError(Enum.ErrorResponse.Forbidden); + } + + Manager._sensitiveResponse(res); + res.end(JSON.stringify(response)); + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Redeem a code for a token. + * @param {*} dbCtx + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async _codeToken(dbCtx, req, res, ctx) { + const _scope = _fileScope('_codeToken'); + this.logger.debug(_scope, 'called', { ctx }); + + await this._restoreSessionFromCode(ctx); + await this._checkSessionMatchingRedirectUri(ctx); + + if (ctx.session.error) { + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + /** + * Note the creation date here rather than in database, so that stored + * expire dates are ensured to match those packed in tokens. + * An alternative would be to return the date generated by the database, + * but then we would need to hold the transaction open while minting the + * tokens to ensure success. Perhaps that would be worth it, but for now + * this is how it is. + */ + const now = new Date(); + const nowEpoch = common.dateToEpoch(now); + const tokenMinters = []; + + tokenMinters.push(this.mysteryBox.pack({ + c: ctx.session.codeId, + ts: nowEpoch, + ...(ctx.session.tokenLifespan && { exp: nowEpoch + ctx.session.tokenLifespan }), + })); + + if (ctx.session.tokenLifespan + && ctx.session.refreshLifespan) { + tokenMinters.push(this.mysteryBox.pack({ + rc: ctx.session.codeId, + ts: nowEpoch, + exp: nowEpoch + ctx.session.refreshLifespan, + })); + } + + const [token, refreshToken] = await Promise.all(tokenMinters); + + const response = { + 'access_token': token, + 'token_type': 'Bearer', + ...(ctx.session.tokenLifespan && { 'expires_in': nowEpoch + ctx.session.tokenLifespan }), + ...(refreshToken && { 'refresh_token': refreshToken }), + scope: ctx.session.acceptedScopes.join(' '), + me: ctx.session.me, + ...(ctx.session.acceptedScopes.includes('profile') && { profile: ctx.session.profile }), + }; + if (!ctx.session.acceptedScopes.includes('email') && response.profile) { + delete response.profile.email; + } + + const isValid = await this.db.redeemCode(dbCtx, { + created: now, + codeId: ctx.session.codeId, + isToken: true, + clientId: ctx.session.clientId, + profile: ctx.session.me, + identifier: ctx.session.identifier, + scopes: ctx.session.acceptedScopes, + lifespanSeconds: ctx.session.tokenLifespan, + refreshLifespanSeconds: ctx.session.refreshLifespan, + profileData: response.profile, + }); + if (!isValid) { + this.logger.debug(_scope, 'redemption failed, already redeemed', { ctx }); + throw new ResponseError(Enum.ErrorResponse.Forbidden); + } + + Manager._sensitiveResponse(res); + res.end(JSON.stringify(response)); + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Issue, refresh, or validate a token. + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postToken(req, res, ctx) { + const _scope = _fileScope('postToken'); + this.logger.debug(_scope, 'called', { ctx }); + + ctx.session = Object.assign({}, ctx.session); + + await this.db.context(async (dbCtx) => { + + // Is this a (legacy) revocation request? + if (ctx.parsedBody['action'] === 'revoke') { + await this._revokeToken(dbCtx, res, ctx); + return; + } + + // Is this a (legacy) token validation request? + if (req.getHeader(Enum.Header.Authorization)) { + await this._validateToken(dbCtx, res, ctx); + return; + } + + const validGrantTypes = [ + 'authorization_code', + 'refresh_token', + ...(this.queuePublisher && ['ticket'] || []), + ]; + this._checkGrantType(ctx, validGrantTypes, 'authorization_code'); + + switch (ctx.session.grantType) { + case 'refresh_token': + return this._refreshToken(dbCtx, req, res, ctx); + + case 'ticket': + return this._ticketAuthToken(dbCtx, req, res, ctx); + + case 'authorization_code': + return this._codeToken(dbCtx, req, res, ctx); + + default: + this.logger.debug(_scope, 'unknown grant_type', { ctx }); + Manager._setError(ctx, 'invalid_request', 'grant_type not supported'); + } + + // Only way of getting here is due to error. + throw new ResponseError(Enum.ErrorResponse.BadRequest); + }); // dbCtx + } + + + /** + * Ingest token from authorization header, setting ctx.bearer.isValid appropriately. + * ctx.bearer not set if auth method not recognized. + * This is for legacy validation on token endpoint. + * @param {*} dbCtx + * @param {http.ClientRequest} req + * @param {Object} ctx + */ + async _checkTokenValidationRequest(dbCtx, req, ctx) { + const _scope = _fileScope('_checkTokenValidationRequest'); + const authHeader = req.getHeader(Enum.Header.Authorization); + + if (authHeader) { + const [authMethod, authString] = common.splitFirst(authHeader, ' ', ''); + switch (authMethod.toLowerCase()) { // eslint-disable-line sonarjs/no-small-switch + case 'bearer': { + ctx.bearer = { + isValid: false, + }; + try { + Object.assign(ctx.bearer, await this.mysteryBox.unpack(authString)); + } catch (e) { + this.logger.debug(_scope, 'failed to unpack token', { ctx }); + Manager._setError(ctx, 'invalid_request', 'invalid token'); + return; + } + if (!ctx.bearer.c) { + this.logger.debug(_scope, 'incomplete token', { ctx }); + Manager._setError(ctx, 'invalid_request', 'invalid token'); + return; + } + + try { + ctx.token = await this.db.tokenGetByCodeId(dbCtx, ctx.bearer.c); + } catch (e) { + this.logger.error(_scope, 'failed to look up token', { error: e, ctx }); + throw e; + } + + if (!ctx.token) { + this.logger.debug(_scope, 'no token found', { ctx }); + Manager._setError(ctx, 'invalid_request', 'invalid token'); + return; + } + + if (!ctx.token.isRevoked + && ctx.token.expires > new Date()) { + ctx.bearer.isValid = true; + } + break; + } + + default: + this.logger.debug(_scope, 'unknown authorization scheme', { ctx }); + return; + } + } + } + + + /** + * Accept an unsolicited ticket proffering. + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postTicket(req, res, ctx) { + const _scope = _fileScope('postTicket'); + this.logger.debug(_scope, 'called', { ctx }); + + if (!this.queuePublisher) { + this.logger.debug(_scope, 'ticket endpoint not configured', { ctx }); + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + const queueName = this.options.queues.ticketPublishName; + const { ticket, resource, subject } = ctx.parsedBody; + + try { + new URL(resource); + } catch (e) { + this.logger.debug(_scope, 'unparsable resource', { ticket, resource, subject, ctx }); + throw new ResponseError(Enum.ErrorResponse.BadRequest); + } + + await this.db.context(async (dbCtx) => { + const isValidProfile = await this.db.profileIsValid(dbCtx, subject); + if (!isValidProfile) { + this.logger.debug(_scope, 'invalid subject', { ticket, resource, subject, ctx }); + throw new ResponseError(Enum.ErrorResponse.NotFound, { error: 'subject not under our purview' }); + } + + try { + const result = await this.queuePublisher.publish(queueName, { ticket, resource, subject }); + this.logger.debug(_scope, 'accepted ticket offer', { queueName, ticket, resource, subject, ctx, result }); + } catch (e) { + this.logger.error(_scope, 'failed to publish ticket to queue', { error: e, queueName, ticket, resource, subject, ctx }); + throw e; // return a 500 + } + + res.statusCode = 202; + res.end(); + this.logger.info(_scope, 'finished', { resource, subject, ctx }); + }); + } + + + /** + * Validate a token and return data about it. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postIntrospection(res, ctx) { + const _scope = _fileScope('postIntrospection'); + this.logger.debug(_scope, 'called', { ctx }); + + let response = { + active: false, + }; + + const tokenIsTicket = (ctx.parsedBody['token_hint_type'] || '').toLowerCase() === 'ticket'; + + try { + const token = ctx.parsedBody['token']; + if (tokenIsTicket) { + ctx.token = await this._unpackTicket(token); + } else { + ctx.token = await this.mysteryBox.unpack(token); + } + } catch (e) { + this.logger.debug(_scope, 'failed to unpack token', { error: e, ctx }); + } + + if (ctx.token + && !tokenIsTicket) { + await this.db.context(async (dbCtx) => { + ctx.token = await this.db.tokenGetByCodeId(dbCtx, ctx.token.c); + }); // dbCtx + } + + if (ctx.token + && !ctx.token.isRevoked) { + // fuss around for postgres 'Infinity' date + const expiresMs = (ctx.token.expires instanceof Date) ? ctx.token.expires.getTime() : ctx.token.expires; + if (expiresMs > Date.now()) { + response = { + active: true, + me: ctx.token.profile, + ...(ctx.token.clientId && { 'client_id': ctx.token.clientId }), + scope: ctx.token.scopes.join(' '), + iat: common.dateToEpoch(ctx.token.created || ctx.token.issued), + ...(isFinite(expiresMs) && { exp: Math.ceil(expiresMs / 1000) }), + ...(tokenIsTicket && { 'token_type': 'ticket' }), + }; + } + } + + Manager._sensitiveResponse(res); + res.end(JSON.stringify(response)); + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Revoke a token or refresh token. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postRevocation(res, ctx) { + const _scope = _fileScope('postRevocation'); + this.logger.debug(_scope, 'called', { ctx }); + + try { + await this.db.context(async (dbCtx) => { + await this._revokeToken(dbCtx, res, ctx); + }); + } catch (e) { + this.logger.error(_scope, 'failed', { error: e, ctx }); + throw e; + } + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Profile information for a token. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postUserInfo(res, ctx) { + const _scope = _fileScope('postUserInfo'); + this.logger.debug(_scope, 'called', { ctx }); + + const token = ctx.parsedBody['token']; + if (!token) { + res.statusCode = 400; + res.end('"invalid_request"'); + this.logger.info(_scope, 'finished, invalid request', { ctx }); + return; + } + + try { + ctx.token = await this.mysteryBox.unpack(ctx.parsedBody['token']); + } catch (e) { + this.logger.debug(_scope, 'failed to unpack token', { error: e, ctx }); + } + + if (ctx.token) { + await this.db.context(async (dbCtx) => { + ctx.token = await this.db.tokenGetByCodeId(dbCtx, ctx.token.c); + }); // dbCtx + } + + if (!ctx.token + || ctx.token.isRevoked + // || tokenIsExpired(token) + ) { + res.statusCode = 401; + res.end('"invalid_token"'); + this.logger.info(_scope, 'finished, invalid token', { ctx }); + return; + } + + if (!ctx.token.scopes.includes('profile')) { + res.statusCode = 403; + res.end('"insufficient_scope"'); + this.logger.info(_scope, 'finished, insufficient scope', { ctx }); + return; + } + + const response = { + ...ctx.token.profile, + }; + if (!ctx.token.scopes.includes('email')) { + delete response.email; + } + + Manager._sensitiveResponse(res); + res.end(JSON.stringify(response)); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Show admin interface, allowing manipulation of profiles and scopes. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getAdmin(res, ctx) { + const _scope = _fileScope('getAdmin'); + this.logger.debug(_scope, 'called', { ctx }); + + const identifier = ctx.session.authenticatedIdentifier; + + await this.db.context(async (dbCtx) => { + ctx.profilesScopes = await this.db.profilesScopesByIdentifier(dbCtx, identifier); + ctx.tokens = await this.db.tokensGetByIdentifier(dbCtx, identifier); + }); // dbCtx + + res.end(Template.adminHTML(ctx, this.options)); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Process admin interface events. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postAdmin(res, ctx) { + const _scope = _fileScope('postAdmin'); + this.logger.debug(_scope, 'called', { ctx }); + + await this.db.context(async (dbCtx) => { + const identifier = ctx.session.authenticatedIdentifier; + const action = ctx?.parsedBody?.['action'] || ''; + + if (action === 'save-scopes') { + // Update the convenience scopes set for profiles. + // Expect 'scopes-' with value of array of scopes + const profileKeys = ctx.parsedBody && Object.keys(ctx.parsedBody) + .filter((k) => k.startsWith('scopes-')); + try { + await this.db.transaction(dbCtx, async (txCtx) => { + await Promise.all( + /* For each scopes-profile submitted, set those. */ + profileKeys.map((profileKey) => { + /* elide 'scope-' prefix to get the profile */ + const profile = profileKey.slice(7); + /* (should validate profile here) */ + + /* remove invalid scopes from submitted list */ + const scopes = ctx.parsedBody[profileKey].filter((scope) => scope && common.validScope(scope)); // eslint-disable-line security/detect-object-injection + return this.db.profileScopesSetAll(txCtx, profile, scopes); + }), + ); + }); // txCtx + ctx.notifications.push('Profile/Scope Availability Matrix updated!'); + } catch (e) { + this.logger.error(_scope, 'did not set profile scopes', { error: e, ctx }); + ctx.errors.push('Failed to update profile scopes.'); + } + + } else if (action === 'new-profile') { + // Validate and create a new profile uri. + let profile; + const profileUri = ctx.parsedBody['profile']; + try { + profile = await this.communication.validateProfile(profileUri); + } catch (e) { + this.logger.debug(_scope, 'invalid profile url', { error: e, ctx }); + ctx.errors.push(`'${profileUri}' is not a valid profile URI.${(e instanceof CommunicationErrors.ValidationError) ? ('(' + e.message + ')') : ''}`); + } + if (profile) { + // Validate profile uri + const profileData = await this.communication.fetchProfile(profile); + if (profileData.metadata.authorizationEndpoint !== this.selfAuthorizationEndpoint) { + this.logger.debug(_scope, 'profile does not list this server as auth', { profileData, ctx }); + ctx.errors.push('Requested profile does not list this service, not adding.'); + } else { + try { + await this.db.transaction(dbCtx, async (txCtx) => { + await this.db.profileIdentifierInsert(txCtx, profile.href, identifier); + await this.db.profileScopesSetAll(txCtx, profile.href, ['profile', 'email']); + }); // txCtx + ctx.notifications.push('Profile added!'); + } catch (e) { + this.logger.error(_scope, 'did not insert profile', { error: e, ctx }); + ctx.errors.push('Failed to add profile.'); + } + } + } + + } else if (action === 'new-scope') { + // Add or update a manually-added convenience scope. + const { scope, application = '', description = '' } = ctx.parsedBody; + if (scope) { + if (!common.validScope(scope)) { + ctx.errors.push(`"${scope}" is not a valid scope name, did not add it.`); + } else { + try { + await this.db.scopeUpsert(dbCtx, scope, application, description, true); + ctx.notifications.push('Scope List updated!'); + } catch (e) { + this.logger.error(_scope, 'did not upsert scope', { error: e, scope, application, description, ctx }); + ctx.errors.push('Failed to update scope.'); + } + } + } + + } else if (action.startsWith('delete-scope-')) { + // Remove a manually-added convenience scope. + const scope = decodeURIComponent(action.slice(13)); + if (scope) { + try { + const deleted = await this.db.scopeDelete(dbCtx, scope); + if (deleted) { + ctx.notifications.push('Scope deleted.'); + } else { + ctx.notifications.push('Unable to delete scope.'); + } + } catch (e) { + this.logger.error(_scope, 'did not delete scope', { error: e, scope, ctx }); + ctx.errors.push('Failed to delete scope.'); + } + } + + } else if (action.startsWith('revoke-')) { + // Revoke an active token. + const codeId = action.slice(8); + if (codeId) { + try { + await this.db.tokenRevokeByCodeId(dbCtx, codeId, identifier); + ctx.notifications.push('Revoked token!'); + } catch (e) { + this.logger.error(_scope, 'did not revoke token', { error: e, codeId, identifier, ctx }); + ctx.errors.push('Unable to revoke token.'); + } + } + + } else if (action) { + ctx.errors.push(`Do not know how to '${action}'.`); + } + + ctx.profilesScopes = await this.db.profilesScopesByIdentifier(dbCtx, identifier); + ctx.tokens = await this.db.tokensGetByIdentifier(dbCtx, identifier); + }); // dbCtx + + res.end(Template.adminHTML(ctx, this.options)); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Show ticket proffer interface. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getAdminTicket(res, ctx) { + const _scope = _fileScope('getAdminTicket'); + this.logger.debug(_scope, 'called', { ctx }); + + const identifier = ctx.session.authenticatedIdentifier; + + await this.db.context(async (dbCtx) => { + ctx.profilesScopes = await this.db.profilesScopesByIdentifier(dbCtx, identifier); + ctx.profiles = ctx.profilesScopes.profiles; + ctx.scopes = Object.keys(ctx.profilesScopes.scopeIndex); + }); // dbCtx + + res.end(Template.adminTicketHTML(ctx, this.options)); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Handle ticket proffer interface submission. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async postAdminTicket(res, ctx) { + const _scope = _fileScope('postAdminTicket'); + this.logger.debug(_scope, 'called', { ctx }); + + switch (ctx.parsedBody['action']) { // eslint-disable-line sonarjs/no-small-switch + case 'proffer-ticket': { + const identifier = ctx.session.authenticatedIdentifier; + [ + { ctxProp: 'ticketProfileUrl', bodyParam: 'profile', err: 'Invalid Profile URL selected.' }, + { ctxProp: 'ticketResourceUrl', bodyParam: 'resource', err: 'Invalid Resource URL.' }, + { ctxProp: 'ticketSubjectUrl', bodyParam: 'subject', err: 'Invalid Recipient URL.' }, + ].forEach((param) => { + try { + ctx[param.ctxProp] = new URL(ctx.parsedBody[param.bodyParam]); + } catch (e) { + this.logger.debug(_scope, `invalid ${param.bodyParam}`, { ctx }); + ctx.errors.push(param.err); + } + }); + + const subjectData = await this.communication.fetchProfile(ctx.ticketSubjectUrl); + if (!subjectData?.metadata?.ticketEndpoint) { + this.logger.debug(_scope, 'subject has no ticket endpoint', { ctx }); + ctx.errors.push('Recipient does not list a ticket endpoint to deliver to.'); + } else { + try { + ctx.ticketEndpointUrl = new URL(subjectData.metadata.ticketEndpoint); + } catch (e) { + this.logger.debug(_scope, 'subject has invalid ticket endpoint', { error: e, ctx }); + ctx.errors.push(`Recipient lists an invalid ticket endpoint, cannot deliver. (${e})`); + } + } + + const scopesSet = new Set(); + const rawScopes = [ + ...(common.ensureArray(ctx.parsedBody['scopes'])), + ...((ctx.parsedBody['adhoc'] || '').split(scopeSplitRE)), + ].filter((scope) => scope); + rawScopes.forEach((scope) => { + if (common.validScope(scope)) { + scopesSet.add(scope); + } else { + this.logger.debug(_scope, 'invalid adhoc scope', { scope, ctx }); + ctx.errors.push(`'${scope}' is not a valid scope.`); + } + }); + ctx.ticketScopes = [...scopesSet]; + const actionScopes = ctx.ticketScopes.filter((scope) => !['profile', 'email'].includes(scope)); + if (!actionScopes.length) { + this.logger.debug(_scope, 'no valid scopes included', { ctx }); + ctx.errors.push('At least one actionable scope must be included.'); + } + + if (!ctx.errors.length) { + const ticketData = { + subject: ctx.ticketSubjectUrl.href, + resource: ctx.ticketResourceUrl.href, + scopes: ctx.ticketScopes, + identifier, + profile: ctx.ticketProfileUrl.href, + ticketLifespanSeconds: this.options.manager.ticketLifespanSeconds, + }; + const ticket = await this._mintTicket(ticketData); + + await this.db.context(async (dbCtx) => { + // re-populate form fields + ctx.profilesScopes = await this.db.profilesScopesByIdentifier(dbCtx, identifier); + + // TODO: queue ticket for delivery/retry to subject instead of trying immediately + // ctx.notifications.push('Success! Ticket will be delivered!'); + + this.logger.debug(_scope, 'ticket created', { ctx, ticketData, subjectData }); + + try { + const result = await this.communication.deliverTicket(ctx.ticketEndpointUrl, ctx.ticketResourceUrl, ctx.ticketSubjectUrl, ticket); + ctx.notifications.push(`Success! Ticket was delivered. (${result?.statusText})`); + this.logger.info(_scope, 'ticket delivered', { ctx, result }); + } catch (e) { + this.logger.error(_scope, 'failed to deliver ticket', { ctx, error: e }); + ctx.errors.push(`Failed to deliver ticket. (${e})`); + } + + }); // dbCtx + + } else { + // populate form fields again + await this.db.context(async (dbCtx) => { + ctx.profilesScopes = await this.db.profilesScopesByIdentifier(dbCtx, identifier); + ctx.scopes = Object.keys(ctx.profilesScopes.scopeIndex); + }); // dbCtx + } + + break; + } + + default: + this.logger.debug(_scope, 'unknown action', { ctx }); + } + + res.end(Template.adminTicketHTML(ctx, this.options)); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * Report on generally uninteresting backend information. + * Also allow a few event invocations. + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getAdminMaintenance(res, ctx) { + const _scope = _fileScope('getAdminMaintenance'); + this.logger.debug(_scope, 'called', { ctx }); + + const maintenanceTasks = []; + + await this.db.context(async (dbCtx) => { + + Object.values(Enum.Chore).forEach((chore) => { + if (chore in ctx.queryParams) { + maintenanceTasks.push( + this.chores.runChore(chore, 0), // Provide arg to force chore run. + ); + ctx.notifications.push(`Running maintenance chore "${chore}".`); + } + }); + + await Promise.all(maintenanceTasks); + + ctx.almanac = await this.db.almanacGetAll(dbCtx); + }); // dbCtx + + const winnowChoreEntry = ([name, value]) => [name, common.pick(value, ['intervalMs', 'nextSchedule'])]; + ctx.chores = Object.fromEntries( + Object.entries(this.chores.chores).map(winnowChoreEntry), + ); + + res.end(Template.adminMaintenanceHTML(ctx, this.options)); + + this.logger.info(_scope, 'finished', { ctx }); + } + + + /** + * + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async getHealthcheck(res, ctx) { + const _scope = _fileScope('getHealthcheck'); + this.logger.debug(_scope, 'called', { ctx }); + await this.db.healthCheck(); + res.end(); + } + +} + +module.exports = Manager; \ No newline at end of file diff --git a/src/service.js b/src/service.js new file mode 100644 index 0000000..5fbf950 --- /dev/null +++ b/src/service.js @@ -0,0 +1,544 @@ +'use strict'; + +/** + * Here we extend the base API server to define our routes and any route-specific + * behavior (middlewares) before handing off to the manager. + */ + +const path = require('path'); +const { Dingus } = require('@squeep/api-dingus'); +const common = require('./common'); +const Manager = require('./manager'); +const { Authenticator, SessionManager } = require('@squeep/authentication-module'); +const { ResourceAuthenticator } = require('@squeep/resource-authentication-module'); +const { TemplateHelper: { initContext } } = require('@squeep/html-template-helper'); +const Enum = require('./enum'); + +const _fileScope = common.fileScope(__filename); + +class Service extends Dingus { + constructor(logger, db, options) { + super(logger, { + ...options.dingus, + ignoreTrailingSlash: false, + }); + + this.staticPath = path.normalize(path.join(__dirname, '..', 'static')); + this.manager = new Manager(logger, db, options); + this.authenticator = new Authenticator(logger, db, options); + this.sessionManager = new SessionManager(logger, this.authenticator, options); + this.resourceAuthenticator = new ResourceAuthenticator(logger, db, options); + this.loginPath = `${options.dingus.proxyPrefix}/admin/login`; + + // N.B. /admin routes not currently configurable + const route = (r) => `/${options.route[r]}`; // eslint-disable-line security/detect-object-injection + + // Service discovery + this.on(['GET', 'HEAD'], route('metadata'), this.handlerGetMeta.bind(this)); + // Also respond with metadata on well-known oauth2 endpoint if base has no prefix + if ((options?.dingus?.selfBaseUrl?.match(/\//g) || []).length === 3) { + this.on(['GET', 'HEAD'], '/.well-known/oauth-authorization-server', this.handlerGetMeta.bind(this)); + } + + // Primary endpoints + this.on(['GET'], route('authorization'), this.handlerGetAuthorization.bind(this)); + this.on(['POST'], route('authorization'), this.handlerPostAuthorization.bind(this)); + this.on(['POST'], route('consent'), this.handlerPostConsent.bind(this)); + this.on(['POST'], route('revocation'), this.handlerPostRevocation.bind(this)); + this.on(['POST'], route('ticket'), this.handlerPostTicket.bind(this)); + this.on(['POST'], route('token'), this.handlerPostToken.bind(this)); + + // Resource endpoints + this.on('POST', route('introspection'), this.handlerPostIntrospection.bind(this)); + this.on('POST', route('userinfo'), this.handlerPostUserInfo.bind(this)); + + // Information page about service + this.on(['GET', 'HEAD'], '/', this.handlerGetRoot.bind(this)); + + // Give load-balancers something to check + this.on(['GET', 'HEAD'], route('healthcheck'), this.handlerGetHealthcheck.bind(this)); + + // These routes are intended for accessing static content during development. + // In production, a proxy server would likely handle these first. + this.on(['GET', 'HEAD'], '/static', this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/static/`); + this.on(['GET', 'HEAD'], '/static/', this.handlerGetStaticFile.bind(this), 'index.html'); + this.on(['GET', 'HEAD'], '/static/:file', this.handlerGetStaticFile.bind(this)); + this.on(['GET', 'HEAD'], '/favicon.ico', this.handlerGetStaticFile.bind(this), 'favicon.ico'); + this.on(['GET', 'HEAD'], '/robots.txt', this.handlerGetStaticFile.bind(this), 'robots.txt'); + + // Profile and token management for authenticated sessions + this.on(['GET', 'HEAD'], '/admin', this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/admin/`); + this.on(['GET', 'HEAD'], '/admin/', this.handlerGetAdmin.bind(this)); + this.on(['POST'], '/admin/', this.handlerPostAdmin.bind(this)); + + // Ticket-proffering interface for authenticated sessions + this.on(['GET', 'HEAD'], '/admin/ticket', this.handlerGetAdminTicket.bind(this)); + this.on(['POST'], '/admin/ticket', this.handlerPostAdminTicket.bind(this)); + + // User authentication and session establishment + this.on(['GET', 'HEAD'], '/admin/login', this.handlerGetAdminLogin.bind(this)); + this.on(['POST'], '/admin/login', this.handlerPostAdminLogin.bind(this)); + this.on(['GET'], '/admin/logout', this.handlerGetAdminLogout.bind(this)); + + // Page for upkeep info et cetera + this.on(['GET', 'HEAD'], '/admin/maintenance', this.handlerGetAdminMaintenance.bind(this)); + + } + + + /** + * Perform any async startup tasks. + */ + async initialize() { + await this.manager.initialize(); + } + + + /** + * Do a little more on each request. + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async preHandler(req, res, ctx) { + await super.preHandler(req, res, ctx); + ctx.url = req.url; // Persist this for logout redirect + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetAdminLogin(req, res, ctx) { + const _scope = _fileScope('handlerGetAdminLogin'); + this.logger.debug(_scope, 'called', { req, ctx }); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + await this.sessionManager.getAdminLogin(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostAdminLogin(req, res, ctx) { + const _scope = _fileScope('handlerPostAdminLogin'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + await this.authenticator.sessionOptionalLocal(req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.sessionManager.postAdminLogin(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetAdminLogout(req, res, ctx) { + const _scope = _fileScope('handlerGetAdminLogout'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + await this.authenticator.sessionOptionalLocal(req, res, ctx); + + await this.sessionManager.getAdminLogout(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetAdmin(req, res, ctx) { + const _scope = _fileScope('handlerGetAdmin'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + if (await this.authenticator.sessionRequiredLocal(req, res, ctx, this.loginPath)) { + await this.manager.getAdmin(res, ctx); + } + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostAdmin(req, res, ctx) { + const _scope = _fileScope('handlerPostAdmin'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + if (await this.authenticator.sessionRequiredLocal(req, res, ctx, this.loginPath)) { + await this.ingestBody(req, res, ctx); + await this.manager.postAdmin(res, ctx); + } + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetAdminTicket(req, res, ctx) { + const _scope = _fileScope('handlerGetAdminTicket'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + if (await this.authenticator.sessionRequiredLocal(req, res, ctx, this.loginPath)) { + await this.manager.getAdminTicket(res, ctx); + } + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostAdminTicket(req, res, ctx) { + const _scope = _fileScope('handlerPostAdminTicket'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + if (await this.authenticator.sessionRequiredLocal(req, res, ctx, this.loginPath)) { + await this.ingestBody(req, res, ctx); + await this.manager.postAdminTicket(res, ctx); + } + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetMeta(req, res, ctx) { + const _scope = _fileScope('handlerGetMeta'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(responseTypes, req, res, ctx); + + await this.authenticator.sessionOptionalLocal(req, res, ctx); + + await this.manager.getMeta(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetAuthorization(req, res, ctx) { + const _scope = _fileScope('handlerGetAuthorization'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + if (await this.authenticator.sessionRequiredLocal(req, res, ctx, this.loginPath)) { + await this.manager.getAuthorization(res, ctx); + } + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostAuthorization(req, res, ctx) { + const _scope = _fileScope('handlerPostAuthorization'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + this.setResponseType(responseTypes, req, res, ctx); + + await this.authenticator.sessionOptionalLocal(req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postAuthorization(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostConsent(req, res, ctx) { + const _scope = _fileScope('handlerPostConsent'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + // This isn't specified as required as any valid payload carries intrinsic auth data. + await this.authenticator.sessionOptionalLocal(req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postConsent(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostTicket(req, res, ctx) { + const _scope = _fileScope('handlerPostTicket'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + this.setResponseType(responseTypes, req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postTicket(req, res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostToken(req, res, ctx) { + const _scope = _fileScope('handlerPostToken'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + this.setResponseType(responseTypes, req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postToken(req, res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostRevocation(req, res, ctx) { + const _scope = _fileScope('handlerPostRevocation'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + this.setResponseType(responseTypes, req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postRevocation(req, res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostIntrospection(req, res, ctx) { + const _scope = _fileScope('handlerPostIntrospection'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + await this.resourceAuthenticator.required(req, res, ctx); + + this.setResponseType(responseTypes, req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postIntrospection(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerPostUserInfo(req, res, ctx) { + const _scope = _fileScope('handlerPostUserInfo'); + this.logger.debug(_scope, 'called', { req, ctx }); + + const responseTypes = [ + Enum.ContentType.ApplicationJson, + Enum.ContentType.TextPlain, + ]; + + this.setResponseType(responseTypes, req, res, ctx); + + await this.ingestBody(req, res, ctx); + + await this.manager.postUserInfo(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetRoot(req, res, ctx) { + const _scope = _fileScope('handlerGetRoot'); + const responseTypes = [ + Enum.ContentType.TextHTML, + ]; + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(responseTypes, req, res, ctx); + + await this.authenticator.sessionOptionalLocal(req, res, ctx); + + await this.manager.getRoot(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetHealthcheck(req, res, ctx) { + const _scope = _fileScope('handlerGetHealthcheck'); + this.logger.debug(_scope, 'called', { req, ctx }); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + await this.manager.getHealthcheck(res, ctx); + } + + + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerGetAdminMaintenance(req, res, ctx) { + const _scope = _fileScope('handlerGetAdminMaintenance'); + this.logger.debug(_scope, 'called', { req, ctx }); + + initContext(ctx); + + Dingus.setHeadHandler(req, res, ctx); + + this.setResponseType(this.responseTypes, req, res, ctx); + + if (await this.authenticator.sessionRequiredLocal(req, res, ctx, this.loginPath)) { + await this.manager.getAdminMaintenance(res, ctx); + } + } + + + /** + * FIXME: This doesn't seem to be working as envisioned. Maybe override render error method instead??? + * Intercept this and redirect if we have enough information, otherwise default to framework. + * The redirect attempt should probably be contained in a Manager method, but here it is for now. + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} res + * @param {Object} ctx + */ + async handlerInternalServerError(req, res, ctx) { + const _scope = _fileScope('handlerInternalServerError'); + this.logger.debug(_scope, 'called', { req, ctx }); + + if (ctx?.session?.redirectUri && ctx?.session?.clientIdentifier) { + Object.entries({ + ...(ctx.session.state && { 'state': ctx.session.state }), + 'error': 'server_error', + 'error_description': 'An internal server error occurred', + }).forEach(([name, value]) => ctx.session.redirectUri.searchParams.set(name, value)); + res.statusCode = 302; // Found + res.setHeader(Enum.Header.Location, ctx.session.redirectUri.href); + res.end(); + return; + } + + super.handlerInternalServerError(req, res, ctx); + } + + +} + +module.exports = Service; + diff --git a/src/template/admin-html.js b/src/template/admin-html.js new file mode 100644 index 0000000..085b95a --- /dev/null +++ b/src/template/admin-html.js @@ -0,0 +1,214 @@ +'use strict'; + +/** + * This renders the administrative view for an account, + * allowing for adding profile URIs, custom scope bundles, + * and management of issued tokens. + */ + +const th = require('./template-helper'); + + +function renderProfileLI(profile) { + return `\t
  • ${profile}
  • `; +} + + +function renderProfileScopeIndicator(profile, scope, selected) { + const checked = selected ? ' checked' : ''; + return `\t\t +\t\t\t +\t\t`; +} + +function renderScopeRow(scope, details, profiles) { + return `\t +${(profiles || []).map((profile) => renderProfileScopeIndicator(profile, scope, details.profiles.includes(profile))).join('\n')} +\t\t