mirror of
https://github.com/tale/headplane.git
synced 2026-08-29 08:27:08 +00:00
feat: update to the v8 middleware api
This commit is contained in:
@@ -6,18 +6,22 @@ import Notice from "~/components/notice";
|
||||
import StatusCircle from "~/components/status-circle";
|
||||
import Text from "~/components/text";
|
||||
import Title from "~/components/title";
|
||||
import { agentsContext, authContext } from "~/server/context";
|
||||
import { formatTimeDelta } from "~/utils/time";
|
||||
|
||||
import type { Route } from "./+types/agent";
|
||||
|
||||
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
await context.auth.require(request);
|
||||
const agents = context.get(agentsContext);
|
||||
const auth = context.get(authContext);
|
||||
|
||||
if (context.agents.state !== "enabled") {
|
||||
return { enabled: false as const, reason: context.agents.reason };
|
||||
await auth.require(request);
|
||||
|
||||
if (agents.state !== "enabled") {
|
||||
return { enabled: false as const, reason: agents.reason };
|
||||
}
|
||||
|
||||
const sync = context.agents.value.lastSync();
|
||||
const sync = agents.value.lastSync();
|
||||
return {
|
||||
enabled: true as const,
|
||||
syncedAt: sync.syncedAt?.toISOString() ?? null,
|
||||
@@ -27,14 +31,17 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
}
|
||||
|
||||
export async function action({ request, context }: Route.ActionArgs) {
|
||||
await context.auth.require(request);
|
||||
const agents = context.get(agentsContext);
|
||||
const auth = context.get(authContext);
|
||||
|
||||
if (context.agents.state !== "enabled") {
|
||||
return { success: false, error: context.agents.reason };
|
||||
await auth.require(request);
|
||||
|
||||
if (agents.state !== "enabled") {
|
||||
return { success: false, error: agents.reason };
|
||||
}
|
||||
|
||||
await context.agents.value.triggerSync();
|
||||
const sync = context.agents.value.lastSync();
|
||||
await agents.value.triggerSync();
|
||||
const sync = agents.value.lastSync();
|
||||
return { success: !sync.error, error: sync.error };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { data } from "react-router";
|
||||
|
||||
import { authContext, requestApiContext } from "~/server/context";
|
||||
import { isUserPrincipal } from "~/server/web/auth";
|
||||
import { getOidcSubject } from "~/server/web/headscale-identity";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
@@ -8,10 +9,13 @@ import type { PreAuthKey } from "~/types";
|
||||
import type { Route } from "./+types/overview";
|
||||
|
||||
export async function authKeysAction({ request, context }: Route.ActionArgs) {
|
||||
const { principal, api } = await context.apiForRequest(request);
|
||||
const auth = context.get(authContext);
|
||||
const getRequestApi = context.get(requestApiContext);
|
||||
|
||||
const canGenerateAny = context.auth.can(principal, Capabilities.generate_authkeys);
|
||||
const canGenerateOwn = context.auth.can(principal, Capabilities.generate_own_authkeys);
|
||||
const { principal, api } = await getRequestApi(request);
|
||||
|
||||
const canGenerateAny = auth.can(principal, Capabilities.generate_authkeys);
|
||||
const canGenerateOwn = auth.can(principal, Capabilities.generate_own_authkeys);
|
||||
|
||||
if (!canGenerateAny && !canGenerateOwn) {
|
||||
throw data("You do not have permission to manage pre-auth keys", {
|
||||
|
||||
@@ -6,6 +6,12 @@ import Link from "~/components/link";
|
||||
import Notice from "~/components/notice";
|
||||
import Select from "~/components/select";
|
||||
import TableList from "~/components/table-list";
|
||||
import {
|
||||
appConfigContext,
|
||||
authContext,
|
||||
headscaleLiveStoreContext,
|
||||
requestApiContext,
|
||||
} from "~/server/context";
|
||||
import { usersResource } from "~/server/headscale/live-store";
|
||||
import { isUserPrincipal } from "~/server/web/auth";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
@@ -20,9 +26,14 @@ import AuthKeyRow from "./auth-key-row";
|
||||
import AddAuthKey from "./dialogs/add-auth-key";
|
||||
|
||||
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
const { principal, api } = await context.apiForRequest(request);
|
||||
const auth = context.get(authContext);
|
||||
const config = context.get(appConfigContext);
|
||||
const getRequestApi = context.get(requestApiContext);
|
||||
const headscaleLiveStore = context.get(headscaleLiveStoreContext);
|
||||
|
||||
const usersSnap = await context.hsLive.get(usersResource, api);
|
||||
const { principal, api } = await getRequestApi(request);
|
||||
|
||||
const usersSnap = await headscaleLiveStore.get(usersResource, api);
|
||||
const users = usersSnap.data;
|
||||
|
||||
let keys: { user: User | null; preAuthKeys: PreAuthKey[] }[];
|
||||
@@ -86,8 +97,8 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
.map(({ user, error }) => ({ error, user }));
|
||||
}
|
||||
|
||||
const canGenerateAny = context.auth.can(principal, Capabilities.generate_authkeys);
|
||||
const canGenerateOwn = context.auth.can(principal, Capabilities.generate_own_authkeys);
|
||||
const canGenerateAny = auth.can(principal, Capabilities.generate_authkeys);
|
||||
const canGenerateOwn = auth.can(principal, Capabilities.generate_own_authkeys);
|
||||
|
||||
return {
|
||||
access: canGenerateAny || canGenerateOwn,
|
||||
@@ -96,7 +107,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
keys,
|
||||
missing,
|
||||
selfServiceOnly: !canGenerateAny && canGenerateOwn,
|
||||
url: context.config.headscale.public_url ?? context.config.headscale.url,
|
||||
url: config.headscale.public_url ?? config.headscale.url,
|
||||
users,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@ import { ArrowRight } from "lucide-react";
|
||||
|
||||
import Link from "~/components/link";
|
||||
import PageError from "~/components/page-error";
|
||||
import { headscaleConfigContext, oidcContext } from "~/server/context";
|
||||
|
||||
import type { Route } from "./+types/overview";
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const headscaleConfig = context.get(headscaleConfigContext);
|
||||
const oidc = context.get(oidcContext);
|
||||
|
||||
return {
|
||||
config: context.hs.writable(),
|
||||
isOidcEnabled:
|
||||
context.oidc.state === "enabled" && context.oidc.value.status().state === "ready",
|
||||
config: headscaleConfig.writable(),
|
||||
isOidcEnabled: oidc.state === "enabled" && oidc.value.status().state === "ready",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import { data } from "react-router";
|
||||
|
||||
import {
|
||||
authContext,
|
||||
headscaleConfigContext,
|
||||
headscaleContext,
|
||||
integrationContext,
|
||||
} from "~/server/context";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
|
||||
import type { Route } from "./+types/overview";
|
||||
|
||||
export async function restrictionAction({ request, context }: Route.ActionArgs) {
|
||||
const principal = await context.auth.require(request);
|
||||
const check = context.auth.can(principal, Capabilities.configure_iam);
|
||||
const auth = context.get(authContext);
|
||||
const headscale = context.get(headscaleContext);
|
||||
const headscaleConfig = context.get(headscaleConfigContext);
|
||||
const integration = context.get(integrationContext);
|
||||
|
||||
const principal = await auth.require(request);
|
||||
const check = auth.can(principal, Capabilities.configure_iam);
|
||||
|
||||
if (!check) {
|
||||
throw data("You do not have permission to modify IAM settings.", {
|
||||
@@ -14,7 +25,7 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
if (!context.hs.writable()) {
|
||||
if (!headscaleConfig.writable()) {
|
||||
throw data("The Headscale configuration file is not editable.", {
|
||||
status: 403,
|
||||
});
|
||||
@@ -37,16 +48,16 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
const domains = [...new Set([...(context.hs.c?.oidc?.allowed_domains ?? []), domain])];
|
||||
const domains = [...new Set([...(headscaleConfig.c?.oidc?.allowed_domains ?? []), domain])];
|
||||
|
||||
await context.hs.patch([
|
||||
await headscaleConfig.patch([
|
||||
{
|
||||
path: "oidc.allowed_domains",
|
||||
value: domains,
|
||||
},
|
||||
]);
|
||||
|
||||
context.integration?.onConfigChange(context.headscale);
|
||||
integration?.onConfigChange(headscale);
|
||||
return data("Domain added successfully.");
|
||||
}
|
||||
|
||||
@@ -58,7 +69,7 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
const storedDomains = context.hs.c?.oidc?.allowed_domains ?? [];
|
||||
const storedDomains = headscaleConfig.c?.oidc?.allowed_domains ?? [];
|
||||
if (!storedDomains.includes(domain)) {
|
||||
// Domain not found in the list
|
||||
throw data(`Domain "${domain}" not found in allowed domains.`, {
|
||||
@@ -68,13 +79,13 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
|
||||
// Filter out the domain to remove it from the list
|
||||
const domains = storedDomains.filter((d: string) => d !== domain);
|
||||
await context.hs.patch([
|
||||
await headscaleConfig.patch([
|
||||
{
|
||||
path: "oidc.allowed_domains",
|
||||
value: domains,
|
||||
},
|
||||
]);
|
||||
context.integration?.onConfigChange(context.headscale);
|
||||
integration?.onConfigChange(headscale);
|
||||
return data("Domain removed successfully.");
|
||||
}
|
||||
|
||||
@@ -86,16 +97,16 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
const groups = [...new Set([...(context.hs.c?.oidc?.allowed_groups ?? []), group])];
|
||||
const groups = [...new Set([...(headscaleConfig.c?.oidc?.allowed_groups ?? []), group])];
|
||||
|
||||
await context.hs.patch([
|
||||
await headscaleConfig.patch([
|
||||
{
|
||||
path: "oidc.allowed_groups",
|
||||
value: groups,
|
||||
},
|
||||
]);
|
||||
|
||||
context.integration?.onConfigChange(context.headscale);
|
||||
integration?.onConfigChange(headscale);
|
||||
return data("Group added successfully.");
|
||||
}
|
||||
|
||||
@@ -107,7 +118,7 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
const storedGroups = context.hs.c?.oidc?.allowed_groups ?? [];
|
||||
const storedGroups = headscaleConfig.c?.oidc?.allowed_groups ?? [];
|
||||
if (!storedGroups.includes(group)) {
|
||||
// Group not found in the list
|
||||
throw data(`Group "${group}" not found in allowed groups.`, {
|
||||
@@ -117,14 +128,14 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
|
||||
// Filter out the group to remove it from the list
|
||||
const groups = storedGroups.filter((d: string) => d !== group);
|
||||
await context.hs.patch([
|
||||
await headscaleConfig.patch([
|
||||
{
|
||||
path: "oidc.allowed_groups",
|
||||
value: groups,
|
||||
},
|
||||
]);
|
||||
|
||||
context.integration?.onConfigChange(context.headscale);
|
||||
integration?.onConfigChange(headscale);
|
||||
return data("Group removed successfully.");
|
||||
}
|
||||
|
||||
@@ -136,16 +147,16 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
const users = [...new Set([...(context.hs.c?.oidc?.allowed_users ?? []), user])];
|
||||
const users = [...new Set([...(headscaleConfig.c?.oidc?.allowed_users ?? []), user])];
|
||||
|
||||
await context.hs.patch([
|
||||
await headscaleConfig.patch([
|
||||
{
|
||||
path: "oidc.allowed_users",
|
||||
value: users,
|
||||
},
|
||||
]);
|
||||
|
||||
context.integration?.onConfigChange(context.headscale);
|
||||
integration?.onConfigChange(headscale);
|
||||
return data("User added successfully.");
|
||||
}
|
||||
|
||||
@@ -157,7 +168,7 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
});
|
||||
}
|
||||
|
||||
const storedUsers = context.hs.c?.oidc?.allowed_users ?? [];
|
||||
const storedUsers = headscaleConfig.c?.oidc?.allowed_users ?? [];
|
||||
if (!storedUsers.includes(user)) {
|
||||
// User not found in the list
|
||||
throw data(`User "${user}" not found in allowed users.`, {
|
||||
@@ -167,14 +178,14 @@ export async function restrictionAction({ request, context }: Route.ActionArgs)
|
||||
|
||||
// Filter out the user to remove it from the list
|
||||
const users = storedUsers.filter((d: string) => d !== user);
|
||||
await context.hs.patch([
|
||||
await headscaleConfig.patch([
|
||||
{
|
||||
path: "oidc.allowed_users",
|
||||
value: users,
|
||||
},
|
||||
]);
|
||||
|
||||
context.integration?.onConfigChange(context.headscale);
|
||||
integration?.onConfigChange(headscale);
|
||||
return data("User removed successfully.");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { data } from "react-router";
|
||||
|
||||
import Link from "~/components/link";
|
||||
import Notice from "~/components/notice";
|
||||
import { authContext, headscaleConfigContext } from "~/server/context";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
|
||||
import type { Route } from "./+types/overview";
|
||||
@@ -12,28 +13,31 @@ import AddUser from "./dialogs/add-user";
|
||||
import RestrictionTable from "./table";
|
||||
|
||||
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
const principal = await context.auth.require(request);
|
||||
const check = context.auth.can(principal, Capabilities.read_users);
|
||||
const auth = context.get(authContext);
|
||||
const headscaleConfig = context.get(headscaleConfigContext);
|
||||
|
||||
const principal = await auth.require(request);
|
||||
const check = auth.can(principal, Capabilities.read_users);
|
||||
if (!check) {
|
||||
throw data("You do not have permission to view IAM settings.", {
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (!context.hs.c?.oidc) {
|
||||
if (!headscaleConfig.c?.oidc) {
|
||||
throw data("OIDC is not configured on this Headscale instance.", {
|
||||
status: 501,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
access: context.auth.can(principal, Capabilities.configure_iam),
|
||||
access: auth.can(principal, Capabilities.configure_iam),
|
||||
settings: {
|
||||
domains: [...new Set(context.hs.c.oidc.allowed_domains)],
|
||||
groups: [...new Set(context.hs.c.oidc.allowed_groups)],
|
||||
users: [...new Set(context.hs.c.oidc.allowed_users)],
|
||||
domains: [...new Set(headscaleConfig.c.oidc.allowed_domains)],
|
||||
groups: [...new Set(headscaleConfig.c.oidc.allowed_groups)],
|
||||
users: [...new Set(headscaleConfig.c.oidc.allowed_users)],
|
||||
},
|
||||
writable: context.hs.writable(),
|
||||
writable: headscaleConfig.writable(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user