Added generate reset client password link

This commit is contained in:
Donald Zou 2025-07-24 23:12:51 +08:00
parent 722cbb6054
commit 674fea7063
4 changed files with 108 additions and 55 deletions

View File

@ -1282,6 +1282,17 @@ def API_Clients_AssignedPeers():
return ResponseObject(False, "Client does not exist")
return ResponseObject(data=d)
@app.post(f'{APP_PREFIX}/api/clients/generatePasswordResetLink')
def API_Clients_GeneratePasswordResetLink():
data = request.get_json()
clientId = data.get("ClientID")
if not clientId:
return ResponseObject(False, "Please provide ClientID")
token = DashboardClients.GenerateClientPasswordResetLink(clientId)
if token:
return ResponseObject(data=token)
return ResponseObject(False, "Failed to generate link")
'''
Index Page

View File

@ -1,3 +1,4 @@
import datetime
import hashlib
import uuid
@ -57,6 +58,18 @@ class DashboardClients:
db.Column('Name', db.String(500)),
extend_existing=True,
)
self.dashboardClientsPasswordResetLinkTable = db.Table(
'DashboardClientsPasswordResetLinks', self.metadata,
db.Column('ResetToken', db.String(255), nullable=False, primary_key=True),
db.Column('ClientID', db.String(255), nullable=False),
db.Column('CreatedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('ExpiryDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
extend_existing=True
)
self.metadata.create_all(self.engine)
self.Clients = {}
@ -112,7 +125,6 @@ class DashboardClients:
return self.ClientsRaw
def GetClient(self, ClientID) -> dict[str, str] | None:
self.__getClients()
c = filter(lambda x: x['ClientID'] == ClientID, self.ClientsRaw)
client = next((dict(client) for client in c), None)
if client is not None:
@ -322,6 +334,33 @@ class DashboardClients:
'''
For WGDashboard Admin to Manage Clients
'''
def GenerateClientPasswordResetLink(self, ClientID) -> bool | str:
c = self.GetClient(ClientID)
if c is None:
return False
newToken = str(uuid.uuid4())
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsPasswordResetLinkTable.update().values({
"ExpiryDate": db.func.now()
}).where(
self.dashboardClientsPasswordResetLinkTable.c.ClientID == ClientID
)
)
conn.execute(
self.dashboardClientsPasswordResetLinkTable.insert().values({
"ResetToken": newToken,
"ClientID": ClientID,
"ExpiryDate": datetime.datetime.now() + datetime.timedelta(minutes=30)
})
)
return newToken
def GetAssignedPeerClients(self, ConfigurationName, PeerID):
c = self.DashboardClientsPeerAssignment.GetAssignedClients(ConfigurationName, PeerID)
for a in c:

View File

@ -1,38 +1,65 @@
<script setup lang="ts">
import LocaleText from "@/components/text/localeText.vue";
import { fetchGet, fetchPost } from "@/utilities/fetch.js"
import {ref} from "vue";
const props = defineProps(['client'])
const alert = ref(false)
const alertStatus = ref(false)
const alertMessage = ref(false)
const sendResetLink = async () => {
let smtpReady = false;
let token = undefined;
await fetchPost('/api/clients/generatePasswordResetLink', {
ClientID: props.client.ClientID
},(res) => {
if (res.status){
token = res.data
alertStatus.value = true
}else{
alertStatus.value = false
alertMessage.value = res.message
alert.value = true
}
})
if (token){
await fetchGet('/api/email/ready', {}, (res) => {
smtpReady = res.status
});
if (smtpReady){
await fetchPost('/api/email/send', {
"Receiver": props.client.Email,
"Body":
`Hi${props.client.Name ? ' ' + props.client.Name: ''},\n`
}, (res) => {
});
}else{
}
}
}
</script>
<template>
<div class="p-3">
<h6>
<LocaleText t="Reset Password"></LocaleText>
</h6>
<div class="row g-2">
<div class="col-sm-4">
<label class="mb-1">
<small class="fw-bold text-muted">
<LocaleText t="Current Password"></LocaleText>
</small>
</label>
<input type="password" class="form-control form-control-sm rounded-3">
</div>
<div class="col-sm-4">
<label class="mb-1">
<small class="fw-bold text-muted">
<LocaleText t="New Password"></LocaleText>
</small>
</label>
<input type="password" class="form-control form-control-sm rounded-3">
</div>
<div class="col-sm-4">
<label class="mb-1">
<small class="fw-bold text-muted">
<LocaleText t="Confirm New Password"></LocaleText>
</small>
</label>
<input type="password" class="form-control form-control-sm rounded-3">
</div>
<div class="p-3 d-flex gap-3 flex-column border-bottom">
<div class="d-flex align-items-center">
<h6 class="mb-0">
<LocaleText t="Reset Password"></LocaleText>
</h6>
<button class="btn btn-sm bg-primary-subtle text-primary-emphasis rounded-3 ms-auto"
@click="sendResetLink()"
>
<i class="bi bi-send me-2"></i>
<LocaleText t="Send Password Reset Link"></LocaleText>
</button>
</div>
<div class="alert rounded-3 mb-0"
:class="[alertStatus ? 'alert-success' : 'alert-danger']"
v-if="alert">
{{ alertMessage }}
</div>
</div>
</template>

View File

@ -1,54 +1,45 @@
[
{
"flag": "🇺🇸",
"lang_id": "en-US",
"lang_name": "English (United States)",
"lang_name_localized": "English (United States)"
},
{
"flag": "🇸🇦",
"lang_id": "ar-SA",
"lang_name": "Arabic (Saudi Arabia)",
"lang_name_localized": "العربية (السعودية)"
},
{
"flag": "🇧🇾",
"lang_id": "be-BY",
"lang_name": "Belarusian (Belarus)",
"lang_name_localized": "Беларуская (Беларусь)"
},
{
"flag": "🏴󠁥󠁳󠁣󠁴󠁿",
"lang_id": "ca-ES",
"lang_name": "Catalan (Spain)",
"lang_name_localized": "Català (Espanya)"
},
{
"flag": "🇨🇿",
"lang_id": "cs-CZ",
"lang_name": "Czech (Czech Republic)",
"lang_name_localized": "Česky (Česká republika)"
},
{
"flag": "🇩🇪",
"lang_id": "de-DE",
"lang_name": "German (Germany)",
"lang_name_localized": "Deutsch (Deutschland)"
},
{
"flag": "🇪🇸",
"lang_id": "es-ES",
"lang_name": "Spanish (Spain)",
"lang_name_localized": "Español (España)"
},
{
"flag": "🇮🇷",
"lang_id": "fa-IR",
"lang_name": "Persian (Iran)",
"lang_name_localized": "فارسی (ایران)"
},
{
"flag": "🇨🇦",
"lang_id": "fr-CA",
"lang_name": "French (Canada)",
"lang_name_localized": "Français (Canada)"
@ -60,91 +51,76 @@
"lang_name_localized": "Français (France)"
},
{
"flag": "🇭🇺",
"lang_id": "hu-HU",
"lang_name": "Hungarian (Hungary)",
"lang_name_localized": "Magyar (Magyarország)"
},
{
"flag": "🇮🇩",
"lang_id": "id-ID",
"lang_name": "Indonesian (Indonesia)",
"lang_name_localized": "Bahasa Indonesia (Indonesia)"
},
{
"flag": "🇮🇹",
"lang_id": "it-IT",
"lang_name": "Italian (Italy)",
"lang_name_localized": "Italiano (Italia)"
},
{
"flag": "🇯🇵",
"lang_id": "ja-JP",
"lang_name": "Japanese (Japan)",
"lang_name_localized": "日本語 (日本)"
},
{
"flag": "🇰🇷",
"lang_id": "ko-KR",
"lang_name": "Korean (South Korea)",
"lang_name_localized": "한국어 (대한민국)"
},
{
"flag": "🇳🇱",
"lang_id": "nl-NL",
"lang_name": "Dutch (Netherlands)",
"lang_name_localized": "Nederlands (Nederland)"
},
{
"flag": "🇵🇱",
"lang_id": "pl-PL",
"lang_name": "Polish (Poland)",
"lang_name_localized": "Polski (Polska)"
},
{
"flag": "🇵🇹",
"lang_id": "pt-BR",
"lang_name": "Portuguese (Brazil)",
"lang_name_localized": "Português (Brasil)"
},
{
"flag": "🇷🇺",
"lang_id": "ru-RU",
"lang_name": "Russian (Russia)",
"lang_name_localized": "Русский (Россия)"
},
{
"flag": "🇸🇪",
"lang_id": "sv-SE",
"lang_name": "Swedish (Sweden)",
"lang_name_localized": "Svenska (Sverige)"
},
{
"flag": "🇹🇭",
"lang_id": "th-TH",
"lang_name": "Thai (Thailand)",
"lang_name_localized": "ภาษาไทย (ประเทศไทย)"
},
{
"flag": "🇹🇷",
"lang_id": "tr-TR",
"lang_name": "Turkish (Turkey)",
"lang_name_localized": "Türkçe (Türkiye)"
},
{
"flag": "🇺🇦",
"lang_id": "uk-UA",
"lang_name": "Ukrainian (Ukraine)",
"lang_name_localized": "Українська (Україна)"
},
{
"flag": "🇨🇳",
"lang_id": "zh-CN",
"lang_name": "Chinese (Simplified, China)",
"lang_name_localized": "中文(简体,中国)"
},
{
"flag": "🇭🇰",
"lang_id": "zh-HK",
"lang_name": "Chinese (Traditional, Hong Kong)",
"lang_name_localized": "中文(繁體,香港)"