Skip to content

Commit

Permalink
Replace Protobuf implementation with CBOR (#84)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Gabaldon <git@inetol.net>
  • Loading branch information
Mrgaton and inetol committed Apr 12, 2024
1 parent 9f724a8 commit afad978
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 431 deletions.
14 changes: 7 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Builder
FROM docker.io/imbios/bun-node:1-21-alpine AS builder
WORKDIR /build
FROM cgr.dev/chainguard/bun:latest-dev AS builder
WORKDIR /home/nonroot

COPY . ./

RUN bun install --production --frozen-lockfile && \
bun run build:bundle
bun run build:standalone

# Runner
FROM cgr.dev/chainguard/bun:latest
FROM cgr.dev/chainguard/cc-dynamic:latest
WORKDIR /home/nonroot

COPY --chown=nonroot --from=builder /build/dist/backend.js ./
COPY --chown=nonroot --from=builder /home/nonroot/dist/backend ./

LABEL org.opencontainers.image.url="https://jspaste.eu" \
org.opencontainers.image.source="https://github.com/jspaste/backend" \
Expand All @@ -21,6 +21,6 @@ LABEL org.opencontainers.image.url="https://jspaste.eu" \
org.opencontainers.image.licenses="EUPL-1.2"

VOLUME /home/nonroot/documents
EXPOSE 4000/tcp
EXPOSE 4000

CMD ["backend.js"]
CMD ["./backend"]
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.6.3/schema.json",
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
"files": {
"ignoreUnknown": true
},
Expand Down
Binary file modified bun.lockb
Binary file not shown.
24 changes: 11 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,32 @@
"type": "module",
"scripts": {
"build": "bun run build:bundle",
"build:bundle": "bun run build:structures && bun run build:structures:dts && bun build --target bun --minify --outfile ./dist/backend.js ./src/index.ts",
"build:standalone": "bun run build:structures && bun run build:structures:dts && bun build --compile --target bun --minify --outfile ./dist/jspaste ./src/index.ts",
"build:structures": "bunx --bun pbjs -t static-module -w es6 -l --force-long --no-create --no-delimited --no-typeurl --no-service -o ./src/structures/Structures.js ./src/structures/proto/**/*.proto",
"build:structures:dts": "bunx pbts -o ./src/structures/Structures.d.ts ./src/structures/Structures.js",
"build:bundle": "bun build --target bun --minify --outfile ./dist/backend.js ./src/index.ts",
"build:standalone": "bun build --compile --target bun --minify --outfile ./dist/backend ./src/index.ts",
"fix": "bun run fix:biome && bun run fix:package",
"fix:biome": "bunx --bun @biomejs/biome check --apply .",
"fix:package": "bunx --bun sort-package-json --quiet",
"fix:biome": "bun --bun biome check --apply .",
"fix:package": "bun --bun sort-package-json --quiet",
"lint": "bun run lint:biome && bun run lint:tsc",
"lint:biome": "bunx --bun @biomejs/biome lint .",
"lint:tsc": "bunx --bun tsc --noEmit",
"lint:biome": "bun --bun biome lint .",
"lint:tsc": "bun --bun tsc --noEmit",
"start": "bun run build && bun --bun ./dist/backend.js",
"start:dev": "bun --bun --watch ./src/index.ts"
},
"dependencies": {
"@elysiajs/swagger": "~1.0.3",
"@types/bun": "~1.0.12",
"cbor-x": "~1.5.9",
"elysia": "~1.0.13",
"env-var": "~7.4.1",
"protobufjs": "~7.2.6",
"protobufjs-cli": "~1.1.2",
"typescript": "~5.4.4"
"typescript": "~5.4.5"
},
"devDependencies": {
"@biomejs/biome": "~1.6.4",
"lefthook": "~1.6.8",
"lefthook": "~1.6.10",
"sort-package-json": "~2.10.0"
},
"trustedDependencies": [
"@biomejs/biome"
"@biomejs/biome",
"lefthook"
]
}
57 changes: 30 additions & 27 deletions src/classes/DocumentHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { unlink } from 'node:fs/promises';
import type { BunFile } from 'bun';
import { DocumentDataStruct, type IDocumentDataStruct } from '../structures/Structures';
import type { Parameters } from '../types/DocumentHandler.ts';
import { decode, encode } from 'cbor-x';
import type { CompatDocumentStruct, Parameters } from '../types/DocumentHandler.ts';
import { ErrorCode } from '../types/ErrorHandler.ts';
import { ServerEndpointVersion } from '../types/Server.ts';
import { StringUtils } from '../utils/StringUtils.ts';
Expand All @@ -10,22 +10,22 @@ import { ErrorHandler } from './ErrorHandler.ts';
import { Server } from './Server.ts';

export class DocumentHandler {
public static async documentRead(file: BunFile): Promise<DocumentDataStruct> {
return DocumentDataStruct.decode(Bun.inflateSync(Buffer.from(await file.arrayBuffer())));
public static async compatDocumentRead(file: BunFile): Promise<CompatDocumentStruct> {
return decode(Bun.inflateSync(await file.arrayBuffer()));
}

public static async documentWrite(filePath: string, document: IDocumentDataStruct): Promise<void> {
await Bun.write(filePath, Bun.deflateSync(DocumentDataStruct.encode(document).finish()));
public static async compatDocumentWrite(filePath: string, document: CompatDocumentStruct): Promise<void> {
await Bun.write(filePath, Bun.deflateSync(encode(document)));
}

public static async accessRaw(params: Parameters['access']) {
DocumentHandler.validateKey(params.key);

const file = await DocumentHandler.retrieveDocument(params.key);
const document = await DocumentHandler.documentRead(file);
const document = await DocumentHandler.compatDocumentRead(file);

DocumentHandler.validateTimestamp(params.key, document.expirationTimestamp);
DocumentHandler.validatePassword(params.password, document.password);
await DocumentHandler.validatePassword(params.password, document.password);

return new Response(document.rawFileData);
}
Expand All @@ -34,10 +34,10 @@ export class DocumentHandler {
DocumentHandler.validateKey(params.key);

const file = await DocumentHandler.retrieveDocument(params.key);
const document = await DocumentHandler.documentRead(file);
const document = await DocumentHandler.compatDocumentRead(file);

DocumentHandler.validateTimestamp(params.key, document.expirationTimestamp);
DocumentHandler.validatePassword(params.password, document.password);
await DocumentHandler.validatePassword(params.password, document.password);

const data = new TextDecoder().decode(document.rawFileData);

Expand All @@ -61,7 +61,7 @@ export class DocumentHandler {
DocumentHandler.validateKey(params.key);

const file = await DocumentHandler.retrieveDocument(params.key);
const document = await DocumentHandler.documentRead(file);
const document = await DocumentHandler.compatDocumentRead(file);

DocumentHandler.validateSecret(params.secret, document.secret);

Expand All @@ -72,7 +72,7 @@ export class DocumentHandler {
document.rawFileData = buffer;

return {
edited: await DocumentHandler.documentWrite(Server.DOCUMENT_PATH + params.key, document)
edited: await DocumentHandler.compatDocumentWrite(Server.DOCUMENT_PATH + params.key, document)
.then(() => true)
.catch(() => false)
};
Expand All @@ -93,32 +93,32 @@ export class DocumentHandler {

DocumentHandler.validateSecretLength(secret);

const bodyBuffer = Buffer.from(params.body as ArrayBuffer);
const bodyArray = new Uint8Array(params.body);

DocumentHandler.validateSizeBetweenLimits(bodyBuffer);
DocumentHandler.validateSizeBetweenLimits(bodyArray);

let lifetime = params.lifetime ?? Server.DOCUMENT_MAXTIME;

// Make the document permanent if the value exceeds 5 years
if (lifetime > 157_784_760) lifetime = 0;

const msLifetime = lifetime * 1000;
const expirationTimestamp = msLifetime > 0 ? BigInt(Date.now() + msLifetime) : undefined;
const expirationTimestamp = msLifetime > 0 ? Date.now() + msLifetime : 0;

const key = params.selectedKey || (await StringUtils.createKey(params.selectedKeyLength));

if (params.selectedKey && (await StringUtils.keyExists(key))) {
ErrorHandler.send(ErrorCode.documentKeyAlreadyExists);
}

const document: IDocumentDataStruct = {
rawFileData: bodyBuffer,
const document: CompatDocumentStruct = {
rawFileData: bodyArray,
secret,
expirationTimestamp,
password: params.password
password: params.password ? await Bun.password.hash(params.password) : null
};

await DocumentHandler.documentWrite(Server.DOCUMENT_PATH + key, document);
await DocumentHandler.compatDocumentWrite(Server.DOCUMENT_PATH + key, document);

switch (version) {
case ServerEndpointVersion.V1: {
Expand All @@ -130,7 +130,7 @@ export class DocumentHandler {
key,
secret,
url: Server.HOSTNAME.concat('/', key) + (params.password ? `/?p=${params.password}` : ''),
expirationTimestamp: Number(expirationTimestamp ?? 0)
expirationTimestamp: expirationTimestamp
};
}
}
Expand All @@ -140,7 +140,7 @@ export class DocumentHandler {
DocumentHandler.validateKey(params.key);

const file = await DocumentHandler.retrieveDocument(params.key);
const document = await DocumentHandler.documentRead(file);
const document = await DocumentHandler.compatDocumentRead(file);

DocumentHandler.validateSecret(params.secret, document.secret);

Expand Down Expand Up @@ -174,7 +174,7 @@ export class DocumentHandler {
}
}

private static validateSecret(secret: string, documentSecret: string): void {
private static validateSecret(secret: string, documentSecret: CompatDocumentStruct['secret']): void {
if (documentSecret && documentSecret !== secret) {
ErrorHandler.send(ErrorCode.documentInvalidSecret);
}
Expand All @@ -189,9 +189,12 @@ export class DocumentHandler {
}
}

private static validatePassword(password: string | undefined, documentPassword: string | null | undefined): void {
private static async validatePassword(
password: string | undefined,
documentPassword: CompatDocumentStruct['password']
): Promise<void> {
if (documentPassword) {
if (!password || password !== documentPassword) {
if (!password || !(await Bun.password.verify(password, documentPassword))) {
ErrorHandler.send(ErrorCode.documentInvalidPassword);
}
}
Expand All @@ -207,15 +210,15 @@ export class DocumentHandler {
}
}

private static validateTimestamp(key: string, timestamp: number): void {
if (timestamp && ValidatorUtils.isLengthWithinRange(timestamp, 0, Date.now())) {
private static validateTimestamp(key: string, timestamp: CompatDocumentStruct['expirationTimestamp']): void {
if (timestamp && ValidatorUtils.isLengthWithinRange(timestamp, 1, Date.now())) {
unlink(Server.DOCUMENT_PATH + key);

ErrorHandler.send(ErrorCode.documentNotFound);
}
}

private static validateSizeBetweenLimits(body: Buffer): void {
private static validateSizeBetweenLimits(body: Uint8Array): void {
if (!ValidatorUtils.isLengthWithinRange(body.length, 1, Server.DOCUMENT_MAXLENGTH)) {
ErrorHandler.send(ErrorCode.documentInvalidLength);
}
Expand Down
91 changes: 0 additions & 91 deletions src/structures/Structures.d.ts

This file was deleted.

Loading

0 comments on commit afad978

Please sign in to comment.