import { data } from "react-router"; import { usersResource } from "~/server/headscale/live-store"; import { getOidcSubject } from "~/server/web/headscale-identity"; import { Capabilities } from "~/server/web/roles"; 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); if (!check) { throw data("You do not have permission to update users", { status: 403, }); } const formData = await request.formData(); const action = formData.get("action_id")?.toString(); if (!action) { throw data("Missing `action_id` in the form data.", { status: 404, }); } const { api } = await context.apiForRequest(request); switch (action) { case "create_user": { const name = formData.get("username")?.toString(); const displayName = formData.get("display_name")?.toString(); const email = formData.get("email")?.toString(); if (!name) { throw data("Missing `username` in the form data.", { status: 400, }); } await api.createUser(name, email, displayName); await context.hsLive.refresh(usersResource, api); return { message: "User created successfully" }; } case "delete_user": { const userId = formData.get("user_id")?.toString(); if (!userId) { throw data("Missing `user_id` in the form data.", { status: 400, }); } await api.deleteUser(userId); await context.hsLive.refresh(usersResource, api); return { message: "User deleted successfully" }; } case "rename_user": { const userId = formData.get("user_id")?.toString(); const newName = formData.get("new_name")?.toString(); if (!userId || !newName) { return data({ success: false }, 400); } const users = await api.getUsers(userId); const user = users.find((user) => user.id === userId); if (!user) { throw data(`No user found with id: ${userId}`, { status: 400 }); } if (user.provider === "oidc") { // OIDC users cannot be renamed via this endpoint, return an error throw data("Users managed by OIDC cannot be renamed", { status: 403, }); } await api.renameUser(userId, newName); await context.hsLive.refresh(usersResource, api); return { message: "User renamed successfully" }; } case "reassign_user": { const userId = formData.get("user_id")?.toString(); const newRole = formData.get("new_role")?.toString(); if (!userId || !newRole) { throw data("Missing `user_id` or `new_role` in the form data.", { status: 400, }); } const users = await api.getUsers(userId); const user = users.find((user) => user.id === userId); if (!user) { throw data("Specified user not found", { status: 400, }); } const subject = getOidcSubject(user); if (!subject) { throw data("Specified user is not an OIDC user or has no subject.", { status: 400 }); } const result = await context.auth.reassignSubject(subject, newRole as Role); if (!result) { throw data("Failed to reassign user role.", { status: 500 }); } return { message: "User reassigned successfully" }; } case "transfer_ownership": { if (principal.kind !== "oidc" || principal.user.role !== "owner") { throw data("Only the owner can transfer ownership.", { status: 403 }); } const userId = formData.get("user_id")?.toString(); if (!userId) { throw data("Missing `user_id` in the form data.", { status: 400 }); } const users = await api.getUsers(userId); const user = users.find((user) => user.id === userId); if (!user) { throw data("Specified user not found", { status: 400 }); } const targetSubject = getOidcSubject(user); if (!targetSubject) { throw data("Target user is not an OIDC user or has no subject.", { status: 400 }); } const result = await context.auth.transferOwnership(principal.user.subject, targetSubject); if (!result) { throw data("Failed to transfer ownership.", { status: 500 }); } return { message: "Ownership transferred successfully" }; } case "link_user": { const userId = formData.get("user_id")?.toString(); const headscaleUserId = formData.get("headscale_user_id")?.toString(); if (!userId || !headscaleUserId) { throw data("Missing `user_id` or `headscale_user_id` in the form data.", { status: 400, }); } const users = await api.getUsers(userId); const user = users.find((user) => user.id === userId); if (!user) { throw data("Specified user not found", { status: 400 }); } const subject = getOidcSubject(user); if (!subject) { throw data("Specified user is not an OIDC user or has no subject.", { status: 400 }); } const linked = await context.auth.linkHeadscaleUserBySubject(subject, headscaleUserId); if (!linked) { throw data("That Headscale user is already linked to another account.", { status: 409 }); } return { message: "Headscale user linked successfully" }; } default: throw data("Invalid `action_id` provided.", { status: 400, }); } }