From e03bf11fa57f8e90f2f4bc26e8c17a8bc689b20d Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Thu, 19 Dec 2024 22:10:08 -0800 Subject: [PATCH] connections page overhaul --- .env.example | 2 + src/lib/components/ui/badge/badge.svelte | 18 ++++++ src/lib/components/ui/badge/index.ts | 21 +++++++ src/lib/opnsense/wg.ts | 75 +++++++++++++----------- src/lib/server/opnsense.ts | 16 +++++ src/routes/api/connections/+server.ts | 9 +-- src/routes/connections/+page.svelte | 54 +++++++---------- src/routes/connections/+page.ts | 2 +- 8 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 src/lib/components/ui/badge/badge.svelte create mode 100644 src/lib/components/ui/badge/index.ts create mode 100644 src/lib/server/opnsense.ts diff --git a/.env.example b/.env.example index 54809c2..d3c3a69 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ AUTH_DOMAIN=auth.lab.cazzzer.com AUTH_CLIENT_ID= AUTH_CLIENT_SECRET= AUTH_REDIRECT_URI=http://localhost:5173/auth/authentik/callback + OPNSENSE_API_URL=https://opnsense.home OPNSENSE_API_KEY= OPNSENSE_API_SECRET= +OPNSENSE_WG_IFNAME=wg2 diff --git a/src/lib/components/ui/badge/badge.svelte b/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..e8817f4 --- /dev/null +++ b/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/ui/badge/index.ts b/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..59c0767 --- /dev/null +++ b/src/lib/components/ui/badge/index.ts @@ -0,0 +1,21 @@ +import { type VariantProps, tv } from "tailwind-variants"; +export { default as Badge } from "./badge.svelte"; + +export const badgeVariants = tv({ + base: "focus:ring-ring inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2", + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/80 border-transparent", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, +}); + +export type Variant = VariantProps["variant"]; diff --git a/src/lib/opnsense/wg.ts b/src/lib/opnsense/wg.ts index 59872fd..b463f58 100644 --- a/src/lib/opnsense/wg.ts +++ b/src/lib/opnsense/wg.ts @@ -12,6 +12,31 @@ export interface PeerRow { ifname: string; } +/** + * Sample response from OPNsense WireGuard API + * ```json + * { + * "total": 17, + * "rowCount": 7, + * "current": 1, + * "rows": [ + * { + * "if": "wg0", + * "type": "peer", + * "public-key": "iJa5JmJbMHNlbEluNwoB2Q8LyrPAfb7S/mluanMcI08=", + * "endpoint": "10.17.20.107:42516", + * "allowed-ips": "fd00::1/112,10.6.0.3/32", + * "latest-handshake": 1729319339, + * "transfer-rx": 1052194743, + * "transfer-tx": 25203263456, + * "persistent-keepalive": "off", + * "name": "Yura-TPX13", + * "ifname": "wg0" + * } + * ] + * } + * ``` + */ export interface OpnsenseWgPeers { total: number; rowCount: number; @@ -19,38 +44,18 @@ export interface OpnsenseWgPeers { rows: PeerRow[]; } -// Sample request to OPNsense WireGuard API -// const url = 'https://opnsense.home/api/wireguard/service/show'; -// const options = { -// method: 'POST', -// headers: { -// Authorization: 'Basic ...', -// 'Content-Type': 'application/json', -// Accept: 'application/json', -// 'content-type': 'application/json' -// }, -// body: '{"current":1,"rowCount":7,"sort":{},"searchPhrase":"","type":["peer"]}' -// }; - - -// Sample response from OPNsense WireGuard API -// { -// "total": 17, -// "rowCount": 7, -// "current": 1, -// "rows": [ -// { -// "if": "wg0", -// "type": "peer", -// "public-key": "iJa5JmJbMHNlbEluNwoB2Q8LyrPAfb7S/mluanMcI08=", -// "endpoint": "10.17.20.107:42516", -// "allowed-ips": "fd00::1/112,10.6.0.3/32", -// "latest-handshake": 1729319339, -// "transfer-rx": 1052194743, -// "transfer-tx": 25203263456, -// "persistent-keepalive": "off", -// "name": "Yura-TPX13", -// "ifname": "wg0" -// } -// ] -// } +/** + * Sample request to OPNsense WireGuard API + * ```js + * const url = 'https://opnsense.home/api/wireguard/service/show'; + * const options = { + * method: 'POST', + * headers: { + * Authorization: 'Basic ...', + * 'Content-Type': 'application/json', + * Accept: 'application/json', + * }, + * body: '{"current":1,"rowCount":7,"sort":{},"searchPhrase":"","type":["peer"]}' + * }; + * ``` + */ diff --git a/src/lib/server/opnsense.ts b/src/lib/server/opnsense.ts new file mode 100644 index 0000000..58655b0 --- /dev/null +++ b/src/lib/server/opnsense.ts @@ -0,0 +1,16 @@ +import { env } from '$env/dynamic/private'; +import assert from 'node:assert'; +import { encodeBasicCredentials } from 'arctic/dist/request'; +import { dev } from '$app/environment'; + +assert(env.OPNSENSE_API_URL, 'OPNSENSE_API_URL is not set'); +assert(env.OPNSENSE_API_KEY, 'OPNSENSE_API_KEY is not set'); +assert(env.OPNSENSE_API_SECRET, 'OPNSENSE_API_SECRET is not set'); +assert(env.OPNSENSE_WG_IFNAME, 'OPNSENSE_WG_IFNAME is not set'); + +export const opnsenseUrl = env.OPNSENSE_API_URL; +export const opnsenseAuth = "Basic " + encodeBasicCredentials(env.OPNSENSE_API_KEY, env.OPNSENSE_API_SECRET); +export const opnsenseIfname = env.OPNSENSE_WG_IFNAME; + +// unset secret for security +if (!dev) env.OPNSENSE_API_SECRET = ""; diff --git a/src/routes/api/connections/+server.ts b/src/routes/api/connections/+server.ts index 8644078..4b2e343 100644 --- a/src/routes/api/connections/+server.ts +++ b/src/routes/api/connections/+server.ts @@ -1,14 +1,14 @@ import { error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { env } from '$env/dynamic/private'; +import { opnsenseAuth, opnsenseIfname, opnsenseUrl } from '$lib/server/opnsense'; import type { OpnsenseWgPeers } from '$lib/opnsense/wg'; -export const GET: RequestHandler = async ({ url }) => { - const apiUrl = `${env.OPNSENSE_API_URL}/api/wireguard/service/show`; +export const GET: RequestHandler = async () => { + const apiUrl = `${opnsenseUrl}/api/wireguard/service/show`; const options: RequestInit = { method: 'POST', headers: { - Authorization: `Basic ${Buffer.from(`${env.OPNSENSE_API_KEY}:${env.OPNSENSE_API_SECRET}`).toString('base64')}`, + Authorization: opnsenseAuth, Accept: 'application/json', 'Content-Type': 'application/json', }, @@ -24,6 +24,7 @@ export const GET: RequestHandler = async ({ url }) => { const res = await fetch(apiUrl, options); const peers = await res.json() as OpnsenseWgPeers; + peers.rows = peers.rows.filter(peer => peer['latest-handshake'] && peer.ifname === opnsenseIfname) if (!peers) { error(500, "Error getting info from OPNsense API"); diff --git a/src/routes/connections/+page.svelte b/src/routes/connections/+page.svelte index 77a617c..f447f5e 100644 --- a/src/routes/connections/+page.svelte +++ b/src/routes/connections/+page.svelte @@ -1,13 +1,10 @@