feat: switch to config file system
This commit is contained in:
parent
5be3cb345e
commit
76d263b7e6
28 changed files with 1071 additions and 883 deletions
|
|
@ -7,12 +7,11 @@ import Link from '~/components/Link';
|
|||
import Notice from '~/components/Notice';
|
||||
import Spinner from '~/components/Spinner';
|
||||
import Tabs from '~/components/Tabs';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { loadConfig } from '~/utils/config/headscale';
|
||||
import { HeadscaleError, pull, put } from '~/utils/headscale';
|
||||
import log from '~/utils/log';
|
||||
import { send } from '~/utils/res';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { hs_getConfig } from '~/utils/state';
|
||||
import toast from '~/utils/toast';
|
||||
import { Differ, Editor } from './components/cm.client';
|
||||
import { ErrorView } from './components/error';
|
||||
|
|
@ -45,10 +44,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
// We can do damage control by checking for write access and if we are not
|
||||
// able to PUT an ACL policy on the v1/policy route, we can already know
|
||||
// that the policy is at the very-least readonly or not available.
|
||||
const context = await loadContext();
|
||||
const { mode, config } = hs_getConfig();
|
||||
let modeGuess = 'database'; // Assume database mode
|
||||
if (context.config.read) {
|
||||
const config = await loadConfig();
|
||||
if (mode !== 'no') {
|
||||
modeGuess = config.policy?.mode ?? 'database';
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +185,7 @@ export default function Page() {
|
|||
<div>
|
||||
{data.read && !data.write ? (
|
||||
<div className="mb-4">
|
||||
<Notice className="w-fit">
|
||||
<Notice>
|
||||
The ACL policy is read-only. You can view the current policy but you
|
||||
cannot make changes to it.
|
||||
<br />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useMemo } from 'react';
|
||||
import {
|
||||
type ActionFunctionArgs,
|
||||
type LoaderFunctionArgs,
|
||||
|
|
@ -10,9 +9,9 @@ import Card from '~/components/Card';
|
|||
import Code from '~/components/Code';
|
||||
import Input from '~/components/Input';
|
||||
import type { Key } from '~/types';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
|
|
@ -24,16 +23,16 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
});
|
||||
}
|
||||
|
||||
const context = await loadContext();
|
||||
const context = hp_getConfig();
|
||||
|
||||
// Only set if OIDC is properly enabled anyways
|
||||
if (context.oidc?.disableKeyLogin) {
|
||||
if (context.oidc?.disable_api_key_login) {
|
||||
return redirect('/oidc/start');
|
||||
}
|
||||
|
||||
return {
|
||||
oidc: context.oidc?.issuer,
|
||||
apiKey: !context.oidc?.disableKeyLogin,
|
||||
apiKey: !context.oidc?.disable_api_key_login,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +42,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||
const session = await getSession(request.headers.get('Cookie'));
|
||||
|
||||
if (oidcStart) {
|
||||
const context = await loadContext();
|
||||
const context = hp_getConfig();
|
||||
|
||||
if (!context.oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided');
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { type LoaderFunctionArgs, redirect } from 'react-router';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { getSession, commitSession } from '~/utils/sessions.server';
|
||||
import { finishAuthFlow, getRedirectUri, formatError } from '~/utils/oidc';
|
||||
import { finishAuthFlow, formatError, getRedirectUri } from '~/utils/oidc';
|
||||
import { send } from '~/utils/res';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// Check if we have 0 query parameters
|
||||
|
|
@ -13,30 +13,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
if (session.has('hsApiKey')) {
|
||||
return redirect('/machines')
|
||||
return redirect('/machines');
|
||||
}
|
||||
|
||||
// This is a hold-over from the old code
|
||||
// TODO: Rewrite checkOIDC in the context loader
|
||||
const { oidc } = await loadContext();
|
||||
const { oidc } = hp_getConfig();
|
||||
if (!oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided');
|
||||
}
|
||||
|
||||
const oidcConfig = {
|
||||
issuer: oidc.issuer,
|
||||
clientId: oidc.client,
|
||||
clientSecret: oidc.secret,
|
||||
redirectUri: oidc.redirectUri,
|
||||
tokenEndpointAuthMethod: oidc.method,
|
||||
}
|
||||
|
||||
const codeVerifier = session.get('oidc_code_verif');
|
||||
const state = session.get('oidc_state');
|
||||
const nonce = session.get('oidc_nonce');
|
||||
const redirectUri = session.get('oidc_redirect_uri');
|
||||
|
||||
if (!codeVerifier || !state || !nonce) {
|
||||
if (!codeVerifier || !state || !nonce || !redirectUri) {
|
||||
return send({ error: 'Missing OIDC state' }, { status: 400 });
|
||||
}
|
||||
|
||||
|
|
@ -50,10 +40,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
codeVerifier,
|
||||
state,
|
||||
nonce: nonce === '<none>' ? undefined : nonce,
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const user = await finishAuthFlow(oidcConfig, flowOptions);
|
||||
const user = await finishAuthFlow(oidc, flowOptions);
|
||||
session.set('user', user);
|
||||
session.unset('oidc_code_verif');
|
||||
session.unset('oidc_state');
|
||||
|
|
@ -63,21 +53,18 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
// keys because they are currently non-deletable in the headscale
|
||||
// database. Look at this in the future once we have a solution
|
||||
// or we have permissioned API keys.
|
||||
session.set('hsApiKey', oidc.rootKey);
|
||||
session.set('hsApiKey', oidc.headscale_api_key);
|
||||
return redirect('/machines', {
|
||||
headers: {
|
||||
'Set-Cookie': await commitSession(session),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(
|
||||
JSON.stringify(formatError(error)),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
return new Response(JSON.stringify(formatError(error)), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,27 @@
|
|||
import { type LoaderFunctionArgs, data, redirect } from 'react-router';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { send } from '~/utils/res';
|
||||
import { type LoaderFunctionArgs, redirect } from 'react-router';
|
||||
import { beginAuthFlow, getRedirectUri } from '~/utils/oidc';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
if (session.has('hsApiKey')) {
|
||||
return redirect('/machines')
|
||||
return redirect('/machines');
|
||||
}
|
||||
|
||||
// This is a hold-over from the old code
|
||||
// TODO: Rewrite checkOIDC in the context loader
|
||||
const { oidc } = await loadContext();
|
||||
const { oidc } = hp_getConfig();
|
||||
if (!oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided');
|
||||
}
|
||||
|
||||
const oidcConfig = {
|
||||
issuer: oidc.issuer,
|
||||
clientId: oidc.client,
|
||||
clientSecret: oidc.secret,
|
||||
redirectUri: oidc.redirectUri,
|
||||
tokenEndpointAuthMethod: oidc.method,
|
||||
}
|
||||
|
||||
const redirectUri = oidcConfig.redirectUri ?? getRedirectUri(request);
|
||||
const data = await beginAuthFlow(oidcConfig, redirectUri);
|
||||
const redirectUri = oidc.redirect_uri ?? getRedirectUri(request);
|
||||
const data = await beginAuthFlow(oidc, redirectUri);
|
||||
session.set('oidc_code_verif', data.codeVerifier);
|
||||
session.set('oidc_state', data.state);
|
||||
session.set('oidc_nonce', data.nonce);
|
||||
session.set('oidc_redirect_uri', redirectUri)
|
||||
session.set('oidc_redirect_uri', redirectUri);
|
||||
|
||||
return redirect(data.url, {
|
||||
status: 302,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import StatusCircle from '~/components/StatusCircle';
|
|||
import Tooltip from '~/components/Tooltip';
|
||||
import type { Machine, Route, User } from '~/types';
|
||||
import cn from '~/utils/cn';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { loadConfig } from '~/utils/config/headscale';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { hs_getConfig } from '~/utils/state';
|
||||
import { menuAction } from './action';
|
||||
import MenuOptions from './components/menu';
|
||||
import Routes from './dialogs/routes';
|
||||
|
|
@ -25,11 +24,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
|
|||
throw new Error('No machine ID provided');
|
||||
}
|
||||
|
||||
const context = await loadContext();
|
||||
const { mode, config } = hs_getConfig();
|
||||
let magic: string | undefined;
|
||||
|
||||
if (context.config.read) {
|
||||
const config = await loadConfig();
|
||||
if (mode !== 'no') {
|
||||
if (config.dns.magic_dns) {
|
||||
magic = config.dns.base_domain;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ import { ErrorPopup } from '~/components/Error';
|
|||
import Link from '~/components/Link';
|
||||
import type { Machine, Route, User } from '~/types';
|
||||
import cn from '~/utils/cn';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { loadConfig } from '~/utils/config/headscale';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { initAgentSocket, queryAgent } from '~/utils/ws-agent';
|
||||
|
||||
import Tooltip from '~/components/Tooltip';
|
||||
import { hp_getConfig, hs_getConfig } from '~/utils/state';
|
||||
import { menuAction } from './action';
|
||||
import MachineRow from './components/machine';
|
||||
import NewMachine from './dialogs/new';
|
||||
|
|
@ -29,11 +28,12 @@ export async function loader({ request, context: lC }: LoaderFunctionArgs) {
|
|||
initAgentSocket(lC);
|
||||
|
||||
const stats = await queryAgent(machines.nodes.map((node) => node.nodeKey));
|
||||
const context = await loadContext();
|
||||
const context = hp_getConfig();
|
||||
const { mode, config } = hs_getConfig();
|
||||
|
||||
let magic: string | undefined;
|
||||
|
||||
if (context.config.read) {
|
||||
const config = await loadConfig();
|
||||
if (mode !== 'no') {
|
||||
if (config.dns.magic_dns) {
|
||||
magic = config.dns.base_domain;
|
||||
}
|
||||
|
|
@ -45,8 +45,8 @@ export async function loader({ request, context: lC }: LoaderFunctionArgs) {
|
|||
users: users.users,
|
||||
magic,
|
||||
stats,
|
||||
server: context.headscaleUrl,
|
||||
publicServer: context.headscalePublicUrl,
|
||||
server: context.headscale.url,
|
||||
publicServer: context.headscale.public_url,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import Link from '~/components/Link';
|
|||
import Select from '~/components/Select';
|
||||
import TableList from '~/components/TableList';
|
||||
import type { PreAuthKey, User } from '~/types';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { post, pull } from '~/utils/headscale';
|
||||
import { send } from '~/utils/res';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
import AuthKeyRow from './components/key';
|
||||
import AddPreAuthKey from './dialogs/new';
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||
}
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const context = await loadContext();
|
||||
const context = hp_getConfig();
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
const users = await pull<{ users: User[] }>(
|
||||
'v1/user',
|
||||
|
|
@ -113,7 +113,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
return {
|
||||
keys: preAuthKeys.flatMap((keys) => keys.preAuthKeys),
|
||||
users: users.users,
|
||||
server: context.headscalePublicUrl ?? context.headscaleUrl,
|
||||
server: context.headscale.public_url ?? context.headscale.url,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
import { OrganizationIcon, PasskeyFillIcon } from '@primer/octicons-react';
|
||||
|
||||
import Card from '~/components/Card';
|
||||
import Link from '~/components/Link';
|
||||
import type { HeadplaneContext } from '~/utils/config/headplane';
|
||||
|
||||
import { HeadplaneConfig } from '~/utils/state';
|
||||
import Add from '../dialogs/add';
|
||||
|
||||
interface Props {
|
||||
readonly oidc: NonNullable<HeadplaneContext['oidc']>;
|
||||
readonly magic: string | undefined;
|
||||
readonly oidc: NonNullable<HeadplaneConfig['oidc']>;
|
||||
}
|
||||
|
||||
export default function Oidc({ oidc, magic }: Props) {
|
||||
export default function Oidc({ oidc }: Props) {
|
||||
return (
|
||||
<Card variant="flat" className="mb-8 w-full max-w-full p-0">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@ import { ErrorPopup } from '~/components/Error';
|
|||
import StatusCircle from '~/components/StatusCircle';
|
||||
import type { Machine, User } from '~/types';
|
||||
import cn from '~/utils/cn';
|
||||
import { loadContext } from '~/utils/config/headplane';
|
||||
import { loadConfig } from '~/utils/config/headscale';
|
||||
import { del, post, pull } from '~/utils/headscale';
|
||||
import { send } from '~/utils/res';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
|
||||
import { hp_getConfig, hs_getConfig } from '~/utils/state';
|
||||
import toast from '~/utils/toast';
|
||||
import Auth from './components/auth';
|
||||
import Oidc from './components/oidc';
|
||||
|
|
@ -36,11 +35,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
machines: machines.nodes.filter((machine) => machine.user.id === user.id),
|
||||
}));
|
||||
|
||||
const context = await loadContext();
|
||||
const context = hp_getConfig();
|
||||
const { mode, config } = hs_getConfig();
|
||||
let magic: string | undefined;
|
||||
|
||||
if (context.config.read) {
|
||||
const config = await loadConfig();
|
||||
if (mode !== 'no') {
|
||||
if (config.dns.magic_dns) {
|
||||
magic = config.dns.base_domain;
|
||||
}
|
||||
|
|
@ -152,11 +151,7 @@ export default function Page() {
|
|||
Manage the users in your network and their permissions. Tip: You can
|
||||
drag machines between users to change ownership.
|
||||
</p>
|
||||
{data.oidc ? (
|
||||
<Oidc oidc={data.oidc} magic={data.magic} />
|
||||
) : (
|
||||
<Auth magic={data.magic} />
|
||||
)}
|
||||
{data.oidc ? <Oidc oidc={data.oidc} /> : <Auth magic={data.magic} />}
|
||||
<ClientOnly fallback={<Users users={users} />}>
|
||||
{() => (
|
||||
<InteractiveUsers
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue