Added update password in settings

This commit is contained in:
Donald Zou 2025-06-20 16:00:19 +08:00
parent d80eb03707
commit 6f848e3df8
7 changed files with 176 additions and 81 deletions

View File

@ -106,4 +106,12 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
def ClientAPI_Settings_GetClientProfile(): def ClientAPI_Settings_GetClientProfile():
return ResponseObject(data=DashboardClients.GetClientProfile(session['ClientID'])) return ResponseObject(data=DashboardClients.GetClientProfile(session['ClientID']))
@client.post(f'{prefix}/api/settings/updatePassword')
@login_required
def ClientAPI_Settings_UpdatePassword():
data = request.json
status, message = DashboardClients.UpdateClientPassword(session['Email'], **data)
return ResponseObject(status, message)
return client return client

View File

@ -10,7 +10,6 @@ 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
from flask import session from flask import session
@ -70,20 +69,33 @@ class DashboardClients:
) )
).mappings().fetchone()) ).mappings().fetchone())
def SignIn(self, Email, Password) -> tuple[bool, str]: def SignIn_ValidatePassword(self, Email, Password) -> bool:
if not all([Email, Password]): if not all([Email, Password]):
return False, "Please fill in all fields" return False
existingClient = self.SignIn_UserExistence(Email)
if existingClient:
return bcrypt.checkpw(Password.encode("utf-8"), existingClient.get("Password").encode("utf-8"))
return False
def SignIn_UserExistence(self, Email):
with self.engine.connect() as conn: with self.engine.connect() as conn:
existingClient = conn.execute( existingClient = conn.execute(
self.dashboardClientsTable.select().where( self.dashboardClientsTable.select().where(
self.dashboardClientsTable.c.Email == Email self.dashboardClientsTable.c.Email == Email
) )
).mappings().fetchone() ).mappings().fetchone()
if existingClient: return existingClient
checkPwd = bcrypt.checkpw(Password.encode("utf-8"), existingClient.get("Password").encode("utf-8"))
if checkPwd: def SignIn(self, Email, Password) -> tuple[bool, str]:
session['ClientID'] = existingClient.get("ClientID") if not all([Email, Password]):
return True, self.DashboardClientsTOTP.GenerateToken(existingClient.get("ClientID")) return False, "Please fill in all fields"
existingClient = self.SignIn_UserExistence(Email)
if existingClient:
checkPwd = self.SignIn_ValidatePassword(Email, Password)
if checkPwd:
session['Email'] = Email
session['ClientID'] = existingClient.get("ClientID")
return True, self.DashboardClientsTOTP.GenerateToken(existingClient.get("ClientID"))
return False, "Email or Password is incorrect" return False, "Email or Password is incorrect"
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]:
@ -120,14 +132,9 @@ class DashboardClients:
if Password != ConfirmPassword: if Password != ConfirmPassword:
return False, "Passwords does not match" return False, "Passwords does not match"
with self.engine.connect() as conn: existingClient = self.SignIn_UserExistence(Email)
existingClient = conn.execute( if existingClient:
self.dashboardClientsTable.select().where( return False, "Email already signed up"
self.dashboardClientsTable.c.Email == Email
)
).mappings().fetchone()
if existingClient:
return False, "Email already signed up"
pwStrength, msg = ValidatePasswordStrength(Password) pwStrength, msg = ValidatePasswordStrength(Password)
if not pwStrength: if not pwStrength:
@ -150,6 +157,7 @@ class DashboardClients:
"ClientID": newClientUUID "ClientID": newClientUUID
}) })
) )
self.logger.log(Message=f"User {Email} signed up")
except Exception as e: except Exception as e:
self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}") self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}")
return False, "Signed up failed." return False, "Signed up failed."
@ -159,5 +167,30 @@ class DashboardClients:
def GetClientAssignedPeers(self, ClientID): def GetClientAssignedPeers(self, ClientID):
return self.DashboardClientsPeerAssignment.GetAssignedPeers(ClientID) return self.DashboardClientsPeerAssignment.GetAssignedPeers(ClientID)
def UpdatePassword(self, CurrentPassword, NewPassword, ConfirmNewPassword): def UpdateClientPassword(self, Email, CurrentPassword, NewPassword, ConfirmNewPassword):
pass if not all([CurrentPassword, NewPassword, ConfirmNewPassword]):
return False, "Please fill in all fields"
if not self.SignIn_ValidatePassword(Email, CurrentPassword):
return False, "Current password does not match"
if NewPassword != ConfirmNewPassword:
return False, "New passwords does not match"
pwStrength, msg = ValidatePasswordStrength(NewPassword)
if not pwStrength:
return pwStrength, msg
try:
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTable.update().values({
"Password": bcrypt.hashpw(NewPassword.encode('utf-8'), bcrypt.gensalt()).decode("utf-8"),
}).where(
self.dashboardClientsTable.c.Email == Email
)
)
self.logger.log(Message=f"User {Email} updated password")
except Exception as e:
self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}")
return False, "Signed up failed."
return True, None

View File

@ -9,7 +9,6 @@ import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router/router.js' import router from './router/router.js'
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
let Locale; let Locale;
await fetch("/api/locale") await fetch("/api/locale")
.then(res => res.json()) .then(res => res.json())

View File

@ -1,69 +1,26 @@
<script setup> <script setup>
import {clientStore} from "@/stores/clientStore.js"; import {clientStore} from "@/stores/clientStore.js";
import {reactive} from "vue";
const store = clientStore() const store = clientStore()
const ProfileLabels = { const ProfileLabels = {
Firstname: "First Name", Firstname: "First Name",
Lastname: "Last Name" Lastname: "Last Name"
} }
const Password = reactive({
CurrentPassword: "",
NewPassword: "",
RepeatNewPassword: ""
})
const ResetPasswordFields = () => {
Password.CurrentPassword = ""
Password.NewPassword = ""
Password.RepeatNewPassword = ""
}
</script> </script>
<template> <template>
<div class="d-flex flex-column gap-4 p-3"> <div class="p-3">
<div> <h5>
<h5> Profile
Profile </h5>
</h5> <div class="row g-2">
<div class="row g-2"> <div class="col-sm-6" v-for="(val, key) in store.clientProfile.Profile">
<div class="col-sm-6" v-for="(val, key) in store.clientProfile.Profile"> <label :for="key" class="text-muted form-label">
<label :for="key" class="text-muted form-label"> <small>{{ ProfileLabels[key] }}</small>
<small>{{ ProfileLabels[key] }}</small> </label>
</label> <input :id="key" class="form-control rounded-3" v-model="store.clientProfile.Profile[key]">
<input :id="key" class="form-control rounded-3" v-model="store.clientProfile.Profile[key]">
</div>
</div> </div>
</div> </div>
<form @submit="undefined" @reset="ResetPasswordFields()">
<h5>
Update Password
</h5>
<div class="row g-2 mb-3">
<div class="col-sm-12">
<label class="text-muted form-label" for="CurrentPassword">
<small>Current Password</small>
</label>
<input class="form-control rounded-3" type="password" autocomplete="current-password" id="CurrentPassword" v-model="Password.CurrentPassword">
</div>
<div class="col-sm-6">
<label class="text-muted form-label" for="NewPassword">
<small>New Password</small>
</label>
<input class="form-control rounded-3" type="password" id="NewPassword" autocomplete="new-password" v-model="Password.NewPassword">
</div>
<div class="col-sm-6">
<label class="text-muted form-label" for="RepeatNewPassword">
<small>Repeat New Password</small>
</label>
<input class="form-control rounded-3" type="password" id="RepeatNewPassword" autocomplete="new-password" v-model="Password.RepeatNewPassword">
</div>
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-secondary rounded-3 ms-auto" type="reset">Clear</button>
<button class="btn btn-sm btn-danger rounded-3" type="submit">Update</button>
</div>
</form>
</div> </div>
</template> </template>

View File

@ -0,0 +1,102 @@
<script setup>
import {reactive, ref} from "vue";
import {axiosPost} from "@/utilities/request.js";
import {clientStore} from "@/stores/clientStore.js";
const Password = reactive({
CurrentPassword: "",
NewPassword: "",
ConfirmNewPassword: ""
})
const ResetPasswordFields = () => {
Password.CurrentPassword = ""
Password.NewPassword = ""
Password.ConfirmNewPassword = ""
}
const store = clientStore()
const UpdatePassword = async (e) => {
e.preventDefault();
document.querySelectorAll("#updatePasswordForm input").forEach(x => x.blur())
const data = await axiosPost('/api/settings/updatePassword', Password)
if(data){
if (!data.status){
formInvalid.value = true;
formInvalidMessage.value = data.message;
}else{
formInvalid.value = false
store.newNotification("Password updated!", "success")
ResetPasswordFields()
}
}else{
formInvalid.value = true;
formInvalidMessage.value = "Error occurred"
}
}
const showPassword = ref(false)
const formInvalid = ref(false)
const formInvalidMessage = ref("")
</script>
<template>
<form @submit="(e) => UpdatePassword(e)"
id="updatePasswordForm"
@reset="ResetPasswordFields()" class="p-3">
<div class="d-flex align-items-start">
<h5>
Update Password
</h5>
<a role="button"
@click="showPassword = !showPassword"
class="text-muted ms-auto text-decoration-none">
<small>
<i
:class="[showPassword ? 'bi-eye-slash-fill':'bi-eye-fill']"
class="bi me-2"></i>{{ showPassword ? 'Hide':'Show'}} Password
</small>
</a>
</div>
<div class="alert alert-danger rounded-3 mt-3" v-if="formInvalid">
{{ formInvalidMessage }}
</div>
<div class="row g-2 mb-3">
<div class="col-sm-12">
<label
class="text-muted form-label" for="CurrentPassword">
<small>Current Password</small>
</label>
<input class="form-control rounded-3" :class="{'is-invalid': formInvalid}" required
:type="showPassword ? 'text':'password'" autocomplete="current-password" id="CurrentPassword" v-model="Password.CurrentPassword">
</div>
<div class="col-sm-6">
<label class="text-muted form-label" for="NewPassword">
<small>New Password</small>
</label>
<input class="form-control rounded-3"
required
:class="{'is-invalid': formInvalid}"
:type="showPassword ? 'text':'password'"
id="NewPassword" autocomplete="new-password" v-model="Password.NewPassword">
</div>
<div class="col-sm-6">
<label class="text-muted form-label" for="ConfirmNewPassword">
<small>Confirm New Password</small>
</label>
<input class="form-control rounded-3"
required
:class="{'is-invalid': formInvalid}"
:type="showPassword ? 'text':'password'"
id="ConfirmNewPassword" autocomplete="new-password" v-model="Password.ConfirmNewPassword">
</div>
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-secondary rounded-3 ms-auto" type="reset">Clear</button>
<button class="btn btn-sm btn-danger rounded-3" type="submit">Update</button>
</div>
</form>
</template>
<style scoped>
</style>

View File

@ -6,18 +6,12 @@ import Configuration from "@/components/Configuration/configuration.vue";
const store = clientStore() const store = clientStore()
const loading = ref(true) const loading = ref(true)
const loadConfigurations = async () => {
await store.getConfigurations()
}
const configurations = computed(() => { const configurations = computed(() => {
return store.configurations return store.configurations
}); });
onMounted(async () => { onMounted(async () => {
await loadConfigurations(); await store.getConfigurations()
loading.value = false; loading.value = false;
}) })
</script> </script>

View File

@ -1,6 +1,7 @@
<script setup async> <script setup async>
import {clientStore} from "@/stores/clientStore.js"; import {clientStore} from "@/stores/clientStore.js";
import Profile from "@/components/Settings/profile.vue"; import Profile from "@/components/Settings/profile.vue";
import UpdatePassword from "@/components/Settings/updatePassword.vue";
const store = clientStore() const store = clientStore()
await store.getClientProfile(); await store.getClientProfile();
@ -16,6 +17,7 @@ await store.getClientProfile();
<strong class="ms-auto">Settings</strong> <strong class="ms-auto">Settings</strong>
</div> </div>
<Profile></Profile> <Profile></Profile>
<UpdatePassword></UpdatePassword>
</div> </div>
</template> </template>