Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions my-portfolio/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NEXT_PUBLIC_STORAGE_API_URL=/api/storage
NEXT_PUBLIC_MEDIA_API_URL=/api/media
NEXT_PUBLIC_ADMIN_API_URL=/api/admin
NEXTAUTH_SECRET="default_dev_secret"
4 changes: 2 additions & 2 deletions my-portfolio/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function AdminPage() {
const fetchContainers = async () => {
try {
setLoading(true);
const baseUrl = process.env.NEXT_PUBLIC_ADMIN_API_URL || (typeof window !== "undefined" ? `http://${window.location.hostname}:4003` : "http://localhost:4003");
const baseUrl = process.env.NEXT_PUBLIC_ADMIN_API_URL || "/api/admin";
const res = await fetch(`${baseUrl}/containers`);
const data = await res.json();
const mappedContainers = data.map((c: any) => ({
Expand Down Expand Up @@ -80,7 +80,7 @@ export default function AdminPage() {
const action = currentStatus === "running" ? "stop" : "start";
try {
addToast(`Requested container ${action}...`, "info");
const baseUrl = process.env.NEXT_PUBLIC_ADMIN_API_URL || (typeof window !== "undefined" ? `http://${window.location.hostname}:4003` : "http://localhost:4003");
const baseUrl = process.env.NEXT_PUBLIC_ADMIN_API_URL || "/api/admin";
const res = await fetch(`${baseUrl}/containers/${id}/${action}`, { method: "POST" });
if (res.ok) {
addToast(`Container ${action}ed successfully`, "success");
Expand Down
67 changes: 67 additions & 0 deletions my-portfolio/app/api/admin/[[...path]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";

const ADMIN_SERVICE_URL = process.env.ADMIN_SERVICE_URL || "http://localhost:4003";
const GATEWAY_SECRET = process.env.GATEWAY_SECRET || "default_safe_secret_key_12345!";

async function proxyRequest(req: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });

if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const resolvedParams = await params;
const path = resolvedParams.path ? resolvedParams.path.join("/") : "";
const searchParams = req.nextUrl.searchParams.toString();
const queryString = searchParams ? `?${searchParams}` : "";
const targetUrl = `${ADMIN_SERVICE_URL}/${path}${queryString}`;

const headers = new Headers();
req.headers.forEach((value, key) => {
// We shouldn't forward host header so fetch can set it
if (key.toLowerCase() !== "host") {
headers.set(key, value);
}
});

// Append the gateway secret
headers.set("x-gateway-secret", GATEWAY_SECRET);

const init: RequestInit = {
method: req.method,
headers,
redirect: "manual",
};

// Only append body for methods that allow it
if (req.method !== "GET" && req.method !== "HEAD") {
const body = await req.text();
if (body) {
init.body = body;
}
}

try {
const response = await fetch(targetUrl, init);
const responseBody = await response.text();

const responseHeaders = new Headers(response.headers);
responseHeaders.delete("content-encoding");

return new NextResponse(responseBody, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
});
} catch (error) {
console.error("Proxy error:", error);
return NextResponse.json({ error: "Gateway Error" }, { status: 502 });
}
}

export const GET = proxyRequest;
export const POST = proxyRequest;
export const PUT = proxyRequest;
export const DELETE = proxyRequest;
export const PATCH = proxyRequest;
4 changes: 0 additions & 4 deletions my-portfolio/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ const nextConfig: NextConfig = {
{
source: "/api/media/:path*",
destination: "http://localhost/api/media/:path*", // Nginx proxy
},
{
source: "/api/admin/:path*",
destination: "http://localhost/api/admin/:path*", // Nginx proxy
}
];
}
Expand Down
12 changes: 9 additions & 3 deletions my-portfolio/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";

export async function proxy(request: NextRequest) {
const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET });

if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}

// Authentication is bypassed for local development — restore later
export function proxy(request: NextRequest) {
return NextResponse.next();
}

export const config = {
matcher: [],
matcher: ["/admin/:path*", "/dashboard/:path*"],
};
9 changes: 9 additions & 0 deletions my-portfolio/server/admin-service/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ require('dotenv').config();
const app = express();
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
const PORT = process.env.PORT || 4003;
const GATEWAY_SECRET = process.env.GATEWAY_SECRET || 'default_safe_secret_key_12345!';

app.use(cors());
app.use(express.json());

app.use((req, res, next) => {
const secret = req.headers['x-gateway-secret'];
if (secret !== GATEWAY_SECRET) {
return res.status(403).json({ error: 'Forbidden: Invalid Gateway Secret' });
}
next();
});

// List containers
app.get('/containers', async (req, res) => {
try {
Expand Down