Prevent not-main domains from serving frontend
This commit is contained in:
parent
095e68b27e
commit
3b5554b9b3
@ -39,6 +39,7 @@ import BackendController from "./controllers/BackendController";
|
|||||||
import CreateUrlRedirectsTable from "./migrations/CreateUrlRedirectsTable";
|
import CreateUrlRedirectsTable from "./migrations/CreateUrlRedirectsTable";
|
||||||
import AuthTokenController from "./controllers/AuthTokenController";
|
import AuthTokenController from "./controllers/AuthTokenController";
|
||||||
import URLRedirectController from "./controllers/URLRedirectController";
|
import URLRedirectController from "./controllers/URLRedirectController";
|
||||||
|
import LinkController from "./controllers/LinkController";
|
||||||
|
|
||||||
export default class App extends Application {
|
export default class App extends Application {
|
||||||
private readonly port: number;
|
private readonly port: number;
|
||||||
@ -132,6 +133,9 @@ export default class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private registerControllers() {
|
private registerControllers() {
|
||||||
|
// Multi-domain + vhost
|
||||||
|
this.use(new LinkController());
|
||||||
|
|
||||||
// Priority
|
// Priority
|
||||||
this.use(new AuthController());
|
this.use(new AuthController());
|
||||||
this.use(new MagicLinkController(this.magicLinkWebSocketListener!));
|
this.use(new MagicLinkController(this.magicLinkWebSocketListener!));
|
||||||
|
@ -18,12 +18,7 @@ export default class FileController extends Controller {
|
|||||||
this.get('/files/upload/script', this.downloadLinuxScript, 'file-linux-script');
|
this.get('/files/upload/script', this.downloadLinuxScript, 'file-linux-script');
|
||||||
this.post('/files/post', this.postFileFrontend, 'post-file-frontend', REQUIRE_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_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/:page([0-9]+)?', this.getFileManager, 'file-manager', REQUIRE_AUTH_MIDDLEWARE);
|
||||||
this.post('/files/delete/:slug', this.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE);
|
this.post('/files/delete/:slug', FileController.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE);
|
||||||
|
|
||||||
this.post('/', this.postFile, 'post-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE);
|
|
||||||
this.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getFileUploader(req: Request, res: Response): Promise<void> {
|
protected async getFileUploader(req: Request, res: Response): Promise<void> {
|
||||||
@ -48,43 +43,10 @@ export default class FileController extends Controller {
|
|||||||
|
|
||||||
protected async postFileFrontend(req: Request, res: Response): Promise<void> {
|
protected async postFileFrontend(req: Request, res: Response): Promise<void> {
|
||||||
req.body.type = 'file';
|
req.body.type = 'file';
|
||||||
await this.handleFileUpload(req.body.autogen_url === undefined && req.body.slug ? req.body.slug : await generateSlug(10), req, res);
|
await FileController.handleFileUpload(req.body.autogen_url === undefined && req.body.slug ? req.body.slug : await generateSlug(10), req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async downloadFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
public static async handleFileUpload(slug: string, req: Request, res: Response): Promise<void> {
|
||||||
const file = await FileModel.getBySlug(req.params.slug);
|
|
||||||
if (!file) return next();
|
|
||||||
if (file.shouldBeDeleted()) {
|
|
||||||
await fs.unlinkSync(file.storage_path);
|
|
||||||
await file.delete();
|
|
||||||
Logger.info('Deleted', file.storage_path, `(${file.real_name})`);
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (file.storage_type) {
|
|
||||||
case 'local':
|
|
||||||
res.download(file.storage_path, file.real_name);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ServerError(`This file cannot be served. Download protocol for ${file.storage_type} storage type not implemented.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async postFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
||||||
if (req.body.type !== 'file') return next();
|
|
||||||
|
|
||||||
await this.handleFileUpload(req.body.slug || await generateSlug(10), req, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async putFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
||||||
if (req.body.type !== 'file') return next();
|
|
||||||
const slug = req.params.slug;
|
|
||||||
if (!slug) throw new BadRequestError('Cannot put without a slug.', 'Either provide a slug or use POST method instead.', req.url);
|
|
||||||
|
|
||||||
await this.handleFileUpload(slug, req, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async handleFileUpload(slug: string, req: Request, res: Response): Promise<void> {
|
|
||||||
// Check for file upload
|
// Check for file upload
|
||||||
if (!req.files || !req.files['upload']) {
|
if (!req.files || !req.files['upload']) {
|
||||||
throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url);
|
throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url);
|
||||||
@ -125,7 +87,7 @@ export default class FileController extends Controller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async deleteFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
public static async deleteFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
const slug = req.params.slug;
|
const slug = req.params.slug;
|
||||||
if (!slug) throw new BadRequestError('Cannot delete nothing.', 'Please provide a slug.', req.url);
|
if (!slug) throw new BadRequestError('Cannot delete nothing.', 'Please provide a slug.', req.url);
|
||||||
|
|
||||||
@ -156,7 +118,7 @@ export default class FileController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const FILE_UPLOAD_FORM_MIDDLEWARE = FILE_UPLOAD_MIDDLEWARE(() => {
|
export const FILE_UPLOAD_FORM_MIDDLEWARE = FILE_UPLOAD_MIDDLEWARE(() => {
|
||||||
const form = new IncomingForm();
|
const form = new IncomingForm();
|
||||||
form.uploadDir = 'storage/tmp';
|
form.uploadDir = 'storage/tmp';
|
||||||
form.maxFileSize = config.get<number>('max_upload_size') * 1024 * 1024;
|
form.maxFileSize = config.get<number>('max_upload_size') * 1024 * 1024;
|
||||||
|
87
src/controllers/LinkController.ts
Normal file
87
src/controllers/LinkController.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import Controller from "wms-core/Controller";
|
||||||
|
import {NextFunction, Request, Response} from "express";
|
||||||
|
import {BadRequestError, NotFoundHttpError, ServerError} from "wms-core/HttpError";
|
||||||
|
import config from "config";
|
||||||
|
import {REQUIRE_REQUEST_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent";
|
||||||
|
import URLRedirect from "../models/URLRedirect";
|
||||||
|
import URLRedirectController from "./URLRedirectController";
|
||||||
|
import FileModel from "../models/FileModel";
|
||||||
|
import fs from "fs";
|
||||||
|
import Logger from "wms-core/Logger";
|
||||||
|
import generateSlug from "../SlugGenerator";
|
||||||
|
import FileController, {FILE_UPLOAD_FORM_MIDDLEWARE} from "./FileController";
|
||||||
|
|
||||||
|
export default class LinkController extends Controller {
|
||||||
|
routes(): void {
|
||||||
|
this.post('/', this.postFile, 'post-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE);
|
||||||
|
this.delete('/:slug', FileController.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);
|
||||||
|
|
||||||
|
this.post('/', URLRedirectController.addURL, 'post-url', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
||||||
|
this.delete('/:slug', this.deleteURL, 'delete-url', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
||||||
|
this.get('/:slug', this.getURLRedirect, 'get-url');
|
||||||
|
this.put('/:slug', URLRedirectController.addURL, 'put-url', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
||||||
|
|
||||||
|
this.get(/(.*)/, this.domainFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async downloadFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const file = await FileModel.getBySlug(req.params.slug);
|
||||||
|
if (!file) return next();
|
||||||
|
if (file.shouldBeDeleted()) {
|
||||||
|
await fs.unlinkSync(file.storage_path);
|
||||||
|
await file.delete();
|
||||||
|
Logger.info('Deleted', file.storage_path, `(${file.real_name})`);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (file.storage_type) {
|
||||||
|
case 'local':
|
||||||
|
res.download(file.storage_path, file.real_name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ServerError(`This file cannot be served. Download protocol for ${file.storage_type} storage type not implemented.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async postFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
if (req.body.type !== 'file') return next();
|
||||||
|
|
||||||
|
await FileController.handleFileUpload(req.body.slug || await generateSlug(10), req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async putFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
if (req.body.type !== 'file') return next();
|
||||||
|
const slug = req.params.slug;
|
||||||
|
if (!slug) throw new BadRequestError('Cannot put without a slug.', 'Either provide a slug or use POST method instead.', req.url);
|
||||||
|
|
||||||
|
await FileController.handleFileUpload(slug, req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getURLRedirect(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const url = await URLRedirect.getBySlug(req.params.slug);
|
||||||
|
if (!url) return next();
|
||||||
|
|
||||||
|
res.redirect(url.target_url, 301);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async deleteURL(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const urlRedirect = await URLRedirect.getBySlug(req.params.slug);
|
||||||
|
if (!urlRedirect) return next();
|
||||||
|
|
||||||
|
throw new BadRequestError(
|
||||||
|
'Deleting url redirects is disabled for security reasons.',
|
||||||
|
'If you still want to disable the redirection, please contact us via email.',
|
||||||
|
req.url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async domainFilter(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
if (req.hostname !== config.get('domain')) {
|
||||||
|
if (req.path === '/') return res.redirect(config.get<string>('base_url'));
|
||||||
|
throw new NotFoundHttpError('Page', req.url);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
import Controller from "wms-core/Controller";
|
import Controller from "wms-core/Controller";
|
||||||
import {NextFunction, Request, Response} from "express";
|
import {NextFunction, Request, Response} from "express";
|
||||||
import URLRedirect from "../models/URLRedirect";
|
import URLRedirect from "../models/URLRedirect";
|
||||||
import {REQUIRE_AUTH_MIDDLEWARE, REQUIRE_REQUEST_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent";
|
import {REQUIRE_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent";
|
||||||
import generateSlug from "../SlugGenerator";
|
import generateSlug from "../SlugGenerator";
|
||||||
import {BadRequestError} from "wms-core/HttpError";
|
|
||||||
import config from "config";
|
import config from "config";
|
||||||
|
|
||||||
export default class URLRedirectController extends Controller {
|
export default class URLRedirectController extends Controller {
|
||||||
@ -12,11 +11,6 @@ export default class URLRedirectController extends Controller {
|
|||||||
this.get('/url/shrink/script', this.downloadLinuxScript, 'url-linux-script');
|
this.get('/url/shrink/script', this.downloadLinuxScript, 'url-linux-script');
|
||||||
this.post('/url/shrink', this.addURLFrontend, 'shrink-url', REQUIRE_AUTH_MIDDLEWARE);
|
this.post('/url/shrink', this.addURLFrontend, 'shrink-url', REQUIRE_AUTH_MIDDLEWARE);
|
||||||
this.get('/urls/:page([0-9]+)?', this.getURLRedirectManager, 'url-manager', REQUIRE_AUTH_MIDDLEWARE);
|
this.get('/urls/:page([0-9]+)?', this.getURLRedirectManager, 'url-manager', REQUIRE_AUTH_MIDDLEWARE);
|
||||||
|
|
||||||
this.post('/', this.addURL, 'post-url', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
|
||||||
this.delete('/:slug', this.deleteURL, 'delete-url', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
|
||||||
this.get('/:slug', this.getURLRedirect, 'get-url');
|
|
||||||
this.put('/:slug', this.addURL, 'put-url', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getURLShrinker(req: Request, res: Response): Promise<void> {
|
protected async getURLShrinker(req: Request, res: Response): Promise<void> {
|
||||||
@ -37,30 +31,12 @@ export default class URLRedirectController extends Controller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getURLRedirect(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
||||||
const url = await URLRedirect.getBySlug(req.params.slug);
|
|
||||||
if (!url) return next();
|
|
||||||
|
|
||||||
res.redirect(url.target_url, 301);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async deleteURL(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
||||||
const urlRedirect = await URLRedirect.getBySlug(req.params.slug);
|
|
||||||
if (!urlRedirect) return next();
|
|
||||||
|
|
||||||
throw new BadRequestError(
|
|
||||||
'Deleting url redirects is disabled for security reasons.',
|
|
||||||
'If you still want to disable the redirection, please contact us via email.',
|
|
||||||
req.url
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async addURLFrontend(req: Request, res: Response, next: NextFunction): Promise<void> {
|
protected async addURLFrontend(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
req.body.type = 'url';
|
req.body.type = 'url';
|
||||||
await this.addURL(req, res, next, req.body.autogen_url === undefined && req.body.slug ? req.body.slug : await generateSlug(10));
|
await URLRedirectController.addURL(req, res, next, req.body.autogen_url === undefined && req.body.slug ? req.body.slug : await generateSlug(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async addURL(req: Request, res: Response, next: NextFunction, slug?: string): Promise<void> {
|
public static async addURL(req: Request, res: Response, next: NextFunction, slug?: string): Promise<void> {
|
||||||
if (req.body.type !== 'url') return next();
|
if (req.body.type !== 'url') return next();
|
||||||
|
|
||||||
slug = slug || req.params.slug || req.body.slug || await generateSlug(10);
|
slug = slug || req.params.slug || req.body.slug || await generateSlug(10);
|
||||||
|
Loading…
Reference in New Issue
Block a user