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

View File

@ -6,6 +6,7 @@ import pyotp
import sqlalchemy as db
from .ConnectionString import ConnectionString
from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment
from .DashboardClientsTOTP import DashboardClientsTOTP
from .Utilities import ValidatePasswordStrength
from .DashboardLogger import DashboardLogger
@ -18,7 +19,6 @@ class DashboardClients:
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.dashboardClientsTable = db.Table(
'DashboardClients', self.metadata,
db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
@ -46,6 +46,7 @@ class DashboardClients:
self.Clients = []
self.__getClients()
self.DashboardClientsTOTP = DashboardClientsTOTP()
self.DashboardClientsPeerAssignment = DashboardClientsPeerAssignment()
def __getClients(self):
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]:
status, data = self.DashboardClientsTOTP.GetTotp(Token)
if not status:
return False, "TOTP Token is invalid"
if UserProvidedTotp is None:
@ -83,7 +85,7 @@ class DashboardClients:
return True, pyotp.totp.TOTP(data.get('TotpKey')).provisioning_uri(name=data.get('Email'),
issuer_name="WGDashboard Client")
else:
totpMatched = pyotp.TOTP(data.get('TotpKey')).verify(UserProvidedTotp)
totpMatched = pyotp.totp.TOTP(data.get('TotpKey')).verify(UserProvidedTotp)
if not totpMatched:
return False, "TOTP is does not match"
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 {clientStore} from "@/stores/clientStore.js";
import axios from "axios";
import {requestURl} from "@/utilities/request.js";
import {axiosPost, requestURl} from "@/utilities/request.js";
import {useRoute, useRouter} from "vue-router";
const loading = ref(false)
const formData = reactive({
@ -20,15 +20,14 @@ const signIn = async (e) => {
return;
}
loading.value = true;
await axios.post(requestURl("/api/signin"), formData).then(res => {
let data = res.data;
if (!data.status){
store.newNotification(data.message, "danger")
loading.value = false;
}else{
emits("totpToken", data.message)
}
})
const data = await axiosPost("/api/signin", formData)
if (!data.status){
store.newNotification(data.message, "danger")
loading.value = false;
}else{
emits("totpToken", data.message)
}
}
const formFilled = computed(() => {

View File

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

View File

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

View File

@ -1,5 +1,26 @@
import axios from "axios";
export const requestURl = (url) => {
return import.meta.env.MODE === 'development' ? '/client' + url
: `${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
}
}