This commit is contained in:
Donald Zou 2025-06-05 15:57:17 +08:00
parent 68fae3b23c
commit 41bf9b8baa
8 changed files with 123 additions and 33 deletions

View File

@ -55,9 +55,9 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
@client.get(f'{prefix}/api/signout') @client.get(f'{prefix}/api/signout')
def ClientAPI_SignOut(): def ClientAPI_SignOut():
session.pop('username') session['username'] = None
session.pop('role') session['role'] = None
session.pop('totpVerified') session['totpVerified'] = None
return ResponseObject(True) return ResponseObject(True)
@client.get(f'{prefix}/api/signin/totp') @client.get(f'{prefix}/api/signin/totp')
@ -81,7 +81,7 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
if session.get('username') is None: if session.get('username') is None:
return ResponseObject(False, "Sign in status is invalid", status_code=401) return ResponseObject(False, "Sign in status is invalid", status_code=401)
session['totpVerified'] = True session['totpVerified'] = True
# return ResponseObject(True, data=) return ResponseObject(True)
return ResponseObject(status, msg) return ResponseObject(status, msg)
@client.get(prefix) @client.get(prefix)

View File

@ -6,6 +6,7 @@ import pyotp
import sqlalchemy as db import sqlalchemy as db
from .ConnectionString import ConnectionString from .ConnectionString import ConnectionString
from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment
from .DashboardClientsTOTP import DashboardClientsTOTP from .DashboardClientsTOTP import DashboardClientsTOTP
from .Utilities import ValidatePasswordStrength from .Utilities import ValidatePasswordStrength
from .DashboardLogger import DashboardLogger from .DashboardLogger import DashboardLogger
@ -18,7 +19,6 @@ class DashboardClients:
self.engine = db.create_engine(ConnectionString("wgdashboard")) self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData() self.metadata = db.MetaData()
self.dashboardClientsTable = db.Table( self.dashboardClientsTable = db.Table(
'DashboardClients', self.metadata, 'DashboardClients', self.metadata,
db.Column('ClientID', db.String(255), nullable=False, primary_key=True), db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
@ -46,6 +46,7 @@ class DashboardClients:
self.Clients = [] self.Clients = []
self.__getClients() self.__getClients()
self.DashboardClientsTOTP = DashboardClientsTOTP() self.DashboardClientsTOTP = DashboardClientsTOTP()
self.DashboardClientsPeerAssignment = DashboardClientsPeerAssignment()
def __getClients(self): def __getClients(self):
with self.engine.connect() as conn: with self.engine.connect() as conn:
@ -76,6 +77,7 @@ class DashboardClients:
def SignIn_GetTotp(self, Token: str, UserProvidedTotp: str = None) -> tuple[bool, str] or tuple[bool, None, str]: def SignIn_GetTotp(self, Token: str, UserProvidedTotp: str = None) -> tuple[bool, str] or tuple[bool, None, str]:
status, data = self.DashboardClientsTOTP.GetTotp(Token) status, data = self.DashboardClientsTOTP.GetTotp(Token)
if not status: if not status:
return False, "TOTP Token is invalid" return False, "TOTP Token is invalid"
if UserProvidedTotp is None: if UserProvidedTotp is None:
@ -83,7 +85,7 @@ class DashboardClients:
return True, pyotp.totp.TOTP(data.get('TotpKey')).provisioning_uri(name=data.get('Email'), return True, pyotp.totp.TOTP(data.get('TotpKey')).provisioning_uri(name=data.get('Email'),
issuer_name="WGDashboard Client") issuer_name="WGDashboard Client")
else: else:
totpMatched = pyotp.TOTP(data.get('TotpKey')).verify(UserProvidedTotp) totpMatched = pyotp.totp.TOTP(data.get('TotpKey')).verify(UserProvidedTotp)
if not totpMatched: if not totpMatched:
return False, "TOTP is does not match" return False, "TOTP is does not match"
else: else:

View File

@ -0,0 +1,46 @@
from .ConnectionString import ConnectionString
from .DashboardLogger import DashboardLogger
import sqlalchemy as db
class DashboardClientsPeerAssignment:
def __init__(self):
self.logger = DashboardLogger()
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.dashboardClientsPeerAssignmentTable = db.Table(
'DashboardClientsPeerAssignment', self.metadata,
db.Column('AssignmentID', db.String(255), nullable=False, primary_key=True),
db.Column('ClientID', db.String(255), nullable=False, index=True),
db.Column('ConfigurationName', db.String(255)),
db.Column('PeerID', db.String(500)),
db.Column('AssignedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('UnassignedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
extend_existing=True
)
self.metadata.create_all(self.engine)
self.assignments = []
def __getAssignments(self):
with self.engine.connect() as conn:
self.assignments = conn.execute(
self.dashboardClientsPeerAssignmentTable.select().where(
self.dashboardClientsPeerAssignmentTable.c.UnassignedDate is None
)
).mappings().fetchall()
def AssignClient(self, ClientID, ConfigurationName, PeerID):
pass
def UnassignClient(self, AssignmentID):
pass
def GetAssignedClient(self, ConfigurationName, PeerID):
pass
def GetAssignedPeers(self, ClientID):
pass

View File

@ -0,0 +1,22 @@
<script setup>
import {onMounted} from "vue";
import QRCode from "qrcode";
const props = defineProps([
"content"
])
onMounted(() => {
QRCode.toCanvas(document.getElementById('qrcode'), props.content, function (error) {})
})
</script>
<template>
<div>
<canvas id="qrcode" class="rounded-3 shadow"></canvas>
</div>
</template>
<style scoped>
</style>

View File

@ -2,7 +2,7 @@
import {computed, reactive, ref} from "vue"; import {computed, reactive, ref} from "vue";
import {clientStore} from "@/stores/clientStore.js"; import {clientStore} from "@/stores/clientStore.js";
import axios from "axios"; import axios from "axios";
import {requestURl} from "@/utilities/request.js"; import {axiosPost, requestURl} from "@/utilities/request.js";
import {useRoute, useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
const loading = ref(false) const loading = ref(false)
const formData = reactive({ const formData = reactive({
@ -20,15 +20,14 @@ const signIn = async (e) => {
return; return;
} }
loading.value = true; loading.value = true;
await axios.post(requestURl("/api/signin"), formData).then(res => {
let data = res.data; const data = await axiosPost("/api/signin", formData)
if (!data.status){ if (!data.status){
store.newNotification(data.message, "danger") store.newNotification(data.message, "danger")
loading.value = false; loading.value = false;
}else{ }else{
emits("totpToken", data.message) emits("totpToken", data.message)
} }
})
} }
const formFilled = computed(() => { const formFilled = computed(() => {

View File

@ -1,10 +1,10 @@
<script setup async> <script setup async>
import {computed, onMounted, reactive, ref} from "vue"; import {computed, onMounted, reactive, ref} from "vue";
import axios from "axios"; import axios from "axios";
import {requestURl} from "@/utilities/request.js"; import {axiosPost, requestURl} from "@/utilities/request.js";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import {clientStore} from "@/stores/clientStore.js"; import {clientStore} from "@/stores/clientStore.js";
import QRCode from "qrcode"; import Qrcode from "@/components/SignIn/qrcode.vue";
const props = defineProps([ const props = defineProps([
'totpToken' 'totpToken'
@ -47,30 +47,28 @@ onMounted(() => {
const emits = defineEmits(['clearToken']) const emits = defineEmits(['clearToken'])
onMounted(() => { onMounted(() => {
if (totpKey.value){
QRCode.toCanvas(document.getElementById('qrcode'), totpKey.value, function (error) {})
}
}) })
const verify = async (e) => { const verify = async (e) => {
e.preventDefault() e.preventDefault()
if (formFilled){ if (formFilled){
loading.value = true loading.value = true
await axios.post(requestURl('/api/signin/totp'), { const data = await axiosPost('/api/signin/totp', {
Token: props.totpToken, Token: props.totpToken,
UserProvidedTOTP: formData.TOTP UserProvidedTOTP: formData.TOTP
}).then(res => { })
loading.value = false loading.value = false
let data = res.data if (data){
if (data.status){ if (data.status){
router.push('/') router.push('/')
}else{ }else{
store.newNotification(data.message, "danger") store.newNotification(data.message, "danger")
} }
}).catch(() => { }else{
store.newNotification("Sign in status is invalid", "danger") store.newNotification("Sign in status is invalid", "danger")
emits('clearToken') emits('clearToken')
}) }
} }
} }
</script> </script>
@ -88,7 +86,7 @@ const verify = async (e) => {
<div class="card-body d-flex gap-3 flex-column"> <div class="card-body d-flex gap-3 flex-column">
<h2 class="mb-0">Initial Setup</h2> <h2 class="mb-0">Initial Setup</h2>
<p class="mb-0">Please scan the following QR Code to generate TOTP with your choice of authenticator</p> <p class="mb-0">Please scan the following QR Code to generate TOTP with your choice of authenticator</p>
<canvas id="qrcode" class="rounded-3 shadow "></canvas> <Qrcode :content="totpKey"></Qrcode>
<p class="mb-0">Or you can click the link below:</p> <p class="mb-0">Or you can click the link below:</p>
<div class="card rounded-3 "> <div class="card rounded-3 ">
<div class="card-body"> <div class="card-body">

View File

@ -3,7 +3,7 @@ import Index from "@/views/index.vue";
import SignIn from "@/views/signin.vue"; import SignIn from "@/views/signin.vue";
import SignUp from "@/views/signup.vue"; import SignUp from "@/views/signup.vue";
import axios from "axios"; import axios from "axios";
import {requestURl} from "@/utilities/request.js"; import {axiosGet, requestURl} from "@/utilities/request.js";
import {clientStore} from "@/stores/clientStore.js"; import {clientStore} from "@/stores/clientStore.js";
const router = createRouter({ const router = createRouter({
@ -37,6 +37,8 @@ router.beforeEach(async (to, from, next) => {
const store = clientStore() const store = clientStore()
if (to.path === '/signout'){ if (to.path === '/signout'){
await axios.get(requestURl('/api/signout')).then(() => { await axios.get(requestURl('/api/signout')).then(() => {
next('/signin') next('/signin')
}).catch(() => { }).catch(() => {
@ -45,13 +47,13 @@ router.beforeEach(async (to, from, next) => {
store.newNotification("Sign in session ended, please sign in again", "warning") store.newNotification("Sign in session ended, please sign in again", "warning")
}else{ }else{
if (to.meta.auth){ if (to.meta.auth){
await axios.get(requestURl('/api/validateAuthentication')).then(res => { const status = await axiosGet('/api/validateAuthentication')
if (status){
next() next()
}).catch(() => { }else{
store.newNotification("Sign in session ended, please sign in again", "warning") store.newNotification("Sign in session ended, please sign in again", "warning")
next('/signin') next('/signin')
}) }
}else{ }else{
next() next()
} }

View File

@ -1,5 +1,26 @@
import axios from "axios";
export const requestURl = (url) => { export const requestURl = (url) => {
return import.meta.env.MODE === 'development' ? '/client' + url return import.meta.env.MODE === 'development' ? '/client' + url
: `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}` : `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`
} }
export const axiosPost = async (URL, body = {}) => {
try{
const res = await axios.post(requestURl(URL), body)
return res.data
} catch (error){
console.log(error)
return undefined
}
}
export const axiosGet = async (URL, query = {}) => {
try{
const res = await axios.get(requestURl(URL), query)
return res.data
} catch (error){
console.log(error)
return undefined
}
}