WIP: Wireguard web dashboard #1
@ -1,15 +1,16 @@
|
|||||||
import DataUsageIcon from '@mui/icons-material/DataUsage';
|
import { Cookies, Users } from "@db/users";
|
||||||
import GroupIcon from '@mui/icons-material/Group';
|
import DataUsageIcon from "@mui/icons-material/DataUsage";
|
||||||
import PersonIcon from '@mui/icons-material/Person';
|
import GroupIcon from "@mui/icons-material/Group";
|
||||||
import StorageIcon from '@mui/icons-material/Storage';
|
import PersonIcon from "@mui/icons-material/Person";
|
||||||
import { cookies } from 'next/headers';
|
import StorageIcon from "@mui/icons-material/Storage";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import "./layout_global.css";
|
import "./layout_global.css";
|
||||||
import { LoginPage, LogoutPage } from './login';
|
import { LoginPage, LogoutPage } from "./login";
|
||||||
import { cookie, users } from '@/db';
|
import { Home } from "@mui/icons-material";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = "WIreguard dashboard",
|
title = "Wireguard dashboard",
|
||||||
description = "Maneger wireguard interface from Web"
|
description = "Maneger wireguard interface from Web"
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
@ -19,25 +20,29 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
let loginPage = true;
|
let loginPage = true;
|
||||||
if (cookiesSessions.has("sessionsLogin")) {
|
if (cookiesSessions.has("sessionsLogin")) {
|
||||||
const ck = cookiesSessions.get("sessionsLogin").value;
|
const ck = cookiesSessions.get("sessionsLogin").value;
|
||||||
const user = await cookie.findOne({ where: { value: ck } });
|
const user = await Cookies.findOne({ where: { value: ck } });
|
||||||
if (user && (await users.findOne({ where: { userID: user.userID } })).activate) loginPage = false;
|
if (user && (await Users.findOne({ where: { userID: user.userID } })).activate) loginPage = false;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="navbar">
|
<div className="navbar">
|
||||||
|
<div className="homeDiv">
|
||||||
|
<Home />
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<span>Main</span>
|
<span>Main</span>
|
||||||
<li>
|
|
||||||
<StorageIcon />
|
|
||||||
<Link href="/">Wireguard Clients</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<PersonIcon />
|
<PersonIcon />
|
||||||
<Link href="/servers">Wireguard Servers</Link>
|
<Link href="/servers">Wireguard Servers</Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<StorageIcon />
|
||||||
|
<Link href="/peers">Wireguard peers</Link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<span>Utils</span>
|
<span>Utils</span>
|
||||||
|
@ -3,11 +3,42 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
min-height: 100vh;
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 800px) {
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.navbar ul {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.navbar ul > span {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 799px) {
|
||||||
|
.navbar {
|
||||||
|
/* display: flex; */
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.navbar > nav {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.navbar ul {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.navbar ul > li {
|
||||||
|
padding-left: 10px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.navbar ul > span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
background-color: rgb(0, 19, 50);
|
background-color: rgb(0, 19, 50);
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
@ -17,7 +48,6 @@ body {
|
|||||||
|
|
||||||
.navbar ul {
|
.navbar ul {
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
display: grid;
|
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +57,6 @@ body {
|
|||||||
border-radius: 12rem;
|
border-radius: 12rem;
|
||||||
width: 42%;
|
width: 42%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,4 +81,12 @@ body {
|
|||||||
|
|
||||||
.logoutButton {
|
.logoutButton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeDiv {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 26px;
|
||||||
|
padding-top: 15px;
|
||||||
}
|
}
|
@ -1,21 +1,22 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
import { Cookies, Users } from "@db/users";
|
||||||
|
import { decrypt } from "@pass";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { users, cookie } from "@/db";
|
|
||||||
import { decrypt } from "@/pass";
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
|
|
||||||
export async function Login(form: FormData) {
|
export async function Login(form: FormData) {
|
||||||
|
"use server";
|
||||||
const username = String(form.get("username")||""), password = String(form.get("password")||"");
|
const username = String(form.get("username")||""), password = String(form.get("password")||"");
|
||||||
const user = await users.findOne({ where: { username } });
|
const user = await Users.findOne({ where: { username } });
|
||||||
if (user) {
|
if (user) {
|
||||||
if (await decrypt(user.password) === password) {
|
if (await decrypt(user.password) === password) {
|
||||||
let cookieVal: string;
|
let cookieVal: string;
|
||||||
do {
|
do {
|
||||||
cookieVal = randomBytes(32).toString("hex");
|
cookieVal = randomBytes(32).toString("hex");
|
||||||
} while (await cookie.findOne({ where: { value: cookieVal } }));
|
} while (await Cookies.findOne({ where: { value: cookieVal } }));
|
||||||
|
|
||||||
await cookie.create({
|
await Cookies.create({
|
||||||
userID: user.userID,
|
userID: user.userID,
|
||||||
value: cookieVal
|
value: cookieVal
|
||||||
});
|
});
|
||||||
@ -27,7 +28,7 @@ export async function Login(form: FormData) {
|
|||||||
|
|
||||||
export async function Logout() {
|
export async function Logout() {
|
||||||
const ck = cookies().get("sessionsLogin")?.value;
|
const ck = cookies().get("sessionsLogin")?.value;
|
||||||
const bk = await cookie.findOne({ where: { value: ck } });
|
const bk = await Cookies.findOne({ where: { value: ck } });
|
||||||
await bk.destroy();
|
await bk.destroy();
|
||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
13
app/page.tsx
13
app/page.tsx
@ -1,7 +1,8 @@
|
|||||||
"use server";
|
export default function Home() {
|
||||||
|
return (<div>
|
||||||
export default async function Home() {
|
<h1>Welcome to Wireguard dashboard</h1>
|
||||||
return <div>
|
<p>
|
||||||
Peers
|
<span>Click in any options in navbar to start navigation</span>
|
||||||
</div>;
|
</p>
|
||||||
|
</div>);
|
||||||
}
|
}
|
43
app/peers/[id]/config/api/route.ts
Normal file
43
app/peers/[id]/config/api/route.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { wgServer } from "@db/server";
|
||||||
|
import { wgPeer } from "@db/peers";
|
||||||
|
import { key, wgQuick } from "wireguard-tools.js";
|
||||||
|
import { isIPv6 } from "net";
|
||||||
|
|
||||||
|
const {
|
||||||
|
WG_HOSTNAME = "localhost",
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest, {params: { id }}) {
|
||||||
|
const peer = await wgPeer.findOne({ where: { id: parseInt(id) } });
|
||||||
|
if (!peer) return new Response(JSON.stringify({ message: "Peer not exists" }, null, 2), { status: 400, headers: { "Content-Type": "application/json" } });
|
||||||
|
const interfaceInfo = await wgServer.findOne({ where: { id: (await peer).interfaceOwner } });
|
||||||
|
if (!interfaceInfo) return new Response(JSON.stringify({ message: "Interface not exists" }, null, 2), { status: 400, headers: { "Content-Type": "application/json" } });
|
||||||
|
|
||||||
|
const type = String(req.nextUrl.searchParams.get("type")||"quick").toLowerCase();
|
||||||
|
const wgConfig: wgQuick.QuickConfig = {
|
||||||
|
privateKey: peer.privateKey,
|
||||||
|
DNS: [ "8.8.8.8", "1.1.1.1", "8.8.4.4", "1.0.0.1" ],
|
||||||
|
Address: [
|
||||||
|
peer.IPv4,
|
||||||
|
peer.IPv6
|
||||||
|
],
|
||||||
|
peers: {
|
||||||
|
[key.publicKey(interfaceInfo.privateKey)]: {
|
||||||
|
presharedKey: peer.presharedKey,
|
||||||
|
endpoint: `${isIPv6(WG_HOSTNAME) ? String.prototype.concat("[", WG_HOSTNAME,"]") : WG_HOSTNAME}:${interfaceInfo.portListen}`,
|
||||||
|
allowedIPs: ["0.0.0.0/0", "::/0"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === "json") return Response.json(wgConfig);
|
||||||
|
|
||||||
|
return new Response(wgQuick.stringify(wgConfig), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Cotent-Type": "text/plain",
|
||||||
|
"Content-Disposition": `inline; filename="peer_${peer.interfaceOwner}_${peer.id}.conf"`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
5
app/peers/api/route.ts
Normal file
5
app/peers/api/route.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { wgPeer } from "@db/peers";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return Response.json(await wgPeer.findAll());
|
||||||
|
}
|
96
app/peers/page.tsx
Normal file
96
app/peers/page.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Peer, wgPeer } from "@db/peers";
|
||||||
|
import { wgServer } from "@db/server";
|
||||||
|
import { Users } from "@db/users";
|
||||||
|
import { Download } from "@mui/icons-material";
|
||||||
|
import { extendNet } from "@sirherobrine23/extends";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { key } from "wireguard-tools.js";
|
||||||
|
|
||||||
|
function PeerComponent(props: Peer) {
|
||||||
|
return (<div>
|
||||||
|
<hr />
|
||||||
|
<div>
|
||||||
|
<div>Owner: {props.owner}</div>
|
||||||
|
<div>interface owner: {props.interfaceOwner}</div>
|
||||||
|
<div>Private key: {props.privateKey}</div>
|
||||||
|
{props.presharedKey && <div>Preshared key: {props.presharedKey}</div>}
|
||||||
|
<div>IPv4: {props.IPv4}</div>
|
||||||
|
<div>IPv6: {props.IPv6}</div>
|
||||||
|
<a href={`peers/${props.id}/config/api?type=quick`} download>
|
||||||
|
<Download />
|
||||||
|
<span>Download quick config file</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NukePeers() {
|
||||||
|
"use client";
|
||||||
|
async function Nuke() {
|
||||||
|
"use server";
|
||||||
|
await wgPeer.destroy({ where: {} });
|
||||||
|
redirect("/peers");
|
||||||
|
}
|
||||||
|
return <form action={Nuke}>
|
||||||
|
<input type="submit" value="Nuke peers" />
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Home() {
|
||||||
|
const peers = (await wgPeer.findAll()).map(s => s.toJSON());
|
||||||
|
const servers = (await wgServer.findAll({ attributes: [ "id", "name" ] })).map(s => s.toJSON());
|
||||||
|
const owners = (await Users.findAll({ attributes: [ "username", "userID" ] })).map(s => s.toJSON());
|
||||||
|
|
||||||
|
async function createPeer(form: FormData) {
|
||||||
|
"use server";
|
||||||
|
const owner = parseInt(form.get("owner") as any), interfaceOwner = parseInt(form.get("interfaceOwner") as any), presharedKeyGen = Boolean(form.get("presharedKeyGen"));
|
||||||
|
const interfaceInfo = await wgServer.findOne({ where: { id: interfaceOwner } });
|
||||||
|
const base: Partial<Peer> = {
|
||||||
|
owner,
|
||||||
|
interfaceOwner,
|
||||||
|
privateKey: await key.privateKey(),
|
||||||
|
presharedKey: presharedKeyGen ? await key.presharedKey() : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
base.IPv4 = await extendNet.randomIp(interfaceInfo.IPv4);
|
||||||
|
} while (await wgPeer.findOne({ where: { IPv4: base.IPv4 } }));
|
||||||
|
base.IPv6 = extendNet.toString(extendNet.toInt(base.IPv4), true);
|
||||||
|
await wgPeer.create(base);
|
||||||
|
revalidatePath("/peers");
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div>
|
||||||
|
<h1>Wireguard peers</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form action={createPeer}>
|
||||||
|
<div>
|
||||||
|
<span>Select peer owner: </span>
|
||||||
|
<select name="owner">
|
||||||
|
{owners.map(s => <option value={s.userID}>{s.username}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Interface peer target: </span>
|
||||||
|
<select name="interfaceOwner">
|
||||||
|
{servers.map(s => <option value={s.id}>{s.name}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Generate preshared key?</span>
|
||||||
|
<input type="checkbox" defaultChecked name="presharedKeyGen" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="submit" value="Create peer" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<NukePeers />
|
||||||
|
<div>
|
||||||
|
{peers.map(s => PeerComponent(s))}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
10
app/servers/[id]/api/route.ts
Normal file
10
app/servers/[id]/api/route.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function GET(Request: NextRequest, {params: { id }}) {
|
||||||
|
return NextResponse.json({
|
||||||
|
url: Request.url,
|
||||||
|
headers: Request.headers,
|
||||||
|
id
|
||||||
|
});
|
||||||
|
}
|
42
app/servers/[id]/edit/page.tsx
Normal file
42
app/servers/[id]/edit/page.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { wgServer, Server } from "@db/server";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
function FormEdit(props: Server) {
|
||||||
|
"use client";
|
||||||
|
async function updateDb(form: FormData) {
|
||||||
|
"use server";
|
||||||
|
const wgInt = await wgServer.findOne({ where: { id: props.id } });
|
||||||
|
wgInt.name = String(form.get("name")||props.name);
|
||||||
|
wgInt.IPv4 = String(form.get("IPv4")||props.IPv4);
|
||||||
|
wgInt.IPv6 = String(form.get("IPv6")||props.IPv6);
|
||||||
|
await wgInt.validate();
|
||||||
|
await wgInt.save();
|
||||||
|
revalidatePath(`/servers/${props.id}/edit`)
|
||||||
|
}
|
||||||
|
return (<form action={updateDb}>
|
||||||
|
<div>
|
||||||
|
Interface name: <input type="text" name="name" defaultValue={props.name} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
IPv4: <input type="text" name="IPv4" defaultValue={props.IPv4} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
IPv6: <input type="text" name="IPv6" defaultValue={props.IPv6} />
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
|
||||||
|
<input type="submit" value="Update" />
|
||||||
|
<a href="..">Back to home</a>
|
||||||
|
</div>
|
||||||
|
</form>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function EditServer({ params: { id } }) {
|
||||||
|
const info = await wgServer.findOne({ where: { id: parseInt(id) } });
|
||||||
|
if (!info) return redirect("/servers");
|
||||||
|
|
||||||
|
return (<div>
|
||||||
|
<h1>Editing {info.name}</h1>
|
||||||
|
<FormEdit {...(info.toJSON())} id={id} />
|
||||||
|
</div>);
|
||||||
|
}
|
6
app/servers/api/route.ts
Normal file
6
app/servers/api/route.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { wgServer } from "@db/server";
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return Response.json((await wgServer.findAll()).map(s => s.toJSON()))
|
||||||
|
}
|
@ -1,5 +1,61 @@
|
|||||||
|
import Size from "@components/size";
|
||||||
|
import { Server, wgServer, createInterface } from "@db/server";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { key } from "wireguard-tools.js";
|
||||||
|
|
||||||
|
function DeleterSever({id}: Server) {
|
||||||
|
async function deleteServer() {
|
||||||
|
"use server";
|
||||||
|
await (await wgServer.findOne({ where: { id } })).destroy();
|
||||||
|
revalidatePath("/servers");
|
||||||
|
}
|
||||||
|
return (<form action={deleteServer}>
|
||||||
|
<input type="submit" value="Delete interface" />
|
||||||
|
</form>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateSever() {
|
||||||
|
async function deleteServer() {
|
||||||
|
"use server";
|
||||||
|
await createInterface();
|
||||||
|
revalidatePath("/servers");
|
||||||
|
}
|
||||||
|
return (<form action={deleteServer}>
|
||||||
|
<input type="submit" value="Create interface" />
|
||||||
|
</form>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make server info
|
||||||
|
function ServerShow({ serverInfo }: { serverInfo: Server }) {
|
||||||
|
return (<div>
|
||||||
|
<h3 id={serverInfo.id.toString()}>Interface name: {serverInfo.name}</h3>
|
||||||
|
<hr />
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
|
||||||
|
<DeleterSever {...serverInfo} />
|
||||||
|
<a href={`/servers/${serverInfo.id}/edit`}>Edit interface</a>
|
||||||
|
</div>
|
||||||
|
<p>Private key: {serverInfo.privateKey}</p>
|
||||||
|
<p>Public key: {key.publicKey(serverInfo.privateKey)}</p>
|
||||||
|
{serverInfo.IPv4 && <p>IPv4: {serverInfo.IPv4}</p>}
|
||||||
|
{serverInfo.IPv6 && <p>IPv4: "{serverInfo.IPv6}"</p>}
|
||||||
|
<p style={{ marginLeft: "18px" }}>
|
||||||
|
<span>Stats</span>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
|
||||||
|
<span>Upload: <Size fileSize={serverInfo.uploadStats}/></span>
|
||||||
|
<span>Download: <Size fileSize={serverInfo.downloadStats}/></span>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export page
|
||||||
export default async function Servers() {
|
export default async function Servers() {
|
||||||
return <div>
|
const servers = (await wgServer.findAll()).map(s => s.toJSON());
|
||||||
Servers
|
return (<div>
|
||||||
</div>;
|
<h1>Wireguard interfaces</h1>
|
||||||
|
<CreateSever />
|
||||||
|
<div>
|
||||||
|
{servers.map(s => <ServerShow key={"wgInterface"+s.id.toString()} serverInfo={s}/>)}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
}
|
}
|
5
app/users/api/route.ts
Normal file
5
app/users/api/route.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Users } from "@db/users";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return Response.json((await Users.findAll({ attributes: [ "userID", "username" ], where: { activate: true } })).map(s => s.toJSON()));
|
||||||
|
}
|
4
components/size.tsx
Normal file
4
components/size.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default Size;
|
||||||
|
export function Size({ fileSize }: { fileSize: number }) {
|
||||||
|
return (<span>{fileSize} B</span>);
|
||||||
|
}
|
4
lib/database/db.ts
Normal file
4
lib/database/db.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
const { DB_CONNECTION = "sqlite:memory:" } = process.env;
|
||||||
|
export const dbConection: Sequelize = global["DB_CONNECTION"] || (global["DB_CONNECTION"] = new Sequelize(DB_CONNECTION));
|
64
lib/database/peers.ts
Normal file
64
lib/database/peers.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { DataTypes, InferAttributes, InferCreationAttributes, Model } from "sequelize";
|
||||||
|
import { dbConection } from "./db.js";
|
||||||
|
|
||||||
|
export const modelName = "wg_peer";
|
||||||
|
export type Peer = InferAttributes<wgPeer, { omit: never; }>
|
||||||
|
export class wgPeer extends Model<InferAttributes<wgPeer>, InferCreationAttributes<wgPeer>> {
|
||||||
|
declare id?: number;
|
||||||
|
interfaceOwner: number;
|
||||||
|
owner: number;
|
||||||
|
privateKey: string;
|
||||||
|
presharedKey?: string;
|
||||||
|
uploadStats: number;
|
||||||
|
downloadStats: number;
|
||||||
|
IPv4?: string;
|
||||||
|
IPv6?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
wgPeer.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
interfaceOwner: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
privateKey: {
|
||||||
|
type: DataTypes.CHAR(44),
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
presharedKey: {
|
||||||
|
type: DataTypes.CHAR(44),
|
||||||
|
allowNull: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
uploadStats: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
downloadStats: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
IPv4: {
|
||||||
|
type: DataTypes.CHAR,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
IPv6: {
|
||||||
|
type: DataTypes.CHAR,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
}, { sequelize: dbConection, modelName });
|
||||||
|
|
||||||
|
wgPeer.sync().then(async () => {
|
||||||
|
if (await wgPeer.count() === 0) {}
|
||||||
|
});
|
80
lib/database/server.ts
Normal file
80
lib/database/server.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import * as net_e from "@sirherobrine23/extends/src/net.js";
|
||||||
|
import { DataTypes, InferAttributes, InferCreationAttributes, Model } from "sequelize";
|
||||||
|
import * as wg from "wireguard-tools.js";
|
||||||
|
import { dbConection } from "./db.js";
|
||||||
|
import { randomBytes, randomInt } from "crypto";
|
||||||
|
|
||||||
|
export const modelName = "wg_servers";
|
||||||
|
export type Server = InferAttributes<wgServer, { omit: never; }>
|
||||||
|
export class wgServer extends Model<InferAttributes<wgServer>, InferCreationAttributes<wgServer>> {
|
||||||
|
declare id?: number;
|
||||||
|
name: string;
|
||||||
|
privateKey: string;
|
||||||
|
portListen: number;
|
||||||
|
IPv4: string;
|
||||||
|
IPv6: string;
|
||||||
|
uploadStats: number;
|
||||||
|
downloadStats: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
wgServer.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
privateKey: {
|
||||||
|
type: DataTypes.CHAR(44),
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
portListen: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
unique: true,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
set(val) {
|
||||||
|
if (val === 0) val = randomInt(2024, 65525);
|
||||||
|
this.setDataValue("portListen", Number(val));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
uploadStats: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
downloadStats: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
IPv4: {
|
||||||
|
type: DataTypes.CHAR,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
IPv6: {
|
||||||
|
type: DataTypes.CHAR,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
}, { sequelize: dbConection, modelName });
|
||||||
|
|
||||||
|
wgServer.sync().then(async () => {
|
||||||
|
if (await wgServer.count() === 0) await createInterface();
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function createInterface() {
|
||||||
|
const wgInt = new wgServer;
|
||||||
|
wgInt.name = randomBytes(4).toString("hex");
|
||||||
|
wgInt.privateKey = await wg.key.privateKey();
|
||||||
|
wgInt.IPv4 = await net_e.randomIp("10.10.0.0/16");
|
||||||
|
wgInt.IPv6 = net_e.toString(net_e.toInt(wgInt.IPv4), true);
|
||||||
|
do { wgInt.portListen = randomInt(2024, 65525); } while (await wgServer.findOne({ where: { portListen: wgInt.portListen } }));
|
||||||
|
await wgInt.validate();
|
||||||
|
await wgInt.save();
|
||||||
|
return wgInt.toJSON();
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes } from "sequelize";
|
import { encrypt } from "@pass";
|
||||||
import { encrypt } from "@/pass";
|
import { DataTypes, InferAttributes, InferCreationAttributes, Model } from "sequelize";
|
||||||
|
import { dbConection } from "./db";
|
||||||
|
|
||||||
const { DB_CONNECTION = "sqlite:memory:" } = process.env;
|
export type User = InferAttributes<Users, { omit: never; }>
|
||||||
export const db = new Sequelize(DB_CONNECTION);
|
export class Users extends Model<InferAttributes<Users>, InferCreationAttributes<Users>> {
|
||||||
|
|
||||||
export interface User {
|
|
||||||
userID?: number;
|
userID?: number;
|
||||||
activate: boolean;
|
activate: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
@ -12,8 +11,29 @@ export interface User {
|
|||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UserSequelize extends Model<InferAttributes<UserSequelize>, InferCreationAttributes<UserSequelize>>, User { };
|
export class Cookies extends Model<InferAttributes<Cookies>, InferCreationAttributes<Cookies>> {
|
||||||
export const users = db.define<UserSequelize>("users", {
|
declare id: number;
|
||||||
|
userID: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
Cookies.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
userID: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
unique: true
|
||||||
|
}
|
||||||
|
}, { sequelize: dbConection });
|
||||||
|
|
||||||
|
Users.init({
|
||||||
userID: {
|
userID: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
autoIncrement: true,
|
autoIncrement: true,
|
||||||
@ -37,38 +57,14 @@ export const users = db.define<UserSequelize>("users", {
|
|||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
}
|
}
|
||||||
});
|
}, { sequelize: dbConection });
|
||||||
|
|
||||||
users.sync().then(async () => {
|
Cookies.sync();
|
||||||
(await users.count() === 0) && await users.create({
|
Users.sync().then(async () => {
|
||||||
|
(await Users.count() === 0) && await Users.create({
|
||||||
activate: true,
|
activate: true,
|
||||||
name: "Root admin",
|
name: "Root admin",
|
||||||
username: "admin",
|
username: "admin",
|
||||||
password: await encrypt("admin"),
|
password: await encrypt("admin"),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface Cookies {
|
|
||||||
id: number;
|
|
||||||
userID: number;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface CookiesSequelize extends Model<InferAttributes<CookiesSequelize>, InferCreationAttributes<CookiesSequelize>>, Cookies { };
|
|
||||||
export const cookie = db.define<CookiesSequelize>("cookies", {
|
|
||||||
id: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
primaryKey: true,
|
|
||||||
unique: true,
|
|
||||||
autoIncrement: true
|
|
||||||
},
|
|
||||||
userID: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cookie.sync();
|
|
12
package.json
12
package.json
@ -3,10 +3,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev"
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.11.16",
|
||||||
"@types/react": "18.2.46",
|
"@types/react": "18.2.46",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
@ -14,15 +16,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.15.6",
|
"@mui/icons-material": "^5.15.7",
|
||||||
"@mui/material": "^5.15.6",
|
"@mui/material": "^5.15.7",
|
||||||
"@sirherobrine23/extends": "^3.7.4",
|
"@sirherobrine23/extends": "^3.7.4",
|
||||||
"neste": "^3.1.2",
|
"neste": "^3.1.2",
|
||||||
"next": "^14.1.0",
|
"next": "^14.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-charts": "^3.0.0-beta.57",
|
"react-charts": "^3.0.0-beta.57",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sequelize": "^6.35.2",
|
"sequelize": "^6.36.0",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"wireguard-tools.js": "^1.8.3"
|
"wireguard-tools.js": "^1.8.3"
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,15 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@components/*": [
|
||||||
"./lib/*"
|
"./components/*"
|
||||||
]
|
],
|
||||||
|
"@pass": [
|
||||||
|
"./lib/pass.ts"
|
||||||
|
],
|
||||||
|
"@db/*": [
|
||||||
|
"./lib/database/*"
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user