Add TLS feature with Caddy
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
import type { BUN_SQLITE_WGUI_TLS_CERTIFICATES } from "@/db/types/db";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { TableType } from "@/src/types";
|
||||
import grabCertDirNames from "@/src/functions/backend/caddy/grab-cert-dir-names";
|
||||
|
||||
type Params = {
|
||||
hostname: string;
|
||||
user_id?: number | "";
|
||||
};
|
||||
|
||||
/**
|
||||
* A route may exist before its certificate. Create the metadata row so the
|
||||
* setup page can list it and offer an "Issue" action.
|
||||
*/
|
||||
export default async function ensureCertificateRecord({
|
||||
hostname,
|
||||
user_id,
|
||||
}: Params) {
|
||||
const existing = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_TLS_CERTIFICATES,
|
||||
TableType
|
||||
>({
|
||||
table: "tls_certificates",
|
||||
query: {
|
||||
query: {
|
||||
hostname: {
|
||||
value: hostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existing.singleRes?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { CERT_FILE, KEY_FILE } = grabCertDirNames({ hostname });
|
||||
|
||||
await BunSQLite.insert({
|
||||
table: "tls_certificates",
|
||||
data: [
|
||||
{
|
||||
user_id,
|
||||
hostname,
|
||||
provider: "letsencrypt",
|
||||
challenge: "dns-01",
|
||||
cert_path: CERT_FILE,
|
||||
key_path: KEY_FILE,
|
||||
status: "missing",
|
||||
auto_renew: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { BUN_SQLITE_WGUI_CLIENT_ROUTES } from "@/db/types/db";
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import syncCaddyIngress from "@/src/functions/backend/caddy/sync-caddy-ingress";
|
||||
import type { AdminCrudAPIParams, TableType } from "@/src/types";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
|
||||
export default async function (
|
||||
params: AdminCrudAPIParams,
|
||||
): Promise<APIResponseObject> {
|
||||
const { id, user_types, user } = params;
|
||||
|
||||
const is_super_admin = checkUserAccess({ user_types });
|
||||
|
||||
if (!is_super_admin.success) {
|
||||
const is_admin = checkUserAccess({
|
||||
user_types,
|
||||
includes: ["admin"],
|
||||
});
|
||||
|
||||
if (!is_admin.success) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
}
|
||||
|
||||
const existing_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
targetId: id,
|
||||
});
|
||||
|
||||
const existing = existing_res.singleRes;
|
||||
|
||||
if (typeof existing?.id !== "number") {
|
||||
return { success: false, msg: `Route not found` };
|
||||
}
|
||||
|
||||
if (!is_super_admin.success && existing.user_id != user.id) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
const deleted = await BunSQLite.delete<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
targetId: existing.id,
|
||||
});
|
||||
|
||||
if (!deleted.success) {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
const sync = await syncCaddyIngress({ reason: "route deleted" });
|
||||
|
||||
return {
|
||||
...deleted,
|
||||
msg: sync.msg,
|
||||
debug: { warnings: sync.warnings },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type {
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
BUN_SQLITE_WGUI_TLS_CERTIFICATES,
|
||||
} from "@/db/types/db";
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import decorateClientRoute from "@/src/functions/backend/acme/decorate-client-route";
|
||||
import { grabManualDnsChallenge } from "@/src/functions/backend/acme/manual-dns-challenges";
|
||||
import type { AdminCrudAPIParams, TableType } from "@/src/types";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import _ from "lodash";
|
||||
|
||||
export default async function (
|
||||
params: AdminCrudAPIParams,
|
||||
): Promise<APIResponseObject> {
|
||||
const { table, user_types, body, id, query } = params;
|
||||
|
||||
const allowed = checkUserAccess({
|
||||
user_types,
|
||||
includes: ["admin"],
|
||||
});
|
||||
|
||||
if (!allowed.success) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
const final_sql_query = _.merge(query?.sql_query, body?.sql_query) || {};
|
||||
|
||||
const GET = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table,
|
||||
query: final_sql_query,
|
||||
targetId: id,
|
||||
});
|
||||
|
||||
const certificates_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_TLS_CERTIFICATES,
|
||||
TableType
|
||||
>({
|
||||
table: "tls_certificates",
|
||||
});
|
||||
|
||||
const certificates_by_hostname = new Map(
|
||||
(certificates_res.payload || [])
|
||||
.filter((certificate) => certificate.hostname)
|
||||
.map((certificate) => [
|
||||
certificate.hostname!.trim().toLowerCase(),
|
||||
certificate,
|
||||
]),
|
||||
);
|
||||
|
||||
const decorate = (route: BUN_SQLITE_WGUI_CLIENT_ROUTES) => {
|
||||
const view = decorateClientRoute({
|
||||
route,
|
||||
certificate: certificates_by_hostname.get(route.hostname || ""),
|
||||
});
|
||||
|
||||
if (
|
||||
typeof route.id === "number" &&
|
||||
grabManualDnsChallenge(route.id)
|
||||
) {
|
||||
view.certificate_status = "issuing";
|
||||
}
|
||||
|
||||
return view;
|
||||
};
|
||||
|
||||
return {
|
||||
...GET,
|
||||
payload: GET.payload?.map((route) =>
|
||||
decorate(route as BUN_SQLITE_WGUI_CLIENT_ROUTES),
|
||||
),
|
||||
singleRes: GET.singleRes
|
||||
? decorate(GET.singleRes as BUN_SQLITE_WGUI_CLIENT_ROUTES)
|
||||
: GET.singleRes,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { AdminCrudAPIParams } from "@/src/types";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import get from "./get";
|
||||
import post from "./post";
|
||||
import put from "./put";
|
||||
import del from "./del";
|
||||
|
||||
export default async function (
|
||||
params: AdminCrudAPIParams,
|
||||
): Promise<APIResponseObject> {
|
||||
switch (params.req.method) {
|
||||
case "GET":
|
||||
return await get(params);
|
||||
case "POST":
|
||||
return await post(params);
|
||||
case "PUT":
|
||||
return await put(params);
|
||||
case "DELETE":
|
||||
return await del(params);
|
||||
|
||||
default:
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type {
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
} from "@/db/types/db";
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import sanitizeClientRoute from "@/src/functions/backend/caddy/sanitize-client-route";
|
||||
import syncCaddyIngress from "@/src/functions/backend/caddy/sync-caddy-ingress";
|
||||
import type { AdminCrudAPIParams, TableType } from "@/src/types";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import ensureCertificateRecord from "./(functions)/ensure-certificate-record";
|
||||
import sealRouteCredentials from "@/src/functions/backend/acme/seal-route-credentials";
|
||||
|
||||
export default async function (
|
||||
params: AdminCrudAPIParams,
|
||||
): Promise<APIResponseObject> {
|
||||
const { body, user_types, user } = params;
|
||||
|
||||
const is_super_admin = checkUserAccess({ user_types });
|
||||
|
||||
if (!is_super_admin.success) {
|
||||
const is_admin = checkUserAccess({
|
||||
user_types,
|
||||
includes: ["admin"],
|
||||
});
|
||||
|
||||
if (!is_admin.success) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
}
|
||||
|
||||
const prepared: BUN_SQLITE_WGUI_CLIENT_ROUTES[] = [];
|
||||
|
||||
for (let i = 0; i < (body?.insert_data || []).length; i++) {
|
||||
const sanitized = sanitizeClientRoute({
|
||||
route: body?.insert_data?.[i],
|
||||
user_id: user.id,
|
||||
});
|
||||
|
||||
if (!sanitized.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: sanitized.msg,
|
||||
};
|
||||
}
|
||||
|
||||
const sealed = await sealRouteCredentials({
|
||||
text: sanitized.route.acme_credentials,
|
||||
});
|
||||
|
||||
if (!sealed.success) {
|
||||
return { success: false, msg: sealed.msg };
|
||||
}
|
||||
|
||||
const { id: _ignored_id, ...route_data } = sanitized.route;
|
||||
|
||||
prepared.push({
|
||||
...route_data,
|
||||
acme_credentials: sealed.value,
|
||||
user_id: is_super_admin.success
|
||||
? route_data.user_id
|
||||
: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (!prepared[0]) {
|
||||
return { success: false, msg: `No routes to insert` };
|
||||
}
|
||||
|
||||
const host_ids = [...new Set(prepared.map((route) => Number(route.host_id)))];
|
||||
|
||||
for (let i = 0; i < host_ids.length; i++) {
|
||||
const host_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
TableType
|
||||
>({
|
||||
table: "hosts",
|
||||
targetId: host_ids[i],
|
||||
});
|
||||
|
||||
const host = host_res.singleRes;
|
||||
|
||||
if (typeof host?.id !== "number") {
|
||||
return { success: false, msg: `Host not found` };
|
||||
}
|
||||
|
||||
if (!is_super_admin.success && host.user_id != user.id) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
}
|
||||
|
||||
const insert = await BunSQLite.insert<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
data: prepared,
|
||||
update_on_duplicate: false,
|
||||
});
|
||||
|
||||
if (!insert.success) {
|
||||
return insert;
|
||||
}
|
||||
|
||||
for (let i = 0; i < prepared.length; i++) {
|
||||
const route = prepared[i];
|
||||
|
||||
if (route?.hostname) {
|
||||
await ensureCertificateRecord({
|
||||
hostname: route.hostname,
|
||||
user_id: user.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const sync = await syncCaddyIngress({ reason: "route created" });
|
||||
|
||||
return {
|
||||
...insert,
|
||||
msg: sync.msg,
|
||||
debug: { warnings: sync.warnings },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type {
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
} from "@/db/types/db";
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import sanitizeClientRoute from "@/src/functions/backend/caddy/sanitize-client-route";
|
||||
import syncCaddyIngress from "@/src/functions/backend/caddy/sync-caddy-ingress";
|
||||
import type { AdminCrudAPIParams, TableType } from "@/src/types";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import ensureCertificateRecord from "./(functions)/ensure-certificate-record";
|
||||
import sealRouteCredentials from "@/src/functions/backend/acme/seal-route-credentials";
|
||||
|
||||
export default async function (
|
||||
params: AdminCrudAPIParams,
|
||||
): Promise<APIResponseObject> {
|
||||
const { body, id, user_types, user } = params;
|
||||
|
||||
if (!body?.update_data) {
|
||||
return { success: false, msg: `No update data` };
|
||||
}
|
||||
|
||||
const is_super_admin = checkUserAccess({ user_types });
|
||||
|
||||
if (!is_super_admin.success) {
|
||||
const is_admin = checkUserAccess({
|
||||
user_types,
|
||||
includes: ["admin"],
|
||||
});
|
||||
|
||||
if (!is_admin.success) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
}
|
||||
|
||||
const existing_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
targetId: id,
|
||||
});
|
||||
|
||||
const existing = existing_res.singleRes;
|
||||
|
||||
if (typeof existing?.id !== "number") {
|
||||
return { success: false, msg: `Route not found` };
|
||||
}
|
||||
|
||||
if (!is_super_admin.success && existing.user_id != user.id) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
const update_data = {
|
||||
...(body.update_data as BUN_SQLITE_WGUI_CLIENT_ROUTES),
|
||||
};
|
||||
const new_credentials_text =
|
||||
typeof update_data.acme_credentials == "string"
|
||||
? update_data.acme_credentials
|
||||
: "";
|
||||
|
||||
// Blank means "keep the existing encrypted credentials".
|
||||
delete update_data.acme_credentials;
|
||||
|
||||
const sanitized = sanitizeClientRoute({
|
||||
route: { ...existing, ...update_data },
|
||||
user_id: existing.user_id,
|
||||
host_id: existing.host_id,
|
||||
});
|
||||
|
||||
if (!sanitized.success) {
|
||||
return { success: false, msg: sanitized.msg };
|
||||
}
|
||||
|
||||
if (new_credentials_text?.trim()) {
|
||||
const sealed = await sealRouteCredentials({
|
||||
text: new_credentials_text,
|
||||
existing: existing.acme_credentials,
|
||||
});
|
||||
|
||||
if (!sealed.success) {
|
||||
return { success: false, msg: sealed.msg };
|
||||
}
|
||||
|
||||
sanitized.route.acme_credentials = sealed.value;
|
||||
}
|
||||
|
||||
const host_res = await BunSQLite.select<BUN_SQLITE_WGUI_HOSTS, TableType>({
|
||||
table: "hosts",
|
||||
targetId: sanitized.route.host_id,
|
||||
});
|
||||
|
||||
if (typeof host_res.singleRes?.id !== "number") {
|
||||
return { success: false, msg: `Host not found` };
|
||||
}
|
||||
|
||||
if (
|
||||
!is_super_admin.success &&
|
||||
host_res.singleRes.user_id != user.id
|
||||
) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
sanitized.route.user_id = is_super_admin.success
|
||||
? existing.user_id
|
||||
: user.id;
|
||||
|
||||
const update = await BunSQLite.update<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
targetId: existing.id,
|
||||
data: sanitized.route,
|
||||
});
|
||||
|
||||
if (!update.success) {
|
||||
return update;
|
||||
}
|
||||
|
||||
await ensureCertificateRecord({
|
||||
hostname: sanitized.route.hostname || "",
|
||||
user_id: sanitized.route.user_id,
|
||||
});
|
||||
|
||||
const sync = await syncCaddyIngress({ reason: "route updated" });
|
||||
|
||||
return {
|
||||
...update,
|
||||
msg: sync.msg,
|
||||
debug: { warnings: sync.warnings },
|
||||
};
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export default async function runClientSetup({
|
||||
|
||||
let host: BUN_SQLITE_WGUI_HOSTS | undefined;
|
||||
|
||||
if (client?.host_id) {
|
||||
if (client?.host_id !== undefined && client.host_id !== "") {
|
||||
const host_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
TableType
|
||||
|
||||
@@ -100,7 +100,7 @@ export default async function (
|
||||
try {
|
||||
let host: BUN_SQLITE_WGUI_HOSTS | undefined;
|
||||
|
||||
if (host_id) {
|
||||
if (Number.isSafeInteger(host_id) && host_id >= 0) {
|
||||
const host_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
TableType
|
||||
|
||||
@@ -15,6 +15,7 @@ import userAuth from "@/src/functions/backend/auth/user-auth";
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import clients from "./(tables)/clients";
|
||||
import clientRules from "./(tables)/client_rules";
|
||||
import clientRoutes from "./(tables)/client_routes";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||
params,
|
||||
@@ -64,6 +65,13 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||
return await clients(crud_params);
|
||||
case "client_rules":
|
||||
return await clientRules(crud_params);
|
||||
case "client_routes":
|
||||
return await clientRoutes(crud_params);
|
||||
case "tls_certificates":
|
||||
return {
|
||||
success: false,
|
||||
msg: `TLS certificates are managed by the TLS ingress`,
|
||||
};
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -33,7 +33,11 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||
try {
|
||||
let host: BUN_SQLITE_WGUI_HOSTS | undefined;
|
||||
|
||||
if (body?.host_id) {
|
||||
if (
|
||||
body?.host_id !== undefined &&
|
||||
body.host_id !== null &&
|
||||
body.host_id !== ""
|
||||
) {
|
||||
const host_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
TableType
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import adminApiGuard from "@/src/functions/backend/auth/admin-api-guard";
|
||||
import syncCaddyIngress from "@/src/functions/backend/caddy/sync-caddy-ingress";
|
||||
import runSetupScript from "@/src/functions/backend/setup/run-setup-script";
|
||||
import { TlsData } from "@/src/data/tls-data";
|
||||
import grabDirNames from "@/src/utils/grab-dir-names";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
|
||||
const INSTALL_CADDY_SCRIPT = `install-caddy.sh`;
|
||||
const INSTALL_CADDY_TIMEOUT_MS = 20 * 60 * 1000;
|
||||
|
||||
const {
|
||||
WGUI_LIB_DIR,
|
||||
WGUI_CADDY_BIN_DIR,
|
||||
WGUI_ACME_HOME,
|
||||
WGUI_ACME_ENV_FILE,
|
||||
} = grabDirNames();
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
}) => {
|
||||
if (req.method !== "POST") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const unauthorized = await adminApiGuard({ req });
|
||||
|
||||
if (unauthorized) {
|
||||
return unauthorized;
|
||||
}
|
||||
|
||||
const install = runSetupScript({
|
||||
script: INSTALL_CADDY_SCRIPT,
|
||||
timeout_ms: INSTALL_CADDY_TIMEOUT_MS,
|
||||
env: {
|
||||
WGUI_LIB_DIR,
|
||||
CADDY_VERSION: process.env.CADDY_VERSION,
|
||||
CADDY_BIN_DIR: WGUI_CADDY_BIN_DIR,
|
||||
CADDY_HTTPS_PORT: String(TlsData.CaddyHttpsPort),
|
||||
CADDY_HTTP_PORT: String(TlsData.CaddyHttpPort),
|
||||
ACME_HOME: WGUI_ACME_HOME,
|
||||
ACME_ENV_FILE: WGUI_ACME_ENV_FILE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!install.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: install.output,
|
||||
};
|
||||
}
|
||||
|
||||
const sync = await syncCaddyIngress({ reason: "caddy installed" });
|
||||
|
||||
return {
|
||||
success: sync.success,
|
||||
msg: [install.output, sync.msg].filter(Boolean).join("\n\n"),
|
||||
debug: { warnings: sync.warnings },
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import type {
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
BUN_SQLITE_WGUI_USER_TYPES,
|
||||
} from "@/db/types/db";
|
||||
import type { User } from "@/src/types";
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import userAuth from "@/src/functions/backend/auth/user-auth";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { TableType } from "@/src/types";
|
||||
|
||||
type Params = {
|
||||
req: Request;
|
||||
route_id?: number | "";
|
||||
};
|
||||
|
||||
type Result =
|
||||
| {
|
||||
success: true;
|
||||
user: User;
|
||||
user_types: BUN_SQLITE_WGUI_USER_TYPES[];
|
||||
route: BUN_SQLITE_WGUI_CLIENT_ROUTES;
|
||||
}
|
||||
| { success: false; msg: string; logoutUser?: boolean };
|
||||
|
||||
export default async function guardRouteAccess({
|
||||
req,
|
||||
route_id,
|
||||
}: Params): Promise<Result> {
|
||||
const { user, user_types } = await userAuth({ req });
|
||||
|
||||
if (!user?.logged_in_status) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Unauthorized`,
|
||||
logoutUser: true,
|
||||
};
|
||||
}
|
||||
|
||||
const is_admin = checkUserAccess({
|
||||
user_types,
|
||||
includes: ["admin"],
|
||||
});
|
||||
|
||||
if (!is_admin.success) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
const final_route_id = Number(route_id);
|
||||
|
||||
if (!Number.isSafeInteger(final_route_id) || final_route_id < 1) {
|
||||
return { success: false, msg: `Invalid route` };
|
||||
}
|
||||
|
||||
const route_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
targetId: final_route_id,
|
||||
});
|
||||
|
||||
const route = route_res.singleRes;
|
||||
|
||||
if (!route) {
|
||||
return { success: false, msg: `Route not found` };
|
||||
}
|
||||
|
||||
const is_super_admin = checkUserAccess({ user_types }).success;
|
||||
|
||||
if (!is_super_admin && route.user_id != user.id) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
return { success: true, user, user_types: user_types || [], route };
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||
import userAuth from "@/src/functions/backend/auth/user-auth";
|
||||
import issueCertificate from "@/src/functions/backend/acme/issue-certificate";
|
||||
import type { BUN_SQLITE_WGUI_CLIENT_ROUTES } from "@/db/types/db";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { ApiReqParams, TableType } from "@/src/types";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
body,
|
||||
}) => {
|
||||
if (req.method !== "POST") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const { user, user_types } = await userAuth({ req });
|
||||
|
||||
if (!user?.logged_in_status) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Unauthorized`,
|
||||
logoutUser: true,
|
||||
};
|
||||
}
|
||||
|
||||
const is_admin = checkUserAccess({
|
||||
user_types,
|
||||
includes: ["admin"],
|
||||
});
|
||||
|
||||
if (!is_admin.success) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
const payload = body as
|
||||
| (ApiReqParams & {
|
||||
route_id?: number | "";
|
||||
hostname?: string;
|
||||
staging?: boolean;
|
||||
force?: boolean;
|
||||
})
|
||||
| undefined;
|
||||
|
||||
let route: BUN_SQLITE_WGUI_CLIENT_ROUTES | undefined;
|
||||
|
||||
if (payload?.route_id !== undefined && payload.route_id !== "") {
|
||||
const route_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
targetId: payload.route_id,
|
||||
});
|
||||
|
||||
route = route_res.singleRes || undefined;
|
||||
} else if (payload?.hostname) {
|
||||
const route_res = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_CLIENT_ROUTES,
|
||||
TableType
|
||||
>({
|
||||
table: "client_routes",
|
||||
query: {
|
||||
query: {
|
||||
hostname: {
|
||||
value: payload.hostname.trim().toLowerCase(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
route = route_res.singleRes || undefined;
|
||||
}
|
||||
|
||||
if (!route || typeof route.id !== "number") {
|
||||
return { success: false, msg: `Route not found` };
|
||||
}
|
||||
|
||||
const is_super_admin = checkUserAccess({ user_types }).success;
|
||||
|
||||
if (!is_super_admin && route.user_id != user.id) {
|
||||
return { success: false, msg: `Unauthorized` };
|
||||
}
|
||||
|
||||
return await issueCertificate({
|
||||
route,
|
||||
route_id: route.id,
|
||||
hostname: route.hostname,
|
||||
user_id: user.id,
|
||||
staging: payload?.staging,
|
||||
force: Boolean(payload?.force),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { removeManualDnsChallenge } from "@/src/functions/backend/acme/manual-dns-challenges";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
import guardRouteAccess from "../(functions)/guard-route-access";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
body,
|
||||
}) => {
|
||||
if (req.method !== "POST") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const payload = body as (ApiReqParams & { route_id?: number | "" }) | undefined;
|
||||
const guard = await guardRouteAccess({
|
||||
req,
|
||||
route_id: payload?.route_id,
|
||||
});
|
||||
|
||||
if (!guard.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: guard.msg,
|
||||
logoutUser: guard.logoutUser,
|
||||
};
|
||||
}
|
||||
|
||||
removeManualDnsChallenge(Number(guard.route.id));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
msg: `Pending certificate request cancelled`,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import completeManualCertificate from "@/src/functions/backend/acme/complete-manual-certificate";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
import guardRouteAccess from "../(functions)/guard-route-access";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
body,
|
||||
}) => {
|
||||
if (req.method !== "POST") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const payload = body as (ApiReqParams & { route_id?: number | "" }) | undefined;
|
||||
const guard = await guardRouteAccess({
|
||||
req,
|
||||
route_id: payload?.route_id,
|
||||
});
|
||||
|
||||
if (!guard.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: guard.msg,
|
||||
logoutUser: guard.logoutUser,
|
||||
};
|
||||
}
|
||||
|
||||
return await completeManualCertificate({
|
||||
route_id: guard.route.id,
|
||||
user_id: guard.user.id,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import startManualCertificate from "@/src/functions/backend/acme/start-manual-certificate";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
import guardRouteAccess from "../(functions)/guard-route-access";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
body,
|
||||
}) => {
|
||||
if (req.method !== "POST") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const payload = body as (ApiReqParams & { route_id?: number | "" }) | undefined;
|
||||
const guard = await guardRouteAccess({
|
||||
req,
|
||||
route_id: payload?.route_id,
|
||||
});
|
||||
|
||||
if (!guard.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: guard.msg,
|
||||
logoutUser: guard.logoutUser,
|
||||
};
|
||||
}
|
||||
|
||||
return await startManualCertificate({
|
||||
route_id: guard.route.id,
|
||||
user_id: guard.user.id,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import adminApiGuard from "@/src/functions/backend/auth/admin-api-guard";
|
||||
import grabTlsStatus from "@/src/functions/backend/acme/grab-tls-status";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
}) => {
|
||||
if (req.method !== "GET") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const unauthorized = await adminApiGuard({ req });
|
||||
|
||||
if (unauthorized) {
|
||||
return unauthorized;
|
||||
}
|
||||
|
||||
try {
|
||||
const status = await grabTlsStatus();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
payload: [status],
|
||||
singleRes: status,
|
||||
} as APIResponseObject;
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
msg: error.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import adminApiGuard from "@/src/functions/backend/auth/admin-api-guard";
|
||||
import syncCaddyIngress from "@/src/functions/backend/caddy/sync-caddy-ingress";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
|
||||
req,
|
||||
}) => {
|
||||
if (req.method !== "POST") {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const unauthorized = await adminApiGuard({ req });
|
||||
|
||||
if (unauthorized) {
|
||||
return unauthorized;
|
||||
}
|
||||
|
||||
const sync = await syncCaddyIngress({ reason: "manual sync" });
|
||||
|
||||
return {
|
||||
success: sync.success,
|
||||
msg: sync.msg,
|
||||
debug: { warnings: sync.warnings },
|
||||
};
|
||||
};
|
||||
@@ -82,8 +82,6 @@ export const handler: BunextAPIRouteHandler<
|
||||
],
|
||||
});
|
||||
|
||||
console.log("add_super_admin_user_types", add_super_admin_user_types);
|
||||
|
||||
return {
|
||||
...POST,
|
||||
singleRes: new_users_res.singleRes,
|
||||
|
||||
Reference in New Issue
Block a user