mirror of https://github.com/tycrek/ass
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
5.9 KiB
128 lines
5.9 KiB
3 years ago
|
import { FileData, AssRequest, AssResponse, ErrWrap, User } from "../definitions";
|
||
|
|
||
3 years ago
|
const fs = require('fs-extra');
|
||
3 years ago
|
//const rateLimit = require('express-rate-limit');
|
||
3 years ago
|
const { DateTime } = require('luxon');
|
||
|
const { WebhookClient, MessageEmbed } = require('discord.js');
|
||
|
const { doUpload, processUploaded } = require('../storage');
|
||
3 years ago
|
const { maxUploadSize, resourceIdSize, gfyIdSize, resourceIdType } = require('../../config.json');
|
||
3 years ago
|
const { path, log, verify, getTrueHttp, getTrueDomain, generateId, formatBytes } = require('../utils');
|
||
3 years ago
|
const { CODE_UNAUTHORIZED, CODE_PAYLOAD_TOO_LARGE } = require('../../MagicNumbers.json');
|
||
3 years ago
|
const data = require('../data');
|
||
|
const users = require('../auth');
|
||
|
|
||
|
const ASS_LOGO = 'https://cdn.discordapp.com/icons/848274994375294986/8d339d4a2f3f54b2295e5e0ff62bd9e6.png?size=1024';
|
||
3 years ago
|
import express from 'express';
|
||
3 years ago
|
const router = express.Router();
|
||
|
|
||
3 years ago
|
// Rate limit middleware
|
||
3 years ago
|
/* router.use('/', rateLimit({
|
||
3 years ago
|
windowMs: 1000 * 60, // 60 seconds // skipcq: JS-0074
|
||
|
max: 90 // Limit each IP to 30 requests per windowMs // skipcq: JS-0074
|
||
3 years ago
|
})); */
|
||
3 years ago
|
|
||
3 years ago
|
// Block unauthorized requests and attempt token sanitization
|
||
3 years ago
|
router.post('/', (req: AssRequest, res: AssResponse, next: Function) => {
|
||
3 years ago
|
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
|
||
3 years ago
|
!verify(req, users) ? log.warn('Upload blocked', 'Unauthorized').callback(() => res.sendStatus(CODE_UNAUTHORIZED)) : next(); // skipcq: JS-0093
|
||
3 years ago
|
});
|
||
|
|
||
|
// Upload file
|
||
3 years ago
|
//router.post('/', doUpload, processUploaded, ({ next }) => next());
|
||
3 years ago
|
router.post('/', (req: AssRequest, res: AssResponse, next: Function) => doUpload(req, res, (err: Error) => {
|
||
3 years ago
|
log.express().Header(req, 'Content-Type');
|
||
3 years ago
|
(err) ? log.error(`Multer encountered an ${!(err.toString().includes('MulterError')) ? 'unknown ' : ''}error`, err).callback(next, err) : log.debug('Multer', 'File saved in temp dir').callback(next);
|
||
3 years ago
|
}), processUploaded, ({ next }: { next: Function }) => next());
|
||
3 years ago
|
|
||
3 years ago
|
router.use('/', (err: ErrWrap, _req: AssRequest, res: AssResponse, next: Function) => err.code && err.code === 'LIMIT_FILE_SIZE' ? log.warn('Upload blocked', 'File too large').callback(() => res.status(CODE_PAYLOAD_TOO_LARGE).send(`Max upload size: ${maxUploadSize}MB`)) : next(err)); // skipcq: JS-0229
|
||
3 years ago
|
|
||
|
// Process uploaded file
|
||
3 years ago
|
router.post('/', (req: AssRequest, res: AssResponse, next: Function) => {
|
||
3 years ago
|
// Load overrides
|
||
3 years ago
|
const trueDomain = getTrueDomain(req.headers['x-ass-domain']);
|
||
|
const generator = req.headers['x-ass-access'] || resourceIdType;
|
||
3 years ago
|
|
||
3 years ago
|
// Save domain with file
|
||
3 years ago
|
req.file!.domain = `${getTrueHttp()}${trueDomain}`;
|
||
3 years ago
|
|
||
3 years ago
|
// Get the uploaded time in milliseconds
|
||
3 years ago
|
req.file!.timestamp = DateTime.now().toMillis();
|
||
3 years ago
|
|
||
|
// Keep track of the token that uploaded the resource
|
||
3 years ago
|
req.file!.token = req.token ?? '';
|
||
3 years ago
|
|
||
|
// Attach any embed overrides, if necessary
|
||
3 years ago
|
req.file!.opengraph = {
|
||
3 years ago
|
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']
|
||
|
};
|
||
|
|
||
|
// Save the file information
|
||
3 years ago
|
const resourceId = generateId(generator, resourceIdSize, req.headers['x-ass-gfycat'] || gfyIdSize, req.file!.originalname);
|
||
3 years ago
|
log.debug('Saving data', data.name);
|
||
3 years ago
|
data.put(resourceId.split('.')[0], req.file).then(() => {
|
||
|
// Log the upload
|
||
3 years ago
|
const logInfo = `${req.file!.originalname} (${req.file!.mimetype}, ${formatBytes(req.file!.size)})`;
|
||
|
log.success('File uploaded', logInfo, `uploaded by ${users[req.token ?? ''] ? users[req.token ?? ''].username : '<token-only>'}`);
|
||
3 years ago
|
|
||
|
// Build the URLs
|
||
|
const resourceUrl = `${getTrueHttp()}${trueDomain}/${resourceId}`;
|
||
|
const thumbnailUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/thumbnail`;
|
||
3 years ago
|
const deleteUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/delete/${req.file!.deleteId}`;
|
||
3 years ago
|
|
||
|
// Send the response
|
||
|
res.type('json').send({ resource: resourceUrl, thumbnail: thumbnailUrl, delete: deleteUrl })
|
||
|
.on('finish', () => {
|
||
3 years ago
|
log.debug('Upload response sent');
|
||
3 years ago
|
|
||
|
// After we have sent the user the response, also send a Webhook to Discord (if headers are present)
|
||
|
if (req.headers['x-ass-webhook-client'] && req.headers['x-ass-webhook-token']) {
|
||
3 years ago
|
const client = req.headers['x-ass-webhook-client']
|
||
3 years ago
|
|
||
|
// Build the webhook client & embed
|
||
3 years ago
|
const whc = new WebhookClient(client, req.headers['x-ass-webhook-token']);
|
||
3 years ago
|
const embed = new MessageEmbed()
|
||
|
.setTitle(logInfo)
|
||
|
.setURL(resourceUrl)
|
||
3 years ago
|
.setDescription(`**Size:** \`${formatBytes(req.file!.size)}\`\n**[Delete](${deleteUrl})**`)
|
||
3 years ago
|
.setThumbnail(thumbnailUrl)
|
||
3 years ago
|
.setColor(req.file!.vibrant)
|
||
|
.setTimestamp(req.file!.timestamp);
|
||
3 years ago
|
|
||
|
// Send the embed to the webhook, then delete the client after to free resources
|
||
3 years ago
|
log.debug('Sending webhook to client', client);
|
||
3 years ago
|
whc.send(null, {
|
||
|
username: req.headers['x-ass-webhook-username'] || 'ass',
|
||
|
avatarURL: req.headers['x-ass-webhook-avatar'] || ASS_LOGO,
|
||
|
embeds: [embed]
|
||
3 years ago
|
}).then(() => log.debug('Webhook sent').callback(() => whc.destroy()));
|
||
3 years ago
|
}
|
||
|
|
||
|
// Also update the users upload count
|
||
3 years ago
|
if (!users[req.token ?? '']) {
|
||
3 years ago
|
const generateUsername = () => generateId('random', 20, null); // skipcq: JS-0074
|
||
3 years ago
|
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
|
||
3 years ago
|
username = generateUsername();
|
||
3 years ago
|
|
||
|
users[req.token ?? ''] = { username, count: 0 };
|
||
3 years ago
|
}
|
||
3 years ago
|
users[req.token ?? ''].count += 1;
|
||
3 years ago
|
fs.writeJsonSync(path('auth.json'), { users }, { spaces: 4 });
|
||
|
|
||
3 years ago
|
log.debug('Upload request flow completed', '');
|
||
3 years ago
|
});
|
||
|
}).catch(next);
|
||
3 years ago
|
});
|
||
|
|
||
|
module.exports = router;
|