mirror of
https://github.com/tale/headplane.git
synced 2026-08-27 15:37:04 +00:00
feat: update to the v8 middleware api
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import PageError from "~/components/page-error";
|
||||
import {
|
||||
appConfigContext,
|
||||
authContext,
|
||||
headscaleConfigContext,
|
||||
headscaleLiveStoreContext,
|
||||
requestApiContext,
|
||||
} from "~/server/context";
|
||||
import { nodesResource, usersResource } from "~/server/headscale/live-store";
|
||||
import { isUserPrincipal } from "~/server/web/auth";
|
||||
import { Capabilities, Roles } from "~/server/web/roles";
|
||||
@@ -36,18 +43,24 @@ export interface UnlinkedHeadscaleUser extends User {
|
||||
}
|
||||
|
||||
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
const principal = await context.auth.require(request);
|
||||
const check = await context.auth.can(principal, Capabilities.read_users);
|
||||
const auth = context.get(authContext);
|
||||
const config = context.get(appConfigContext);
|
||||
const getRequestApi = context.get(requestApiContext);
|
||||
const headscaleConfig = context.get(headscaleConfigContext);
|
||||
const headscaleLiveStore = context.get(headscaleLiveStoreContext);
|
||||
|
||||
const principal = await auth.require(request);
|
||||
const check = await auth.can(principal, Capabilities.read_users);
|
||||
if (!check) {
|
||||
throw new Error(
|
||||
"You do not have permission to view this page. Please contact your administrator.",
|
||||
);
|
||||
}
|
||||
|
||||
const writablePermission = await context.auth.can(principal, Capabilities.write_users);
|
||||
const writablePermission = await auth.can(principal, Capabilities.write_users);
|
||||
|
||||
// Primary data: Headplane users from the database (always available)
|
||||
const hpUsers = await context.auth.listUsers();
|
||||
const hpUsers = await auth.listUsers();
|
||||
|
||||
// Secondary data: Headscale API (may fail)
|
||||
let apiUsers: User[] = [];
|
||||
@@ -55,10 +68,10 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
let apiError: string | undefined;
|
||||
|
||||
try {
|
||||
const { api } = await context.apiForRequest(request);
|
||||
const { api } = await getRequestApi(request);
|
||||
const [nodesSnap, usersSnap] = await Promise.all([
|
||||
context.hsLive.get(nodesResource, api),
|
||||
context.hsLive.get(usersResource, api),
|
||||
headscaleLiveStore.get(nodesResource, api),
|
||||
headscaleLiveStore.get(usersResource, api),
|
||||
]);
|
||||
nodes = nodesSnap.data;
|
||||
apiUsers = usersSnap.data;
|
||||
@@ -68,7 +81,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
"Could not connect to the Headscale API. Headscale user data and machine information are unavailable.";
|
||||
}
|
||||
|
||||
const useGravatar = context.config.oidc?.profile_picture_source === "gravatar";
|
||||
const useGravatar = config.oidc?.profile_picture_source === "gravatar";
|
||||
|
||||
function resolveProfilePic(email?: string, profilePicUrl?: string): string | undefined {
|
||||
if (!useGravatar) return profilePicUrl;
|
||||
@@ -126,9 +139,9 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
}));
|
||||
|
||||
let magic: string | undefined;
|
||||
if (context.hs.readable()) {
|
||||
if (context.hs.c?.dns.magic_dns) {
|
||||
magic = context.hs.c.dns.base_domain;
|
||||
if (headscaleConfig.readable()) {
|
||||
if (headscaleConfig.c?.dns.magic_dns) {
|
||||
magic = headscaleConfig.c.dns.base_domain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +151,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
writable: writablePermission,
|
||||
currentUserId: isUserPrincipal(principal) ? principal.user.id : undefined,
|
||||
isOwner,
|
||||
oidc: context.config.oidc ? { issuer: context.config.oidc.issuer } : undefined,
|
||||
oidc: config.oidc ? { issuer: config.oidc.issuer } : undefined,
|
||||
magic,
|
||||
apiError,
|
||||
headplaneUsers,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { data } from "react-router";
|
||||
|
||||
import { 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";
|
||||
@@ -8,8 +9,12 @@ import type { Role } from "~/server/web/roles";
|
||||
import type { Route } from "./+types/overview";
|
||||
|
||||
export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
const principal = await context.auth.require(request);
|
||||
const check = await context.auth.can(principal, Capabilities.write_users);
|
||||
const auth = context.get(authContext);
|
||||
const getRequestApi = context.get(requestApiContext);
|
||||
const headscaleLiveStore = context.get(headscaleLiveStoreContext);
|
||||
|
||||
const principal = await auth.require(request);
|
||||
const check = await auth.can(principal, Capabilities.write_users);
|
||||
if (!check) {
|
||||
throw data("You do not have permission to update users", {
|
||||
status: 403,
|
||||
@@ -24,7 +29,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
const { api } = await context.apiForRequest(request);
|
||||
const { api } = await getRequestApi(request);
|
||||
switch (action) {
|
||||
case "create_user": {
|
||||
const name = formData.get("username")?.toString();
|
||||
@@ -38,7 +43,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
}
|
||||
|
||||
await api.users.create({ name, email, displayName });
|
||||
await context.hsLive.refresh(usersResource, api);
|
||||
await headscaleLiveStore.refresh(usersResource, api);
|
||||
return { message: "User created successfully" };
|
||||
}
|
||||
case "delete_user": {
|
||||
@@ -50,7 +55,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
}
|
||||
|
||||
await api.users.delete(headscaleUserId);
|
||||
await context.hsLive.refresh(usersResource, api);
|
||||
await headscaleLiveStore.refresh(usersResource, api);
|
||||
return { message: "User deleted successfully" };
|
||||
}
|
||||
case "rename_user": {
|
||||
@@ -74,7 +79,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
}
|
||||
|
||||
await api.users.rename(headscaleUserId, newName);
|
||||
await context.hsLive.refresh(usersResource, api);
|
||||
await headscaleLiveStore.refresh(usersResource, api);
|
||||
return { message: "User renamed successfully" };
|
||||
}
|
||||
case "reassign_user": {
|
||||
@@ -86,7 +91,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
const result = await context.auth.reassignUser(headplaneUserId, newRole as Role);
|
||||
const result = await auth.reassignUser(headplaneUserId, newRole as Role);
|
||||
if (!result) {
|
||||
throw data("Failed to reassign user role.", { status: 500 });
|
||||
}
|
||||
@@ -103,7 +108,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
throw data("Missing `headplane_user_id` in the form data.", { status: 400 });
|
||||
}
|
||||
|
||||
const result = await context.auth.transferOwnership(principal.user.id, headplaneUserId);
|
||||
const result = await auth.transferOwnership(principal.user.id, headplaneUserId);
|
||||
if (!result) {
|
||||
throw data("Failed to transfer ownership.", { status: 500 });
|
||||
}
|
||||
@@ -119,7 +124,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
const linked = await context.auth.linkHeadscaleUser(headplaneUserId, headscaleUserId);
|
||||
const linked = await auth.linkHeadscaleUser(headplaneUserId, headscaleUserId);
|
||||
if (!linked) {
|
||||
throw data("That Headscale user is already linked to another account.", { status: 409 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user