From 9e1b10c3c339b94b92e56921c6c06ddd6d6c6e3d Mon Sep 17 00:00:00 2001 From: "P. Douglas Reeder" Date: Thu, 22 Feb 2024 16:10:54 -0500 Subject: [PATCH] WIP: Adds signup form --- .github/workflows/test-and-lint.yml | 8 +- bin/www | 11 +- lib/app.js | 17 +- lib/routes/signup.js | 51 +++ lib/streaming_stores/S3.js | 98 ++++++ lib/views/signup.html | 2 +- notes/S3 streaming store.md | 31 ++ package-lock.json | 510 ++++++++++++++++++++++++---- package.json | 1 + spec/armadietto/a_signup_spec.js | 47 +++ spec/modular/m_signup.spec.js | 52 +++ spec/not_found.spec.js | 2 +- spec/root.spec.js | 2 +- spec/runner.js | 2 + spec/signup.spec.js | 131 +++++++ spec/static_files.spec.js | 4 +- spec/streaming_store.spec.js | 40 +++ spec/streaming_stores/S3.spec.js | 24 ++ 18 files changed, 950 insertions(+), 83 deletions(-) create mode 100644 lib/routes/signup.js create mode 100644 lib/streaming_stores/S3.js create mode 100644 notes/S3 streaming store.md create mode 100644 spec/armadietto/a_signup_spec.js create mode 100644 spec/modular/m_signup.spec.js create mode 100644 spec/signup.spec.js create mode 100644 spec/streaming_store.spec.js create mode 100644 spec/streaming_stores/S3.spec.js diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml index 5af17ca9..69c4a5fd 100644 --- a/.github/workflows/test-and-lint.yml +++ b/.github/workflows/test-and-lint.yml @@ -1,9 +1,9 @@ name: test-and-lint on: push: - branches: [ master ] + branches: [ master, modular ] pull_request: - branches: [ master ] + branches: [ master, modular ] jobs: build: name: node.js @@ -13,9 +13,9 @@ jobs: # Support LTS versions based on https://nodejs.org/en/about/releases/ node-version: ['18', '20', '21'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/bin/www b/bin/www index 61d79876..acbc8c88 100755 --- a/bin/www +++ b/bin/www @@ -1,6 +1,5 @@ #!/usr/bin/env node -const app = require('../lib/app'); const http = require('http'); const fs = require("fs"); const path = require("path"); @@ -29,12 +28,18 @@ try { configureLogger(conf.logging); +conf.basePath ||= ''; +if (conf.basePath && !conf.basePath.startsWith('/')) { conf.basePath = '/' + conf.basePath; } +process.env.basePath = conf.basePath; + +const app = require('../lib/app'); + const port = normalizePort( process.env.PORT || conf.http?.port || '8000'); app.set('port', port); app.locals.title = "Modular Armadietto"; -app.locals.basePath = ''; -// rendering should set host: getHost(req) +app.locals.basePath = conf.basePath; +// rendering should set locals.host: getHost(req) app.locals.host = conf.http?.host + (port ? ':' + port : ''); app.locals.signup = conf.allow_signup; diff --git a/lib/app.js b/lib/app.js index eb648cd6..dd147831 100644 --- a/lib/app.js +++ b/lib/app.js @@ -2,10 +2,14 @@ const express = require('express'); const path = require('path'); const logger = require('morgan'); const indexRouter = require('./routes/index'); +const signupRouter = require('./routes/signup'); const errorPage = require('./util/errorPage'); const helmet = require('helmet'); const shorten = require('./util/shorten'); +let basePath = process.env.basePath || ''; +if (basePath && !basePath.startsWith('/')) { basePath = '/' + basePath; } + const app = express(); // view engine setup @@ -38,16 +42,23 @@ app.use(helmet({ })); // app.use(express.json()); // app.use(express.urlencoded({ extended: false })); -app.use('/assets', express.static(path.join(__dirname, 'assets'))); +app.use(`${basePath}/assets`, express.static(path.join(__dirname, 'assets'))); + +app.use(`${basePath}/`, indexRouter); -app.use('/', indexRouter); +app.use(`${basePath}/signup`, signupRouter); // catches 404 and forwards to error handler -app.use(function (req, res, next) { +app.use(basePath, function (req, res, next) { const name = req.path.slice(1); errorPage(req, res, 404, { title: 'Not Found', message: `“${name}” doesn't exist` }); }); +// redirect for paths outside the app +app.use(function (req, res, next) { + res.status(302).set('Location', basePath).end(); +}); + // error handler app.use(function (err, req, res, _next) { errorPage(req, res, err.status || 500, { diff --git a/lib/routes/signup.js b/lib/routes/signup.js new file mode 100644 index 00000000..05c6d3ae --- /dev/null +++ b/lib/routes/signup.js @@ -0,0 +1,51 @@ +const express = require('express'); +const router = express.Router(); +const getHost = require('../util/getHost'); +const errorPage = require('../util/errorPage'); +const { getLogger } = require('../logger'); + +const DISABLED_LOCALS = { title: 'Forbidden', message: 'Signing up is not allowed currently' }; +const DISABLED_LOG_NOTE = 'signups disabled'; + +/* initial entry */ +router.get('/', function (req, res) { + if (req.app?.locals?.signup) { + res.render('signup.html', { + title: 'Signup', + params: {}, + error: null, + host: getHost(req) + }); + } else { + errorPage(req, res, 403, DISABLED_LOCALS, DISABLED_LOG_NOTE); + } +}); + +/* submission or re-submission */ +router.post('/', + express.urlencoded({ extended: false }), + async function (req, res) { + if (req.app?.locals?.signup) { + try { + const store = req.app?.get('streaming_store'); + const bucketName = await store.createUser(req.body); + getLogger().notice(`created bucket “${bucketName}” for user “${req.body.username}”`); + res.status(201).render('signup-success.html', { + title: 'Signup Success', + params: req.body, + host: getHost(req) + }); + } catch (err) { + res.status(409).render('signup.html', { + title: 'Signup Failure', + params: req.body, + error: err, + host: getHost(req) + }); + } + } else { + errorPage(req, res, 403, DISABLED_LOCALS, DISABLED_LOG_NOTE); + } + }); + +module.exports = router; diff --git a/lib/streaming_stores/S3.js b/lib/streaming_stores/S3.js new file mode 100644 index 00000000..3047dd22 --- /dev/null +++ b/lib/streaming_stores/S3.js @@ -0,0 +1,98 @@ +const Minio = require('minio'); +const core = require('../stores/core'); +const { getLogger } = require('../logger'); + +// const FILE_PREFIX = 'remoteStorageBlob/'; +// const AUTH_PREFIX = 'remoteStorageAuth/'; + +/** uses the min.io client to connect to any S3-compatible storage that supports versioning */ +class S3 { + #minioClient; + + /** Using the default arguments connects you to a public server where anyone can read and delete your data! */ + constructor (endPoint = 'play.min.io', port = 9000, accessKey = 'Q3AM3UQ867SPQQA43P2F', secretKey = 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG') { + this.#minioClient = new Minio.Client({ + endPoint, + port, + accessKey, + secretKey, + useSSL: !['localhost', '10.0.0.2', '127.0.0.1'].includes(endPoint) + }); + } + + async createUser (params) { + const errors = core.validateUser(params); + if (errors.length > 0) { + const msg = errors.map(err => err.message).join('|'); + throw new Error(msg); + } + + const bucketName = params.username; + const exists = await this.#minioClient.bucketExists(bucketName); + if (exists) { + throw new Error(`Username “${params.username}” is already taken`); + } else { + await this.#minioClient.makeBucket(bucketName); + await this.#minioClient.setBucketVersioning(bucketName, + { Status: 'Enabled', ExcludedPrefixes: [{ Prefix: 'permissions' }, { Prefix: 'meta' }] }); + getLogger().info(`bucket ${bucketName} created.`); + return bucketName; + } + } + + /** + * Deletes all of user's files and the bucket. NOT REVERSIBLE. + * @param username + * @returns {Promise} number of files deleted + */ + async deleteUser (username) { + if (!await this.#minioClient.bucketExists(username)) { return 0; } + + return new Promise((resolve, reject) => { + const GROUP_SIZE = 100; + const objectVersions = []; + let numRequested = 0; let numRemoved = 0; let isReceiveComplete = false; + + const removeObjectVersions = async () => { + const group = objectVersions.slice(0); + objectVersions.length = 0; + numRequested += group.length; + await this.#minioClient.removeObjects(username, group); + numRemoved += group.length; + + if (isReceiveComplete && numRemoved === numRequested) { + await this.#minioClient.removeBucket(username); // will fail if any object versions remain + resolve(numRemoved); + } + }; + + const removeObjectVersionsAndBucket = async err => { + try { + isReceiveComplete = true; + await removeObjectVersions(); + if (err) { + reject(err); + } + } catch (err2) { + reject(err || err2); + } + }; + + const objectVersionStream = this.#minioClient.listObjects(username, '', true, { IncludeVersion: true }); + objectVersionStream.on('data', async item => { + try { + objectVersions.push(item); + if (objectVersions.length >= GROUP_SIZE) { + await removeObjectVersions(); + } + } catch (err) { // keeps going + getLogger().error(`while deleting user “${username}” object version ${JSON.stringify(item)}:`, err); + } + }); + objectVersionStream.on('error', removeObjectVersionsAndBucket); + objectVersionStream.on('end', removeObjectVersionsAndBucket); + }); + } +} + +module.exports = S3; diff --git a/lib/views/signup.html b/lib/views/signup.html index e5cdb596..bd2b752e 100644 --- a/lib/views/signup.html +++ b/lib/views/signup.html @@ -1,6 +1,6 @@ <%- include('header.html'); %> -

Sign up

+

Sign up

<% if (error) { %> diff --git a/notes/S3 streaming store.md b/notes/S3 streaming store.md new file mode 100644 index 00000000..9cfa3999 --- /dev/null +++ b/notes/S3 streaming store.md @@ -0,0 +1,31 @@ +# S3-compatible Streaming Stores + +Streaming Stores can only be used with the modular server. + +You should be able to connect to any S3-compatible service that supports versioning. Tested services include: + +* min.io (both self-hosted and cloud) + + +Configure the store by passing to the constructor the endpoint (host name), access key (admin user name) and secret key (password). For non-Amazon providers, you may need to pass in a port number as well. You can provide these however you like, but typically they are stored in these environment variables: + +* S3_HOSTNAME +* S3_PORT +* S3_ACCESS_KEY +* S3_SECRET_KEY + +Creating a client then resembles: + +```javascript +const store = new S3(process.env.S3_HOSTNAME, + process.env.S3_PORT ? parseInt(process.env.S3_PORT) : undefined, + process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY); +``` + +This one access key is used to create a bucket for each user. +The bucket name is the username. +Buckets can be administered using the service's tools, such as a webapp console or command-line tools. +The bucket can contain non-remoteStorage blobs outside these prefixes: + +* remoteStorageBlob/ +* remoteStorageAuth/ diff --git a/package-lock.json b/package-lock.json index 912249b7..fa8c5849 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "helmet": "^7.1.0", "http-errors": "^2.0.0", "lockfile": "^1.0.4", + "minio": "^7.1.3", "mkdirp": "^1.0.4", "morgan": "^1.10.0", "pug": "^3.0.2", @@ -282,6 +283,12 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -559,9 +566,9 @@ } }, "node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -573,7 +580,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -646,6 +652,14 @@ "node": ">=8" } }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -717,12 +731,25 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" + }, "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/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -1091,6 +1118,14 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -2011,6 +2046,27 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", + "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -2076,6 +2132,14 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -2143,7 +2207,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -2606,6 +2669,21 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -2681,7 +2759,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2753,6 +2830,20 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2900,7 +2991,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.14" }, @@ -2993,6 +3083,11 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/json-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-stream/-/json-stream-1.0.0.tgz", + "integrity": "sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==" + }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -3040,6 +3135,11 @@ "signal-exit": "^3.0.2" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3171,6 +3271,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minio": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minio/-/minio-7.1.3.tgz", + "integrity": "sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^0.2.13", + "fast-xml-parser": "^4.2.2", + "ipaddr.js": "^2.0.1", + "json-stream": "^1.0.0", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml": "^1.0.1", + "xml2js": "^0.5.0" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minio/node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "engines": { + "node": ">= 10" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -3710,7 +3842,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -3886,6 +4017,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3937,6 +4085,19 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4123,6 +4284,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -4308,6 +4474,14 @@ "node": ">=10" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -4324,6 +4498,14 @@ "node": ">= 0.8" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4424,6 +4606,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/superagent": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", @@ -4486,6 +4673,14 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4713,6 +4908,18 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4742,6 +4949,17 @@ "node": ">=0.10.0" } }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4777,7 +4995,6 @@ "version": "1.1.14", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.6", "call-bind": "^1.0.5", @@ -4826,19 +5043,6 @@ "node": ">= 12.0.0" } }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/winston/node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -4847,19 +5051,6 @@ "node": ">=8" } }, - "node_modules/winston/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/with": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -4903,6 +5094,31 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -5179,6 +5395,12 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -5380,9 +5602,9 @@ "dev": true }, "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, "asynckit": { "version": "0.4.0", @@ -5394,7 +5616,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "requires": { "possible-typed-array-names": "^1.0.0" } @@ -5433,6 +5654,14 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "requires": { + "readable-stream": "^3.4.0" + } + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -5493,12 +5722,22 @@ "fill-range": "^7.0.1" } }, + "browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" + }, "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 }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" + }, "builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -5776,6 +6015,11 @@ } } }, + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" + }, "deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -6446,6 +6690,14 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "fast-xml-parser": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", + "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", + "requires": { + "strnum": "^1.0.5" + } + }, "fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6504,6 +6756,11 @@ "to-regex-range": "^5.0.1" } }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -6564,7 +6821,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -6885,6 +7141,15 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -6935,8 +7200,7 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, "is-core-module": { "version": "2.13.1", @@ -6983,6 +7247,14 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -7079,7 +7351,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, "requires": { "which-typed-array": "^1.1.14" } @@ -7148,6 +7419,11 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-stream/-/json-stream-1.0.0.tgz", + "integrity": "sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==" + }, "json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -7189,6 +7465,11 @@ "signal-exit": "^3.0.2" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7284,6 +7565,34 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, + "minio": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minio/-/minio-7.1.3.tgz", + "integrity": "sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==", + "requires": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^0.2.13", + "fast-xml-parser": "^4.2.2", + "ipaddr.js": "^2.0.1", + "json-stream": "^1.0.0", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml": "^1.0.1", + "xml2js": "^0.5.0" + }, + "dependencies": { + "ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==" + } + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -7673,8 +7982,7 @@ "possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==" }, "prelude-ls": { "version": "1.2.1", @@ -7832,6 +8140,17 @@ "side-channel": "^1.0.4" } }, + "query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "requires": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7863,6 +8182,16 @@ "unpipe": "1.0.0" } }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7980,6 +8309,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, "semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -8135,6 +8469,11 @@ "semver": "^7.5.3" } }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -8145,6 +8484,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -8218,6 +8562,11 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "superagent": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", @@ -8267,6 +8616,14 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "requires": { + "readable-stream": "3" + } + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -8437,6 +8794,18 @@ "punycode": "^2.1.0" } }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8457,6 +8826,15 @@ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" }, + "web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "requires": { + "@zxing/text-encoding": "0.9.0", + "util": "^0.12.3" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8483,7 +8861,6 @@ "version": "1.1.14", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", - "dev": true, "requires": { "available-typed-arrays": "^1.0.6", "call-bind": "^1.0.5", @@ -8514,16 +8891,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - }, - "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" - } } } }, @@ -8535,18 +8902,6 @@ "logform": "^2.3.2", "readable-stream": "^3.6.0", "triple-beam": "^1.3.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "with": { @@ -8583,6 +8938,25 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index bd1be9cc..02ec914c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "helmet": "^7.1.0", "http-errors": "^2.0.0", "lockfile": "^1.0.4", + "minio": "^7.1.3", "mkdirp": "^1.0.4", "morgan": "^1.10.0", "pug": "^3.0.2", diff --git a/spec/armadietto/a_signup_spec.js b/spec/armadietto/a_signup_spec.js new file mode 100644 index 00000000..0aeb22a6 --- /dev/null +++ b/spec/armadietto/a_signup_spec.js @@ -0,0 +1,47 @@ +/* eslint-env mocha, chai, node */ + +const Armadietto = require('../../lib/armadietto'); +const { shouldBlockSignups, shouldAllowSignupsBasePath } = require('../signup.spec'); +const core = require('../../lib/stores/core'); + +const store = { + async createUser (params) { + const errors = core.validateUser(params); + if (errors.length > 0) throw new Error(errors[0]); + } +}; + +describe('Signup (monolithic)', function () { + describe('Signup disabled and no base path', function () { + before(function () { + this.app = new Armadietto({ + bare: true, + store, + http: { }, + logging: { log_dir: './test-log', stdout: [], log_files: ['notice'] } + }); + }); + + // test of home page w/ signup disabled moved to root.spec.js + + // test that style sheet can be fetched moved to static_files.spec.js + + shouldBlockSignups(); + }); + + describe('Signup w/ base path & signup enabled', function () { + before(function () { + this.app = new Armadietto({ + bare: true, + store, + allow: { signup: true }, + http: { }, + logging: { log_dir: './test-log', stdout: [], log_files: ['notice'] }, + basePath: '/basic' + }); + this.username = 'john'; + }); + + shouldAllowSignupsBasePath(); + }); +}); diff --git a/spec/modular/m_signup.spec.js b/spec/modular/m_signup.spec.js new file mode 100644 index 00000000..ed29e3a0 --- /dev/null +++ b/spec/modular/m_signup.spec.js @@ -0,0 +1,52 @@ +/* eslint-env mocha */ + +const { configureLogger } = require('../../lib/logger'); +const { shouldBlockSignups, shouldAllowSignupsBasePath } = require('../signup.spec'); +const S3 = require('../../lib/streaming_stores/S3'); + +describe('Signup (modular)', function () { + describe('w/ signup disabled', function () { + before(async function () { + configureLogger({ log_dir: './test-log', stdout: [], log_files: ['debug'] }); + + delete require.cache[require.resolve('../../lib/app')]; + const app = require('../../lib/app'); + app.locals.title = 'Test Armadietto'; + app.locals.basePath = ''; + app.locals.host = 'localhost:xxxx'; + app.locals.signup = false; + this.app = app; + }); + + shouldBlockSignups(); + }); + + describe('w/ base path & signup enabled', function () { + before(async function () { + configureLogger({ log_dir: './test-log', stdout: [], log_files: ['debug'] }); + + // If the environment variables aren't set, tests are run using a shared public account on play.min.io + this.store = new S3(process.env.S3_HOSTNAME, + process.env.S3_PORT ? parseInt(process.env.S3_PORT) : undefined, + process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY); + + process.env.basePath = 'basic'; + delete require.cache[require.resolve('../../lib/app')]; + const app = require('../../lib/app'); + app.set('streaming_store', this.store); + process.env.basePath = ''; + app.locals.title = 'Test Armadietto'; + app.locals.basePath = '/basic'; + app.locals.host = 'localhost:xxxx'; + app.locals.signup = true; + this.app = app; + this.username = 'john-' + Math.round(Math.random() * Number.MAX_SAFE_INTEGER); + }); + + after(async function () { + await this.store.deleteUser(this.username); + }); + + shouldAllowSignupsBasePath(); + }); +}); diff --git a/spec/not_found.spec.js b/spec/not_found.spec.js index ea4cc3a4..0a3437b5 100644 --- a/spec/not_found.spec.js +++ b/spec/not_found.spec.js @@ -9,7 +9,7 @@ exports.shouldHandleNonexistingResource = function () { it('should return 404 Not Found', async function () { const res = await chai.request(this.app).get('/zorp/gnu/'); expect(res).to.have.status(404); - // expect(res).to.have.header('Content-Security-Policy'); + expect(res).to.have.header('Content-Security-Policy', /sandbox.*default-src 'self'/); expect(res).to.have.header('Referrer-Policy', 'no-referrer'); expect(res).to.have.header('X-Content-Type-Options', 'nosniff'); // expect(res).to.have.header('Strict-Transport-Security', /^max-age=/); diff --git a/spec/root.spec.js b/spec/root.spec.js index b039feaa..96073a8a 100644 --- a/spec/root.spec.js +++ b/spec/root.spec.js @@ -9,7 +9,7 @@ exports.shouldBeWelcomeWithoutSignup = function () { it('should return Welcome page w/o signup link, when signup:false', async function welcomeWithout () { const res = await chai.request(this.app).get('/'); expect(res).to.have.status(200); - expect(res).to.have.header('Content-Security-Policy'); + expect(res).to.have.header('Content-Security-Policy', /sandbox.*default-src 'self'/); expect(res).to.have.header('Referrer-Policy', 'no-referrer'); expect(res).to.have.header('X-Content-Type-Options', 'nosniff'); // expect(res).to.have.header('Strict-Transport-Security', /^max-age=/); diff --git a/spec/runner.js b/spec/runner.js index 54ecce42..0e53ab9e 100644 --- a/spec/runner.js +++ b/spec/runner.js @@ -7,6 +7,7 @@ require('./armadietto/storage_spec'); require('./armadietto/a_root_spec'); require('./armadietto/a_not_found_spec'); require('./armadietto/a_static_spec'); +require('./armadietto/a_signup_spec'); require('./stores/file_tree_spec'); // require('./stores/redis_spec'); @@ -14,3 +15,4 @@ require('./stores/file_tree_spec'); require('./modular/m_root.spec'); require('./modular/m_not_found.spec'); require('./modular/m_static.spec'); +require('./modular/m_signup.spec'); diff --git a/spec/signup.spec.js b/spec/signup.spec.js new file mode 100644 index 00000000..608bc64a --- /dev/null +++ b/spec/signup.spec.js @@ -0,0 +1,131 @@ +/* eslint-env mocha, chai, node */ +/* eslint-disable no-unused-expressions */ +const chai = require('chai'); +const expect = chai.expect; +const chaiHttp = require('chai-http'); +chai.use(chaiHttp); + +// arrow functions are incompatible with sharing tests +exports.shouldBlockSignups = function () { + it('blocks access to the signup page', async function () { + const res = await chai.request(this.app).get('/signup'); + expect(res).to.have.status(403); + expect(res).to.have.header('Content-Security-Policy', /sandbox.*default-src 'self'/); + expect(res).to.have.header('Referrer-Policy', 'no-referrer'); + expect(res).to.have.header('X-Content-Type-Options', 'nosniff'); + expect(res).to.be.html; + expect(res.text).to.match(/Forbidden/); + expect(res.text).to.match(/Signing up is not allowed currently/); + // navigation + expect(res.text).to.match(/]*href="\/"[^>]*>Home<\/a>/); + expect(res.text).to.match(/]*href="\/account"[^>]*>Account<\/a>/); + expect(res.text).not.to.contain('Sign up'); + }); + + it('blocks signup ', async function () { + const res = await chai.request(this.app).post('/signup').type('form').send({ + username: '123', + email: 'foo@bar.com', + password: 'iloveyou' + }); + + // const res = await req.post('/signup').type('form').send({ + // username: '123', + // email: 'foo@bar.com', + // password: 'iloveyou' + // }); + expect(res).to.have.status(403); + expect(res).to.have.header('Content-Security-Policy', /sandbox.*default-src 'self'/); + expect(res).to.have.header('Referrer-Policy', 'no-referrer'); + expect(res).to.have.header('X-Content-Type-Options', 'nosniff'); + expect(res).to.be.html; + expect(res.text).to.match(/Forbidden/); + }); +}; + +exports.shouldAllowSignupsBasePath = function () { + it('redirects to the home page', async function () { + const res = await chai.request(this.app).get('/'); + expect(res).to.redirect; + expect(res).to.redirectTo(/http:\/\/127.0.0.1:\d{1,5}\/basic/); + }); + + it('returns a home page w/ signup link', async function () { + const res = await chai.request(this.app).get('/basic/'); + expect(res).to.have.status(200); + expect(res).to.have.header('Content-Security-Policy', /sandbox.*default-src 'self'/); + expect(res).to.have.header('Referrer-Policy', 'no-referrer'); + expect(res).to.have.header('X-Content-Type-Options', 'nosniff'); + expect(res).to.be.html; + expect(res.text).to.match(/]*href="\/basic\/"[^>]*>Home<\/a>/); + expect(res.text).to.match(/]*href="\/basic\/account"[^>]*>Account<\/a>/); + expect(res.text).to.match(/]*href="\/basic\/signup"[^>]*>Sign up<\/a>/); + expect(res.text).to.match(/Signup — Armadietto'); + expect(res.text).to.match(/Sign up<\/h\d>/i); + expect(res.text).to.match(/]*method="post"[^>]*action="\/basic\/signup"/); + expect(res.text).to.match(/]*type="text"[^>]*name="username"[^>]*value=""/); + expect(res.text).to.match(/]*type="text"[^>]*name="email"[^>]*value=""/); + expect(res.text).to.match(/]*type="password"[^>]*name="password"[^>]*value=""/); + expect(res.text).to.match(/