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 {
|
try {
|
||||||
const body: BodyPatch = await request.json();
|
const body: BodyPatch = await request.json();
|
||||||
|
|
||||||
console.log(body);
|
|
||||||
const status = body.status
|
const status = body.status
|
||||||
const backupId = body.backupId
|
const backupId = body.backupId
|
||||||
const backupSize = body.size
|
const backupSize = body.size
|
||||||
|
|||||||
@@ -7,17 +7,13 @@ export async function POST(request: Request) {
|
|||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const event = body.Event
|
const event = body.Event
|
||||||
console.log(body);
|
|
||||||
const headers = event.HTTPRequest.Header
|
const headers = event.HTTPRequest.Header
|
||||||
const uploadLength = headers["X-File-Size"]?.[0];
|
const uploadLength = headers["X-File-Size"]?.[0];
|
||||||
const uploadOffset = headers["Upload-Offset"]?.[0];
|
const uploadOffset = headers["Upload-Offset"]?.[0];
|
||||||
const status = headers["X-Status"]?.[0];
|
const status = headers["X-Status"]?.[0];
|
||||||
|
|
||||||
console.log(headers);
|
|
||||||
|
|
||||||
console.log(`Upload ID : ${event.Upload.ID} (${uploadOffset}/${uploadLength})`);
|
console.log(`Upload ID : ${event.Upload.ID} (${uploadOffset}/${uploadLength})`);
|
||||||
|
|
||||||
|
|
||||||
if (status === "success") {
|
if (status === "success") {
|
||||||
if (
|
if (
|
||||||
body.Type === "post-receive" &&
|
body.Type === "post-receive" &&
|
||||||
|
|||||||
@@ -17,35 +17,11 @@ RUN apt-get update && apt-get install -y \
|
|||||||
nginx \
|
nginx \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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
|
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS tusd-base
|
||||||
|
|
||||||
ARG TUSD_VERSION=2.8.0
|
ARG TUSD_VERSION=2.8.0
|
||||||
|
|
||||||
# Install git
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@@ -54,16 +30,13 @@ WORKDIR /build
|
|||||||
RUN git clone https://github.com/tus/tusd.git . \
|
RUN git clone https://github.com/tus/tusd.git . \
|
||||||
&& git checkout v${TUSD_VERSION}
|
&& git checkout v${TUSD_VERSION}
|
||||||
|
|
||||||
# Build statically linked binary
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \
|
||||||
go build -ldflags="-s -w" -o /tusddist/tusd ./cmd/tusd
|
go build -ldflags="-s -w" -o /tusddist/tusd ./cmd/tusd
|
||||||
|
|
||||||
# Minimal dist stage (scratch keeps final image small)
|
|
||||||
FROM scratch AS tusd-dist
|
FROM scratch AS tusd-dist
|
||||||
COPY --from=tusd-base /tusddist/tusd /tusd
|
COPY --from=tusd-base /tusddist/tusd /tusd
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FROM base AS build-env
|
FROM base AS build-env
|
||||||
|
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
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 --chown=1001:1001 src/db ./src/db
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
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
|
COPY --from=tusd-dist /tusd /usr/local/bin/tusd
|
||||||
RUN chmod +x /usr/local/bin/tusd
|
RUN chmod +x /usr/local/bin/tusd
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ fi
|
|||||||
|
|
||||||
mkdir -p /app/private/uploads/tmp
|
mkdir -p /app/private/uploads/tmp
|
||||||
echo "▶ Starting tusd server..."
|
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 &
|
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..."
|
echo "▶ Starting Next.js server..."
|
||||||
@@ -23,5 +18,3 @@ echo "▶ Starting nginx..."
|
|||||||
exec nginx -g "daemon off;"
|
exec nginx -g "daemon off;"
|
||||||
|
|
||||||
|
|
||||||
#exec "$@"
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,48 +7,24 @@ http {
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
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/ {
|
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 http://127.0.0.1:1080/tus/;
|
||||||
|
|
||||||
proxy_pass_request_headers on;
|
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_request_buffering off;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
# Add X-Forwarded-* headers
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
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 Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
# # proxy_set_header Connection "";
|
|
||||||
client_max_body_size 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Next.js
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:3000;
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
|
||||||
|
|||||||
@@ -47,20 +47,6 @@ const nextConfig: NextConfig = {
|
|||||||
},
|
},
|
||||||
proxyClientMaxBodySize: '10gb',
|
proxyClientMaxBodySize: '10gb',
|
||||||
},
|
},
|
||||||
// async rewrites() {
|
|
||||||
// return [
|
|
||||||
// {
|
|
||||||
// source: '/tus/metrics',
|
|
||||||
// destination: 'http://localhost:1080/metrics',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// source: '/tus/files',
|
|
||||||
// destination: 'http://localhost:1080/files',
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
|
|
||||||
|
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ function checkRouteExists(pathname: string) {
|
|||||||
/^\/api\/files\/backups\/?$/,
|
/^\/api\/files\/backups\/?$/,
|
||||||
/^\/api\/tus\/hooks\/?$/,
|
/^\/api\/tus\/hooks\/?$/,
|
||||||
/^\/api\/events\/?$/,
|
/^\/api\/events\/?$/,
|
||||||
/^\/api\/init\/?$/,
|
|
||||||
/^\/api\/config\/?$/,
|
/^\/api\/config\/?$/,
|
||||||
/^\/api\/google\/drive\/callback\/?$/,
|
/^\/api\/google\/drive\/callback\/?$/,
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user