added auth

This commit is contained in:
WeeXnes 2025-02-27 02:56:21 +01:00
parent f6b3c35b70
commit 42a77072e1
10 changed files with 145 additions and 45 deletions

6
core/globals.ts Normal file
View file

@ -0,0 +1,6 @@
import {reactive} from "vue";
export const jwt_globals = reactive({
secret: "",
});

View file

@ -4,10 +4,10 @@ import { reactive, ref } from 'vue';
import axios from 'axios'; import axios from 'axios';
import type {networkInterface} from "~/types/networkInterface"; import type {networkInterface} from "~/types/networkInterface";
import type {serviceInterface} from "~/types/serviceInterface"; import type {serviceInterface} from "~/types/serviceInterface";
import {checkAuth} from "~/util/auth";
const ignoreCache = true;
const startVm = async (vm: any) => { const startVm = async (vm: any) => {
@ -195,20 +195,21 @@ const fetchSettings = async () => {
} }
onMounted(async () => { onMounted(async () => {
await fetchSettings() let isAuthed = await checkAuth(useRouter())
if(isAuthed){
if(settings.enable_qemu_controls) await fetchVMs() await fetchSettings()
if(settings.enable_qemu_controls) await fetchVMs()
await fetchOsInfo() await fetchOsInfo()
await fetchCpuTemp() await fetchCpuTemp()
cpuInfo.isLoaded = true cpuInfo.isLoaded = true
await fetchMemoryInfo() await fetchMemoryInfo()
await fetchNetworkInfo() await fetchNetworkInfo()
if(settings.enable_services) await fetchServiceInfo() if(settings.enable_services) await fetchServiceInfo()
const intervalId = setInterval(fetchCpuTemp, 7000); const intervalId = setInterval(fetchCpuTemp, 7000);
onUnmounted(() => { onUnmounted(() => {
clearInterval(intervalId); clearInterval(intervalId);
}); });
}
}) })

View file

@ -4,19 +4,6 @@
<h2 class="text-2xl font-bold text-center">Login</h2> <h2 class="text-2xl font-bold text-center">Login</h2>
<form @submit.prevent="handleLogin" class="mt-4"> <form @submit.prevent="handleLogin" class="mt-4">
<div class="form-control">
<label class="label">
<span class="label-text">E-Mail</span>
</label>
<input
v-model="email"
type="email"
placeholder="Enter your email"
class="input input-bordered"
required
/>
</div>
<div class="form-control mt-2"> <div class="form-control mt-2">
<label class="label"> <label class="label">
<span class="label-text">Password</span> <span class="label-text">Password</span>
@ -34,10 +21,6 @@
<button type="submit" class="btn btn-primary w-full">Login</button> <button type="submit" class="btn btn-primary w-full">Login</button>
</div> </div>
</form> </form>
<div class="mt-4 text-center text-sm">
<p>Don't have an account? <a href="/register" class="text-blue-500 hover:underline">Register here</a></p>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -57,7 +40,24 @@ export default {
}, },
methods: { methods: {
async handleLogin() { 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);
}
} }
} }
}; };

View file

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VM } from '../types/VM'; import {checkAuth} from "~/util/auth";
import axios from 'axios';
onMounted(async () => {
let isAuthed = await checkAuth(useRouter())
})
</script> </script>

View file

@ -5,7 +5,7 @@ export const settings = reactive({
//Leave empty to scan all interfaces //Leave empty to scan all interfaces
//or change item to "disabled" to disable interface scanning //or change item to "disabled" to disable interface scanning
interfaces_to_scan:[ interfaces_to_scan:[
"eth0" "enp4s0"
], ],
enable_qemu_controls: true, enable_qemu_controls: true,
qemu_vms: [ qemu_vms: [
@ -22,5 +22,9 @@ export const settings = reactive({
systemctl_services:[ systemctl_services:[
"libvirt", "libvirt",
"frp" "frp"
] ],
password:{
hash: "$2y$10$04HVBBemPypGbaMhTmUxX.DUMir1HA4hT6cst.dGabot1ZWR5IQ.6",
salt_rounds: 10
},
}); });

30
server/api/auth.ts Normal file
View file

@ -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',
});
}
});

View file

@ -45,10 +45,6 @@ export default defineEventHandler(async () => {
state: network.operstate as "up" | "down" | "unknown" state: network.operstate as "up" | "down" | "unknown"
}) })
} }
interfaces.forEach(obj => {
console.log(obj.name + " is " + obj.state);
})
return interfaces; return interfaces;
} catch (error) { } catch (error) {

36
server/api/login.ts Normal file
View file

@ -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' }));
}
});

View file

@ -1,7 +1,13 @@
import { defineNitroPlugin } from "#imports"; import { defineNitroPlugin } from "#imports";
import { reactive } from "vue";
import * as crypto from 'crypto';
import {jwt_globals} from "~/core/globals";
export default defineNitroPlugin((nitroApp) => { 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)
}); });

21
util/auth.ts Normal file
View file

@ -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;
}
}