refactor: add interface for wg provider with opnsense implementation

This commit is contained in:
Yuri Tatishchev 2025-03-16 00:11:31 -07:00
parent e9d4be1d53
commit 0e23c8e21c
Signed by: CaZzzer
GPG Key ID: E0EBF441EA424369
16 changed files with 369 additions and 255 deletions

View File

@ -1,6 +1,7 @@
{
"name": "vpgen",
"version": "0.0.1",
"license": "AGPL-3.0-or-later",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@ -2,10 +2,9 @@ import { type Handle, redirect } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { dev } from '$app/environment';
import * as auth from '$lib/server/auth';
import { fetchOpnsenseServer } from '$lib/server/opnsense';
import wgProvider from '$lib/server/wg-provider';
// fetch opnsense server info on startup
await fetchOpnsenseServer();
await wgProvider.init();
const handleAuth: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get(auth.sessionCookieName);

View File

@ -1,3 +0,0 @@
export function opnsenseSanitezedUsername(username: string) {
return username.slice(0, 63).replace(/[^a-zA-Z0-9_-]/g, '_');
}

View File

@ -50,7 +50,7 @@ export async function validateSession(sessionId: string) {
const [result] = await db
.select({
// Adjust user table here to tweak returned data
user: { id: table.users.id, username: table.users.username, name: table.users.name },
user: { id: table.users.id, authSource: table.users.authSource, username: table.users.username, name: table.users.name },
session: table.sessions
})
.from(table.sessions)

View File

@ -3,9 +3,8 @@ import { err, ok, type Result } from '$lib/types';
import { db } from '$lib/server/db';
import { count, eq, isNull } from 'drizzle-orm';
import { env } from '$env/dynamic/private';
import { opnsenseAuth, opnsenseUrl, serverUuid } from '$lib/server/opnsense';
import { opnsenseSanitezedUsername } from '$lib/opnsense';
import { getIpsFromIndex } from './utils';
import wgProvider from '$lib/server/wg-provider';
export async function createDevice(params: {
name: string;
@ -19,17 +18,15 @@ export async function createDevice(params: {
if (deviceCount >= parseInt(env.MAX_CLIENTS_PER_USER))
return err([400, 'Maximum number of devices reached'] as [400, string]);
// this is going to be quite long
// 1. fetch params for new device from opnsense api
// 1. fetch params for new device from provider
// 2.1 get an allocation for the device
// 2.2. insert new device into db
// 2.3. update the allocation with the device id
// 3. create the client in opnsense
// 4. reconfigure opnsense to enable the new client
// 3. create the client in provider
return await db.transaction(async (tx) => {
const [keys, availableAllocation, lastAllocation] = await Promise.all([
// fetch params for new device from opnsense api
getKeys(),
const [keysResult, availableAllocation, lastAllocation] = await Promise.all([
// fetch params for new device from provider
wgProvider.generateKeys(),
// find first unallocated IP
await tx.query.ipAllocations.findFirst({
columns: {
@ -46,9 +43,12 @@ export async function createDevice(params: {
}),
]);
if (keysResult?._tag === 'err') return err([500, 'Failed to get keys']);
const keys = keysResult.value;
// check for existing allocation or if we have any IPs left
if (!availableAllocation && lastAllocation && lastAllocation.id >= parseInt(env.IP_MAX_INDEX)) {
return err([500, 'No more IP addresses available'] as [500, string]);
return err([500, 'No more IP addresses available']);
}
// use existing allocation or create a new one
@ -65,9 +65,9 @@ export async function createDevice(params: {
.values({
userId: params.user.id,
name: params.name,
publicKey: keys.pubkey,
privateKey: keys.privkey,
preSharedKey: keys.psk,
publicKey: keys.publicKey,
privateKey: keys.privateKey,
preSharedKey: keys.preSharedKey,
})
.returning({ id: devices.id });
@ -77,80 +77,18 @@ export async function createDevice(params: {
.set({ deviceId: newDevice.id })
.where(eq(ipAllocations.id, ipAllocationId));
// create client in opnsense
const opnsenseRes = await opnsenseCreateClient({
username: params.user.username,
pubkey: keys.pubkey,
psk: keys.psk,
// create client in provider
const providerRes = await wgProvider.createClient({
user: params.user,
publicKey: keys.publicKey,
preSharedKey: keys.preSharedKey,
allowedIps: getIpsFromIndex(ipAllocationId).join(','),
});
const opnsenseResJson = await opnsenseRes.json();
if (opnsenseResJson['result'] !== 'saved') {
if (providerRes._tag === 'err') {
tx2.rollback();
console.error(`Error creating client in OPNsense: \n${opnsenseResJson}`);
return err([500, 'Error creating client in OPNsense'] as [500, string]);
return err([500, 'Failed to create client in provider']);
}
// reconfigure opnsense
await opnsenseReconfigure();
return ok(newDevice.id);
});
});
}
async function getKeys() {
// fetch key pair from opnsense
const options: RequestInit = {
method: 'GET',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
},
};
const resKeyPair = await fetch(`${opnsenseUrl}/api/wireguard/server/key_pair`, options);
const resPsk = await fetch(`${opnsenseUrl}/api/wireguard/client/psk`, options);
const keyPair = await resKeyPair.json();
const psk = await resPsk.json();
return {
pubkey: keyPair['pubkey'] as string,
privkey: keyPair['privkey'] as string,
psk: psk['psk'] as string,
};
}
async function opnsenseCreateClient(params: {
username: string;
pubkey: string;
psk: string;
allowedIps: string;
}) {
return fetch(`${opnsenseUrl}/api/wireguard/client/addClientBuilder`, {
method: 'POST',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
configbuilder: {
enabled: '1',
name: `vpgen-${opnsenseSanitezedUsername(params.username)}`,
pubkey: params.pubkey,
psk: params.psk,
tunneladdress: params.allowedIps,
server: serverUuid,
endpoint: env.VPN_ENDPOINT,
},
}),
});
}
async function opnsenseReconfigure() {
return fetch(`${opnsenseUrl}/api/wireguard/service/reconfigure`, {
method: 'POST',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
},
});
}

View File

@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { devices } from '$lib/server/db/schema';
import { err, ok, type Result } from '$lib/types';
import { opnsenseAuth, opnsenseUrl } from '$lib/server/opnsense';
import wgProvider from '$lib/server/wg-provider';
export async function deleteDevice(
userId: string,
@ -15,59 +15,13 @@ export async function deleteDevice(
where: and(eq(devices.userId, userId), eq(devices.id, deviceId)),
});
if (!device) return err([400, 'Device not found']);
const opnsenseClientUuid = (await opnsenseFindClient(device.publicKey))?.['uuid'];
if (typeof opnsenseClientUuid !== 'string') {
console.error(
'failed to get OPNsense client for deletion for device',
deviceId,
device.publicKey,
);
return err([500, 'Error getting client from OPNsense']);
}
const opnsenseDeletionResult = await opnsenseDeleteClient(opnsenseClientUuid);
if (opnsenseDeletionResult?.['result'] !== 'deleted') {
console.error(
'failed to delete OPNsense client for device',
deviceId,
device.publicKey,
'\n',
'OPNsense client uuid:',
opnsenseClientUuid,
);
return err([500, 'Error deleting client in OPNsense']);
const providerDeletionResult = await wgProvider.deleteClient(device.publicKey);
if (providerDeletionResult._tag === 'err') {
console.error('failed to delete provider client for device', deviceId, device.publicKey);
return err([500, 'Error deleting client in provider']);
}
await db.delete(devices).where(eq(devices.id, deviceId));
return ok(null);
}
async function opnsenseFindClient(pubkey: string) {
const res = await fetch(`${opnsenseUrl}/api/wireguard/client/searchClient`, {
method: 'POST',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
current: 1,
// "rowCount": 7,
sort: {},
searchPhrase: pubkey,
type: ['peer'],
}),
});
return (await res.json())?.rows?.[0] ?? null;
}
async function opnsenseDeleteClient(clientUuid: string) {
const res = await fetch(`${opnsenseUrl}/api/wireguard/client/delClient/${clientUuid}`, {
method: 'POST',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
},
});
return res.json();
}

View File

@ -2,9 +2,9 @@ import { db } from '$lib/server/db';
import { and, eq } from 'drizzle-orm';
import { devices } from '$lib/server/db/schema';
import type { DeviceDetails } from '$lib/devices';
import { serverPublicKey } from '$lib/server/opnsense';
import { env } from '$env/dynamic/private';
import { getIpsFromIndex } from '$lib/server/devices/index';
import wgProvider from '$lib/server/wg-provider';
export async function findDevices(userId: string) {
return db.query.devices.findMany({
@ -49,7 +49,7 @@ export function mapDeviceToDetails(
privateKey: device.privateKey,
preSharedKey: device.preSharedKey,
ips,
vpnPublicKey: serverPublicKey,
vpnPublicKey: wgProvider.getServerPublicKey(),
vpnEndpoint: env.VPN_ENDPOINT,
vpnDns: env.VPN_DNS,
};

View File

@ -1,49 +0,0 @@
import { env } from '$env/dynamic/private';
import assert from 'node:assert';
import { encodeBasicCredentials } from 'arctic/dist/request';
import { dev } from '$app/environment';
import type { OpnsenseWgServers } from '$lib/opnsense/wg';
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 = '';
export let serverUuid: string, serverPublicKey: string;
export async function fetchOpnsenseServer() {
// this might be pretty bad if the server is down and in a bunch of other cases
// TODO: write a retry loop later
const resServers = await fetch(`${opnsenseUrl}/api/wireguard/client/list_servers`, {
method: 'GET',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
},
});
assert(resServers.ok, 'Failed to fetch OPNsense WireGuard servers');
const servers = (await resServers.json()) as OpnsenseWgServers;
assert.equal(servers.status, 'ok', 'Failed to fetch OPNsense WireGuard servers');
const uuid = servers.rows.find((server) => server.name === opnsenseIfname)?.uuid;
assert(uuid, 'Failed to find server UUID for OPNsense WireGuard server');
serverUuid = uuid;
console.log('OPNsense WireGuard server UUID:', serverUuid);
const resServerInfo = await fetch(
`${opnsenseUrl}/api/wireguard/client/get_server_info/${serverUuid}`,
{
method: 'GET',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
},
},
);
assert(resServerInfo.ok, 'Failed to fetch OPNsense WireGuard server info');
const serverInfo = await resServerInfo.json();
assert.equal(serverInfo.status, 'ok', 'Failed to fetch OPNsense WireGuard server info');
serverPublicKey = serverInfo['pubkey'];
}

View File

@ -0,0 +1,38 @@
import type { Result } from '$lib/types';
import type { User } from '$lib/server/db/schema';
export interface IWgProvider {
init(): Promise<Result<null, Error>>;
getServerPublicKey(): string;
generateKeys(): Promise<Result<WgKeys, Error>>;
createClient(params: CreateClientParams): Promise<Result<null, Error>>;
findConnections(user: User): Promise<Result<ClientConnection[], Error>>;
deleteClient(publicKey: string): Promise<Result<null, Error>>;
}
export type WgKeys = {
publicKey: string;
privateKey: string;
preSharedKey: string;
};
export type CreateClientParams = {
user: User;
publicKey: string;
preSharedKey: string;
allowedIps: string;
}
export type ClientConnection = {
publicKey: string;
endpoint: string;
allowedIps: string;
transferRx: number;
transferTx: number;
latestHandshake: number;
}

View File

@ -0,0 +1,12 @@
import { WgProviderOpnsense } from '$lib/server/wg-providers/opnsense';
import { env } from '$env/dynamic/private';
import type { IWgProvider } from '$lib/server/types';
const wgProvider: IWgProvider = new WgProviderOpnsense({
opnsenseUrl: env.OPNSENSE_API_URL,
opnsenseApiKey: env.OPNSENSE_API_KEY,
opnsenseApiSecret: env.OPNSENSE_API_SECRET,
opnsenseWgIfname: env.OPNSENSE_WG_IFNAME,
});
export default wgProvider;

View File

@ -0,0 +1,244 @@
import type { ClientConnection, CreateClientParams, IWgProvider, WgKeys } from '$lib/server/types';
import { encodeBasicCredentials } from 'arctic/dist/request';
import { is } from 'typia';
import type { OpnsenseWgPeers, OpnsenseWgServers } from '$lib/server/wg-providers/opnsense/types';
import { err, ok, type Result } from '$lib/types';
import assert from 'node:assert';
import type { User } from '$lib/server/db/schema';
export class WgProviderOpnsense implements IWgProvider {
private opnsenseUrl: string;
private opnsenseAuth: string;
private opnsenseIfname: string;
private opnsenseWgServerUuid: string | undefined;
private opnsenseWgServerPublicKey: string | undefined;
public constructor(params: OpnsenseParams) {
this.opnsenseUrl = params.opnsenseUrl;
this.opnsenseAuth =
'Basic ' + encodeBasicCredentials(params.opnsenseApiKey, params.opnsenseApiSecret);
this.opnsenseIfname = params.opnsenseWgIfname;
}
public async init(): Promise<Result<null, Error>> {
const resServers = await fetch(`${this.opnsenseUrl}/api/wireguard/client/list_servers`, {
method: 'GET',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
},
});
const servers = await resServers.json();
if (!is<OpnsenseWgServers>(servers)) {
console.error('Unexpected response for OPNsense WireGuard servers', servers);
return err(new Error('Failed to fetch OPNsense WireGuard servers'));
}
const uuid = servers.rows.find((server) => server.name === this.opnsenseIfname)?.uuid;
if (!uuid) {
console.error('OPNsense WireGuard servers', servers);
return err(new Error('Failed to find server UUID for OPNsense WireGuard server'));
}
const resServerInfo = await fetch(
`${this.opnsenseUrl}/api/wireguard/client/get_server_info/${uuid}`,
{
method: 'GET',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
},
},
);
const serverInfo = await resServerInfo.json();
const serverPublicKey = serverInfo['pubkey'];
if (serverInfo['status'] !== 'ok' || typeof serverPublicKey !== 'string') {
console.error('Failed to fetch OPNsense WireGuard server info', serverInfo);
return err(new Error('Failed to fetch OPNsense WireGuard server info'));
}
console.debug('OPNsense WireGuard server UUID:', uuid);
console.debug('OPNsense WireGuard server public key:', serverPublicKey);
this.opnsenseWgServerUuid = uuid;
this.opnsenseWgServerPublicKey = serverPublicKey;
return ok(null);
}
getServerPublicKey(): string {
assert(
this.opnsenseWgServerPublicKey,
'OPNsense server public key not set, init() must be called first',
);
return this.opnsenseWgServerPublicKey;
}
public async generateKeys(): Promise<Result<WgKeys, Error>> {
const options: RequestInit = {
method: 'GET',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
},
};
const resKeyPair = await fetch(`${this.opnsenseUrl}/api/wireguard/server/key_pair`, options);
const resPsk = await fetch(`${this.opnsenseUrl}/api/wireguard/client/psk`, options);
const keyPair = await resKeyPair.json();
const psk = await resPsk.json();
if (!is<{ pubkey: string; privkey: string }>(keyPair)) {
console.error('Unexpected response for OPNsense key pair', keyPair);
return err(new Error('Failed to fetch OPNsense key pair'));
}
if (!is<{ psk: string }>(psk)) {
console.error('Unexpected response for OPNsense PSK', psk);
return err(new Error('Failed to fetch OPNsense PSK'));
}
return ok({
publicKey: keyPair.pubkey,
privateKey: keyPair.privkey,
preSharedKey: psk.psk,
});
}
async createClient(params: CreateClientParams): Promise<Result<null, Error>> {
assert(this.opnsenseWgServerUuid, 'OPNsense server UUID not set, init() must be called first');
const createClientRes = await fetch(
`${this.opnsenseUrl}/api/wireguard/client/addClientBuilder`,
{
method: 'POST',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
configbuilder: {
enabled: '1',
name: `vpgen-${opnsenseSanitezedUsername(params.user.username)}`,
pubkey: params.publicKey,
psk: params.preSharedKey,
tunneladdress: params.allowedIps,
server: this.opnsenseWgServerUuid,
endpoint: '',
},
}),
},
);
const createClientResJson = await createClientRes.json();
if (createClientResJson['result'] !== 'saved') {
console.error('Error creating client in OPNsense', createClientResJson);
return err(new Error('Failed to create client in OPNsense'));
}
const reconfigureRes = await fetch(`${this.opnsenseUrl}/api/wireguard/service/reconfigure`, {
method: 'POST',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
},
});
if (reconfigureRes.status !== 200) {
console.error('Error reconfiguring OPNsense', reconfigureRes);
return err(new Error('Failed to reconfigure OPNsense'));
}
return ok(null);
}
async findConnections(user: User): Promise<Result<ClientConnection[], Error>> {
const res = await fetch(`${this.opnsenseUrl}/api/wireguard/service/show`, {
method: 'POST',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
current: 1,
// "rowCount": 7,
sort: {},
// TODO: use a more unique search phrase
// unfortunately 64 character limit,
// but it should be fine if users can't change their own username
searchPhrase: `vpgen-${opnsenseSanitezedUsername(user.username)}`,
type: ['peer'],
}),
});
const peers = await res.json();
if (!is<OpnsenseWgPeers>(peers)) {
console.error('Unexpected response for OPNsense WireGuard peers', peers);
return err(new Error('Failed to fetch OPNsense WireGuard peers'));
}
return ok(
peers.rows.map((peer) => {
return {
publicKey: peer['public-key'],
endpoint: peer['endpoint'],
allowedIps: peer['allowed-ips'],
transferRx: peer['transfer-rx'],
transferTx: peer['transfer-tx'],
latestHandshake: peer['latest-handshake'] * 1000,
};
}),
);
}
async deleteClient(publicKey: string): Promise<Result<null, Error>> {
const client = await this.findOpnsenseClient(publicKey);
const clientUuid = client?.uuid;
if (typeof clientUuid !== 'string') {
console.error('Failed to get OPNsense client UUID for deletion', client);
return err(new Error('Failed to get OPNsense client UUID for deletion'));
}
const res = await fetch(`${this.opnsenseUrl}/api/wireguard/client/delClient/${clientUuid}`, {
method: 'POST',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
},
});
const resJson = await res.json();
if (resJson['result'] !== 'deleted') {
console.error('Failed to delete OPNsense client', resJson);
return err(new Error('Failed to delete OPNsense client'));
}
return ok(null);
}
private async findOpnsenseClient(publicKey: string) {
const res = await fetch(`${this.opnsenseUrl}/api/wireguard/client/searchClient`, {
method: 'POST',
headers: {
Authorization: this.opnsenseAuth,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
current: 1,
sort: {},
searchPhrase: publicKey,
type: ['peer'],
}),
});
return (await res.json())?.rows?.[0] ?? null;
}
}
function opnsenseSanitezedUsername(username: string) {
return username.slice(0, 63).replace(/[^a-zA-Z0-9_-]/g, '_');
}
export type OpnsenseParams = {
opnsenseUrl: string;
opnsenseApiKey: string;
opnsenseApiSecret: string;
opnsenseWgIfname: string;
};

View File

@ -1,27 +1,2 @@
class Ok<T> {
readonly _tag = 'ok';
value: T;
constructor(value: T) {
this.value = value;
}
}
class Err<E> {
readonly _tag = 'err';
error: E;
constructor(error: E) {
this.error = error;
}
}
export type Result<T, E> = Ok<T> | Err<E>;
export function err<E>(e: E): Err<E> {
return new Err(e);
}
export function ok<T>(t: T): Ok<T> {
return new Ok(t);
}
export type { Result } from './result';
export { ok, err } from './result';

27
src/lib/types/result.ts Normal file
View File

@ -0,0 +1,27 @@
class Ok<T> {
readonly _tag = 'ok';
value: T;
constructor(value: T) {
this.value = value;
}
}
class Err<E> {
readonly _tag = 'err';
error: E;
constructor(error: E) {
this.error = error;
}
}
export type Result<T, E> = Ok<T> | Err<E>;
export function err<E>(e: E): Err<E> {
return new Err(e);
}
export function ok<T>(t: T): Ok<T> {
return new Ok(t);
}

View File

@ -1,44 +1,43 @@
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { opnsenseAuth, opnsenseUrl } from '$lib/server/opnsense';
import type { OpnsenseWgPeers } from '$lib/opnsense/wg';
import { findDevices } from '$lib/server/devices';
import type { ConnectionDetails } from '$lib/connections';
import { opnsenseSanitezedUsername } from '$lib/opnsense';
import type { Result } from '$lib/types';
import type { ClientConnection } from '$lib/server/types';
import wgProvider from '$lib/server/wg-provider';
export const GET: RequestHandler = async (event) => {
if (!event.locals.user) {
return error(401, 'Unauthorized');
}
console.debug('/api/connections');
const peers = await fetchOpnsensePeers(event.locals.user.username);
console.debug('/api/connections: fetched opnsense peers', peers.rowCount);
const peersResult: Result<ClientConnection[], Error> = await wgProvider.findConnections(event.locals.user);
if (peersResult._tag === 'err') return error(500, peersResult.error.message);
const devices = await findDevices(event.locals.user.id);
console.debug('/api/connections: fetched db devices');
if (!peers) {
return error(500, 'Error getting info from OPNsense API');
}
// TODO: this is all garbage performance
// filter devices with no recent handshakes
peers.rows = peers.rows.filter((peer) => peer['latest-handshake']);
const peers = peersResult.value.filter((peer) => peer.latestHandshake);
// start from devices, to treat db as the source of truth
const connections: ConnectionDetails[] = [];
for (const device of devices) {
const peerData = peers.rows.find((peer) => peer['public-key'] === device.publicKey);
const peerData = peers.find((peer) => peer.publicKey === device.publicKey);
if (!peerData) continue;
connections.push({
deviceId: device.id,
deviceName: device.name,
devicePublicKey: device.publicKey,
deviceIps: peerData['allowed-ips'].split(','),
endpoint: peerData['endpoint'],
deviceIps: peerData.allowedIps.split(','),
endpoint: peerData.endpoint,
// swap rx and tx, since the opnsense values are from the server perspective
transferRx: peerData['transfer-tx'],
transferTx: peerData['transfer-rx'],
latestHandshake: peerData['latest-handshake'] * 1000,
transferRx: peerData.transferTx,
transferTx: peerData.transferRx,
latestHandshake: peerData.latestHandshake,
});
}
@ -49,25 +48,3 @@ export const GET: RequestHandler = async (event) => {
},
});
};
async function fetchOpnsensePeers(username: string) {
const res = await fetch(`${opnsenseUrl}/api/wireguard/service/show`, {
method: 'POST',
headers: {
Authorization: opnsenseAuth,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
current: 1,
// "rowCount": 7,
sort: {},
// TODO: use a more unique search phrase
// unfortunately 64 character limit,
// but it should be fine if users can't change their own username
searchPhrase: `vpgen-${opnsenseSanitezedUsername(username)}`,
type: ['peer'],
}),
});
return (await res.json()) as OpnsenseWgPeers;
}

View File

@ -1,6 +1,7 @@
import type { Actions } from './$types';
import { createDevice } from '$lib/server/devices';
import { error, fail, redirect } from '@sveltejs/kit';
import wgProvider from '$lib/server/wg-provider';
export const actions = {
create: async (event) => {