diff --git a/.github/README.md b/.github/README.md index e179b447..4a80f3f9 100644 --- a/.github/README.md +++ b/.github/README.md @@ -199,10 +199,6 @@ For HTTPS support, you must configure a reverse proxy. I recommend Caddy but any [Caddy]: https://caddyserver.com/ [my tutorial]: https://old.jmoore.dev/tutorials/2021/03/caddy-express-reverse-proxy/ -## Generating new tokens - -If you need to generate a new token at any time, run `npm run new-token `. This will **automatically** load the new token so there is no need to restart ass. Username field is optional; if left blank, a random username will be created. - ## Cloudflare users In your Cloudflare DNS dashboard, set your domain/subdomain to **DNS Only** if you experience issues with **Proxied**. @@ -247,7 +243,7 @@ If you primarily share media on Discord, you can add these additional (optional) | Header | Purpose | | ------ | ------- | -| **`X-Ass-OG-Title`** | Large text shown above your media | +| **`X-Ass-OG-Title`** | Large text shown above your media. Required for embeds to appear on desktop. | | **`X-Ass-OG-Description`** | Small text shown below the title but above the media (does not show up on videos) | | **`X-Ass-OG-Author`** | Small text shown above the title | | **`X-Ass-OG-Author-Url`** | URL to open when the Author is clicked | @@ -265,6 +261,20 @@ You can insert certain metadata into your embeds with these placeholders: | **`&filename`** | The original filename of the uploaded file | | **`×tamp`** | The timestamp of when the file was uploaded (example: `Oct 14, 1983, 1:30 PM`) | +#### Server-side embed configuration + +You may also specify a default embed config on the server. Keep in mind that if users specify the `X-Ass-OG-Title` header, the server-side config will be ignored. To configure the server-side embed, create a new file in the `share/` directory named `embed.json`. Available options are: + +- **`title`** +- `description` +- `author` +- `authorUrl` +- `provider` +- `providerUrl` +- `color` + +Their values are equivalent to the headers listed above. + ### Webhooks You may use Discord webhooks as an easy way to keep track of your uploads. The first step is to [create a new Webhook]. You only need to follow the first section, **Making a Webhook**. Once you are done that, click **Copy Webhook URL**. Finally, add these headers to your custom uploader: @@ -290,6 +300,7 @@ If you want to customize the font or colours of the viewer page, create a file i | **`bgViewer`** | Background colour for the viewer element | | **`txtPrimary`** | Primary text colour; this should be your main brand colour. | | **`txtSecondary`** | Secondary text colour; this is used for the file details. | +| **`linkPrimary`** | Primary link colour | | **`linkHover`** | Colour of the `hover` effect for links | | **`linkActive`** | Colour of the `active` effect for links | | **`borderHover`** | Colour of the `hover` effect for borders; this is used for the underlining links. | @@ -337,10 +348,65 @@ S3 servers are generally very fast & have very good uptime, though this will dep [Amazon S3]: https://en.wikipedia.org/wiki/Amazon_S3 [Skynet Labs]: https://github.com/SkynetLabs +## New user system (v0.14.0) + +The user system was overhauled in v0.14.0 to allow more features and flexibility. New fields on users include `admin`, `passhash`, `unid`, and `meta` (these will be documented more once the system is finalized). + +New installs will automatically generate a default user. Check the `auth.json` file for the token. You will use this for API requests and to authenticate within ShareX. + +ass will automatically convert your old `auth.json` to the new format. **Always backup your `auth.json` and `data.json` before updating**. By default, the original user (named `ass`) will be marked as an admin. + +### Adding users + +You may add users via the CLI or the API. I'll document the API further in the future. + +#### CLI + +```bash +npm run cli-adduser [admin] [meta] +``` + +| Argument | Purpose | +| -------- | ------- | +| **`username`** `string` | The username of the user. | +| **`password`** `string` | The password of the user. | +| **`admin?`** `boolean` | Whether the user is an admin. Defaults to `false`. | +| **`meta?`** `string` | Any additional metadata to store on the user, as a JSON string. | + +**Things still not added:** + +- Modifying/deleting users via the API + +## Developer API + +ass includes an API (v0.14.0) for frontend developers to easily integrate with. Right now the API is pretty limited but I will expand on it in the future, with frontend developer feedback. + +Any endpoints requiring authorization will require an `Authorization` header with the value being the user's upload token. Admin users are a new feature introduced in v0.14.0. Admin users can access all endpoints, while non-admin users can only access those relevant to them. + +Other things to note: + +- **All endpoints are prefixed with `/api/`**. +- All endpoints will return a JSON object unless otherwise specified. +- Successful endpoints *should* return a `200` status code. Any errors will use the corresponding `4xx` or `5xx` status code (such as `401 Unauthorized`). + +### API endpoints + +| Endpoint | Purpose | Admin? | +| -------- | ------- | ------ | +| **`GET /user/all`** | Returns a list of all users | Yes | +| **`GET /user/self`** | Returns the current user | No | +| **`GET /user/token/:token`** | Returns the user with the given token | No | +| **`POST /user/reset`** | Resets the current user's **password** (token resets coming soon). Request body must be a JSON object including `username` and `password`. | No | +| **`GET /user/:id`** | Returns the user with the given ID | Yes | +| **`POST /user/new`** | Creates a new user. Request body must be a JSON object including `username` and `password`. You may optionally include `admin` (boolean) or `meta` (object). Returns 400 if fails. | Yes | + + ## Custom frontends - OUTDATED **Please be aware that this section is outdated (marked as of 2022-04-15). It will be updated when I overhaul the frontend system.** +**Update 2022-12-24: I plan to overhaul this early in 2023.** + ass is intended to provide a strong backend for developers to build their own frontends around. [Git Submodules] make it easy to create custom frontends. Submodules are their own projects, which means you are free to build the router however you wish, as long as it exports the required items. A custom frontend is really just an [Express.js router]. **For a detailed walkthrough on developing your first frontend, [consult the wiki][ctw1].** @@ -395,7 +461,6 @@ ass has a number of pre-made npm scripts for you to use. **All** of these script | `setup` | Starts the easy setup process. Should be run after any updates that introduce new config options. | | `metrics` | Runs the metrics script. This is a simple script that outputs basic resource statistics. | | `purge` | Purges all uploads & data associated with them. This does **not** delete any users, however. | -| `new-token` | Generates a new API token. Accepts one parameter for specifying a username, like `npm run new-token `. ass automatically detects the new token & reloads it, so there's no need to restart the server. | | `engine-check` | Ensures your environment meets the minimum Node & npm version requirements. | [`FORCE_COLOR`]: https://nodejs.org/dist/latest-v16.x/docs/api/cli.html#cli_force_color_1_2_3 diff --git a/.gitignore b/.gitignore index d339572c..57c63b16 100755 --- a/.gitignore +++ b/.gitignore @@ -104,11 +104,11 @@ dist .tern-port # tokens -auth.json +auth.json* auth.*.json # data -data.json +data.json* # uploads uploads/ diff --git a/package-lock.json b/package-lock.json index 6ed19be0..f9a8d35b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "ass", - "version": "0.13.0", + "version": "0.14.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ass", - "version": "0.13.0", + "version": "0.14.0-alpha.1", "license": "ISC", "dependencies": { "@skynetlabs/skynet-nodejs": "^2.3.0", "@tsconfig/node16": "^1.0.1", + "@tycrek/discord-hookr": "^0.1.0", "@tycrek/express-nofavicon": "^1.0.3", "@tycrek/express-postcss": "^0.2.4", "@tycrek/isprod": "^2.0.2", @@ -20,10 +21,10 @@ "any-shell-escape": "^0.1.1", "autoprefixer": "^10.4.4", "aws-sdk": "^2.1115.0", + "bcrypt": "^5.1.0", "check-node-version": "^4.2.1", "crypto-random-string": "3.3.1", "cssnano": "^5.1.7", - "discord-webhook-node": "^1.1.8", "escape-html": "^1.0.3", "express": "^4.17.3", "express-brute": "^1.0.1", @@ -32,6 +33,7 @@ "fs-extra": "^10.0.1", "helmet": "^4.6.0", "luxon": "^2.3.1", + "nanoid": "^3.3.4", "node-fetch": "^2.6.7", "node-vibrant": "^3.1.6", "postcss-font-magician": "^3.0.0", @@ -46,6 +48,7 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@types/bcrypt": "^5.0.0", "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", "@types/express-brute": "^1.0.1", @@ -759,6 +762,39 @@ "@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==", + "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/@mapbox/node-pre-gyp/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/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -831,6 +867,33 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, + "node_modules/@tycrek/discord-hookr": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@tycrek/discord-hookr/-/discord-hookr-0.1.0.tgz", + "integrity": "sha512-Xlo99oeRBgwtV7YmW3ZjorGX5x6gxSUt/x7PUX8EMejWeWsE/YwyEqIO1g29wRiz3C2c0mZt53hWELesZVg9Aw==", + "dependencies": { + "@tsconfig/node16": "^1.0.3", + "@types/node": "^18.11.17", + "axios": "^1.2.1", + "formdata-polyfill": "^4.0.10", + "typescript": "^4.9.4" + } + }, + "node_modules/@tycrek/discord-hookr/node_modules/@types/node": { + "version": "18.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", + "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" + }, + "node_modules/@tycrek/discord-hookr/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/@tycrek/express-nofavicon": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tycrek/express-nofavicon/-/express-nofavicon-1.0.3.tgz", @@ -917,6 +980,15 @@ "url": "https://patreon.com/tycrek" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -1137,6 +1209,11 @@ "jdataview": "^2.5.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==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1246,6 +1323,23 @@ "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==" + }, + "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==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1380,6 +1474,11 @@ "node": ">= 10.0.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==" + }, "node_modules/base32-decode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base32-decode/-/base32-decode-1.0.0.tgz", @@ -1417,6 +1516,19 @@ } ] }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1576,6 +1688,15 @@ "node": ">=0.8.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==", + "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", @@ -1874,6 +1995,14 @@ "simple-swizzle": "^0.2.2" } }, + "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==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -1915,6 +2044,11 @@ "node": ">= 10" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -1940,6 +2074,11 @@ "node": ">=0.8.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==" + }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -2260,6 +2399,11 @@ "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==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2330,28 +2474,6 @@ "is-woff2": "^1.0.0" } }, - "node_modules/discord-webhook-node": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/discord-webhook-node/-/discord-webhook-node-1.1.8.tgz", - "integrity": "sha512-3u0rrwywwYGc6HrgYirN/9gkBYqmdpvReyQjapoXARAHi0P0fIyf3W5tS5i3U3cc7e44E+e7dIHYUeec7yWaug==", - "dependencies": { - "form-data": "^3.0.0", - "node-fetch": "^2.6.0" - } - }, - "node_modules/discord-webhook-node/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -2428,6 +2550,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz", "integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==" }, + "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==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2721,6 +2848,28 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/ffmpeg-static": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-4.4.1.tgz", @@ -2836,6 +2985,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2882,6 +3042,22 @@ "node": ">=12" } }, + "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==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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==" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -2925,6 +3101,65 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "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/gauge/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2992,6 +3227,25 @@ "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.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "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", @@ -3117,6 +3371,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==" + }, "node_modules/helmet": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", @@ -3194,6 +3453,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" }, + "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==", + "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", @@ -3800,6 +4068,20 @@ "node": ">=12" } }, + "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==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -3932,6 +4214,17 @@ "dom-walk": "^0.1.0" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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", @@ -3940,6 +4233,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4025,6 +4341,24 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -4077,6 +4411,20 @@ "querystring": "0.2.0" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "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", @@ -4115,6 +4463,17 @@ "node": ">=4" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "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/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -4337,6 +4696,14 @@ "node": ">=4" } }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -5010,6 +5377,11 @@ "node": ">= 0.10" } }, + "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/pug": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", @@ -5361,6 +5733,20 @@ "node": ">= 0.4.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==", + "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", @@ -5964,6 +6350,22 @@ "postcss": "^8.0.9" } }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.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", @@ -5990,6 +6392,14 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/timm": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", @@ -6100,9 +6510,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6257,6 +6667,14 @@ "node": ">=0.10.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -6321,6 +6739,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/winston": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", @@ -7020,6 +7446,32 @@ "@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==", + "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" + }, + "dependencies": { + "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" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7080,6 +7532,35 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, + "@tycrek/discord-hookr": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@tycrek/discord-hookr/-/discord-hookr-0.1.0.tgz", + "integrity": "sha512-Xlo99oeRBgwtV7YmW3ZjorGX5x6gxSUt/x7PUX8EMejWeWsE/YwyEqIO1g29wRiz3C2c0mZt53hWELesZVg9Aw==", + "requires": { + "@tsconfig/node16": "^1.0.3", + "@types/node": "^18.11.17", + "axios": "^1.2.1", + "formdata-polyfill": "^4.0.10", + "typescript": "^4.9.4" + }, + "dependencies": { + "@types/node": { + "version": "18.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", + "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" + }, + "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" + } + } + } + }, "@tycrek/express-nofavicon": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tycrek/express-nofavicon/-/express-nofavicon-1.0.3.tgz", @@ -7136,6 +7617,15 @@ "fs-extra": "^10.0.0" } }, + "@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -7354,6 +7844,11 @@ "jdataview": "^2.5.0" } }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7433,6 +7928,20 @@ "picomatch": "^2.0.4" } }, + "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==" + }, + "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==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, "arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -7535,6 +8044,11 @@ "@babel/types": "^7.9.6" } }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, "base32-decode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base32-decode/-/base32-decode-1.0.0.tgz", @@ -7558,6 +8072,15 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + } + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -7698,6 +8221,15 @@ } } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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", @@ -7915,6 +8447,11 @@ "simple-swizzle": "^0.2.2" } }, + "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==" + }, "colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -7947,6 +8484,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -7966,6 +8508,11 @@ "busboy": "~0.3.1" } }, + "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==" + }, "constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -8188,6 +8735,11 @@ "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==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8239,27 +8791,6 @@ "is-woff2": "^1.0.0" } }, - "discord-webhook-node": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/discord-webhook-node/-/discord-webhook-node-1.1.8.tgz", - "integrity": "sha512-3u0rrwywwYGc6HrgYirN/9gkBYqmdpvReyQjapoXARAHi0P0fIyf3W5tS5i3U3cc7e44E+e7dIHYUeec7yWaug==", - "requires": { - "form-data": "^3.0.0", - "node-fetch": "^2.6.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } - }, "dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -8318,6 +8849,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz", "integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==" }, + "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==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -8560,6 +9096,15 @@ "reusify": "^1.0.4" } }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "ffmpeg-static": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-4.4.1.tgz", @@ -8644,6 +9189,14 @@ "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8674,6 +9227,19 @@ "universalify": "^2.0.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -8701,6 +9267,52 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "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==" + }, + "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==", + "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==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8753,6 +9365,19 @@ "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.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "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", @@ -8843,6 +9468,11 @@ "has-symbols": "^1.0.2" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "helmet": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", @@ -8912,6 +9542,15 @@ } } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -9359,6 +9998,14 @@ "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.0.tgz", "integrity": "sha512-IDkEPB80Rb6gCAU+FEib0t4FeJ4uVOuX1CQ9GsvU3O+JAGIgu0J7sf1OarXKaKDygTZIoJyU6YdZzTFRu+YR0A==" }, + "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==", + "requires": { + "semver": "^6.0.0" + } + }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -9452,11 +10099,36 @@ "dom-walk": "^0.1.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -9520,6 +10192,11 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -9563,6 +10240,14 @@ } } }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9586,6 +10271,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -9751,6 +10447,11 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" }, + "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==" + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -10164,6 +10865,11 @@ "ipaddr.js": "1.9.1" } }, + "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==" + }, "pug": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", @@ -10442,6 +11148,14 @@ "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==" }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10861,6 +11575,26 @@ "resolve": "^1.22.1" } }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + } + } + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -10973,9 +11707,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==" }, "unbox-primitive": { "version": "1.0.2", @@ -11086,6 +11820,11 @@ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -11138,6 +11877,14 @@ "is-typed-array": "^1.1.9" } }, + "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==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "winston": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", diff --git a/package.json b/package.json index d74eb314..6c78ad0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ass", - "version": "0.13.0", + "version": "0.14.0-alpha.1", "description": "The superior self-hosted ShareX server", "main": "ass.js", "engines": { @@ -9,12 +9,12 @@ }, "scripts": { "dev": "npm run build && npm start", + "dev-win": "npm run build-skip-options && npm run start", "build": "NODE_OPTIONS=\"--max-old-space-size=1024\" tsc", "build-skip-options": "tsc", "start": "node dist/ass.js", "setup": "node dist/setup.js", "metrics": "node dist/metrics.js", - "new-token": "node dist/generators/token.js", "engine-check": "node dist/checkEngine.js", "prestart": "npm run engine-check", "presetup": "npm run engine-check", @@ -23,7 +23,10 @@ "docker-update": "git pull && npm run docker-uplite", "docker-uplite": "docker-compose up --force-recreate --build -d && docker image prune -f", "docker-upfull": "npm run docker-update && npm run docker-resetup", - "docker-resetup": "docker-compose exec ass npm run setup && docker-compose restart" + "docker-resetup": "docker-compose exec ass npm run setup && docker-compose restart", + "cli-setpassword": "node dist/tools/script.setpassword.js", + "cli-testpassword": "node dist/tools/script.testpassword.js", + "cli-adduser": "node dist/tools/script.adduser.js" }, "repository": "github:tycrek/ass", "keywords": [ @@ -41,6 +44,7 @@ "dependencies": { "@skynetlabs/skynet-nodejs": "^2.3.0", "@tsconfig/node16": "^1.0.1", + "@tycrek/discord-hookr": "^0.1.0", "@tycrek/express-nofavicon": "^1.0.3", "@tycrek/express-postcss": "^0.2.4", "@tycrek/isprod": "^2.0.2", @@ -50,10 +54,10 @@ "any-shell-escape": "^0.1.1", "autoprefixer": "^10.4.4", "aws-sdk": "^2.1115.0", + "bcrypt": "^5.1.0", "check-node-version": "^4.2.1", "crypto-random-string": "3.3.1", "cssnano": "^5.1.7", - "discord-webhook-node": "^1.1.8", "escape-html": "^1.0.3", "express": "^4.17.3", "express-brute": "^1.0.1", @@ -62,6 +66,7 @@ "fs-extra": "^10.0.1", "helmet": "^4.6.0", "luxon": "^2.3.1", + "nanoid": "^3.3.4", "node-fetch": "^2.6.7", "node-vibrant": "^3.1.6", "postcss-font-magician": "^3.0.0", @@ -76,6 +81,7 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@types/bcrypt": "^5.0.0", "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", "@types/express-brute": "^1.0.1", @@ -92,4 +98,4 @@ "@types/uuid": "^8.3.1", "@types/ws": "^7.4.7" } -} +} \ No newline at end of file diff --git a/src/ass.ts b/src/ass.ts index 5b41b6bc..097812f6 100644 --- a/src/ass.ts +++ b/src/ass.ts @@ -3,13 +3,14 @@ import { Config, MagicNumbers, Package } from 'ass-json'; //#region Imports import fs from 'fs-extra'; -import express, { Request, Response } from 'express'; +import express, { Request, Response, json as BodyParserJson } from 'express'; import nofavicon from '@tycrek/express-nofavicon'; import { epcss } from '@tycrek/express-postcss'; import tailwindcss from 'tailwindcss'; import helmet from 'helmet'; import { path, log, getTrueHttp, getTrueDomain } from './utils'; +import { onStart as ApiOnStart } from './routers/api'; //#endregion //#region Setup - Run first time setup if using Docker (pseudo-process, setup will be run with docker exec) @@ -43,7 +44,7 @@ const ROUTERS = { }; // Read users and data -import { users } from './auth'; +import { onStart as AuthOnStart, users } from './auth'; import { data } from './data'; //#endregion @@ -79,6 +80,10 @@ app.get(['/'], bruteforce.prevent, (_req, _res, next) => next()); // Express logger middleware app.use(log.middleware()); +// Body parser for API POST requests +// (I really don't like this being top level but it does not work inside the API Router as of 2022-12-24) +app.use(BodyParserJson()); + // Helmet security middleware app.use(helmet.noSniff()); app.use(helmet.ieNoOpen()); @@ -105,6 +110,9 @@ ASS_FRONTEND.enabled && app.use(ASS_FRONTEND.endpoint, ASS_FRONTEND.router); // // Upload router (has to come after custom frontends as express-busboy interferes with all POST calls) app.use('/', ROUTERS.upload); +// API +app.use('/api', ApiOnStart()); + // CSS app.use('/css', epcss({ cssPath: path('tailwind.css'), @@ -123,10 +131,12 @@ app.use('/:resourceId', (req, _res, next) => (req.resourceId = req.params.resour // Error handler app.use((err: ErrWrap, _req: Request, res: Response) => log.error(err.message).err(err).callback(() => res.sendStatus(CODE_INTERNAL_SERVER_ERROR))); // skipcq: JS-0128 -(function start() { +(async function start() { + await AuthOnStart(); + if (data() == null) setTimeout(start, 100); else log - .info('Users', `${Object.keys(users).length}`) + .info('Users', `${users.length}`) .info('Files', `${data().size}`) .info('Data engine', data().name, data().type) .info('Frontend', ASS_FRONTEND.enabled ? ASS_FRONTEND.brand : 'disabled', `${ASS_FRONTEND.enabled ? `${getTrueHttp()}${getTrueDomain()}${ASS_FRONTEND.endpoint}` : ''}`) diff --git a/src/auth.ts b/src/auth.ts index 304d7bae..8b1b3921 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -3,18 +3,238 @@ */ import fs from 'fs-extra'; -import { log, path, arrayEquals } from './utils'; - -export const users = require('../auth.json').users || {}; - -// Monitor auth.json for changes (triggered by running 'npm run new-token') -fs.watch(path('auth.json'), { persistent: false }, - (eventType: String) => eventType === 'change' && fs.readJson(path('auth.json')) - .then((json: { users: JSON[] }) => { - if (!(arrayEquals(Object.keys(users), Object.keys(json.users)))) { - // @ts-ignore - Object.keys(json.users).forEach((token) => (!Object.prototype.hasOwnProperty.call(users, token)) && (users[token] = json.users[token])); - log.info('New token added', Object.keys(users)[Object.keys(users).length - 1] || 'No new token'); +import { nanoid } from 'nanoid'; +import { Request } from 'express'; +import bcrypt from 'bcrypt'; +import { log, path } from './utils'; +import { data } from './data'; +import { User, Users, OldUsers } from './types/auth'; +import { FileData } from './types/definitions'; + +const SALT_ROUNDS = 10; + +/** + * !!!!! + * Things for tycrek to do: + * - [x] Add a way to configure passwords + * - [x] Create new users + * - [ ] Modify user (admin, meta, replace token/token history) + * - [ ] Delete user + * - [x] Get user + * - [x] Get users + * - [x] Get user by token + */ + +/** + * Map of users + */ +export const users = [] as User[]; + +/** + * Migrates the old auth.json format to the new one + * @since v0.14.0 + */ +const migrate = (authFileName = 'auth.json'): Promise => new Promise(async (resolve, reject) => { + + // Get ready to read the old auth.json file + const authPath = path(authFileName); + const oldUsers = fs.readJsonSync(authPath).users as OldUsers; + + // Create a new users object + const newUsers: Users = { users: [], meta: {} }; + newUsers.migrated = true; + + // Loop through each user + await Promise.all(Object.entries(oldUsers).map(async ([token, { username }]) => { + + // Determine if this user is the admin + const admin = Object.keys(oldUsers).indexOf(token) === 0; + const passhash = admin ? await bcrypt.hash(nanoid(32), SALT_ROUNDS) : ''; + + // Create a new user object + const newUser: User = { + unid: nanoid(), + username, + passhash, + token, + admin, + meta: {} + }; + + newUsers.users.push(newUser); + })); + + // Save the new users object to auth.json + fs.writeJson(authPath, newUsers, { spaces: '\t' }) + .catch(reject) + + // Migrate the datafile (token => uploader) + .then(() => data().get()) + .then((fileData: [string, FileData][]) => + + // ! A note about this block. + // I know it's gross. But using Promise.all crashes low-spec servers, so I had to do it this way. Sorry. + // Thanks to CoPilot for writing `runQueue` :D + + // Wait for all the deletions and puts to finish + new Promise((resolve, reject) => { + + // Create a queue of functions to run + const queue = fileData.map(([key, file]) => async () => { + + // We need to use `newUsers` because `users` hasn't been re-assigned yet + const user = newUsers.users.find((user) => user.token === file.token!)?.unid ?? ''; // ? This is probably fine + + // Because of the stupid way I wrote papito, we need to DEL before we can PUT + await data().del(key); + + // PUT the new data + return data().put(key, { ...file, uploader: user }); + }); + + // Recursively run the queue, hopefully sequentially without running out of memory + const runQueue = (index: number) => { + if (index >= queue.length) return resolve(void 0); + queue[index]().then(() => runQueue(index + 1)).catch(reject); + }; + + runQueue(0); + })) + + // We did it hoofuckingray + .then(() => log.success('Migrated all auth & file data to new auth system')) + .then(() => resolve(newUsers)) + .catch(reject); +}); + +/** + * Creates a new user account + * @since v0.14.0 + */ +export const createNewUser = (username: string, password: string, admin: boolean, meta?: { [key: string]: any }): Promise => new Promise(async (resolve, reject) => { + + // Create a new user object + const newUser: User = { + unid: nanoid(), + username, + passhash: await bcrypt.hash(password, SALT_ROUNDS), + token: nanoid(32), + admin, + meta: meta || {} + }; + + // Add the user to the users map + users.push(newUser); + + // Save the new user to auth.json + const authPath = path('auth.json'); + const authData = fs.readJsonSync(authPath) as Users; + + if (!authData.users) authData.users = []; + authData.users.push(newUser); + + if (!authData.meta) authData.meta = {}; + + fs.writeJson(authPath, authData, { spaces: '\t' }) + .then(() => log.info('Created new user', newUser.username, newUser.unid)) + .then(() => resolve(newUser)) + .catch(reject); +}); + +/** + * Sets the password for a user + * @since v0.14.0 + */ +export const setUserPassword = (unid: string, password: string): Promise => new Promise(async (resolve, reject) => { + + // Find the user + const user = users.find((user) => user.unid === unid); + if (!user) return reject(new Error('User not found')); + + // Set the password + user.passhash = await bcrypt.hash(password, SALT_ROUNDS); + + // Save the new user to auth.json + const authPath = path('auth.json'); + const authData = fs.readJsonSync(authPath) as Users; + const userIndex = authData.users.findIndex((user) => user.unid === unid); + authData.users[userIndex] = user; + fs.writeJson(authPath, authData, { spaces: '\t' }) + .then(() => resolve(user)) + .catch(reject); +}); + +/** + * Called by ass.ts on startup + * @since v0.14.0 + */ +export const onStart = (authFile = 'auth.json') => new Promise((resolve, reject) => { + // Reset user array (https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript#1232046) + // ! I don't think this works properly..? + users.splice(0, users.length); + + const file = path(authFile); + log.debug('Reading', file); + + // Check if the file exists + fs.stat(file) + + // Create the file if it doesn't exist + .catch((_errStat) => { + log.debug('File does not exist', authFile, 'will be created automatically'); + return fs.writeJson(file, { migrated: true }); + }) + .catch((errWriteJson) => log.error('Failed to create auth.json').callback(reject, errWriteJson)) + + // File exists or was created + .then(() => fs.readJson(file)) + .then((json: Users) => { + + // Check if the file is the old format + if (json.migrated === undefined || !json.migrated) return ( + log.debug('auth.json is in old format, migrating'), + migrate(authFile)); + else return json; + }) + .then(async (json) => { + + // Check if the file is empty + if (!json.users || json.users.length === 0) { + log.debug('auth.json is empty, creating default user'); + return await createNewUser('ass', nanoid(), true); } + + // Check if the CLI key is set + if (!json.cliKey || json.cliKey.length === 0) { + log.debug('CLI key is not set, generating new key'); + json.cliKey = nanoid(32); + fs.writeJsonSync(file, json, { spaces: '\t' }); + } + + // Add users to the map + return json.users.forEach((user) => users.push(user)); }) - .catch(console.error)); + .catch((errReadJson) => log.error('Failed to read auth.json').callback(reject, errReadJson)) + .then(resolve); +}); + +/** + * Retrieves a user using their upload token. Returns `null` if the user does not exist. + * @since v0.14.0 + */ +export const findFromToken = (token: string) => users.find((user) => user.token === token) || null; + +/** + * Verifies that the upload token in the request exists in the user map + * @since v0.14.0 + */ +export const verifyValidToken = (req: Request) => req.headers.authorization && findFromToken(req.headers.authorization); + +/** + * Verifies that the CLI key in the request matches the one in auth.json + * @since v0.14.0 + */ +export const verifyCliKey = (req: Request) => { + const cliKey: string = fs.readJsonSync(path('auth.json')).cliKey; + return req.headers.authorization != null && req.headers.authorization === cliKey; +}; diff --git a/src/generators/nanoid.ts b/src/generators/nanoid.ts new file mode 100644 index 00000000..cd87e7e6 --- /dev/null +++ b/src/generators/nanoid.ts @@ -0,0 +1,2 @@ +import { nanoid } from 'nanoid'; +export default ({ length }: { length?: number }) => nanoid(length); diff --git a/src/generators/zws.ts b/src/generators/zws.ts index 9f54cbe1..4d91d231 100644 --- a/src/generators/zws.ts +++ b/src/generators/zws.ts @@ -1,3 +1,4 @@ import lengthGen from './lengthGen'; const zeroWidthChars = ['\u200B', '\u200C', '\u200D', '\u2060']; export default ({ length }: { length: number }) => lengthGen(length, zeroWidthChars); +export const checkIfZws = (str: string) => str.split('').every(char => zeroWidthChars.includes(char)); diff --git a/src/routers/api.ts b/src/routers/api.ts new file mode 100644 index 00000000..fb45e921 --- /dev/null +++ b/src/routers/api.ts @@ -0,0 +1,98 @@ +/** + * Developer API + * - Users + * - Resources + */ + +import { Router, Request, Response, NextFunction } from 'express'; +import { findFromToken, setUserPassword, users, createNewUser, verifyCliKey } from '../auth'; +import { log } from '../utils'; +import { data } from '../data'; +import { User } from '../types/auth'; + +/** + * The primary API router + */ +const RouterApi = Router(); + +/** + * Token authentication middleware for Admins + * @since v0.14.0 + */ +const adminAuthMiddleware = (req: Request, res: Response, next: NextFunction) => { + const user = findFromToken(req.headers.authorization ?? ''); + (verifyCliKey(req) || (user && user.admin)) ? next() : res.sendStatus(401); +}; + +/** + * Simple function to either return JSON or a 404, so I don't have to write it 40 times. + * @since v0.14.0 + */ +const userFinder = (res: Response, user: User | undefined) => user ? res.json(user) : res.sendStatus(404); + +function buildUserRouter() { + const userRouter = Router(); + + // Index + userRouter.get('/', (_req: Request, res: Response) => res.sendStatus(200)); + + // Get all users + // Admin only + userRouter.get('/all', adminAuthMiddleware, (req: Request, res: Response) => res.json(users)); + + // Get self + userRouter.get('/self', (req: Request, res: Response) => + userFinder(res, findFromToken(req.headers['authorization'] ?? '') ?? undefined)); + + // Get user by token + userRouter.get('/token/:token', (req: Request, res: Response) => + userFinder(res, users.find(user => user.token === req.params.token))); + + // Reset password (new plaintext password in form data; HOST SHOULD BE USING HTTPS) + // Admin only + userRouter.post('/reset', adminAuthMiddleware, (req: Request, res: Response) => { + const id = req.body.id; + const newPassword = req.body.password; + + setUserPassword(id, newPassword) + .then(() => res.sendStatus(200)) + .catch((err) => (log.error(err), res.sendStatus(500))); + }); + + // Create a new user + // Admin only + userRouter.post('/new', adminAuthMiddleware, (req: Request, res: Response) => { + const username: string | undefined = req.body.username; + const password: string | undefined = req.body.password; + const admin = req.body.admin ?? false; + const meta: any = req.body.meta ?? {}; + + // Block if username or password is empty, or if username is already taken + if (username == null || username.length === 0 || password == null || password.length == 0 || users.find(user => user.username === username)) + return res.sendStatus(400); + + createNewUser(username, password, admin, meta) + .then((user) => res.send(user)) + .catch((err) => (log.error(err), res.sendStatus(500))); + }); + + // Get a user (must be last as it's a catch-all) + // Admin only + userRouter.get('/:id', adminAuthMiddleware, (req: Request, res: Response) => + userFinder(res, users.find(user => user.unid === req.params.id || user.username === req.params.id))); + + return userRouter; +} + +function buildResourceRouter() { + const resourceRouter = Router(); + + return resourceRouter; +} + +export const onStart = () => { + RouterApi.use('/user', buildUserRouter()); + RouterApi.use('/resource', buildResourceRouter()); + + return RouterApi; +}; diff --git a/src/routers/resource.ts b/src/routers/resource.ts index 9bcc2d32..b8189722 100644 --- a/src/routers/resource.ts +++ b/src/routers/resource.ts @@ -7,8 +7,9 @@ import fetch, { Response as FetchResponse } from 'node-fetch'; import { Request, Response } from 'express'; import { deleteS3 } from '../storage'; import { SkynetDelete, SkynetDownload } from '../skynet'; +import { checkIfZws } from '../generators/zws'; import { path, log, getTrueHttp, getTrueDomain, formatBytes, formatTimestamp, getS3url, getDirectUrl, getResourceColor, replaceholder } from '../utils'; -const { diskFilePath, s3enabled, viewDirect, useSia }: Config = fs.readJsonSync(path('config.json')); +const { diskFilePath, s3enabled, viewDirect, useIdInViewer, idInViewerExtension, useSia }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_NOT_FOUND, }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); import { data } from '../data'; import { users } from '../auth'; @@ -47,9 +48,9 @@ router.get('/', (req: Request, res: Response, next) => data().get(req.ass.resour // Send the view to the client res.render('view', { fileIs: fileData.is, - title: escape(fileData.originalname), + title: useIdInViewer && !checkIfZws(resourceId) ? `${resourceId}${idInViewerExtension ? `${fileData.ext}` : ''}` : escape(fileData.originalname), mimetype: fileData.mimetype, - uploader: users[fileData.token].username, + uploader: users.find(user => user.unid === fileData.uploader)?.username || 'Unknown', timestamp: formatTimestamp(fileData.timestamp, fileData.timeoffset), size: formatBytes(fileData.size), // todo: figure out how to not ignore this diff --git a/src/routers/upload.ts b/src/routers/upload.ts index bcdfa7db..f04ebd49 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -1,17 +1,20 @@ -import { ErrWrap, User } from '../types/definitions'; -import { Config, MagicNumbers } from 'ass-json'; +import { ErrWrap } from '../types/definitions'; +import { Config, MagicNumbers, Package, ServerSideEmbed } from 'ass-json'; import fs from 'fs-extra'; import bb from 'express-busboy'; //const rateLimit = require('express-rate-limit'); import { DateTime } from 'luxon'; -import { Webhook, MessageBuilder } from 'discord-webhook-node'; +import { Webhook, EmbedBuilder } from '@tycrek/discord-hookr'; + import { processUploaded } from '../storage'; -import { path, log, verify, getTrueHttp, getTrueDomain, generateId, formatBytes } from '../utils'; +import { path, log, getTrueHttp, getTrueDomain, generateId, formatBytes } from '../utils'; import { data } from '../data'; -import { users } from '../auth'; +import { findFromToken, verifyValidToken } from '../auth'; + const { maxUploadSize, resourceIdSize, gfyIdSize, resourceIdType, spaceReplace, adminWebhookEnabled, adminWebhookUrl, adminWebhookUsername, adminWebhookAvatar }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_PAYLOAD_TOO_LARGE }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); +const { name, version, homepage }: Package = fs.readJsonSync(path('package.json')); const ASS_LOGO = 'https://cdn.discordapp.com/icons/848274994375294986/8d339d4a2f3f54b2295e5e0ff62bd9e6.png?size=1024'; import express, { Request, Response } from 'express'; @@ -34,8 +37,8 @@ bb.extend(router, { // Block unauthorized requests and attempt token sanitization router.post('/', (req: Request, res: Response, next: Function) => { req.headers.authorization = req.headers.authorization || ''; - req.token = req.headers.authorization.replace(/[^\da-z]/gi, ''); // Strip anything that isn't a digit or ASCII letter - !verify(req, users) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093 + req.token = req.headers.authorization.replace(/[^\da-z_-]/gi, ''); // Strip anything that isn't a digit, ASCII letter, or underscore/hyphen + !verifyValidToken(req) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093 }); // Upload file @@ -60,17 +63,22 @@ router.post('/', (req: Request, res: Response, next: Function) => { req.file!.timeoffset = req.headers['x-ass-timeoffset']?.toString() || 'UTC+0'; // Keep track of the token that uploaded the resource - req.file.token = req.token ?? ''; + req.file.uploader = findFromToken(req.token)?.unid ?? ''; + + // Load server-side embed config, if it exists + const ssePath = path('share/embed.json'); + const sse: ServerSideEmbed | undefined = fs.existsSync(ssePath) ? fs.readJsonSync(path('share/embed.json')) : undefined; + const useSse = sse && sse.title != undefined && sse.title != ''; // Attach any embed overrides, if necessary req.file.opengraph = { - title: req.headers['x-ass-og-title'], - description: req.headers['x-ass-og-description'], - author: req.headers['x-ass-og-author'], - authorUrl: req.headers['x-ass-og-author-url'], - provider: req.headers['x-ass-og-provider'], - providerUrl: req.headers['x-ass-og-provider-url'], - color: req.headers['x-ass-og-color'] + title: useSse ? sse.title : req.headers['x-ass-og-title'], + description: useSse ? sse.description : req.headers['x-ass-og-description'], + author: useSse ? sse.author : req.headers['x-ass-og-author'], + authorUrl: useSse ? sse.authorUrl : req.headers['x-ass-og-author-url'], + provider: useSse ? sse.provider : req.headers['x-ass-og-provider'], + providerUrl: useSse ? sse.providerUrl : req.headers['x-ass-og-provider-url'], + color: useSse ? sse.color : req.headers['x-ass-og-color'] }; // Fix spaces in originalname @@ -110,7 +118,7 @@ router.post('/', (req: Request, res: Response, next: Function) => { .then(() => { // Log the upload const logInfo = `${req.file!.originalname} (${req.file!.mimetype}, ${formatBytes(req.file.size)})`; - const uploader = users[req.token ?? ''] ? users[req.token ?? ''].username : ''; + const uploader = findFromToken(req.token)?.username ?? 'Unknown'; log.success('File uploaded', logInfo, `uploaded by ${uploader}`); // Build the URLs @@ -127,19 +135,18 @@ router.post('/', (req: Request, res: Response, next: Function) => { hook.setAvatar(avatar); // Build the embed - const embed = new MessageBuilder() + const embed = new EmbedBuilder() .setTitle(logInfo) - // @ts-ignore - .setUrl(resourceUrl) + .setURL(resourceUrl) + .setAuthor({ name: `${name} ${version}`, url: homepage, icon_url: ASS_LOGO }) .setDescription(`${admin ? `**User:** \`${uploader}\`\n` : ''}**Size:** \`${formatBytes(req.file.size)}\`\n**[Delete](${deleteUrl})**`) - .setThumbnail(thumbnailUrl) - // @ts-ignore + .setThumbnail({ url: thumbnailUrl }) .setColor(req.file.vibrant) .setTimestamp(); // Send the embed to the webhook, then delete the client after to free resources log.debug(`Sending${admin ? ' admin' : ''} embed to webhook`); - hook.send(embed) + hook.addEmbed(embed).send() .then(() => log.debug(`Webhook${admin ? ' admin' : ''} sent`)) .catch((err) => log.error('Webhook error').err(err)); } @@ -163,22 +170,6 @@ router.post('/', (req: Request, res: Response, next: Function) => { adminWebhookUsername.trim().length === 0 ? 'ass admin logs' : adminWebhookUsername, adminWebhookAvatar.trim().length === 0 ? ASS_LOGO : adminWebhookAvatar, true); - - // Also update the users upload count - if (!users[req.token ?? '']) { - const generateUsername = () => generateId('random', 20, 0, req.file.size.toString()); // skipcq: JS-0074 - let username: string = generateUsername(); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - while (Object.values(users).findIndex((user: User) => user.username === username) !== -1) // skipcq: JS-0073 - username = generateUsername(); - - users[req.token ?? ''] = { username, count: 0 }; - } - users[req.token ?? ''].count += 1; - fs.writeJsonSync(path('auth.json'), { users }, { spaces: 4 }); - log.debug('Upload request flow completed', ''); }); }) diff --git a/src/setup.js b/src/setup.js index 4a1a9641..b1a2ce76 100644 --- a/src/setup.js +++ b/src/setup.js @@ -12,6 +12,8 @@ const config = { spaceReplace: '_', mediaStrict: false, viewDirect: false, + useIdInViewer: false, + idInViewerExtension: false, dataEngine: '@tycrek/papito', frontendName: 'ass-x', useSia: false, @@ -168,6 +170,18 @@ function doSetup() { default: config.viewDirect, required: false }, + useIdInViewer: { + description: 'Use the ID in the web viewer instead of the filename', + type: 'boolean', + default: config.useIdInViewer, + required: false + }, + idInViewerExtension: { + description: '(Only applies if "useIdInViewer" is true) Include the file extension in the ID in the web viewer', + type: 'boolean', + default: config.idInViewerExtension, + required: false + }, dataEngine: { description: 'Data engine to use (must match an npm package name. If unsure, leave blank)', type: 'string', diff --git a/src/tools/script.adduser.ts b/src/tools/script.adduser.ts new file mode 100644 index 00000000..95c61df7 --- /dev/null +++ b/src/tools/script.adduser.ts @@ -0,0 +1,29 @@ +import path from 'path'; +import fs from 'fs-extra'; +import axios from 'axios'; +import logger from '../logger'; +import { User } from '../types/auth'; + +// Port from config.json +const { port } = fs.readJsonSync(path.join(process.cwd(), 'config.json')); + +// CLI key from auth.json +const { cliKey } = fs.readJsonSync(path.join(process.cwd(), 'auth.json')); + +if (process.argv.length < 4) { + logger.error('Missing username or password'); + logger.error('Usage: node script.adduser.js [admin] [meta]'); + process.exit(1); +} else { + const username = process.argv[2]; + const password = process.argv[3]; + const admin = process.argv[4] ? process.argv[4].toLowerCase() === 'true' : false; + const meta = process.argv[5] ? JSON.parse(process.argv[5]) : {}; + + axios.post(`http://localhost:${port}/api/user/new`, { username, password, admin, meta }, { headers: { 'Authorization': cliKey } }) + .then((response) => { + const user = response.data as User; + logger.info('User created', username, user.unid).callback(() => process.exit(0)) + }) + .catch((err) => logger.error(err).callback(() => process.exit(1))); +} diff --git a/src/tools/script.setpassword.ts b/src/tools/script.setpassword.ts new file mode 100644 index 00000000..ebbd55a7 --- /dev/null +++ b/src/tools/script.setpassword.ts @@ -0,0 +1,19 @@ +import logger from '../logger'; +import { onStart, users, setUserPassword } from '../auth'; + +if (process.argv.length < 4) { + logger.error('Missing username/unid or password'); + process.exit(1); +} else { + const id = process.argv[2]; + const password = process.argv[3]; + + onStart(process.argv[4] || 'auth.json') + .then(() => { + const user = users.find((user) => user.unid === id || user.username === id); + if (!user) throw new Error('User not found'); + else return setUserPassword(user.unid, password); + }) + .then(() => logger.info('Password changed successfully').callback(() => process.exit(0))) + .catch((err) => logger.error(err).callback(() => process.exit(1))); +} diff --git a/src/tools/script.testpassword.ts b/src/tools/script.testpassword.ts new file mode 100644 index 00000000..699ec9a2 --- /dev/null +++ b/src/tools/script.testpassword.ts @@ -0,0 +1,20 @@ +import logger from '../logger'; +import { onStart, users } from '../auth'; +import { compare } from 'bcrypt'; + +if (process.argv.length < 4) { + logger.error('Missing username/unid or password'); + process.exit(1); +} else { + const id = process.argv[2]; + const password = process.argv[3]; + + onStart(process.argv[4] || 'auth.json') + .then(() => { + const user = users.find((user) => user.unid === id || user.username === id); + if (!user) throw new Error('User not found'); + else return compare(password, user.passhash); + }) + .then((result) => logger.info('Matches', `${result}`).callback(() => process.exit(0))) + .catch((err) => logger.error(err).callback(() => process.exit(1))); +} diff --git a/src/types/auth.d.ts b/src/types/auth.d.ts new file mode 100644 index 00000000..acabc7af --- /dev/null +++ b/src/types/auth.d.ts @@ -0,0 +1,72 @@ +/** + * Defines the structure of a user + */ +export interface User { + /** + * Unique ID, provided by Nano ID + */ + unid: string + + /** + * Name of the user + */ + username: string + + /** + * Hashed password. Passwords are hashed using bcrypt. + */ + passhash: string + + /** + * Token used for upload authentication + */ + token: string + + /** + * Indicates whether the user is an admin + */ + admin: boolean + + /** + * Extra metadata. Frontends can use this to store extra data. + */ + meta: { + [key: string]: any + } +} + +/** + * Defines the structure of the users.json file + */ +export interface Users { + /** + * List of users. The key is the user's unique ID. + */ + users: User[] + + /** + * Indicates whether auth.json has been migrated + */ + migrated?: boolean + + /** + * Access key for the CLI + */ + cliKey?: string + + /** + * Extra metadata. Frontends can use this to store extra data. + */ + meta: { + [key: string]: any + } +} + +export interface OldUser { + username: string + count: number +} + +export interface OldUsers { + [key: string]: OldUser +} diff --git a/src/types/definitions.d.ts b/src/types/definitions.d.ts index 75c5021c..cbbdfbf4 100644 --- a/src/types/definitions.d.ts +++ b/src/types/definitions.d.ts @@ -12,11 +12,6 @@ declare global { } } -export interface User { - token: string - username: string -} - export interface FileData { // Data from request file object uuid?: string @@ -43,7 +38,11 @@ export interface FileData { domain: string timestamp: number timeoffset: string - token: string + /** + * @deprecated + */ + token?: string + uploader: string opengraph: OpenGraphData // I found this in utils and idk where it comes from diff --git a/src/types/json.d.ts b/src/types/json.d.ts index 7d0fd5ea..da3fba65 100644 --- a/src/types/json.d.ts +++ b/src/types/json.d.ts @@ -12,6 +12,8 @@ declare module 'ass-json' { gfyIdSize: number mediaStrict: boolean viewDirect: boolean + useIdInViewer: boolean + idInViewerExtension: boolean dataEngine: string frontendName: string indexFile: string @@ -50,4 +52,14 @@ declare module 'ass-json' { version: string homepage: string } + + interface ServerSideEmbed { + title: string + description?: string + author?: string + authorUrl?: string + provider?: string + providerUrl?: string + color?: string + } } diff --git a/src/utils.ts b/src/utils.ts index 0748fb11..abdb81a3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -68,14 +68,6 @@ export function replaceholder(data: string, size: number, timestamp: number, tim .replace(/×tamp/g, formatTimestamp(timestamp, timeoffset)); } -export function arrayEquals(arr1: any[], arr2: any[]) { - return arr1.length === arr2.length && arr1.slice().sort().every((value: string, index: number) => value === arr2.slice().sort()[index]) -}; - -export function verify(req: Request, users: JSON) { - return req.headers.authorization && Object.prototype.hasOwnProperty.call(users, req.headers.authorization); -} - const idModes = { zws: 'zws', // Zero-width spaces (see: https://zws.im/) og: 'original', // Use original uploaded filename @@ -108,7 +100,6 @@ module.exports = { replaceholder, randomHexColour, sanitize, - verify, renameFile: (req: Request, newName: string) => new Promise((resolve: Function, reject) => { try { const paths = [req.file.destination, newName]; @@ -121,7 +112,6 @@ module.exports = { }), generateToken: () => token(), generateId, - arrayEquals, downloadTempS3: (file: FileData) => new Promise((resolve: Function, reject) => fetch(getS3url(file.randomId, file.ext)) .then((f2) => f2.body!.pipe(fs.createWriteStream(Path.join(__dirname, diskFilePath, sanitize(file.originalname))).on('close', () => resolve()))) diff --git a/tailwind.config.js b/tailwind.config.js index fa3e321f..81e6b341 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -18,10 +18,11 @@ const defaults = { bgViewer: '#151515', // Text colours - txtPrimary: '#FD842D', - txtSecondary: '#BDBDBD', + txtPrimary: '#BDBDBD', + txtSecondary: '#8D8D8D', // Links + linkPrimary: '#FD842D', linkHover: '#FD710D', linkActive: '#DE5E02', @@ -52,6 +53,7 @@ module.exports = { colors: { 'primary': theme.txtPrimary || defaults.txtPrimary, 'secondary': theme.txtSecondary || defaults.txtSecondary, + 'link-primary': theme.linkPrimary || defaults.linkPrimary, 'link-hover': theme.linkHover || defaults.linkHover, 'link-active': theme.linkActive || defaults.linkActive, }, diff --git a/tailwind.css b/tailwind.css index adb200cd..4a529832 100644 --- a/tailwind.css +++ b/tailwind.css @@ -13,7 +13,7 @@ @apply no-underline hover_no-underline active_no-underline visited_no-underline /* regular, visited */ - text-primary visited_text-primary + text-link-primary visited_text-link-primary border-b-2 visited_border-b-2 border-transparent visited_border-transparent rounded-sm visited_rounded-sm diff --git a/tsconfig.json b/tsconfig.json index 5cec8f8a..6b63df67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "extends": "@tsconfig/node16/tsconfig.json", "compilerOptions": { "outDir": "./dist", - "target": "ES2021", + "target": "ES2022", "lib": [ - "ES2021", + "ES2022", "DOM" ], "allowJs": true, diff --git a/views/view.pug b/views/view.pug index c6aefa50..4c175edf 100644 --- a/views/view.pug +++ b/views/view.pug @@ -21,25 +21,26 @@ html * { display: none !important; } meta(http-equiv='refresh' content=`0; url='${resourceAttr.src}'`) - body.font-main.text-secondary.bg-page + body.font-main.bg-page .w-full.h-full.flex.justify-center.items-center.text-center .bg-viewer.rounded-24 - h4.mx-4.mt-6.mb-4.text-3xl.font-main!=title - figure.block.mx-10.my-4.flex.flex-col.align-items-center - if fileIs.video - video.res-media(controls loop muted playsinline preload='metadata')&attributes(resourceAttr) - else if fileIs.image - img.res-media(decoding='async')&attributes(resourceAttr) - else if fileIs.audio - audio.res-media(controls loop preload='metadata')&attributes(resourceAttr) - else - code!=mimetype + h4.mx-4.mt-6.mb-4.text-3xl.font-main.text-primary!=title + figure.mx-10.my-4.flex.flex-col.align-items-center.justify-center + .flex.justify-center + if fileIs.video + video.res-media(controls loop muted playsinline preload='metadata')&attributes(resourceAttr) + else if fileIs.image + img.res-media(decoding='async')&attributes(resourceAttr) + else if fileIs.audio + audio.res-media(controls loop preload='metadata')&attributes(resourceAttr) + else + code!=mimetype figcaption br - span.text-2xl Uploaded by #[strong!=uploader] + span.text-2xl.text-primary Uploaded by #[strong!=uploader] br - span #{timestamp} (#{size}) + span.text-secondary #{timestamp} (#{size}) br span: a.link(href='#' onclick=`window.location = '${resourceAttr.src}?download=yes'; return false;` download=title) Download if showAd - .mx-4.mb-8.text-footer: p Image hosted by #[a.link(href='https://github.com/tycrek/ass' target='_blank'): strong ass], the superior self-hosted ShareX server + .mx-4.mb-8.text-footer.text-secondary: p Image hosted by #[a.link(href='https://github.com/tycrek/ass' target='_blank'): strong ass], the superior self-hosted ShareX server