From 42a77072e12685437e69e57a75e05be7722f06f8 Mon Sep 17 00:00:00 2001 From: WeeXnes Date: Thu, 27 Feb 2025 02:56:21 +0100 Subject: [PATCH] added auth --- core/globals.ts | 6 +++++ pages/index.vue | 31 ++++++++++++------------- pages/login.vue | 34 ++++++++++++++-------------- pages/settings.vue | 8 +++---- panel.config.ts | 8 +++++-- server/api/auth.ts | 30 +++++++++++++++++++++++++ server/api/getNetworkInterfaces.ts | 4 ---- server/api/login.ts | 36 ++++++++++++++++++++++++++++++ server/plugins/init.ts | 12 +++++++--- util/auth.ts | 21 +++++++++++++++++ 10 files changed, 145 insertions(+), 45 deletions(-) create mode 100644 core/globals.ts create mode 100644 server/api/auth.ts create mode 100644 server/api/login.ts create mode 100644 util/auth.ts diff --git a/core/globals.ts b/core/globals.ts new file mode 100644 index 0000000..7e07dd6 --- /dev/null +++ b/core/globals.ts @@ -0,0 +1,6 @@ +import {reactive} from "vue"; + + +export const jwt_globals = reactive({ + secret: "", +}); diff --git a/pages/index.vue b/pages/index.vue index 42cb71e..523799a 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -4,10 +4,10 @@ import { reactive, ref } from 'vue'; import axios from 'axios'; import type {networkInterface} from "~/types/networkInterface"; import type {serviceInterface} from "~/types/serviceInterface"; +import {checkAuth} from "~/util/auth"; -const ignoreCache = true; const startVm = async (vm: any) => { @@ -195,20 +195,21 @@ const fetchSettings = async () => { } onMounted(async () => { - await fetchSettings() - - if(settings.enable_qemu_controls) await fetchVMs() - - await fetchOsInfo() - await fetchCpuTemp() - cpuInfo.isLoaded = true - await fetchMemoryInfo() - await fetchNetworkInfo() - if(settings.enable_services) await fetchServiceInfo() - const intervalId = setInterval(fetchCpuTemp, 7000); - onUnmounted(() => { - clearInterval(intervalId); - }); + let isAuthed = await checkAuth(useRouter()) + if(isAuthed){ + await fetchSettings() + if(settings.enable_qemu_controls) await fetchVMs() + await fetchOsInfo() + await fetchCpuTemp() + cpuInfo.isLoaded = true + await fetchMemoryInfo() + await fetchNetworkInfo() + if(settings.enable_services) await fetchServiceInfo() + const intervalId = setInterval(fetchCpuTemp, 7000); + onUnmounted(() => { + clearInterval(intervalId); + }); + } }) diff --git a/pages/login.vue b/pages/login.vue index 35329e3..646dd50 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -4,19 +4,6 @@

Login

-
- - -
-
- -
-

Don't have an account? Register here

-
@@ -57,7 +40,24 @@ export default { }, methods: { async handleLogin() { + try { + const response = await axios.post('/api/login', { + password: this.password, + }); + console.log(response.data); + if (response?.data?.message == 'Login successful!') { + const token = response.data.token; + const cookie = useCookie('token'); + cookie.value = token; + + this.$router.push('/'); + } else { + console.error('Login failed'); + } + } catch (error) { + console.error('Login error:', error); + } } } }; diff --git a/pages/settings.vue b/pages/settings.vue index 1261a7f..796cf8c 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -1,9 +1,9 @@ diff --git a/panel.config.ts b/panel.config.ts index ce258f1..8c2d37c 100644 --- a/panel.config.ts +++ b/panel.config.ts @@ -5,7 +5,7 @@ export const settings = reactive({ //Leave empty to scan all interfaces //or change item to "disabled" to disable interface scanning interfaces_to_scan:[ - "eth0" + "enp4s0" ], enable_qemu_controls: true, qemu_vms: [ @@ -22,5 +22,9 @@ export const settings = reactive({ systemctl_services:[ "libvirt", "frp" - ] + ], + password:{ + hash: "$2y$10$04HVBBemPypGbaMhTmUxX.DUMir1HA4hT6cst.dGabot1ZWR5IQ.6", + salt_rounds: 10 + }, }); \ No newline at end of file diff --git a/server/api/auth.ts b/server/api/auth.ts new file mode 100644 index 0000000..235d126 --- /dev/null +++ b/server/api/auth.ts @@ -0,0 +1,30 @@ +import { defineEventHandler, getCookie, createError } from 'h3'; +import jwt from 'jsonwebtoken'; +import {jwt_globals} from "~/core/globals"; + +export default defineEventHandler(async (event) => { + try { + const token = getCookie(event, 'token'); + console.log("Checking token " + token); + if (!token) { + throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); + } + + const secret = jwt_globals.secret; + if (!secret) { + throw createError({ statusCode: 500, statusMessage: 'JWT secret not set' }); + } + + const decoded = jwt.verify(token, secret) as { userId: string }; + if (!decoded?.userId) { + throw createError({ statusCode: 401, statusMessage: 'Invalid token' }); + } + console.log("user has been authed, hash: " + decoded.userId); + return { success: true }; + } catch (error: any) { + return createError({ + statusCode: error.statusCode || 500, + statusMessage: error.statusMessage || 'Invalid or expired token', + }); + } +}); \ No newline at end of file diff --git a/server/api/getNetworkInterfaces.ts b/server/api/getNetworkInterfaces.ts index 8410a32..168a167 100644 --- a/server/api/getNetworkInterfaces.ts +++ b/server/api/getNetworkInterfaces.ts @@ -45,10 +45,6 @@ export default defineEventHandler(async () => { state: network.operstate as "up" | "down" | "unknown" }) } - - interfaces.forEach(obj => { - console.log(obj.name + " is " + obj.state); - }) return interfaces; } catch (error) { diff --git a/server/api/login.ts b/server/api/login.ts new file mode 100644 index 0000000..67b6af0 --- /dev/null +++ b/server/api/login.ts @@ -0,0 +1,36 @@ +import bcrypt from 'bcryptjs'; +import { sendError, createError } from 'h3'; +import jwt from 'jsonwebtoken'; +import {settings} from "~/panel.config"; +import {jwt_globals} from "~/core/globals"; + +export default defineEventHandler(async (event) => { + try { + const { password } = await readBody(event); + + if (!password) { + console.log("password is required"); + return sendError(event, createError({ statusCode: 400, message: 'password is required' })); + } + + + + const isMatch = await bcrypt.compare(password, settings.password.hash); + if (!isMatch) { + console.log("Invalid credentials! password"); + return sendError(event, createError({ statusCode: 400, message: 'Invalid credentials!' })); + } + + const token = jwt.sign({ userId: password }, jwt_globals.secret!, { + expiresIn: '1h', + }); + + return { + message: 'Login successful!', + token + }; + } catch (error) { + console.error("Login error: ", error); + return sendError(event, createError({ statusCode: 500, message: 'Internal server error' })); + } +}); diff --git a/server/plugins/init.ts b/server/plugins/init.ts index e7c83cf..47ffa21 100644 --- a/server/plugins/init.ts +++ b/server/plugins/init.ts @@ -1,7 +1,13 @@ import { defineNitroPlugin } from "#imports"; - +import { reactive } from "vue"; +import * as crypto from 'crypto'; +import {jwt_globals} from "~/core/globals"; export default defineNitroPlugin((nitroApp) => { - console.log("Loading Server Settings"); - + console.log("Running init..."); + console.log("Generating jwt secret...") + jwt_globals.secret = crypto.randomBytes(32).toString('base64'); + console.log("secret: " + jwt_globals.secret) }); + + diff --git a/util/auth.ts b/util/auth.ts new file mode 100644 index 0000000..ad7b067 --- /dev/null +++ b/util/auth.ts @@ -0,0 +1,21 @@ +// util/auth.ts +import axios from 'axios'; +import { useRouter } from 'vue-router'; // Import useRouter + +export async function checkAuth(router: any) { + try { + const response = await axios.get('/api/auth', { + withCredentials: true, // Ensure cookies are sent with the request + }); + + if (!response.data.success) { + router.push('/login'); + } + + return response.data.success; + } catch (error) { + console.log("Not authenticated, redirecting to /login"); + router.push('/login'); + return false; + } +}