mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: refactoring
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import forge from "node-forge";
|
||||
|
||||
|
||||
export async function decryptedDump(file: File, aesKeyHex: string, ivHex: string, fileExtension: string): Promise<File> {
|
||||
const privateKeyPem = fs.readFileSync("private/keys/server_private.pem", "utf8");
|
||||
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
|
||||
|
||||
// Decrypt AES key with RSA-OAEP
|
||||
const encryptedAesKey = forge.util.hexToBytes(aesKeyHex);
|
||||
const aesKey = privateKey.decrypt(encryptedAesKey, "RSA-OAEP", {
|
||||
md: forge.md.sha256.create(),
|
||||
mgf1: {md: forge.md.sha256.create()},
|
||||
});
|
||||
|
||||
// Read encrypted file content
|
||||
const encryptedBuffer = Buffer.from(await file.arrayBuffer());
|
||||
const iv = forge.util.hexToBytes(ivHex);
|
||||
|
||||
// AES decryption
|
||||
const decipher = forge.cipher.createDecipher("AES-CBC", aesKey);
|
||||
decipher.start({iv});
|
||||
decipher.update(forge.util.createBuffer(encryptedBuffer.toString("binary")));
|
||||
const success = decipher.finish();
|
||||
|
||||
if (!success) {
|
||||
throw new Error("Decryption failed");
|
||||
}
|
||||
|
||||
const decryptedBytes = decipher.output.getBytes();
|
||||
const decryptedBuffer = Buffer.from(decryptedBytes, "binary");
|
||||
|
||||
// Return a File so you can use file.arrayBuffer() later
|
||||
return new File(
|
||||
[decryptedBuffer],
|
||||
file.name.replace(/\.enc$/, fileExtension),
|
||||
{type: "application/octet-stream"}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function getFileExtension(dbType: string) {
|
||||
switch (dbType) {
|
||||
case "postgresql":
|
||||
return ".dump";
|
||||
case "mysql":
|
||||
return ".sql";
|
||||
default:
|
||||
return ".dump";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import {NextResponse} from "next/server";
|
||||
import {isUuidv4} from "@/utils/verify-uuid";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {db} from "@/db";
|
||||
import {Backup} from "@/db/schema/07_database";
|
||||
import {and, eq} from "drizzle-orm";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
import {decryptedDump, getFileExtension} from "./helpers";
|
||||
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
|
||||
import {storeBackupFiles} from "@/features/storages/helpers";
|
||||
import {eventEmitter} from "@/features/shared/event";
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{params}: { params: Promise<{ agentId: string }> }
|
||||
) {
|
||||
try {
|
||||
const contentType = request.headers.get("Content-Type");
|
||||
|
||||
if (!contentType || !contentType.includes("multipart/form-data")) {
|
||||
return NextResponse.json(
|
||||
{error: "Unsupported or missing Content-Type"},
|
||||
{status: 400}
|
||||
);
|
||||
}
|
||||
|
||||
eventEmitter.emit('modification', {update: true});
|
||||
|
||||
const agentId = (await params).agentId;
|
||||
const formData = await request.formData();
|
||||
const aesKeyHex = formData.get("aes_key") as string;
|
||||
const ivHex = formData.get("iv") as string;
|
||||
const generatedId = formData.get("generatedId") as string | null;
|
||||
const method = formData.get("method") as string | null;
|
||||
|
||||
|
||||
if (!generatedId || !isUuidv4(generatedId)) {
|
||||
return NextResponse.json(
|
||||
{error: "generatedId is not a valid UUID"},
|
||||
{status: 400}
|
||||
);
|
||||
}
|
||||
|
||||
const agent = await db.query.agent.findFirst({
|
||||
where: eq(drizzleDb.schemas.agent.id, agentId),
|
||||
});
|
||||
|
||||
if (!agent) {
|
||||
return NextResponse.json(
|
||||
{error: "Agent not found"},
|
||||
{status: 404}
|
||||
);
|
||||
}
|
||||
|
||||
const database = await db.query.database.findFirst({
|
||||
where: eq(drizzleDb.schemas.database.agentDatabaseId, generatedId),
|
||||
with: {
|
||||
project: true,
|
||||
alertPolicies: true,
|
||||
storagePolicies: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!database) {
|
||||
return NextResponse.json(
|
||||
{error: "Database associated with generatedId not found"},
|
||||
{status: 404}
|
||||
);
|
||||
}
|
||||
|
||||
let backup: Backup | null | undefined = null;
|
||||
|
||||
if (method === "automatic") {
|
||||
[backup] = await db
|
||||
.insert(drizzleDb.schemas.backup)
|
||||
.values({
|
||||
status: 'ongoing',
|
||||
databaseId: database.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
||||
if (!backup) {
|
||||
return NextResponse.json(
|
||||
{error: "Unable to create an automatic backup"},
|
||||
{status: 500}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
backup = await db.query.backup.findFirst({
|
||||
where: and(
|
||||
eq(drizzleDb.schemas.backup.status, 'ongoing'),
|
||||
eq(drizzleDb.schemas.backup.databaseId, database.id),
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
if (!backup) {
|
||||
return NextResponse.json(
|
||||
{error: "Unable to find the corresponding backup"},
|
||||
{status: 404}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const status = formData.get("status") as string | null;
|
||||
|
||||
if (status === "success") {
|
||||
const file = formData.get("file") as File | null;
|
||||
const extension = formData.get("extension") as string | null;
|
||||
if (!aesKeyHex || !ivHex) {
|
||||
return NextResponse.json({error: "Missing fields"}, {status: 400});
|
||||
}
|
||||
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json(
|
||||
{error: "File is required for successful backup"},
|
||||
{status: 400}
|
||||
);
|
||||
}
|
||||
const fileSizeBytes = file.size;
|
||||
// const fileExtension = '.' + (file.name.split('.').pop()?.toLowerCase() || '');
|
||||
const fileExtension = extension ? extension : getFileExtension(database.dbms)
|
||||
const decryptedFile = await decryptedDump(file, aesKeyHex, ivHex, fileExtension);
|
||||
const uuid = uuidv4();
|
||||
const fileName = `${uuid}${fileExtension}`;
|
||||
const buffer = Buffer.from(await decryptedFile.arrayBuffer());
|
||||
|
||||
|
||||
const storageResults = await storeBackupFiles(backup, database, buffer, fileName)
|
||||
eventEmitter.emit('modification', {update: true});
|
||||
|
||||
await sendNotificationsBackupRestore(database, "success_backup");
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "Backup successfully uploaded",
|
||||
},
|
||||
{status: 200}
|
||||
);
|
||||
} else {
|
||||
|
||||
await db
|
||||
.update(drizzleDb.schemas.backup)
|
||||
.set(withUpdatedAt({status: 'failed'}))
|
||||
.where(eq(drizzleDb.schemas.backup.id, backup.id));
|
||||
|
||||
eventEmitter.emit('modification', {update: true});
|
||||
|
||||
await sendNotificationsBackupRestore(database, "error_backup");
|
||||
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "Backup successfully updated with status failed",
|
||||
},
|
||||
{status: 200}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in POST handler:", error);
|
||||
return NextResponse.json(
|
||||
{error: "Internal server error"},
|
||||
{status: 500}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,6 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: {
|
||||
try {
|
||||
const body: BodyPatch = await request.json();
|
||||
|
||||
console.log(body);
|
||||
const status = body.status
|
||||
const backupId = body.backupId
|
||||
const backupSize = body.size
|
||||
|
||||
@@ -7,17 +7,13 @@ export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const event = body.Event
|
||||
console.log(body);
|
||||
const headers = event.HTTPRequest.Header
|
||||
const uploadLength = headers["X-File-Size"]?.[0];
|
||||
const uploadOffset = headers["Upload-Offset"]?.[0];
|
||||
const status = headers["X-Status"]?.[0];
|
||||
|
||||
console.log(headers);
|
||||
|
||||
console.log(`Upload ID : ${event.Upload.ID} (${uploadOffset}/${uploadLength})`);
|
||||
|
||||
|
||||
if (status === "success") {
|
||||
if (
|
||||
body.Type === "post-receive" &&
|
||||
|
||||
@@ -17,35 +17,11 @@ RUN apt-get update && apt-get install -y \
|
||||
nginx \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#FROM base AS tusd-base
|
||||
#
|
||||
#ENV TUSD_VERSION=2.8.0
|
||||
#
|
||||
#RUN apt-get update && apt-get install -y \
|
||||
# curl \
|
||||
# ca-certificates \
|
||||
# bash \
|
||||
# tar \
|
||||
# gzip \
|
||||
# libssl-dev \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
#
|
||||
## Download and extract tusd
|
||||
#RUN curl -L -o /tmp/tusd.tar.gz \
|
||||
# https://github.com/tus/tusd/releases/download/v${TUSD_VERSION}/tusd_linux_amd64.tar.gz \
|
||||
# && mkdir -p /usr/local/bin \
|
||||
# && tar -xzf /tmp/tusd.tar.gz -C /tmp \
|
||||
# # Move the actual binary, whatever its name/folder inside tar
|
||||
# && mv /tmp/*/tusd /usr/local/bin/tusd \
|
||||
# && chmod +x /usr/local/bin/tusd \
|
||||
# && /usr/local/bin/tusd --version
|
||||
|
||||
# Use Debian-based Go for reliable networking/DNS during build
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS tusd-base
|
||||
|
||||
ARG TUSD_VERSION=2.8.0
|
||||
|
||||
# Install git
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -54,16 +30,13 @@ WORKDIR /build
|
||||
RUN git clone https://github.com/tus/tusd.git . \
|
||||
&& git checkout v${TUSD_VERSION}
|
||||
|
||||
# Build statically linked binary
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \
|
||||
go build -ldflags="-s -w" -o /tusddist/tusd ./cmd/tusd
|
||||
|
||||
# Minimal dist stage (scratch keeps final image small)
|
||||
FROM scratch AS tusd-dist
|
||||
COPY --from=tusd-base /tusddist/tusd /tusd
|
||||
|
||||
|
||||
|
||||
FROM base AS build-env
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
@@ -116,7 +89,6 @@ COPY --from=builder --chown=1001:1001 /app/.next/static ./.next/static
|
||||
COPY --chown=1001:1001 src/db ./src/db
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
|
||||
#COPY --from=tusd-base /usr/local/bin/tusd /usr/local/bin/tusd
|
||||
COPY --from=tusd-dist /tusd /usr/local/bin/tusd
|
||||
RUN chmod +x /usr/local/bin/tusd
|
||||
|
||||
|
||||
@@ -9,11 +9,6 @@ fi
|
||||
|
||||
mkdir -p /app/private/uploads/tmp
|
||||
echo "▶ Starting tusd server..."
|
||||
#tusd --dir /app/private/uploads/tmp --hooks-http http://localhost:80/api/tus/hooks --port 1080 &
|
||||
#tusd --upload-dir /app/private/uploads/tmp --hooks-http http://localhost:3000/api/tus/hooks --port 1080 --max-size 21474836480 &
|
||||
#tusd --base-path /tus --upload-dir /app/private/uploads/tmp --hooks-http http://127.0.0.1:3000/api/tus/hooks --port 1080 --max-size 21474836480 --hooks-http-forward-headers "*" &
|
||||
#tusd --upload-dir /app/private/uploads/tmp --hooks-http http://127.0.0.1:3000/api/tus/hooks --port 1080 --max-size 21474836480 --hooks-http-forward-headers "X-Generated-Id" &
|
||||
#tusd --upload-dir /app/private/uploads/tmp --hooks-http http://127.0.0.1:3000/api/tus/hooks --port 1080 --max-size 21474836480 --behind-proxy true --hooks-http-forward-headers "X-Generated-Id" &
|
||||
tusd --base-path /tus/files/ --upload-dir /app/private/uploads/tmp --hooks-http http://127.0.0.1:3000/api/tus/hooks --port 1080 --max-size 21474836480 &
|
||||
|
||||
echo "▶ Starting Next.js server..."
|
||||
@@ -23,5 +18,3 @@ echo "▶ Starting nginx..."
|
||||
exec nginx -g "daemon off;"
|
||||
|
||||
|
||||
#exec "$@"
|
||||
|
||||
|
||||
@@ -7,48 +7,24 @@ http {
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# TUS uploads – zero buffering
|
||||
# location /tus/ {
|
||||
# proxy_pass http://127.0.0.1:1080/;
|
||||
# # proxy_request_buffering off;
|
||||
# # proxy_buffering off;
|
||||
# # proxy_http_version 1.1;
|
||||
location /tus/ {
|
||||
# rewrite ^/tus/(.*)$ /$1 break;
|
||||
# proxy_pass http://127.0.0.1:1080/;
|
||||
# proxy_request_buffering off;
|
||||
# proxy_buffering off;
|
||||
# proxy_http_version 1.1;
|
||||
proxy_pass http://127.0.0.1:1080/tus/;
|
||||
|
||||
proxy_pass_request_headers on;
|
||||
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Forwarded-Host $host;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# Disable request and response buffering
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_http_version 1.1;
|
||||
# Add X-Forwarded-* headers
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
|
||||
# proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
# # proxy_set_header Connection "";
|
||||
client_max_body_size 0;
|
||||
}
|
||||
|
||||
# Next.js
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
|
||||
|
||||
@@ -47,20 +47,6 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
proxyClientMaxBodySize: '10gb',
|
||||
},
|
||||
// async rewrites() {
|
||||
// return [
|
||||
// {
|
||||
// source: '/tus/metrics',
|
||||
// destination: 'http://localhost:1080/metrics',
|
||||
// },
|
||||
// {
|
||||
// source: '/tus/files',
|
||||
// destination: 'http://localhost:1080/files',
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
|
||||
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user