1<template>
2 <Dialog v-model:open="show" :title="__('Change Password')">
3 <template #default>
4 <div class="flex flex-col gap-4">
5 <!-- Without its own username field and `new-password`, the browser fills
6 the saved login: the password here, the username into whatever text
7 field the page has (the portal's article search). -->
8 <input
9 type="text"
10 autocomplete="username"
11 :value="user"
12 readonly
13 hidden
14 />
15 <div>
16 <Password
17 v-model="newPassword"
18 :placeholder="__('New Password')"
19 autocomplete="new-password"
20 maxLength="50"
21 >
22 <template #prefix>
23 <LockKeyhole class="size-4 text-ink-gray-4" />
24 </template>
25 </Password>
26 </div>
27 <div>
28 <Password
29 v-model="confirmPassword"
30 :placeholder="__('Confirm Password')"
31 autocomplete="new-password"
32 maxLength="50"
33 >
34 <template #prefix>
35 <LockKeyhole class="size-4 text-ink-gray-4" />
36 </template>
37 </Password>
38 </div>
39 </div>
40 </template>
41 <template #actions>
42 <div class="flex justify-between items-center">
43 <div>
44 <p
45 v-if="confirmPasswordMessage"
46 class="text-sm text-ink-gray-5"
47 :class="
48 confirmPasswordMessage === __('Passwords match')
49 ? 'text-ink-green-6'
50 : 'text-ink-red-6'
51 "
52 >
53 {{ confirmPasswordMessage }}
54 </p>
55 </div>
56
57 <Button
58 variant="solid"
59 :label="__('Update')"
60 :disabled="
61 !newPassword ||
62 !confirmPassword ||
63 newPassword !== confirmPassword ||
64 !isStrongPassword(newPassword)
65 "
66 :loading="updatePassword.loading"
67 @click="updatePassword.submit()"
68 />
69 </div>
70 </template>
71 </Dialog>
72</template>
73<script setup lang="ts">
74import LockKeyhole from "~icons/lucide/lock-keyhole";
75import { createResource, Dialog, Password, toast } from "frappe-ui";
76import { ref, watch } from "vue";
77import { __ } from "./translation";
78
79const props = defineProps<{ user: string }>();
80
81const show = defineModel<boolean>();
82
83const newPassword = ref("");
84const confirmPassword = ref("");
85const confirmPasswordMessage = ref("");
86
87const updatePassword = createResource({
88 url: "frappe.client.set_value",
89 makeParams() {
90 return {
91 doctype: "User",
92 name: props.user,
93 fieldname: "new_password",
94 value: newPassword.value,
95 };
96 },
97 onSuccess: () => {
98 toast.success(__("Password updated successfully."));
99 show.value = false;
100 newPassword.value = "";
101 confirmPassword.value = "";
102 confirmPasswordMessage.value = "";
103 },
104});
105
106function isStrongPassword(password) {
107 const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d\s]).{8,}$/;
108 return regex.test(password);
109}
110
111watch([newPassword, confirmPassword], () => {
112 confirmPasswordMessage.value = "";
113
114 if (newPassword.value.length < 8) {
115 confirmPasswordMessage.value = __("Password must be at least 8 characters");
116 return;
117 } else if (!isStrongPassword(newPassword.value)) {
118 confirmPasswordMessage.value = __(
119 "Password must contain lowercase, uppercase, number, and symbol"
120 );
121 return;
122 }
123
124 if (
125 confirmPassword.value.length &&
126 newPassword.value !== confirmPassword.value
127 ) {
128 confirmPasswordMessage.value = __("Passwords do not match");
129 } else if (
130 newPassword.value === confirmPassword.value &&
131 newPassword.value.length &&
132 confirmPassword.value.length
133 ) {
134 confirmPasswordMessage.value = __("Passwords match");
135 }
136});
137</script>
138