fix: use headscale.api_key where possible

This commit is contained in:
Aarnav Tale
2026-04-03 16:34:50 -04:00
parent 73b5d5514e
commit 4cd0c1e206
17 changed files with 23 additions and 32 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ export async function loader({ request, context, ...rest }: Route.LoaderArgs) {
try { try {
const principal = await context.auth.require(request); const principal = await context.auth.require(request);
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
const user = const user =
+1 -1
View File
@@ -26,7 +26,7 @@ export async function aclAction({ request, context }: Route.ActionArgs) {
}); });
} }
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
try { try {
const { policy, updatedAt } = await api.setPolicy(policyData); const { policy, updatedAt } = await api.setPolicy(policyData);
+1 -1
View File
@@ -29,7 +29,7 @@ export async function aclLoader({ request, context }: Route.LoaderArgs) {
}; };
// Try to load the ACL policy from the API. // Try to load the ACL policy from the API.
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
try { try {
const { policy, updatedAt } = await api.getPolicy(); const { policy, updatedAt } = await api.getPolicy();
+1 -1
View File
@@ -107,7 +107,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
}); });
try { try {
const hsApi = context.hsApi.getRuntimeClient(context.oidc!.apiKey); const hsApi = context.hsApi.getRuntimeClient(context.headscaleApiKey!);
const hsUsers = await hsApi.getUsers(); const hsUsers = await hsApi.getUsers();
const hsUser = findHeadscaleUserBySubject(hsUsers, claims.sub, userInfo.email); const hsUser = findHeadscaleUserBySubject(hsUsers, claims.sub, userInfo.email);
if (hsUser) { if (hsUser) {
+2 -2
View File
@@ -28,7 +28,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
principal.kind === "oidc" && principal.kind === "oidc" &&
!principal.user.headscaleUserId !principal.user.headscaleUserId
) { ) {
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
let headscaleUsers: { id: string; name: string }[] = []; let headscaleUsers: { id: string; name: string }[] = [];
@@ -64,7 +64,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
} }
// No UI access — show the download/connect page // No UI access — show the download/connect page
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
let linkedUserName: string | undefined; let linkedUserName: string | undefined;
+1 -3
View File
@@ -10,9 +10,7 @@ export async function machineAction({ request, context }: Route.ActionArgs) {
const principal = await context.auth.require(request); const principal = await context.auth.require(request);
const formData = await request.formData(); const formData = await request.formData();
const api = context.hsApi.getRuntimeClient( const api = context.hsApi.getRuntimeClient(context.auth.getHeadscaleApiKey(principal));
context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey),
);
const action = formData.get("action_id")?.toString(); const action = formData.get("action_id")?.toString();
if (!action) { if (!action) {
+1 -3
View File
@@ -38,9 +38,7 @@ export async function loader({ request, params, context }: Route.LoaderArgs) {
} }
} }
const api = context.hsApi.getRuntimeClient( const api = context.hsApi.getRuntimeClient(context.auth.getHeadscaleApiKey(principal));
context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey),
);
const [nodesSnap, usersSnap] = await Promise.all([ const [nodesSnap, usersSnap] = await Promise.all([
context.hsLive.get(nodesResource, api), context.hsLive.get(nodesResource, api),
context.hsLive.get(usersResource, api), context.hsLive.get(usersResource, api),
+1 -3
View File
@@ -30,9 +30,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
const writablePermission = context.auth.can(principal, Capabilities.write_machines); const writablePermission = context.auth.can(principal, Capabilities.write_machines);
const api = context.hsApi.getRuntimeClient( const api = context.hsApi.getRuntimeClient(context.auth.getHeadscaleApiKey(principal));
context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey),
);
const [nodesSnap, usersSnap] = await Promise.all([ const [nodesSnap, usersSnap] = await Promise.all([
context.hsLive.get(nodesResource, api), context.hsLive.get(nodesResource, api),
context.hsLive.get(usersResource, api), context.hsLive.get(usersResource, api),
+1 -1
View File
@@ -7,7 +7,7 @@ import type { Route } from "./+types/overview";
export async function authKeysAction({ request, context }: Route.ActionArgs) { export async function authKeysAction({ request, context }: Route.ActionArgs) {
const principal = await context.auth.require(request); const principal = await context.auth.require(request);
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
const canGenerateAny = context.auth.can(principal, Capabilities.generate_authkeys); const canGenerateAny = context.auth.can(principal, Capabilities.generate_authkeys);
+1 -1
View File
@@ -20,7 +20,7 @@ import AddAuthKey from "./dialogs/add-auth-key";
export async function loader({ request, context }: Route.LoaderArgs) { export async function loader({ request, context }: Route.LoaderArgs) {
const principal = await context.auth.require(request); const principal = await context.auth.require(request);
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
const usersSnap = await context.hsLive.get(usersResource, api); const usersSnap = await context.hsLive.get(usersResource, api);
+1 -1
View File
@@ -41,7 +41,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
throw data("Only OAuth users are allowed to use WebSSH", 403); throw data("Only OAuth users are allowed to use WebSSH", 403);
} }
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
const users = await api.getUsers(); const users = await api.getUsers();
+1 -1
View File
@@ -54,7 +54,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
let apiError: string | undefined; let apiError: string | undefined;
try { try {
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
const [nodesSnap, usersSnap] = await Promise.all([ const [nodesSnap, usersSnap] = await Promise.all([
context.hsLive.get(nodesResource, api), context.hsLive.get(nodesResource, api),
+1 -1
View File
@@ -24,7 +24,7 @@ export async function userAction({ request, context }: Route.ActionArgs) {
}); });
} }
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
switch (action) { switch (action) {
case "create_user": { case "create_user": {
+1 -1
View File
@@ -5,7 +5,7 @@ import type { Route } from "./+types/live";
export async function loader({ request, context }: Route.LoaderArgs) { export async function loader({ request, context }: Route.LoaderArgs) {
const principal = await context.auth.require(request); const principal = await context.auth.require(request);
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
// Ensure resources are loaded before streaming // Ensure resources are loaded before streaming
+1 -1
View File
@@ -18,7 +18,7 @@ export async function pruneEphemeralNodes({ context, request }: Route.LoaderArgs
return; return;
} }
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); const apiKey = context.auth.getHeadscaleApiKey(principal);
const api = context.hsApi.getRuntimeClient(apiKey); const api = context.hsApi.getRuntimeClient(apiKey);
const nodes = await api.getNodes(); const nodes = await api.getNodes();
const toPrune = nodes.filter((node) => { const toPrune = nodes.filter((node) => {
+2 -1
View File
@@ -84,6 +84,7 @@ const appLoadContext = {
auth: createAuthService({ auth: createAuthService({
secret: config.server.cookie_secret, secret: config.server.cookie_secret,
headscaleApiKey,
db, db,
cookie: { cookie: {
name: "_hp_auth", name: "_hp_auth",
@@ -93,13 +94,13 @@ const appLoadContext = {
}, },
}), }),
headscaleApiKey,
hsApi, hsApi,
agents, agents,
integration: await loadIntegration(config.integration), integration: await loadIntegration(config.integration),
oidc: oidc:
config.oidc && config.oidc.enabled !== false && headscaleApiKey config.oidc && config.oidc.enabled !== false && headscaleApiKey
? { ? {
apiKey: headscaleApiKey,
connector: createLazyOidcConnector( connector: createLazyOidcConnector(
config.server.base_url, config.server.base_url,
config.oidc, config.oidc,
+5 -9
View File
@@ -58,6 +58,7 @@ interface CookiePayload {
export interface AuthServiceOptions { export interface AuthServiceOptions {
secret: string; secret: string;
headscaleApiKey?: string;
db: NodeSQLiteDatabase; db: NodeSQLiteDatabase;
cookie: { cookie: {
name: string; name: string;
@@ -230,21 +231,16 @@ export class AuthService {
return this.encodeCookie({ sid, api_key: apiKey }, Math.floor(maxAge / 1000)); return this.encodeCookie({ sid, api_key: apiKey }, Math.floor(maxAge / 1000));
} }
/** getHeadscaleApiKey(principal: Principal): string {
* Get the Headscale API key for making API calls.
* OIDC sessions use the configured oidc.headscale_api_key.
* API key sessions use the user-provided key stored in the cookie.
*/
getHeadscaleApiKey(principal: Principal, oidcApiKey?: string): string {
if (principal.kind === "api_key") { if (principal.kind === "api_key") {
return principal.apiKey; return principal.apiKey;
} }
if (!oidcApiKey) { if (!this.opts.headscaleApiKey) {
throw new Error("OIDC sessions require oidc.headscale_api_key"); throw new Error("OIDC sessions require headscale.api_key to be configured");
} }
return oidcApiKey; return this.opts.headscaleApiKey;
} }
/** /**