Skip to content

Commit

Permalink
Merge pull request #84 from VictoriqueMoe/specal-secret
Browse files Browse the repository at this point in the history
added a secret key to bypass expiry, and modified prettier
  • Loading branch information
VictoriqueMoe authored Mar 3, 2024
2 parents 0e2dd5f + ada5550 commit bd0f61d
Show file tree
Hide file tree
Showing 21 changed files with 195 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"bracketSpacing": true,
"arrowParens": "avoid",
"proseWrap": "never",
"printWidth": 250
"printWidth": 120
}
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ Required Settings
Optional Settings

| Setting | Description |
|------------------|-----------------------------------------------------|
| CLAM_PATH | The path to your Clam Antivirus installation |
| MS_DEFENDER_PATH | The path to your MS Defender Antivirus installation |
| Setting | Description |
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| CLAM_PATH | The path to your Clam Antivirus installation |
| MS_DEFENDER_PATH | The path to your MS Defender Antivirus installation |
| UPLOAD_SECRET | A secret passcode you can set, when used as on the upload as a query paramater `secret_token`, the file will have no expiry and will persist forever |
> **Note Well** if a path to an Antivirus engine is not defined it will not be used, if no paths are defined then no antivirus scanning will be used
### Build and Run commands
Expand Down
32 changes: 23 additions & 9 deletions src/controllers/rest/impl/FileUploadController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,20 @@ export class FileUploadController extends BaseRestController {
@Returns(StatusCodes.CREATED, FileUploadModelResponse).Description("If the file was stored successfully")
@Returns(StatusCodes.BAD_REQUEST, BadRequest).Description("If the request was malformed")
@Returns(StatusCodes.OK, FileUploadModelResponse).Description("If the file already exists")
@Returns(StatusCodes.UNSUPPORTED_MEDIA_TYPE, UnsupportedMediaType).Description("If the media type of the file specified was blocked")
@Returns(StatusCodes.UNSUPPORTED_MEDIA_TYPE, UnsupportedMediaType).Description(
"If the media type of the file specified was blocked",
)
@Example({
description: "foo",
summary: "bnar",
})
@Summary("Upload a file or send URL")
@Description("Upload a file or specify URL to a file. Use the location header in the response or the url prop in the JSON to get the URL of the file")
@Description(
"Upload a file or specify URL to a file. Use the location header in the response or the url prop in the JSON to get the URL of the file",
)
public async addEntry(
@Req()
req: Req,
@Res()
res: Res,
@Req() req: Req,
@Res() res: Res,
@QueryParams("expires")
@Examples({
empty: {
Expand All @@ -58,7 +60,9 @@ export class FileUploadController extends BaseRestController {
)
customExpiry?: string,
@QueryParams("hide_filename")
@Description("if set to true, then your filename will not appear in the URL. if false, then it will appear in the URL. defaults to false")
@Description(
"if set to true, then your filename will not appear in the URL. if false, then it will appear in the URL. defaults to false",
)
hideFileName?: boolean,
@QueryParams("password")
@Description(
Expand All @@ -67,6 +71,7 @@ export class FileUploadController extends BaseRestController {
password?: string,
@MultipartFile("file") file?: PlatformMulterFile,
@BodyParams("url") url?: string,
@QueryParams("secret_token") @Description("Shh, it's a secret ;)") secretToken?: string,
): Promise<unknown> {
if (file && url) {
if (file) {
Expand All @@ -88,7 +93,14 @@ export class FileUploadController extends BaseRestController {
let uploadModelResponse: FileUploadModelResponse;
let alreadyExists: boolean;
try {
[uploadModelResponse, alreadyExists] = await this.fileUploadService.processUpload(ip, url || file!, customExpiry, hideFileName, password);
[uploadModelResponse, alreadyExists] = await this.fileUploadService.processUpload(
ip,
url || file!,
customExpiry,
hideFileName,
password,
secretToken,
);
} catch (e) {
this.logger.error(e.message);
if (file) {
Expand Down Expand Up @@ -116,7 +128,9 @@ export class FileUploadController extends BaseRestController {
@PathParams("token")
token: string,
@QueryParams("formatted")
@Description("If true, this will format the time remaining to a human readable string instead of an epoch if set to false")
@Description(
"If true, this will format the time remaining to a human readable string instead of an epoch if set to false",
)
humanReadable: boolean,
): Promise<unknown> {
if (!token) {
Expand Down
18 changes: 9 additions & 9 deletions src/controllers/rest/impl/security/AdminController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@ import type { DatatableColumn, DatatableOrder, DatatableSearch } from "../../../
@Hidden()
@Controller("/admin")
@Returns(StatusCodes.FORBIDDEN, Forbidden).Description("If your IP has been blocked")
@Authorize("loginAuthProvider")
export class AdminController extends BaseRestController {
public constructor(@Inject() private adminService: AdminService) {
super();
}

@Authorize("loginAuthProvider")
@Get("/allEntries")
public getAllEntries(): Promise<unknown> {
return this.adminService.getAllEntries();
}

@Authorize("loginAuthProvider")
@Get("/datatablesEntries")
public async getDatatablesEntries(
@QueryParams("draw") draw: number,
Expand All @@ -41,7 +40,9 @@ export class AdminController extends BaseRestController {
sortColumn = columns[order[0]?.column ?? 0]?.data;
}
const data = await this.adminService.getPagedEntries(start, length, sortColumn, sortOrder, searchVal);
const records = searchVal ? await this.adminService.getFileSearchRecordCount(search.value) : await this.adminService.getFileRecordCount();
const records = searchVal
? await this.adminService.getFileSearchRecordCount(search.value)
: await this.adminService.getFileRecordCount();
return {
draw: draw,
recordsTotal: records,
Expand All @@ -50,26 +51,26 @@ export class AdminController extends BaseRestController {
};
}

@Authorize("loginAuthProvider")
@Get("/statsData")
public async getStatsData(): Promise<unknown> {
return this.adminService.getStatsData(await this.adminService.getAllEntries());
}

@Authorize("loginAuthProvider")
@Get("/blockedIps")
public getAllBlockedIps(): Promise<unknown> {
return this.adminService.getAllBlockedIps();
}

@Authorize("loginAuthProvider")
@Post("/blockIp")
public async blockIp(@Res() res: PlatformResponse, @QueryParams("removeRelatedData", Boolean) removeRelatedData = false, @Required() @BodyParams("ip") ip: string): Promise<unknown> {
public async blockIp(
@Res() res: PlatformResponse,
@QueryParams("removeRelatedData", Boolean) removeRelatedData = false,
@Required() @BodyParams("ip") ip: string,
): Promise<unknown> {
await this.adminService.blockIp(ip, removeRelatedData);
return super.doSuccess(res, "IP blocked");
}

@Authorize("loginAuthProvider")
@Post("/unblockIps")
public async unblockIps(@Res() res: PlatformResponse, @BodyParams() ips: string[]): Promise<unknown> {
const success = await this.adminService.removeBlockedIps(ips);
Expand All @@ -79,7 +80,6 @@ export class AdminController extends BaseRestController {
return super.doSuccess(res, "IP un-blocked");
}

@Authorize("loginAuthProvider")
@Delete("/deleteEntries")
public async deleteEntries(@Res() res: PlatformResponse, @BodyParams() ids: number[]): Promise<unknown> {
const result = await this.adminService.deleteEntries(ids);
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/rest/impl/security/PassportCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ export class PassportCtrl extends BaseRestController {
@Authorize("loginAuthProvider")
@Security("loginAuthProvider")
@Returns(StatusCodes.OK)
public async changeDetails(@Res() res: PlatformResponse, @Req() req: Req, @BodyParams() userDetails: UserModel): Promise<PlatformResponse> {
public async changeDetails(
@Res() res: PlatformResponse,
@Req() req: Req,
@BodyParams() userDetails: UserModel,
): Promise<PlatformResponse> {
const loggedInUser = req.user as CustomUserInfoModel;
await this.usersService.changeDetails(userDetails, loggedInUser);
return this.doSuccess(res, "User details changed");
Expand Down
7 changes: 6 additions & 1 deletion src/controllers/serve/FileServerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ export class FileServerController {
) {}

@Get("/:t/:file?")
public async getFile(@Res() res: Res, @PathParams("t") resource: string, @HeaderParams("x-password") password?: string, @PathParams("file") requestedFileName?: string): Promise<void> {
public async getFile(
@Res() res: Res,
@PathParams("t") resource: string,
@HeaderParams("x-password") password?: string,
@PathParams("file") requestedFileName?: string,
): Promise<void> {
await this.hasPassword(resource, password);
const [buff, entry] = await this.fileService.getEntry(resource, requestedFileName, password);
const mimeType = await this.mimeService.findMimeTypeFromBuffer(buff, entry.fullFileNameOnSystem);
Expand Down
16 changes: 14 additions & 2 deletions src/db/dao/FileDao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export class FileDao extends AbstractDao<FileUploadModel> {
return this.getRepository(transaction).find();
}

public getAllEntriesOrdered(start: number, records: number, sortColumn?: string, sortOrder?: string, search?: string, transaction?: EntityManager): Promise<FileUploadModel[]> {
public getAllEntriesOrdered(
start: number,
records: number,
sortColumn?: string,
sortOrder?: string,
search?: string,
transaction?: EntityManager,
): Promise<FileUploadModel[]> {
const orderOptions = sortColumn ? { [sortColumn]: sortOrder } : {};
if (search) {
return this.getRepository(transaction).find({
Expand Down Expand Up @@ -86,7 +93,12 @@ export class FileDao extends AbstractDao<FileUploadModel> {

private getSearchQuery(search: string): Record<string, FindOperator<string>>[] {
search = `%${search}%`;
return [{ fileName: Like(search) }, { fileExtension: Like(search) }, { ip: Like(search) }, { originalFileName: Like(search) }];
return [
{ fileName: Like(search) },
{ fileExtension: Like(search) },
{ ip: Like(search) },
{ originalFileName: Like(search) },
];
}

public getAllEntriesForIp(ip: string, transaction?: EntityManager): Promise<FileUploadModel[]> {
Expand Down
8 changes: 7 additions & 1 deletion src/db/repo/FileRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ export class FileRepo {
return this.fileDao.getAllEntriesForIp(ip);
}

public getAllEntriesOrdered(start: number, records: number, sortColumn?: string, sortDir?: string, search?: string): Promise<FileUploadModel[]> {
public getAllEntriesOrdered(
start: number,
records: number,
sortColumn?: string,
sortDir?: string,
search?: string,
): Promise<FileUploadModel[]> {
return this.fileDao.getAllEntriesOrdered(start, records, sortColumn, sortDir, search);
}

Expand Down
4 changes: 3 additions & 1 deletion src/engine/impl/av/MsDefenderEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export class MsDefenderEngine implements IAvEngine {
const toScan = path.resolve(`${filesDir}/${resource}`);
const execPromise = promisify(exec);
try {
await execPromise(`"${this.msDefenderPath}/MpCmdRun.exe" -scan -scantype 3 -file ${toScan} -DisableRemediation`);
await execPromise(
`"${this.msDefenderPath}/MpCmdRun.exe" -scan -scantype 3 -file ${toScan} -DisableRemediation`,
);
} catch (e) {
return {
errorCode: e.code,
Expand Down
4 changes: 3 additions & 1 deletion src/factory/HttpErrorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { DefaultHttpRenderEngine } from "../engine/impl/index.js";
export class HttpErrorFactory {
private readonly defaultRenderEngine: IHttpErrorRenderEngine<unknown, Exception>;

public constructor(@Inject(HTTP_RENDER_ENGINE) private readonly engines: IHttpErrorRenderEngine<unknown, Exception>[]) {
public constructor(
@Inject(HTTP_RENDER_ENGINE) private readonly engines: IHttpErrorRenderEngine<unknown, Exception>[],
) {
this.defaultRenderEngine = engines.find(engine => engine instanceof DefaultHttpRenderEngine)!;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class ModifyFileUploadModalMakeExpiresNullable1709469943108 implements MigrationInterface {
name = 'ModifyFileUploadModalMakeExpiresNullable1709469943108'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_c8524f9ddc7a24cbae2013fe97"`);
await queryRunner.query(`CREATE TABLE "temporary_file_upload_model" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "fileName" text NOT NULL, "token" text NOT NULL, "checksum" text NOT NULL, "ip" text NOT NULL, "fileSize" integer NOT NULL, "expires" integer NOT NULL, "originalFileName" text NOT NULL DEFAULT (''), "fileExtension" text, "settings" text, "mediaType" text, "encrypted" boolean NOT NULL DEFAULT (0))`);
await queryRunner.query(`INSERT INTO "temporary_file_upload_model"("id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted") SELECT "id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted" FROM "file_upload_model"`);
await queryRunner.query(`DROP TABLE "file_upload_model"`);
await queryRunner.query(`ALTER TABLE "temporary_file_upload_model" RENAME TO "file_upload_model"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c8524f9ddc7a24cbae2013fe97" ON "file_upload_model" ("token") `);
await queryRunner.query(`DROP INDEX "IDX_c8524f9ddc7a24cbae2013fe97"`);
await queryRunner.query(`CREATE TABLE "temporary_file_upload_model" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "fileName" text NOT NULL, "token" text NOT NULL, "checksum" text NOT NULL, "ip" text NOT NULL, "fileSize" integer NOT NULL, "expires" integer, "originalFileName" text NOT NULL DEFAULT (''), "fileExtension" text, "settings" text, "mediaType" text, "encrypted" boolean NOT NULL DEFAULT (0))`);
await queryRunner.query(`INSERT INTO "temporary_file_upload_model"("id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted") SELECT "id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted" FROM "file_upload_model"`);
await queryRunner.query(`DROP TABLE "file_upload_model"`);
await queryRunner.query(`ALTER TABLE "temporary_file_upload_model" RENAME TO "file_upload_model"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c8524f9ddc7a24cbae2013fe97" ON "file_upload_model" ("token") `);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_c8524f9ddc7a24cbae2013fe97"`);
await queryRunner.query(`ALTER TABLE "file_upload_model" RENAME TO "temporary_file_upload_model"`);
await queryRunner.query(`CREATE TABLE "file_upload_model" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "fileName" text NOT NULL, "token" text NOT NULL, "checksum" text NOT NULL, "ip" text NOT NULL, "fileSize" integer NOT NULL, "expires" integer NOT NULL, "originalFileName" text NOT NULL DEFAULT (''), "fileExtension" text, "settings" text, "mediaType" text, "encrypted" boolean NOT NULL DEFAULT (0))`);
await queryRunner.query(`INSERT INTO "file_upload_model"("id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted") SELECT "id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted" FROM "temporary_file_upload_model"`);
await queryRunner.query(`DROP TABLE "temporary_file_upload_model"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c8524f9ddc7a24cbae2013fe97" ON "file_upload_model" ("token") `);
await queryRunner.query(`DROP INDEX "IDX_c8524f9ddc7a24cbae2013fe97"`);
await queryRunner.query(`ALTER TABLE "file_upload_model" RENAME TO "temporary_file_upload_model"`);
await queryRunner.query(`CREATE TABLE "file_upload_model" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "fileName" text NOT NULL, "token" text NOT NULL, "checksum" text NOT NULL, "ip" text NOT NULL, "fileSize" integer NOT NULL, "expires" integer NOT NULL, "originalFileName" text NOT NULL DEFAULT (''), "fileExtension" text, "settings" text, "mediaType" text, "encrypted" boolean NOT NULL DEFAULT (0))`);
await queryRunner.query(`INSERT INTO "file_upload_model"("id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted") SELECT "id", "createdAt", "updatedAt", "fileName", "token", "checksum", "ip", "fileSize", "expires", "originalFileName", "fileExtension", "settings", "mediaType", "encrypted" FROM "temporary_file_upload_model"`);
await queryRunner.query(`DROP TABLE "temporary_file_upload_model"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c8524f9ddc7a24cbae2013fe97" ON "file_upload_model" ("token") `);
}

}
1 change: 1 addition & 0 deletions src/model/constants/GlobalEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum GlobalEnv {
RECAPTCHA_SECRET_KEY = `${prefix}RECAPTCHA_SECRET_KEY`,
FILE_CLEANER_CRON = `${prefix}FILE_CLEANER_CRON`,
SALT = `${prefix}SALT`,
UPLOAD_SECRET = `${prefix}UPLOAD_SECRET`,
}

export default GlobalEnv;
9 changes: 6 additions & 3 deletions src/model/db/FileUpload.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export class FileUploadModel extends AbstractModel {
public fileSize: number;

@Column({
nullable: false,
nullable: true,
type: "integer",
unique: false,
})
public expires: number;
public expires: number | null;

@Column({
nullable: true,
Expand All @@ -86,7 +86,10 @@ export class FileUploadModel extends AbstractModel {
})
public encrypted: boolean;

public get expiresIn(): number {
public get expiresIn(): number | null {
if (this.expires === null) {
return null;
}
return FileUtils.getTImeLeft(this);
}

Expand Down
9 changes: 6 additions & 3 deletions src/model/rest/FileEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export class FileEntry {
public createdAt: Date;

@Property()
public expires: string;
@Nullable(String)
public expires: string | null;

@Property()
public url: string;
Expand Down Expand Up @@ -53,11 +54,13 @@ export class FileEntry {
.fileSize(entry.fileSize)
.fileName(entry.fileName)
.mediaType(entry.mediaType)
.expires(ObjectUtils.timeToHuman(entry.expiresIn))
.ipBanned(ipBlocked)
.fileProtectionLevel(entry.fileProtectionLevel)
.ip(entry.ip);

const expiresIn = entry.expiresIn;
if (expiresIn !== null) {
fileEntryBuilder.expires(ObjectUtils.timeToHuman(expiresIn));
}
return fileEntryBuilder.build();
}

Expand Down
Loading

0 comments on commit bd0f61d

Please sign in to comment.