Key additions: * Separate platform Manager stack: [stacks/manager/compose.yaml] * Dedicated manager authentication with signed sessions. * Business overview dashboard. * Add Business workflow with slug suggestions and deployment progress. * Business detail page with start, stop, restart, update, logs, Dockge, and conservative removal actions. * Explicit management API endpoints under [src/app/api/manager] * Shared provisioning service layer in [provisioning.service.ts] * CLI and UI now use the same [create-business.sh] provisioning logic. * Registry metadata, database status, container health, and media usage reporting. * Manager-only entrypoint mode that does not require a business database. * Updated deployment documentation with Manager setup and Caddy routing. Validation passed: * Infra, Caddy, Manager, and generated business Compose configurations. * Shell syntax checks. * Registry generation with registry image tags. * git diff --check.
This commit is contained in:
24 files changed
+596
-11
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import { createHmac, timingSafeEqual } from "node:crypto";
|
||||
import { cookies } from "next/headers";
|
||||
import { getManagerConfig } from "./manager-config";
|
||||
|
||||
const COOKIE_NAME = "nextchair_manager_session";
|
||||
|
||||
function signature(value: string, secret: string) {
|
||||
return createHmac("sha256", secret).update(value).digest("hex");
|
||||
}
|
||||
|
||||
export function isManagerConfigured() {
|
||||
const config = getManagerConfig();
|
||||
return Boolean(config.adminEmail && config.adminPassword && config.authSecret);
|
||||
}
|
||||
|
||||
export function authenticateManager(email: string, password: string) {
|
||||
const config = getManagerConfig();
|
||||
if (!isManagerConfigured() || email.trim().toLowerCase() !== config.adminEmail.trim().toLowerCase()) return false;
|
||||
const expected = Buffer.from(config.adminPassword);
|
||||
const received = Buffer.from(password);
|
||||
return expected.length === received.length && timingSafeEqual(expected, received);
|
||||
}
|
||||
|
||||
export async function createManagerSession() {
|
||||
const config = getManagerConfig();
|
||||
const value = `${config.adminEmail.trim().toLowerCase()}.${Date.now()}`;
|
||||
(await cookies()).set(COOKIE_NAME, `${value}.${signature(value, config.authSecret)}`, {
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
path: "/manager",
|
||||
maxAge: 60 * 60 * 8,
|
||||
});
|
||||
}
|
||||
|
||||
export async function clearManagerSession() {
|
||||
(await cookies()).delete(COOKIE_NAME);
|
||||
}
|
||||
|
||||
export async function isManagerAuthenticated() {
|
||||
const config = getManagerConfig();
|
||||
if (!config.authSecret) return false;
|
||||
const value = (await cookies()).get(COOKIE_NAME)?.value;
|
||||
if (!value) return false;
|
||||
const separator = value.lastIndexOf(".");
|
||||
if (separator < 1) return false;
|
||||
const payload = value.slice(0, separator);
|
||||
const received = Buffer.from(value.slice(separator + 1));
|
||||
const expected = Buffer.from(signature(payload, config.authSecret));
|
||||
return received.length === expected.length && timingSafeEqual(received, expected);
|
||||
}
|
||||
Reference in new issue
Block a user