mirror of
https://github.com/tale/headplane.git
synced 2026-08-20 09:52:19 +00:00
feat(auth): support reverse-proxy driven proxy auth
Closes HP-353.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { data } from "react-router";
|
||||
|
||||
import { isUserPrincipal } from "~/server/web/auth";
|
||||
import { getOidcSubject } from "~/server/web/headscale-identity";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
import type { PreAuthKey } from "~/types";
|
||||
@@ -25,7 +26,10 @@ export async function authKeysAction({ request, context }: Route.ActionArgs) {
|
||||
throw data("User not found.", { status: 404 });
|
||||
}
|
||||
const targetSubject = getOidcSubject(targetUser);
|
||||
if (principal.kind !== "oidc" || targetSubject !== principal.user.subject) {
|
||||
const ownsTarget =
|
||||
isUserPrincipal(principal) &&
|
||||
(principal.user.headscaleUserId === userId || targetSubject === principal.user.subject);
|
||||
if (!ownsTarget) {
|
||||
throw data("You do not have permission to manage this user's pre-auth keys", {
|
||||
status: 403,
|
||||
});
|
||||
|
||||
@@ -18,10 +18,22 @@ interface AddAuthKeyProps {
|
||||
users: User[];
|
||||
url: string;
|
||||
selfServiceOnly: boolean;
|
||||
currentHeadscaleUserId?: string;
|
||||
currentSubject?: string;
|
||||
}
|
||||
|
||||
function findCurrentUser(users: User[], subject: string | undefined): User | undefined {
|
||||
function findCurrentUser(
|
||||
users: User[],
|
||||
headscaleUserId: string | undefined,
|
||||
subject: string | undefined,
|
||||
): User | undefined {
|
||||
if (headscaleUserId) {
|
||||
const linked = users.find((u) => u.id === headscaleUserId);
|
||||
if (linked) {
|
||||
return linked;
|
||||
}
|
||||
}
|
||||
|
||||
if (!subject) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -38,6 +50,7 @@ export default function AddAuthKey({
|
||||
users,
|
||||
url,
|
||||
selfServiceOnly,
|
||||
currentHeadscaleUserId,
|
||||
currentSubject,
|
||||
}: AddAuthKeyProps) {
|
||||
const fetcher = useFetcher();
|
||||
@@ -46,7 +59,9 @@ export default function AddAuthKey({
|
||||
const [reusable, setReusable] = useState(false);
|
||||
const [ephemeral, setEphemeral] = useState(false);
|
||||
const [tagOnly, setTagOnly] = useState(false);
|
||||
const currentUser = selfServiceOnly ? findCurrentUser(users, currentSubject) : null;
|
||||
const currentUser = selfServiceOnly
|
||||
? findCurrentUser(users, currentHeadscaleUserId, currentSubject)
|
||||
: null;
|
||||
const availableUsers = selfServiceOnly && currentUser ? [currentUser] : users;
|
||||
const [userId, setUserId] = useState<string | null>(availableUsers[0]?.id);
|
||||
const [tags, setTags] = useState("");
|
||||
|
||||
@@ -7,6 +7,7 @@ import Notice from "~/components/notice";
|
||||
import Select from "~/components/select";
|
||||
import TableList from "~/components/table-list";
|
||||
import { usersResource } from "~/server/headscale/live-store";
|
||||
import { isUserPrincipal } from "~/server/web/auth";
|
||||
import { Capabilities } from "~/server/web/roles";
|
||||
import type { PreAuthKey } from "~/types";
|
||||
import type { User } from "~/types/User";
|
||||
@@ -90,7 +91,8 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
|
||||
return {
|
||||
access: canGenerateAny || canGenerateOwn,
|
||||
currentSubject: principal.kind === "oidc" ? principal.user.subject : undefined,
|
||||
currentHeadscaleUserId: isUserPrincipal(principal) ? principal.user.headscaleUserId : undefined,
|
||||
currentSubject: isUserPrincipal(principal) ? principal.user.subject : undefined,
|
||||
keys,
|
||||
missing,
|
||||
selfServiceOnly: !canGenerateAny && canGenerateOwn,
|
||||
@@ -103,7 +105,16 @@ export const action = authKeysAction;
|
||||
|
||||
type Status = "all" | "active" | "expired" | "reusable" | "ephemeral";
|
||||
export default function Page({
|
||||
loaderData: { keys, missing, users, url, access, selfServiceOnly, currentSubject },
|
||||
loaderData: {
|
||||
keys,
|
||||
missing,
|
||||
users,
|
||||
url,
|
||||
access,
|
||||
selfServiceOnly,
|
||||
currentHeadscaleUserId,
|
||||
currentSubject,
|
||||
},
|
||||
}: Route.ComponentProps) {
|
||||
const [selectedUser, setSelectedUser] = useState("__headplane_all");
|
||||
const [status, setStatus] = useState<Status>("active");
|
||||
@@ -199,6 +210,7 @@ export default function Page({
|
||||
</Link>
|
||||
</p>
|
||||
<AddAuthKey
|
||||
currentHeadscaleUserId={currentHeadscaleUserId}
|
||||
currentSubject={currentSubject}
|
||||
selfServiceOnly={selfServiceOnly}
|
||||
url={url}
|
||||
|
||||
Reference in New Issue
Block a user