diff --git a/assets/js/fm.js b/assets/js/fm.js index 3e74c5c..8fb2c06 100644 --- a/assets/js/fm.js +++ b/assets/js/fm.js @@ -1,9 +1,17 @@ document.addEventListener('DOMContentLoaded', () => { + if (!document.getElementById('upload-form')) return; + const neverExpireCheckbox = document.getElementById('field-never_expire'); const expireAfterDaysField = document.getElementById('field-expire_after_days'); + const autogenUrlCheckbox = document.getElementById('field-autogen_url'); + const slugField = document.getElementById('field-slug'); + neverExpireCheckbox.addEventListener('change', () => { - // noinspection RedundantConditionalExpressionJS - expireAfterDaysField.disabled = neverExpireCheckbox.checked ? true : false; + expireAfterDaysField.disabled = neverExpireCheckbox.checked; + }); + + autogenUrlCheckbox.addEventListener('change', () => { + slugField.disabled = autogenUrlCheckbox.checked; }); }); \ No newline at end of file diff --git a/package.json b/package.json index 8bdab9c..b172545 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "@babel/preset-env": "^7.9.5", "@types/config": "^0.0.36", "@types/express": "^4.17.6", + "@types/formidable": "^1.0.31", "@types/jest": "^25.2.1", - "@types/multer": "^1.4.3", "@types/node": "^13.13.2", "babel-loader": "^8.1.0", "concurrently": "^5.1.0", @@ -47,6 +47,6 @@ "dependencies": { "config": "^3.3.1", "express": "^4.17.1", - "multer": "^1.4.2" + "formidable": "^1.2.2" } } diff --git a/src/App.ts b/src/App.ts index ae036ad..5ca97d1 100644 --- a/src/App.ts +++ b/src/App.ts @@ -64,23 +64,15 @@ export default class App extends Application { const redisComponent = new RedisComponent(); const mysqlComponent = new MysqlComponent(); - // Session - this.use(redisComponent); - this.use(new SessionComponent(redisComponent)); - - // Static files - this.use(new ServeStaticDirectoryComponent('public')); - this.use(new ServeStaticDirectoryComponent('node_modules/feather-icons/dist', '/icons')); - - // Utils - this.use(new RedirectBackComponent()); - this.use(new FormHelperComponent()); - const expressAppComponent = new ExpressAppComponent(this.port); this.use(expressAppComponent); this.use(new NunjucksComponent()); this.use(new LogRequestsComponent()); + // Static files + this.use(new ServeStaticDirectoryComponent('public')); + this.use(new ServeStaticDirectoryComponent('node_modules/feather-icons/dist', '/icons')); + // Maintenance this.use(new MaintenanceComponent(this, () => { return redisComponent.canServe() && mysqlComponent.canServe(); @@ -91,7 +83,9 @@ export default class App extends Application { this.use(mysqlComponent); this.use(new MailComponent()); - // Auth + // Session + this.use(redisComponent); + this.use(new SessionComponent(redisComponent)); this.use(new AuthComponent(new class extends AuthGuard { public async getProofForSession(session: Express.Session): Promise { return await MagicLink.bySessionID(session.id, [MagicLinkActionType.LOGIN, MagicLinkActionType.REGISTER]); @@ -100,13 +94,22 @@ export default class App extends Application { public async getProofForRequest(req: Request): Promise { const authorization = req.header('Authorization'); if (authorization) { - return await AuthToken.getBySecret(authorization); + const token = await AuthToken.getBySecret(authorization); + if (token) { + token.use(); + await token.save(); + } + return token; } return super.getProofForRequest(req); } })); + // Utils + this.use(new RedirectBackComponent()); + this.use(new FormHelperComponent()); + // Middlewares this.use(new CsrfProtectionComponent()); diff --git a/src/controllers/FileController.ts b/src/controllers/FileController.ts index d1ef990..0d64591 100644 --- a/src/controllers/FileController.ts +++ b/src/controllers/FileController.ts @@ -1,39 +1,74 @@ import Controller from "wms-core/Controller"; -import {REQUIRE_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent"; -import {Request, Response, Router} from "express"; +import {REQUIRE_AUTH_MIDDLEWARE, REQUIRE_REQUEST_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent"; +import {Request, Response} from "express"; import {BadRequestError, ForbiddenHttpError, NotFoundHttpError, ServerError} from "wms-core/HttpError"; import FileModel from "../models/FileModel"; import {cryptoRandomDictionary} from "wms-core/Utils"; import config from "config"; import * as fs from "fs"; -import multer from "multer"; +import AuthToken from "../models/AuthToken"; +import {IncomingForm} from "formidable"; +import {FILE_UPLOAD_MIDDLEWARE} from "wms-core/components/ExpressAppComponent"; const SLUG_DICTIONARY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + export default class FileController extends Controller { - setupRequestParsingMiddlewares(router: Router) { - router.use('/', FILE_UPLOAD_MIDDLEWARE); - router.use('/:slug', FILE_UPLOAD_MIDDLEWARE); + routes(): void { + this.get('/files/upload', this.getFileUploader, 'file-upload', REQUIRE_AUTH_MIDDLEWARE); + this.post('/files/post', this.postFileFrontend, 'post-file-frontend', REQUIRE_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE); + this.get('/files/:page([0-9]+)?', this.getFileManager, 'file-manager', REQUIRE_AUTH_MIDDLEWARE); + this.get('/files/delete/:slug', this.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE); + this.post('/gen-auth-token', this.postGenAuthToken, 'generate-token', REQUIRE_AUTH_MIDDLEWARE); + this.get('/revoke-auth-token/:id', this.getRevokeAuthToken, 'revoke-token', REQUIRE_AUTH_MIDDLEWARE); + + this.post('/', this.postFile, 'post-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE); + this.delete('/delete/:slug', this.deleteFile, 'delete-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE); + this.get('/:slug', this.downloadFile, 'get-file'); + this.put('/:slug', this.putFile, 'put-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE); } - routes(): void { - this.get('/files/:page?', this.getFileManager, 'file-manager', REQUIRE_AUTH_MIDDLEWARE); - this.get('/files/delete/:slug', this.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE); - - this.post('/', this.postFile, 'post-file', REQUIRE_AUTH_MIDDLEWARE); - this.delete('/delete/:slug', this.deleteFile, 'delete-file', REQUIRE_AUTH_MIDDLEWARE); - this.get('/:slug', this.downloadFile, 'get-file'); - this.put('/:slug', this.putFile, 'put-file', REQUIRE_AUTH_MIDDLEWARE); + protected async getFileUploader(req: Request, res: Response): Promise { + res.render('file-upload', { + max_upload_size: config.get('max_upload_size'), + auth_tokens: await AuthToken.getForUser(req.models.user!.id!), + }); } protected async getFileManager(req: Request, res: Response): Promise { res.render('file-manager', { - files: await FileModel.paginate(req, 100), - max_upload_size: config.get('max_upload_size'), + files: await FileModel.paginateForUser(req, 100, req.models.user!.id!), }); } + protected async postGenAuthToken(req: Request, res: Response): Promise { + const authToken = new AuthToken({ + user_id: req.models.user!.id, + ttl: req.body.ttl ? parseInt(req.body.ttl) : 365 * 24 * 3600, + }); + await authToken.save(); + req.flash('success', 'Successfully created auth token.'); + res.redirectBack(Controller.route('file-upload')); + } + + protected async getRevokeAuthToken(req: Request, res: Response): Promise { + const id = req.params.id; + if (!id) throw new BadRequestError('Cannot revoke token without an id.', 'Please provide an id.', req.url); + + const authToken = await AuthToken.getById(`${id}`); + if (!authToken) throw new NotFoundHttpError('Auth token', req.url); + if (!authToken.canDelete(req.models.user!.id!)) throw new ForbiddenHttpError('auth token', req.url); + + await authToken.delete(); + + req.flash('success', 'Successfully deleted auth token.'); + res.redirectBack(Controller.route('file-upload')); + } + + protected async postFileFrontend(req: Request, res: Response): Promise { + await this.handleFileUpload(req.body.autogen_url === undefined && req.body.slug ? req.body.slug : await this.generateSlug(10), req, res); + } + protected async downloadFile(req: Request, res: Response): Promise { - console.log('heeey'); const file = await FileModel.getBySlug(req.params.slug); if (!file || file.shouldBeDeleted()) throw new NotFoundHttpError('File', req.url); @@ -59,11 +94,11 @@ export default class FileController extends Controller { protected async handleFileUpload(slug: string, req: Request, res: Response): Promise { // Check for file upload - if (!req.file) { + if (!req.files || !req.files['upload']) { throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url); } - let upload = req.file; + let upload = req.files['upload']; // TTL let ttl = config.get('default_file_ttl'); @@ -74,7 +109,7 @@ export default class FileController extends Controller { const file = new FileModel({ user_id: req.models.user!.id, slug: slug, - real_name: upload.originalname, + real_name: upload.name, storage_type: 'local', storage_path: 'storage/uploads/' + slug, size: upload.size, @@ -138,9 +173,10 @@ export default class FileController extends Controller { } } -const FILE_UPLOAD_MIDDLEWARE = multer({ - dest: 'storage/tmp', - limits: { - fileSize: config.get('max_upload_size') * 1024 * 1024, - }, -}).single('upload'); + +const FILE_UPLOAD_FORM_MIDDLEWARE = FILE_UPLOAD_MIDDLEWARE(() => { + const form = new IncomingForm(); + form.uploadDir = 'storage/tmp'; + form.maxFileSize = config.get('max_upload_size') * 1024 * 1024; + return form; +}, 'upload'); diff --git a/src/migrations/CreateAuthTokensTable.ts b/src/migrations/CreateAuthTokensTable.ts index c43a937..9bb7687 100644 --- a/src/migrations/CreateAuthTokensTable.ts +++ b/src/migrations/CreateAuthTokensTable.ts @@ -8,6 +8,7 @@ export default class CreateAuthTokensTable extends Migration { 'user_id INT NOT NULL,' + 'secret VARCHAR(64) UNIQUE NOT NULL,' + 'created_at DATETIME NOT NULL DEFAULT NOW(),' + + 'used_at DATETIME NOT NULL DEFAULT NOW(),' + 'ttl INT UNSIGNED NOT NULL,' + 'PRIMARY KEY (id)' + ')', connection); diff --git a/src/models/AuthToken.ts b/src/models/AuthToken.ts index 47532dc..958c5f8 100644 --- a/src/models/AuthToken.ts +++ b/src/models/AuthToken.ts @@ -3,6 +3,7 @@ import AuthProof from "wms-core/auth/AuthProof"; import UserEmail from "wms-core/auth/models/UserEmail"; import User from "wms-core/auth/models/User"; import Validator from "wms-core/db/Validator"; +import {cryptoRandomDictionary} from "wms-core/Utils"; export default class AuthToken extends Model implements AuthProof { public static async getBySecret(secret: string): Promise { @@ -10,19 +11,41 @@ export default class AuthToken extends Model implements AuthProof { return models.length > 0 ? models[0] : null; } + public static async getForUser(user_id: number): Promise { + return await this.models(this.select().where('user_id', user_id)); + } + protected readonly user_id!: number; protected readonly secret!: string; protected created_at?: Date; + protected used_at?: Date; protected readonly ttl!: number; + constructor(props: any) { + super(props); + + if (!this.secret) { + this.secret = cryptoRandomDictionary(64, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'); + } + } + protected defineProperties() { this.defineProperty('user_id', new Validator().defined().exists(User, 'id')); this.defineProperty('secret', new Validator().defined().between(32, 64)); this.defineProperty('created_at', new Validator()); + this.defineProperty('used_at', new Validator()); this.defineProperty('ttl', new Validator().defined().min(1).max(5 * 365 * 24 * 3600)); // max 5 years } + public use() { + this.used_at = new Date(); + } + + public canDelete(user_id: number) { + return this.user_id === user_id; + } + public getExpirationDate(): Date { if (!this.created_at) return new Date(); diff --git a/src/models/FileModel.ts b/src/models/FileModel.ts index 7d73dfa..40e1ff0 100644 --- a/src/models/FileModel.ts +++ b/src/models/FileModel.ts @@ -3,6 +3,7 @@ import Validator from "wms-core/db/Validator"; import Controller from "wms-core/Controller"; import config from "config"; import User from "wms-core/auth/models/User"; +import {Request} from "express"; export default class FileModel extends Model { public static get table(): string { @@ -14,6 +15,10 @@ export default class FileModel extends Model { return models.length > 0 ? models[0] : null; } + public static async paginateForUser(req: Request, perPage: number, user_id: number): Promise { + return await this.paginate(req, perPage, this.select().where('user_id', user_id)); + } + public readonly user_id!: number; public readonly slug!: string; public readonly real_name!: string; diff --git a/views/file-manager.njk b/views/file-manager.njk index 9e7491b..259831f 100644 --- a/views/file-manager.njk +++ b/views/file-manager.njk @@ -10,24 +10,6 @@

File manager

You're their manager, please be nice with them.

-
-
-

Upload a file

- -
- {{ macros.field(_locals, 'file', 'upload', '', 'Choose wisely', 'The maximum upload size is ' + max_upload_size + 'MiB', validation_attributes='required') }} - - {{ macros.field(_locals, 'number', 'expire_after_days', '30', 'How many days to delete this file after', null, validation_attributes='max="1825"') }} - - {{ macros.field(_locals, 'checkbox', 'never_expire', '', 'Never delete this file') }} - - {{ macros.csrf(getCSRFToken) }} - - -
-
-
-

File list

diff --git a/views/file-upload.njk b/views/file-upload.njk new file mode 100644 index 0000000..0d6a36b --- /dev/null +++ b/views/file-upload.njk @@ -0,0 +1,128 @@ +{% extends 'layouts/base.njk' %} + +{% set title = 'ily.li - File manager' %} + +{% block scripts %} + +{% endblock %} + +{% block body %} +

Upload files

+

(except illegal ones)

+ +
+
+

Upload a file

+ +
+ {{ macros.field(_locals, 'file', 'upload', '', 'Choose wisely', 'The maximum upload size is ' + max_upload_size + 'MiB', validation_attributes='required') }} + + {{ macros.field(_locals, 'number', 'expire_after_days', '30', 'How many days to delete this file after', null, validation_attributes='max="1825"') }} + {{ macros.field(_locals, 'checkbox', 'never_expire', '', 'Never delete this file') }} + + {{ macros.field(_locals, 'text', 'slug', '', 'Custom url slug', 'Example: beautiful_image.jpg sets url to https://ily.li/beautiful_image.jpg', validation_attributes='disabled') }} + {{ macros.field(_locals, 'checkbox', 'autogen_url', '', 'Generate url automatically', null, validation_attributes='checked') }} + + {{ macros.csrf(getCSRFToken) }} + + + +
+ +
+

Setup a desktop utility

+

There may be a desktop client at some point. For now, if you're an advanced user, you can setup + scripts/macros.

+

+ To upload the file, you must: +

+
    +
  • Set the "Authorization" HTTP header to an auth token (generate one with the form below)
  • +
  • Make a proper file upload request either with the method "POST" on / (auto-generates a short url) or + "PUT" (choose the target url you want, alphanum) +
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Field nameDescriptionOptional?Example
uploadThe file fieldNo-
ttlHow much time (in seconds) to keep the fileYes0 (never delete), 30 (delete after 30s)
+

Example with curl:

+
curl -X POST -H 'Accept: application/json' \
+-H "Authorization: very_secret_and_personal_token" \
+-F 'upload=@path/to/local/file' \
+https://ily.li/
+
curl -X PUT -H 'Accept: application/json' \
+-H "Authorization: very_secret_and_personal_token" \
+-F 'upload=@path/to/local/file' \
+https://ily.li/my_very_important_file.png
+
curl -X POST -H 'Accept: application/json' \
+-H "Authorization: very_secret_and_personal_token" \
+-F 'upload=@path/to/local/file' \
+-F 'ttl=30' \
+https://ily.li/
+
+ + +
+

Auth tokens

+
+ {{ macros.csrf(getCSRFToken) }} + + +
+ + + + + + + + + + + + + + {% for token in auth_tokens %} + + + + + + + + {% endfor %} + +
#SecretCreated atLast used atActions
{{ token.id }} +
+
{{ token.secret }}
+ +
+
{{ token.created_at.toISOString() }}{{ token.used_at.toISOString() }} + Revoke +
+
+{% endblock %} \ No newline at end of file diff --git a/views/layouts/base.njk b/views/layouts/base.njk index 9b3ed9c..acc1fd6 100644 --- a/views/layouts/base.njk +++ b/views/layouts/base.njk @@ -17,6 +17,7 @@
  • About
  • {% if user %} +
  • File uploader
  • File manager
  • Logout
  • {% else %} diff --git a/yarn.lock b/yarn.lock index 8aeabe5..0587f97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1110,6 +1110,11 @@ dependencies: "@types/node" "*" +"@types/events@*": + version "3.0.0" + resolved "https://registry.toot.party/@types%2fevents/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + "@types/express-serve-static-core@*": version "4.17.7" resolved "https://registry.toot.party/@types%2fexpress-serve-static-core/-/express-serve-static-core-4.17.7.tgz#dfe61f870eb549dc6d7e12050901847c7d7e915b" @@ -1137,6 +1142,14 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/formidable@^1.0.31": + version "1.0.31" + resolved "https://registry.toot.party/@types%2fformidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b" + integrity sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q== + dependencies: + "@types/events" "*" + "@types/node" "*" + "@types/glob@^7.1.1": version "7.1.2" resolved "https://registry.toot.party/@types%2fglob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" @@ -1195,13 +1208,6 @@ resolved "https://registry.toot.party/@types%2fminimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/multer@^1.4.3": - version "1.4.3" - resolved "https://registry.toot.party/@types%2fmulter/-/multer-1.4.3.tgz#bdff74b334c38a8ee1de9fbedb5d1d3dbc377422" - integrity sha512-tWsKbF5LYtXrJ7eOfI0aLBgEv9B7fnJe1JRXTj5+Z6EMfX0yHVsRFsNGnKyN8Bs0gtDv+JR37xAqsPnALyVTqg== - dependencies: - "@types/express" "*" - "@types/mysql@^2.15.10": version "2.15.13" resolved "https://registry.toot.party/@types%2fmysql/-/mysql-2.15.13.tgz#153dc2e2f8dffd39f7bba556c2679f14bdbecde1" @@ -1591,11 +1597,6 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -append-field@^1.0.0: - version "1.0.0" - resolved "https://registry.toot.party/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" - integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= - aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.toot.party/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -2210,14 +2211,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.toot.party/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -busboy@^0.2.11: - version "0.2.14" - resolved "https://registry.toot.party/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" - integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= - dependencies: - dicer "0.2.5" - readable-stream "1.1.x" - bytes@3.1.0: version "3.1.0" resolved "https://registry.toot.party/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2623,7 +2616,7 @@ concat-map@0.0.1: resolved "https://registry.toot.party/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.toot.party/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3210,14 +3203,6 @@ detect-newline@^3.0.0: resolved "https://registry.toot.party/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -dicer@0.2.5: - version "0.2.5" - resolved "https://registry.toot.party/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" - integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= - dependencies: - readable-stream "1.1.x" - streamsearch "0.1.2" - diff-sequences@^25.2.6: version "25.2.6" resolved "https://registry.toot.party/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" @@ -4066,6 +4051,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formidable@^1.2.2: + version "1.2.2" + resolved "https://registry.toot.party/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.toot.party/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -5190,11 +5180,6 @@ is-yarn-global@^0.3.0: resolved "https://registry.toot.party/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.toot.party/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.toot.party/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -6784,20 +6769,6 @@ ms@^2.1.1: resolved "https://registry.toot.party/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multer@^1.4.2: - version "1.4.2" - resolved "https://registry.toot.party/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" - integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== - dependencies: - append-field "^1.0.0" - busboy "^0.2.11" - concat-stream "^1.5.2" - mkdirp "^0.5.1" - object-assign "^4.1.1" - on-finished "^2.3.0" - type-is "^1.6.4" - xtend "^4.0.0" - mysql@^2.18.1: version "2.18.1" resolved "https://registry.toot.party/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717" @@ -8049,16 +8020,6 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.toot.party/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.toot.party/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -8931,11 +8892,6 @@ stream-shift@^1.0.0: resolved "https://registry.toot.party/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.toot.party/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.toot.party/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -9007,11 +8963,6 @@ string_decoder@^1.0.0, string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.toot.party/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.toot.party/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -9480,7 +9431,7 @@ type-fest@^0.8.1: resolved "https://registry.toot.party/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.toot.party/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -9974,13 +9925,12 @@ widest-line@^3.1.0: string-width "^4.0.0" wms-core@^0: - version "0.7.12" - resolved "https://registry.toot.party/wms-core/-/wms-core-0.7.12.tgz#9e198599df089962f636de7b28fa10d96f4167b7" - integrity sha512-msKHjh+7QnPugpcxrLcSiVFBemtGkSQOG0fLFIRoTk5CORbZHLdghBeL7DGmSsfM1SV97om2XI9xSjz2A/Rj1Q== + version "0.8.10" + resolved "https://registry.toot.party/wms-core/-/wms-core-0.8.10.tgz#8db64926f9bd9eaa61556ad98dcd215361b16950" + integrity sha512-1GJkpOX/efoDGK9JOwHnBkD55Q6dEKTQsdHpgMmF3g0h7XxJFAAsZTv0tOyfYHIEpJo5mNdR/+mzdZTPsgtAdw== dependencies: "@types/express" "^4.17.6" "@types/express-session" "^1.17.0" - "@types/multer" "^1.4.3" "@types/mysql" "^2.15.10" "@types/nodemailer" "^6.4.0" "@types/nunjucks" "^3.1.3" @@ -9994,6 +9944,7 @@ wms-core@^0: cookie-parser "^1.4.5" express "^4.17.1" express-session "^1.17.1" + formidable "^1.2.2" geoip-lite "^1.4.2" mjml "^4.6.2" mysql "^2.18.1"