WIP: Wireguard web dashboard #1
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"files.autoSave": "afterDelay",
|
||||
"files.autoSaveDelay": 2000,
|
||||
"files.exclude": {
|
||||
"**/.next/": true,
|
||||
"**/node_modules/": true,
|
||||
"**/*-lock*/": true,
|
||||
},
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
"use client";
|
||||
import DynamicInput from "../component/DynamicInput";
|
||||
import { CreateInterface, CreatePeer, DeleteInterface } from "./wg";
|
||||
|
||||
export interface CreateInterfaceProps {
|
||||
WGcustomName: boolean;
|
||||
randomIp: string
|
||||
}
|
||||
|
||||
export async function WGInterfaceForm({ WGcustomName, randomIp }: CreateInterfaceProps) {
|
||||
return <form action={(e) => CreateInterface(e)}>
|
||||
{WGcustomName && <div><span>Interface name: </span><DynamicInput type="text" name="name" /></div>}
|
||||
<div>
|
||||
{/* <span>IPv4 Address: </span> */}
|
||||
<DynamicInput width="10px" type="text" name="ipv4" required placeholder="IPv4 address, example: 10.0.0.1" defaultValue={randomIp} />
|
||||
</div>
|
||||
<DynamicInput type="submit" value="Create interface" />
|
||||
</form>;
|
||||
}
|
||||
|
||||
export interface WDDeleteInterfaceProps {
|
||||
idInterface: number;
|
||||
}
|
||||
|
||||
export function WDDeleteInterface({ idInterface }: WDDeleteInterfaceProps) {
|
||||
return <form action={DeleteInterface}>
|
||||
<DynamicInput type="hidden" name="interfaceid" defaultValue={idInterface} />
|
||||
<DynamicInput type="submit" value="Delete interface" />
|
||||
</form>;
|
||||
}
|
||||
|
||||
export function WgCreatePeer() {
|
||||
return <form action={CreatePeer}>
|
||||
<DynamicInput type="text" />
|
||||
</form>
|
||||
}
|
@ -1,4 +1,12 @@
|
||||
import "./global.css";
|
||||
import DataUsageIcon from '@mui/icons-material/DataUsage';
|
||||
import GroupIcon from '@mui/icons-material/Group';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import { cookies } from 'next/headers';
|
||||
import Link from "next/link";
|
||||
import "./layout_global.css";
|
||||
import { LoginPage, LogoutPage } from './login';
|
||||
import { cookie, users } from '@/db';
|
||||
|
||||
const {
|
||||
title = "WIreguard dashboard",
|
||||
@ -7,11 +15,50 @@ const {
|
||||
|
||||
export const metadata = { title, description };
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const cookiesSessions = cookies();
|
||||
let loginPage = true;
|
||||
if (cookiesSessions.has("sessionsLogin")) {
|
||||
const ck = cookiesSessions.get("sessionsLogin").value;
|
||||
const user = await cookie.findOne({ where: { value: ck } });
|
||||
if (user && (await users.findOne({ where: { userID: user.userID } })).activate) loginPage = false;
|
||||
}
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div className="bodyCamp">
|
||||
{children}
|
||||
<div className="container">
|
||||
<div className="navbar">
|
||||
<nav>
|
||||
<ul>
|
||||
<span>Main</span>
|
||||
<li>
|
||||
<StorageIcon />
|
||||
<Link href="/">Wireguard Clients</Link>
|
||||
</li>
|
||||
<li>
|
||||
<PersonIcon />
|
||||
<Link href="/servers">Wireguard Servers</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<span>Utils</span>
|
||||
<li>
|
||||
<GroupIcon />
|
||||
<Link href="/users">Users</Link>
|
||||
</li>
|
||||
<li>
|
||||
<DataUsageIcon />
|
||||
<Link href="/stats">Status and Stats</Link>
|
||||
</li>
|
||||
</ul>
|
||||
{!loginPage && <ul>
|
||||
<LogoutPage />
|
||||
</ul>}
|
||||
</nav>
|
||||
</div>
|
||||
<div className="bodyCamp">
|
||||
{loginPage && <LoginPage />}
|
||||
{!loginPage && children}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
55
app/layout_global.css
Normal file
55
app/layout_global.css
Normal file
@ -0,0 +1,55 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: rgb(0, 19, 50);
|
||||
margin-right: 12px;
|
||||
padding-right: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
padding-left: 14px;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.navbar ul > span {
|
||||
background-color: grey;
|
||||
color: black;
|
||||
border-radius: 12rem;
|
||||
width: 42%;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navbar ul > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.navbar ul > li > svg {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logoutButton {
|
||||
cursor: pointer;
|
||||
}
|
30
app/login.tsx
Normal file
30
app/login.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
import { Login, Logout } from "./login_back";
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
|
||||
export function LoginPage() {
|
||||
return <div className="loginArea">
|
||||
<h1>Login</h1>
|
||||
<form action={Login}>
|
||||
<div>
|
||||
<label htmlFor="username">Username: </label>
|
||||
<input type="text" name="username" placeholder="Username" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password">Password: </label>
|
||||
<input type="password" name="password" placeholder="Password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Login" />
|
||||
</div>
|
||||
</form>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function LogoutPage() {
|
||||
return <form action={Logout}>
|
||||
<button>
|
||||
<LogoutIcon className="logoutButton" />
|
||||
</button>
|
||||
</form>
|
||||
}
|
33
app/login_back.ts
Normal file
33
app/login_back.ts
Normal file
@ -0,0 +1,33 @@
|
||||
"use server";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { users, cookie } from "@/db";
|
||||
import { decrypt } from "@/pass";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
export async function Login(form: FormData) {
|
||||
const username = String(form.get("username")||""), password = String(form.get("password")||"");
|
||||
const user = await users.findOne({ where: { username } });
|
||||
if (user) {
|
||||
if (await decrypt(user.password) === password) {
|
||||
let cookieVal: string;
|
||||
do {
|
||||
cookieVal = randomBytes(32).toString("hex");
|
||||
} while (await cookie.findOne({ where: { value: cookieVal } }));
|
||||
|
||||
await cookie.create({
|
||||
userID: user.userID,
|
||||
value: cookieVal
|
||||
});
|
||||
cookies().set("sessionsLogin", cookieVal);
|
||||
}
|
||||
}
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
export async function Logout() {
|
||||
const ck = cookies().get("sessionsLogin")?.value;
|
||||
const bk = await cookie.findOne({ where: { value: ck } });
|
||||
await bk.destroy();
|
||||
redirect("/");
|
||||
}
|
47
app/page.tsx
47
app/page.tsx
@ -1,52 +1,7 @@
|
||||
"use server";
|
||||
import { wg_interfaces, wg_peer } from "@/database";
|
||||
import { WDDeleteInterface, WGInterfaceForm, WgCreatePeer } from "./clients";
|
||||
import { key_experimental } from "wireguard-tools.js";
|
||||
import DynamicInput from "../component/DynamicInput";
|
||||
import { randomIp } from "@sirherobrine23/extends/src/net";
|
||||
|
||||
export default async function Home() {
|
||||
const wgInterfaces = (await wg_interfaces.findAll()).map(s => s.toJSON());
|
||||
const wgPeers = (await wg_peer.findAll()).map(s => s.toJSON());
|
||||
const customName = process.platform === "win32" || process.platform === "linux" || process.platform === "android";
|
||||
return <div>
|
||||
<div>
|
||||
<h1>Create interface</h1>
|
||||
<WGInterfaceForm WGcustomName={customName} randomIp={await randomIp("10.0.0.0/8")} />
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
{wgInterfaces.map(wg => {
|
||||
const peers = wgPeers.filter(s => s.interfaceID === wg.interfaceID);
|
||||
return <div id={String.prototype.concat("wgInterface_", String(wg.interfaceID))}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div>
|
||||
<h1>{wg.name}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<WDDeleteInterface idInterface={wg.interfaceID} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ margin: "12px" }}>
|
||||
<div>
|
||||
{wg.privateKey && <div><DynamicInput placeholder="Private key" type="text" disabled defaultValue={wg.privateKey} /></div>}
|
||||
{wg.portListen && <div><DynamicInput placeholder="Port listen" type="number" defaultValue={wg.portListen} /></div>}
|
||||
{wg.ipv4 && <div><DynamicInput placeholder="IPv4" type="text" defaultValue={wg.ipv4} /></div>}
|
||||
{wg.ipv6 && <div><DynamicInput placeholder="IPv6" type="text" defaultValue={wg.ipv6} /></div>}
|
||||
</div>
|
||||
<div>
|
||||
<WgCreatePeer />
|
||||
</div>
|
||||
{peers.length > 0 && <div>
|
||||
{peers.map((peer, pIndex) => {
|
||||
return <div key={String.prototype.concat("peer_", String(wg.interfaceID), "_", String(peer.peerID))}>
|
||||
<span>{key_experimental.publicKey(peer.privateKey)}</span>
|
||||
</div>;
|
||||
})}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
Peers
|
||||
</div>;
|
||||
}
|
5
app/servers/page.tsx
Normal file
5
app/servers/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
export default async function Servers() {
|
||||
return <div>
|
||||
Servers
|
||||
</div>;
|
||||
}
|
5
app/stats/page.tsx
Normal file
5
app/stats/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
export default async function Stats() {
|
||||
return <div>
|
||||
Stats
|
||||
</div>;
|
||||
}
|
5
app/users/page.tsx
Normal file
5
app/users/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
export default async function Users() {
|
||||
return <div>
|
||||
Users
|
||||
</div>
|
||||
}
|
34
app/wg.tsx
34
app/wg.tsx
@ -1,34 +0,0 @@
|
||||
"use server";
|
||||
import { key_experimental } from "wireguard-tools.js";
|
||||
import { extendNet } from "@sirherobrine23/extends";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { wg_interfaces, wg_peer } from "@/database";
|
||||
import { randomBytes } from "crypto";
|
||||
import { randomBigint } from "@sirherobrine23/extends/src/crypto";
|
||||
|
||||
export async function CreateInterface(form: FormData) {
|
||||
const ports = (await wg_interfaces.findAll({ attributes: [ "portListen" ] })).map(s => s.toJSON().portListen);
|
||||
let port: number;
|
||||
do {
|
||||
port = Number(await randomBigint(1, 25565));
|
||||
} while (ports.includes(port));
|
||||
await wg_interfaces.create({
|
||||
name: randomBytes(4).toString("hex"),
|
||||
portListen: port,
|
||||
privateKey: await key_experimental.privateKey(),
|
||||
ipv4: form.get("ipv4").toString(),
|
||||
ipv6: extendNet.toString(extendNet.toInt(form.get("ipv4").toString()), true),
|
||||
});
|
||||
revalidatePath("/");
|
||||
}
|
||||
|
||||
export async function DeleteInterface(form: FormData) {
|
||||
await wg_peer.destroy({ where: { interfaceID: Number(form.get("interfaceid")) } });
|
||||
await wg_interfaces.destroy({ where: { interfaceID: Number(form.get("interfaceid")) } });
|
||||
revalidatePath("/");
|
||||
}
|
||||
|
||||
export async function CreatePeer(form: FormData) {
|
||||
|
||||
revalidatePath("/");
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
.resizeContainer, .resizeText {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.resizeContainer {
|
||||
padding: 2px 0px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resizeText {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
white-space: pre;
|
||||
padding: 2px 0px 4px 0px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.resizeInput {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
"use client";
|
||||
import { DetailedHTMLProps, InputHTMLAttributes } from "react";
|
||||
import CssMd from "./DynamicInput.module.css";
|
||||
|
||||
export interface DynamicInputProps extends DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {}
|
||||
|
||||
export default DynamicInput;
|
||||
export function DynamicInput(props: DynamicInputProps) {
|
||||
return <div className={CssMd["input-label"]}>
|
||||
{ props.placeholder && <span>{props.placeholder}: </span> }
|
||||
<div className={CssMd["resizeContainer"]}>
|
||||
<span className={CssMd["resizeText"]}>{props.defaultValue||props.value}</span>
|
||||
<input {...props} className={CssMd["resizeInput"]}
|
||||
onInput={e => (e.currentTarget && e.currentTarget.previousElementSibling) && (e.currentTarget.previousElementSibling.textContent = e.currentTarget.value)}
|
||||
onChange={e => (e.currentTarget && e.currentTarget.previousElementSibling) && (e.currentTarget.previousElementSibling.textContent = e.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
164
lib/database.ts
164
lib/database.ts
@ -1,164 +0,0 @@
|
||||
import path from "node:path";
|
||||
import sequelize from "sequelize";
|
||||
|
||||
export enum UserType {
|
||||
root = "wg::root",
|
||||
user = "wg::user"
|
||||
}
|
||||
|
||||
export interface User {
|
||||
userID: string;
|
||||
username: string;
|
||||
password: string;
|
||||
type?: UserType;
|
||||
}
|
||||
|
||||
/** Interface struct */
|
||||
export interface Wg {
|
||||
interfaceID: number;
|
||||
name: string;
|
||||
privateKey: string;
|
||||
portListen: number;
|
||||
ipv4: string;
|
||||
ipv6: string|null;
|
||||
}
|
||||
|
||||
/** Peer base struct */
|
||||
export interface PeerWg {
|
||||
interfaceID: number;
|
||||
peerID: number;
|
||||
privateKey: string;
|
||||
presharedKey?: string;
|
||||
ipv4: string;
|
||||
ipv6: string|null;
|
||||
}
|
||||
|
||||
export const databasePath = path.join(process.cwd(), "database.db");
|
||||
export const db = new sequelize.Sequelize({
|
||||
dialect: "sqlite",
|
||||
storage: databasePath,
|
||||
logging(sql, _timing) {
|
||||
console.error(sql);
|
||||
},
|
||||
});
|
||||
|
||||
export interface UserSequelize extends sequelize.Model<sequelize.InferAttributes<UserSequelize>, sequelize.InferCreationAttributes<UserSequelize>>, User { };
|
||||
export interface WgSequelize extends sequelize.Model<sequelize.InferAttributes<WgSequelize>, sequelize.InferCreationAttributes<WgSequelize>>, Wg { };
|
||||
export interface PeerWgSequelize extends sequelize.Model<sequelize.InferAttributes<PeerWgSequelize>, sequelize.InferCreationAttributes<PeerWgSequelize>>, PeerWg { };
|
||||
|
||||
export const user = db.define<UserSequelize>("user", {
|
||||
userID: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
autoIncrement: false,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
username: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
unique: false,
|
||||
},
|
||||
type: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
defaultValue: UserType.user,
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
is: [UserType.root, UserType.user]
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const wg_interfaces = db.define<WgSequelize>("wg", {
|
||||
interfaceID: {
|
||||
type: sequelize.DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
privateKey: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
portListen: {
|
||||
type: sequelize.DataTypes.NUMBER,
|
||||
defaultValue: 0,
|
||||
allowNull: false,
|
||||
set(val: number) {
|
||||
if (isNaN(val)) throw new TypeError("Invalid port listen!");
|
||||
else if (!(isFinite(val))) throw new TypeError("Invalid port listen!");
|
||||
else if (val < 0) throw new TypeError("Invalid port listen!");
|
||||
this.setDataValue("portListen", val);
|
||||
},
|
||||
},
|
||||
ipv4: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
set(val: string) {
|
||||
this.setDataValue("ipv4", String(val).split("/")[0]);
|
||||
},
|
||||
},
|
||||
ipv6: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: true,
|
||||
set(val: string) {
|
||||
this.setDataValue("ipv6", String(val).split("/")[0]);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const wg_peer = db.define<PeerWgSequelize>("peers", {
|
||||
interfaceID: {
|
||||
type: sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
peerID: {
|
||||
type: sequelize.DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
privateKey: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
presharedKey: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: true,
|
||||
},
|
||||
ipv4: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
ipv6: {
|
||||
type: sequelize.DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: true,
|
||||
},
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
user.sync(),
|
||||
wg_interfaces.sync(),
|
||||
wg_peer.sync(),
|
||||
]).then(() => console.info("Databases created"), err => {
|
||||
console.error(err);
|
||||
process.exit(-1);
|
||||
});
|
74
lib/db.ts
Normal file
74
lib/db.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes } from "sequelize";
|
||||
import { encrypt } from "@/pass";
|
||||
|
||||
const { DB_CONNECTION = "sqlite:memory:" } = process.env;
|
||||
export const db = new Sequelize(DB_CONNECTION);
|
||||
|
||||
export interface User {
|
||||
userID?: number;
|
||||
activate: boolean;
|
||||
name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export interface UserSequelize extends Model<InferAttributes<UserSequelize>, InferCreationAttributes<UserSequelize>>, User { };
|
||||
export const users = db.define<UserSequelize>("users", {
|
||||
userID: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
},
|
||||
activate: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.CHAR(46),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
}
|
||||
});
|
||||
|
||||
users.sync().then(async () => {
|
||||
(await users.count() === 0) && await users.create({
|
||||
activate: true,
|
||||
name: "Root admin",
|
||||
username: "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();
|
18
lib/pass.ts
Normal file
18
lib/pass.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import crypto from "node:crypto";
|
||||
import util from "node:util";
|
||||
|
||||
const scrypt = util.promisify(crypto.scrypt) as (password: crypto.BinaryLike, salt: crypto.BinaryLike, keylen: number) => Promise<Buffer>;
|
||||
|
||||
export async function encrypt(text: string): Promise<string> {
|
||||
const iv = crypto.randomBytes(16), secret = crypto.randomBytes(24);
|
||||
const key = await scrypt(secret, "salt", 24);
|
||||
const cipher = crypto.createCipheriv("aes-192-cbc", key, iv); cipher.on("error", () => {});
|
||||
return (String()).concat(iv.toString("hex"), secret.toString("hex"), cipher.update(text, "utf8", "hex"), cipher.final("hex"));
|
||||
}
|
||||
|
||||
export async function decrypt(hash: string): Promise<string> {
|
||||
const iv = Buffer.from(hash.substring(0, 32), "hex"), secret = Buffer.from(hash.substring(32, 80), "hex"), cipher = hash.substring(80);
|
||||
const key = await scrypt(secret, "salt", 24);
|
||||
const decipher = crypto.createDecipheriv("aes-192-cbc", key, iv); decipher.on("error", () => {});
|
||||
return (String()).concat(decipher.update(cipher, "hex", "utf8"), decipher.final("utf8"));
|
||||
}
|
@ -12,6 +12,10 @@
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.15.6",
|
||||
"@mui/material": "^5.15.6",
|
||||
"@sirherobrine23/extends": "^3.7.4",
|
||||
"neste": "^3.1.2",
|
||||
"next": "^14.1.0",
|
||||
@ -20,6 +24,6 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"sequelize": "^6.35.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"wireguard-tools.js": "^1.8.2"
|
||||
"wireguard-tools.js": "^1.8.3"
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user