diff --git a/src/App.ts b/src/App.ts index 77a791d..65168fc 100644 --- a/src/App.ts +++ b/src/App.ts @@ -104,7 +104,7 @@ export default class App extends Application { this.use(redisComponent); this.use(new SessionComponent(redisComponent)); this.use(new AuthComponent(new class extends AuthGuard { - public async getProofForSession(session: Session): Promise { + public async getProofForSession(session: Session): Promise { return await MagicLink.bySessionId( session.id, [MagicLinkActionType.LOGIN, MagicLinkActionType.REGISTER], @@ -122,7 +122,7 @@ export default class App extends Application { return token; } - return super.getProofForRequest(req); + return await super.getProofForRequest(req); } }(this))); diff --git a/src/SlugGenerator.ts b/src/SlugGenerator.ts index e0e92b1..dda5c6b 100644 --- a/src/SlugGenerator.ts +++ b/src/SlugGenerator.ts @@ -14,4 +14,4 @@ export default async function generateSlug(tries: number): Promise { i++; } while (i < tries); throw new ServerError('Failed to generate slug; newly generated slug size should be increased by 1.'); -}; +} diff --git a/src/Utils.ts b/src/Utils.ts index 366f680..ef61cb4 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,4 +1,4 @@ -export function encodeRFC5987ValueChars(str: string) { +export function encodeRFC5987ValueChars(str: string): string { return encodeURIComponent(str). // Note that although RFC3986 reserves "!", RFC5987 does not, // so we do not need to escape it diff --git a/src/controllers/AboutController.ts b/src/controllers/AboutController.ts index 281c3be..4b92b19 100644 --- a/src/controllers/AboutController.ts +++ b/src/controllers/AboutController.ts @@ -2,7 +2,7 @@ import Controller from "swaf/Controller"; import {Request, Response} from "express"; export default class AboutController extends Controller { - routes(): void { + public routes(): void { this.get('/', this.getAbout, 'home'); this.get('/', this.getAbout, 'about'); } diff --git a/src/controllers/AuthTokenController.ts b/src/controllers/AuthTokenController.ts index 974f543..5cfdbc9 100644 --- a/src/controllers/AuthTokenController.ts +++ b/src/controllers/AuthTokenController.ts @@ -5,7 +5,7 @@ import AuthToken from "../models/AuthToken"; import {BadRequestError, ForbiddenHttpError, NotFoundHttpError} from "swaf/HttpError"; export default class AuthTokenController extends Controller { - routes(): void { + public routes(): void { this.post('/gen-auth-token', this.postGenAuthToken, 'generate-token', RequireAuthMiddleware); this.post('/revoke-auth-token/:id', this.postRevokeAuthToken, 'revoke-token', RequireAuthMiddleware); } diff --git a/src/controllers/FileController.ts b/src/controllers/FileController.ts index 332b371..f997260 100644 --- a/src/controllers/FileController.ts +++ b/src/controllers/FileController.ts @@ -13,7 +13,7 @@ import FileUploadMiddleware from "swaf/FileUploadMiddleware"; export default class FileController extends Controller { - routes(): void { + public routes(): void { this.get('/files/upload', this.getFileUploader, 'file-upload', RequireAuthMiddleware); this.get('/files/upload/script', this.downloadLinuxScript, 'file-linux-script'); this.post('/files/post', this.postFileFrontend, 'post-file-frontend', RequireAuthMiddleware, FileUploadFormMiddleware); @@ -45,12 +45,18 @@ export default class FileController extends Controller { protected async postFileFrontend(req: Request, res: Response): Promise { req.body.type = 'file'; - await FileController.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, + ); } public static async handleFileUpload(slug: string, req: Request, res: Response): Promise { // Check for file upload - if (!req.files || !req.files['upload']) { + if (Object.keys(req.files).indexOf('upload') < 0) { throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url); } @@ -75,7 +81,7 @@ export default class FileController extends Controller { }); await file.save(); - fs.renameSync(upload.path, file.storage_path!); + fs.renameSync(upload.path, file.getOrFail('storage_path')); const domain = req.body.url_domain || config.get('allowed_url_domains')[config.get('default_url_domain_for_files')]; res.format({ @@ -122,7 +128,7 @@ export default class FileController extends Controller { } public static async deleteFile(file: FileModel): Promise { - fs.unlinkSync(file.storage_path!); + fs.unlinkSync(file.getOrFail('storage_path')); await file.delete(); log.info('Deleted', file.storage_path, `(${file.real_name})`); } diff --git a/src/controllers/LinkController.ts b/src/controllers/LinkController.ts index 60680e7..6535e28 100644 --- a/src/controllers/LinkController.ts +++ b/src/controllers/LinkController.ts @@ -14,7 +14,7 @@ import {promisify} from "util"; import {log} from "swaf/Logger"; export default class LinkController extends Controller { - routes(): void { + public routes(): void { this.post('/', this.postFile, 'post-file', RequireRequestAuthMiddleware, FileUploadFormMiddleware); this.delete('/:slug', FileController.deleteFileRoute, 'delete-file', RequireRequestAuthMiddleware); this.get('/:slug', this.getFile, 'get-file'); @@ -29,7 +29,6 @@ export default class LinkController extends Controller { } protected async getFile(req: Request, res: Response, next: NextFunction): Promise { - console.log('get file', req.params.slug) const file = await FileModel.getBySlug(req.params.slug); if (!file) return next(); if (file.shouldBeDeleted()) { @@ -37,16 +36,16 @@ export default class LinkController extends Controller { return next(); } - const fileName = file.real_name!; + const fileName = file.getOrFail('real_name'); switch (file.storage_type) { - case 'local': - const stats = await promisify(fs.stat)(file.storage_path!); + case 'local': { + const stats = await promisify(fs.stat)(file.getOrFail('storage_path')); // If file is bigger than max hotlink size, fallback to express download if (stats.size > config.get('max_hotlink_size') * 1024 * 1024) { log.info(`Fallback to express download for file of size ${stats.size}`); - return res.download(file.storage_path!, fileName); + return res.download(file.getOrFail('storage_path'), fileName); } // File type @@ -56,11 +55,12 @@ export default class LinkController extends Controller { // File name res.header('Content-Disposition', `inline; filename*=UTF-8''${encodeRFC5987ValueChars(fileName)}`); - fs.readFile(file.storage_path!, (err, data) => { + fs.readFile(file.getOrFail('storage_path'), (err, data) => { if (err) return next(err); res.send(data); }); break; + } default: throw new ServerError(`This file cannot be served. Download protocol for ${file.storage_type} storage type not implemented.`); } @@ -84,7 +84,7 @@ export default class LinkController extends Controller { const url = await URLRedirect.getBySlug(req.params.slug); if (!url) return next(); - res.redirect(url.target_url!, 301); + res.redirect(url.getOrFail('target_url'), 301); } protected async deleteURL(req: Request, res: Response, next: NextFunction): Promise { @@ -94,7 +94,7 @@ export default class LinkController extends Controller { 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 + req.url, ); } diff --git a/src/controllers/MagicLinkController.ts b/src/controllers/MagicLinkController.ts index 16fe536..942fb90 100644 --- a/src/controllers/MagicLinkController.ts +++ b/src/controllers/MagicLinkController.ts @@ -9,16 +9,16 @@ import App from "../App"; import AuthComponent from "swaf/auth/AuthComponent"; export default class MagicLinkController extends _MagicLinkController { - constructor(magicLinkWebSocketListener: MagicLinkWebSocketListener) { + public constructor(magicLinkWebSocketListener: MagicLinkWebSocketListener) { super(magicLinkWebSocketListener); } protected async performAction(magicLink: MagicLink, req: Request, res: Response): Promise { switch (magicLink.action_type) { case MagicLinkActionType.LOGIN: - case MagicLinkActionType.REGISTER: + case MagicLinkActionType.REGISTER: { await AuthController.checkAndAuth(req, res, magicLink); - const proof = await this.getApp().as(AuthComponent).getAuthGuard().isAuthenticated(req.session!); + const proof = await this.getApp().as(AuthComponent).getAuthGuard().isAuthenticated(req.getSession()); const user = await proof?.getResource(); if (!res.headersSent && user) { @@ -27,6 +27,7 @@ export default class MagicLinkController extends _MagicLinkController { res.redirect(req.query.redirect_uri?.toString() || Controller.route('home')); } break; + } } } } diff --git a/src/controllers/URLRedirectController.ts b/src/controllers/URLRedirectController.ts index cf42e2c..7e1a73c 100644 --- a/src/controllers/URLRedirectController.ts +++ b/src/controllers/URLRedirectController.ts @@ -7,7 +7,7 @@ import config from "config"; import AuthToken from "../models/AuthToken"; export default class URLRedirectController extends Controller { - routes(): void { + public routes(): void { this.get('/url/shrink', this.getURLShrinker, 'url-shrinker', RequireAuthMiddleware); this.get('/url/shrink/script', this.downloadLinuxScript, 'url-linux-script'); this.post('/url/shrink', this.addURLFrontend, 'shrink-url', RequireAuthMiddleware); @@ -37,7 +37,14 @@ export default class URLRedirectController extends Controller { protected async addURLFrontend(req: Request, res: Response, next: NextFunction): Promise { req.body.type = 'url'; - await URLRedirectController.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), + ); } public static async addURL(req: Request, res: Response, next: NextFunction, slug?: string): Promise { diff --git a/src/migrations/IncreaseFilesSizeField.ts b/src/migrations/IncreaseFilesSizeField.ts index 52cef57..4f906da 100644 --- a/src/migrations/IncreaseFilesSizeField.ts +++ b/src/migrations/IncreaseFilesSizeField.ts @@ -9,7 +9,4 @@ export default class IncreaseFilesSizeField extends Migration { public async rollback(connection: Connection): Promise { await this.query(`ALTER TABLE files MODIFY size INT UNSIGNED`, connection); } - - public registerModels(): void { - } } diff --git a/src/models/AuthToken.ts b/src/models/AuthToken.ts index c6a234f..f102941 100644 --- a/src/models/AuthToken.ts +++ b/src/models/AuthToken.ts @@ -6,34 +6,35 @@ import {cryptoRandomDictionary} from "swaf/Utils"; export default class AuthToken extends Model implements AuthProof { public id?: number = undefined; protected readonly user_id?: number = undefined; - protected readonly secret?: string = undefined; + private secret?: string = undefined; protected created_at?: Date = undefined; protected used_at?: Date = undefined; protected readonly ttl?: number = undefined; - protected init() { + protected init(): void { this.setValidation('user_id').defined().exists(User, 'id'); this.setValidation('secret').defined().between(32, 64); this.setValidation('ttl').defined().min(1).max(5 * 365 * 24 * 3600 /* 5 years */); } protected async autoFill(): Promise { - // @ts-ignore - if (!this.secret) this['secret'] = cryptoRandomDictionary(64, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'); + if (!this.secret) { + this.secret = cryptoRandomDictionary(64, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'); + } } - public use() { + public use(): void { this.used_at = new Date(); } - public canDelete(user_id: number) { + public canDelete(user_id: number): boolean { return this.user_id === user_id; } public getExpirationDate(): Date { if (!this.created_at) return new Date(); - return new Date(this.created_at.getTime() + this.ttl! * 1000); + return new Date(this.created_at.getTime() + this.getOrFail('ttl') * 1000); } public async getResource(): Promise { @@ -51,4 +52,8 @@ export default class AuthToken extends Model implements AuthProof { public async revoke(): Promise { await this.delete(); } + + protected getSecret(): string | undefined { + return this.secret; + } } diff --git a/src/models/FileModel.ts b/src/models/FileModel.ts index 5ac6ac0..e7a19ea 100644 --- a/src/models/FileModel.ts +++ b/src/models/FileModel.ts @@ -28,7 +28,7 @@ export default class FileModel extends Model { public created_at?: Date = undefined; public readonly ttl?: number = undefined; - protected init() { + protected init(): void { this.setValidation('user_id').defined().exists(User, 'id'); this.setValidation('slug').defined().minLength(1).maxLength(259).unique(FileModel, 'slug').unique(URLRedirect, 'slug'); this.setValidation('real_name').defined().minLength(1).maxLength(259); @@ -40,7 +40,7 @@ export default class FileModel extends Model { public getURL(domain: string = config.get('base_url')): string { return (/^https?:\/\//.test(domain) ? '' : 'https://') + domain + Controller.route('get-file', { - slug: this.slug!, + slug: this.getOrFail('slug'), }); } @@ -48,7 +48,7 @@ export default class FileModel extends Model { if (!this.created_at) return new Date(); if (this.ttl === 0) return null; - return new Date(this.created_at.getTime() + this.ttl! * 1000); + return new Date(this.created_at.getTime() + this.getOrFail('ttl') * 1000); } public shouldBeDeleted(): boolean { diff --git a/src/models/URLRedirect.ts b/src/models/URLRedirect.ts index 77281aa..a9a1626 100644 --- a/src/models/URLRedirect.ts +++ b/src/models/URLRedirect.ts @@ -32,7 +32,7 @@ export default class URLRedirect extends Model { public getURL(domain: string = config.get('base_url')): string { return (/^https?:\/\//.test(domain) ? '' : 'https://') + domain + Controller.route('get-url', { - slug: this.slug!, + slug: this.getOrFail('slug'), }); } }