@adminforth/open-signup 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom/SignupPage.vue +55 -47
- package/dist/custom/SignupPage.vue +55 -47
- package/dist/index.js +57 -85
- package/index.ts +68 -96
- package/package.json +1 -1
- package/types.ts +4 -13
- package/dist/custom/ResetPassword.vue +0 -295
- package/dist/custom/ResetPasswordUnderLogin.vue +0 -13
package/custom/SignupPage.vue
CHANGED
|
@@ -37,26 +37,28 @@
|
|
|
37
37
|
<!-- Modal body -->
|
|
38
38
|
<div class="p-4 md:p-5">
|
|
39
39
|
<form v-if="!requestSent" class="space-y-4" role="alert" @submit.prevent>
|
|
40
|
-
<div class="relative">
|
|
40
|
+
<div v-if="!verifyToken" class="relative">
|
|
41
41
|
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
|
42
42
|
<input type="email" name="email" id="email"
|
|
43
43
|
tabindex="1"
|
|
44
|
-
|
|
44
|
+
autocomplete="username"
|
|
45
|
+
@keydown.enter="passwordInput?.focus()"
|
|
45
46
|
ref="emailInput"
|
|
46
47
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
47
48
|
placeholder="name@company.com"
|
|
48
49
|
required
|
|
49
50
|
/>
|
|
50
51
|
</div>
|
|
51
|
-
<div class="relative">
|
|
52
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
52
53
|
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
|
|
53
54
|
<input
|
|
54
55
|
tabindex="2"
|
|
56
|
+
autocomplete="new-password"
|
|
55
57
|
ref="passwordInput"
|
|
56
58
|
:type="unmasked ? 'text' : 'password'"
|
|
57
59
|
name="password" id="password"
|
|
58
60
|
v-model="password"
|
|
59
|
-
@keydown.enter="passwordConfirmationInput
|
|
61
|
+
@keydown.enter="passwordConfirmationInput?.focus()"
|
|
60
62
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
61
63
|
placeholder="New password" required
|
|
62
64
|
/>
|
|
@@ -71,10 +73,11 @@
|
|
|
71
73
|
</button>
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
|
-
<div class="relative">
|
|
76
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
75
77
|
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
|
|
76
78
|
<input
|
|
77
79
|
ref="passwordConfirmationInput"
|
|
80
|
+
autocomplete="new-password"
|
|
78
81
|
:type="unmasked ? 'text' : 'password'"
|
|
79
82
|
name="password_confirmation" id="password_confirmation"
|
|
80
83
|
tabindex="3"
|
|
@@ -105,7 +108,7 @@
|
|
|
105
108
|
|
|
106
109
|
<Button
|
|
107
110
|
:disabled="inProgress || (validationRunning && validationError)"
|
|
108
|
-
@click="doSignup"
|
|
111
|
+
@click="() => verifyToken ? signupAfterEmailConfirmation() : doSignup()"
|
|
109
112
|
type="submit"
|
|
110
113
|
class="w-full"
|
|
111
114
|
:loader="inProgress"
|
|
@@ -115,9 +118,11 @@
|
|
|
115
118
|
</form>
|
|
116
119
|
<!-- END of set new paasord -->
|
|
117
120
|
<div v-else class="flex items center justify-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-green-800 dark:text-green-400" role="alert">
|
|
118
|
-
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
+
Please check your email at {{ sentToEmail }} to confirm your email address.
|
|
122
|
+
</div>
|
|
123
|
+
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
124
|
+
or <Link to="/login">{{ toLoginText }}</Link>
|
|
125
|
+
</p>
|
|
121
126
|
</div>
|
|
122
127
|
</div>
|
|
123
128
|
</div>
|
|
@@ -128,7 +133,6 @@
|
|
|
128
133
|
|
|
129
134
|
|
|
130
135
|
<script setup lang="ts">
|
|
131
|
-
|
|
132
136
|
import { onMounted, ref, computed, Ref } from 'vue';
|
|
133
137
|
import { useCoreStore } from '@/stores/core';
|
|
134
138
|
import { useUserStore } from '@/stores/user';
|
|
@@ -136,27 +140,33 @@ import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
|
|
|
136
140
|
import { useRoute, useRouter } from 'vue-router';
|
|
137
141
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
138
142
|
import Button from '@/afcl/Button.vue';
|
|
143
|
+
import Link from '@/afcl/Link.vue';
|
|
139
144
|
|
|
140
145
|
const inProgress = ref(false);
|
|
141
146
|
|
|
142
147
|
const coreStore = useCoreStore();
|
|
143
148
|
const requestSent = ref(false);
|
|
144
149
|
|
|
145
|
-
const emailInput = ref(null);
|
|
146
|
-
const passwordInput = ref(null);
|
|
147
|
-
const passwordConfirmationInput = ref(null);
|
|
150
|
+
const emailInput: Ref<HTMLInputElement | null> = ref(null);
|
|
151
|
+
const passwordInput: Ref<HTMLInputElement | null> = ref(null);
|
|
152
|
+
const passwordConfirmationInput: Ref<HTMLInputElement | null> = ref(null);
|
|
148
153
|
|
|
149
154
|
const password = ref("");
|
|
150
155
|
const passwordConfirmation = ref("");
|
|
151
156
|
const unmasked = ref(false);
|
|
152
157
|
const sentToEmail: Ref<string> = ref('');
|
|
153
158
|
|
|
159
|
+
const requestEmailConfirmation = computed(() => route.meta.requestEmailConfirmation);
|
|
160
|
+
const verifyToken = computed(() => route.query.token);
|
|
161
|
+
const toLoginText = computed(() => `${verifyToken.value ? 'Go' : 'Back'} to login`);
|
|
162
|
+
const isPasswordNeeded = computed(() => !requestEmailConfirmation.value || (requestEmailConfirmation.value && verifyToken.value));
|
|
163
|
+
|
|
154
164
|
const user = useUserStore();
|
|
155
165
|
|
|
156
166
|
const route = useRoute();
|
|
157
167
|
const router = useRouter();
|
|
158
168
|
|
|
159
|
-
function
|
|
169
|
+
function checkPassword() {
|
|
160
170
|
|
|
161
171
|
if (!password.value || !passwordConfirmation.value) {
|
|
162
172
|
return 'Please enter both password and password confirmation';
|
|
@@ -187,39 +197,15 @@ const validationRunning = ref(false);
|
|
|
187
197
|
|
|
188
198
|
const validationError = computed(() => {
|
|
189
199
|
if (validationRunning.value) {
|
|
190
|
-
return
|
|
200
|
+
return checkPassword();
|
|
191
201
|
}
|
|
192
202
|
return null;
|
|
193
203
|
});
|
|
194
204
|
|
|
195
|
-
const error: Ref<
|
|
205
|
+
const error: Ref<string | null> = ref(null);
|
|
196
206
|
|
|
197
207
|
onMounted(async () => {
|
|
198
|
-
|
|
199
|
-
// call method completeVerifiedSignup to confirm { token }
|
|
200
|
-
// if success, then redirect to login page
|
|
201
|
-
|
|
202
|
-
const resp = await callAdminForthApi({
|
|
203
|
-
path: `/plugin/${route.meta.pluginInstanceId}/complete-verified-signup`,
|
|
204
|
-
method: 'POST',
|
|
205
|
-
body: {
|
|
206
|
-
token: route.query.token,
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
if (resp.error) {
|
|
210
|
-
window.adminforth.alert({
|
|
211
|
-
message: `Error fetching data: ${error.message}`,
|
|
212
|
-
variant: 'danger',
|
|
213
|
-
});
|
|
214
|
-
} else if (resp.redirectTo) {
|
|
215
|
-
router.push(resp.redirectTo);
|
|
216
|
-
} else {
|
|
217
|
-
await user.finishLogin();
|
|
218
|
-
}
|
|
219
|
-
return;
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
emailInput.value.focus();
|
|
208
|
+
emailInput.value?.focus();
|
|
223
209
|
});
|
|
224
210
|
|
|
225
211
|
|
|
@@ -242,7 +228,7 @@ async function doSignup() {
|
|
|
242
228
|
error.value = 'Please enter your email';
|
|
243
229
|
return;
|
|
244
230
|
}
|
|
245
|
-
if (
|
|
231
|
+
if (!requestEmailConfirmation.value && checkPassword()) {
|
|
246
232
|
validationRunning.value = true;
|
|
247
233
|
return;
|
|
248
234
|
}
|
|
@@ -254,7 +240,7 @@ async function doSignup() {
|
|
|
254
240
|
body: {
|
|
255
241
|
email,
|
|
256
242
|
url: window.location.origin + window.location.pathname,
|
|
257
|
-
password: password.value,
|
|
243
|
+
...(!requestEmailConfirmation.value ? {password: password.value} : {}),
|
|
258
244
|
}
|
|
259
245
|
});
|
|
260
246
|
inProgress.value = false;
|
|
@@ -264,15 +250,37 @@ async function doSignup() {
|
|
|
264
250
|
error.value = null;
|
|
265
251
|
requestSent.value = true;
|
|
266
252
|
sentToEmail.value = email;
|
|
267
|
-
if (resp.redirectTo) {
|
|
253
|
+
if (resp.redirectTo && !requestEmailConfirmation.value) {
|
|
268
254
|
router.push(resp.redirectTo);
|
|
269
|
-
} else {
|
|
255
|
+
} else if (!requestEmailConfirmation.value) {
|
|
270
256
|
error.value = null;
|
|
271
257
|
await user.finishLogin();
|
|
272
258
|
}
|
|
273
259
|
}
|
|
274
260
|
}
|
|
275
261
|
|
|
276
|
-
|
|
277
|
-
|
|
262
|
+
const signupAfterEmailConfirmation = async () => {
|
|
263
|
+
if (checkPassword()) {
|
|
264
|
+
validationRunning.value = true;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const resp = await callAdminForthApi({
|
|
268
|
+
path: `/plugin/${route.meta.pluginInstanceId}/complete-verified-signup`,
|
|
269
|
+
method: 'POST',
|
|
270
|
+
body: {
|
|
271
|
+
token: verifyToken.value,
|
|
272
|
+
password: password.value,
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (resp.error) {
|
|
276
|
+
window.adminforth.alert({
|
|
277
|
+
message: `Error fetching data: ${resp.error}`,
|
|
278
|
+
variant: 'danger',
|
|
279
|
+
});
|
|
280
|
+
} else if (resp.redirectTo) {
|
|
281
|
+
router.push(resp.redirectTo);
|
|
282
|
+
} else {
|
|
283
|
+
await user.finishLogin();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
278
286
|
</script>
|
|
@@ -37,26 +37,28 @@
|
|
|
37
37
|
<!-- Modal body -->
|
|
38
38
|
<div class="p-4 md:p-5">
|
|
39
39
|
<form v-if="!requestSent" class="space-y-4" role="alert" @submit.prevent>
|
|
40
|
-
<div class="relative">
|
|
40
|
+
<div v-if="!verifyToken" class="relative">
|
|
41
41
|
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
|
42
42
|
<input type="email" name="email" id="email"
|
|
43
43
|
tabindex="1"
|
|
44
|
-
|
|
44
|
+
autocomplete="username"
|
|
45
|
+
@keydown.enter="passwordInput?.focus()"
|
|
45
46
|
ref="emailInput"
|
|
46
47
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
47
48
|
placeholder="name@company.com"
|
|
48
49
|
required
|
|
49
50
|
/>
|
|
50
51
|
</div>
|
|
51
|
-
<div class="relative">
|
|
52
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
52
53
|
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
|
|
53
54
|
<input
|
|
54
55
|
tabindex="2"
|
|
56
|
+
autocomplete="new-password"
|
|
55
57
|
ref="passwordInput"
|
|
56
58
|
:type="unmasked ? 'text' : 'password'"
|
|
57
59
|
name="password" id="password"
|
|
58
60
|
v-model="password"
|
|
59
|
-
@keydown.enter="passwordConfirmationInput
|
|
61
|
+
@keydown.enter="passwordConfirmationInput?.focus()"
|
|
60
62
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
61
63
|
placeholder="New password" required
|
|
62
64
|
/>
|
|
@@ -71,10 +73,11 @@
|
|
|
71
73
|
</button>
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
|
-
<div class="relative">
|
|
76
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
75
77
|
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
|
|
76
78
|
<input
|
|
77
79
|
ref="passwordConfirmationInput"
|
|
80
|
+
autocomplete="new-password"
|
|
78
81
|
:type="unmasked ? 'text' : 'password'"
|
|
79
82
|
name="password_confirmation" id="password_confirmation"
|
|
80
83
|
tabindex="3"
|
|
@@ -105,7 +108,7 @@
|
|
|
105
108
|
|
|
106
109
|
<Button
|
|
107
110
|
:disabled="inProgress || (validationRunning && validationError)"
|
|
108
|
-
@click="doSignup"
|
|
111
|
+
@click="() => verifyToken ? signupAfterEmailConfirmation() : doSignup()"
|
|
109
112
|
type="submit"
|
|
110
113
|
class="w-full"
|
|
111
114
|
:loader="inProgress"
|
|
@@ -115,9 +118,11 @@
|
|
|
115
118
|
</form>
|
|
116
119
|
<!-- END of set new paasord -->
|
|
117
120
|
<div v-else class="flex items center justify-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-green-800 dark:text-green-400" role="alert">
|
|
118
|
-
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
+
Please check your email at {{ sentToEmail }} to confirm your email address.
|
|
122
|
+
</div>
|
|
123
|
+
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
124
|
+
or <Link to="/login">{{ toLoginText }}</Link>
|
|
125
|
+
</p>
|
|
121
126
|
</div>
|
|
122
127
|
</div>
|
|
123
128
|
</div>
|
|
@@ -128,7 +133,6 @@
|
|
|
128
133
|
|
|
129
134
|
|
|
130
135
|
<script setup lang="ts">
|
|
131
|
-
|
|
132
136
|
import { onMounted, ref, computed, Ref } from 'vue';
|
|
133
137
|
import { useCoreStore } from '@/stores/core';
|
|
134
138
|
import { useUserStore } from '@/stores/user';
|
|
@@ -136,27 +140,33 @@ import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
|
|
|
136
140
|
import { useRoute, useRouter } from 'vue-router';
|
|
137
141
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
138
142
|
import Button from '@/afcl/Button.vue';
|
|
143
|
+
import Link from '@/afcl/Link.vue';
|
|
139
144
|
|
|
140
145
|
const inProgress = ref(false);
|
|
141
146
|
|
|
142
147
|
const coreStore = useCoreStore();
|
|
143
148
|
const requestSent = ref(false);
|
|
144
149
|
|
|
145
|
-
const emailInput = ref(null);
|
|
146
|
-
const passwordInput = ref(null);
|
|
147
|
-
const passwordConfirmationInput = ref(null);
|
|
150
|
+
const emailInput: Ref<HTMLInputElement | null> = ref(null);
|
|
151
|
+
const passwordInput: Ref<HTMLInputElement | null> = ref(null);
|
|
152
|
+
const passwordConfirmationInput: Ref<HTMLInputElement | null> = ref(null);
|
|
148
153
|
|
|
149
154
|
const password = ref("");
|
|
150
155
|
const passwordConfirmation = ref("");
|
|
151
156
|
const unmasked = ref(false);
|
|
152
157
|
const sentToEmail: Ref<string> = ref('');
|
|
153
158
|
|
|
159
|
+
const requestEmailConfirmation = computed(() => route.meta.requestEmailConfirmation);
|
|
160
|
+
const verifyToken = computed(() => route.query.token);
|
|
161
|
+
const toLoginText = computed(() => `${verifyToken.value ? 'Go' : 'Back'} to login`);
|
|
162
|
+
const isPasswordNeeded = computed(() => !requestEmailConfirmation.value || (requestEmailConfirmation.value && verifyToken.value));
|
|
163
|
+
|
|
154
164
|
const user = useUserStore();
|
|
155
165
|
|
|
156
166
|
const route = useRoute();
|
|
157
167
|
const router = useRouter();
|
|
158
168
|
|
|
159
|
-
function
|
|
169
|
+
function checkPassword() {
|
|
160
170
|
|
|
161
171
|
if (!password.value || !passwordConfirmation.value) {
|
|
162
172
|
return 'Please enter both password and password confirmation';
|
|
@@ -187,39 +197,15 @@ const validationRunning = ref(false);
|
|
|
187
197
|
|
|
188
198
|
const validationError = computed(() => {
|
|
189
199
|
if (validationRunning.value) {
|
|
190
|
-
return
|
|
200
|
+
return checkPassword();
|
|
191
201
|
}
|
|
192
202
|
return null;
|
|
193
203
|
});
|
|
194
204
|
|
|
195
|
-
const error: Ref<
|
|
205
|
+
const error: Ref<string | null> = ref(null);
|
|
196
206
|
|
|
197
207
|
onMounted(async () => {
|
|
198
|
-
|
|
199
|
-
// call method completeVerifiedSignup to confirm { token }
|
|
200
|
-
// if success, then redirect to login page
|
|
201
|
-
|
|
202
|
-
const resp = await callAdminForthApi({
|
|
203
|
-
path: `/plugin/${route.meta.pluginInstanceId}/complete-verified-signup`,
|
|
204
|
-
method: 'POST',
|
|
205
|
-
body: {
|
|
206
|
-
token: route.query.token,
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
if (resp.error) {
|
|
210
|
-
window.adminforth.alert({
|
|
211
|
-
message: `Error fetching data: ${error.message}`,
|
|
212
|
-
variant: 'danger',
|
|
213
|
-
});
|
|
214
|
-
} else if (resp.redirectTo) {
|
|
215
|
-
router.push(resp.redirectTo);
|
|
216
|
-
} else {
|
|
217
|
-
await user.finishLogin();
|
|
218
|
-
}
|
|
219
|
-
return;
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
emailInput.value.focus();
|
|
208
|
+
emailInput.value?.focus();
|
|
223
209
|
});
|
|
224
210
|
|
|
225
211
|
|
|
@@ -242,7 +228,7 @@ async function doSignup() {
|
|
|
242
228
|
error.value = 'Please enter your email';
|
|
243
229
|
return;
|
|
244
230
|
}
|
|
245
|
-
if (
|
|
231
|
+
if (!requestEmailConfirmation.value && checkPassword()) {
|
|
246
232
|
validationRunning.value = true;
|
|
247
233
|
return;
|
|
248
234
|
}
|
|
@@ -254,7 +240,7 @@ async function doSignup() {
|
|
|
254
240
|
body: {
|
|
255
241
|
email,
|
|
256
242
|
url: window.location.origin + window.location.pathname,
|
|
257
|
-
password: password.value,
|
|
243
|
+
...(!requestEmailConfirmation.value ? {password: password.value} : {}),
|
|
258
244
|
}
|
|
259
245
|
});
|
|
260
246
|
inProgress.value = false;
|
|
@@ -264,15 +250,37 @@ async function doSignup() {
|
|
|
264
250
|
error.value = null;
|
|
265
251
|
requestSent.value = true;
|
|
266
252
|
sentToEmail.value = email;
|
|
267
|
-
if (resp.redirectTo) {
|
|
253
|
+
if (resp.redirectTo && !requestEmailConfirmation.value) {
|
|
268
254
|
router.push(resp.redirectTo);
|
|
269
|
-
} else {
|
|
255
|
+
} else if (!requestEmailConfirmation.value) {
|
|
270
256
|
error.value = null;
|
|
271
257
|
await user.finishLogin();
|
|
272
258
|
}
|
|
273
259
|
}
|
|
274
260
|
}
|
|
275
261
|
|
|
276
|
-
|
|
277
|
-
|
|
262
|
+
const signupAfterEmailConfirmation = async () => {
|
|
263
|
+
if (checkPassword()) {
|
|
264
|
+
validationRunning.value = true;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const resp = await callAdminForthApi({
|
|
268
|
+
path: `/plugin/${route.meta.pluginInstanceId}/complete-verified-signup`,
|
|
269
|
+
method: 'POST',
|
|
270
|
+
body: {
|
|
271
|
+
token: verifyToken.value,
|
|
272
|
+
password: password.value,
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (resp.error) {
|
|
276
|
+
window.adminforth.alert({
|
|
277
|
+
message: `Error fetching data: ${resp.error}`,
|
|
278
|
+
variant: 'danger',
|
|
279
|
+
});
|
|
280
|
+
} else if (resp.redirectTo) {
|
|
281
|
+
router.push(resp.redirectTo);
|
|
282
|
+
} else {
|
|
283
|
+
await user.finishLogin();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
278
286
|
</script>
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
|
|
11
|
-
import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses';
|
|
12
11
|
export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
13
12
|
constructor(options) {
|
|
14
13
|
super(options, import.meta.url);
|
|
@@ -23,9 +22,15 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
23
22
|
if (!this.options.emailField) {
|
|
24
23
|
throw new Error(`emailField is required and should be a name of field in auth resource`);
|
|
25
24
|
}
|
|
25
|
+
// find field with name resourceConfig.emailField in adminforth.auth.usersResourceId and show error if it doesn't exist
|
|
26
|
+
const authResource = adminforth.config.resources.find(r => r.resourceId === adminforth.config.auth.usersResourceId);
|
|
27
|
+
if (!authResource) {
|
|
28
|
+
throw new Error(`Resource with id config.auth.usersResourceId=${adminforth.config.auth.usersResourceId} not found`);
|
|
29
|
+
}
|
|
30
|
+
this.authResource = authResource;
|
|
26
31
|
if (this.options.confirmEmails) {
|
|
27
|
-
if (!this.options.confirmEmails.
|
|
28
|
-
throw new Error(`confirmEmails.
|
|
32
|
+
if (!this.options.confirmEmails.adapter) {
|
|
33
|
+
throw new Error(`confirmEmails.adapter is required and should be a name of field in auth resource`);
|
|
29
34
|
}
|
|
30
35
|
if (!this.options.confirmEmails.emailConfirmedField) {
|
|
31
36
|
throw new Error(`confirmEmails.emailConfirmedField is required and should be a name of field in auth resource`);
|
|
@@ -38,16 +43,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
38
43
|
`);
|
|
39
44
|
}
|
|
40
45
|
this.emailConfirmedField = emailConfirmedField;
|
|
41
|
-
if (this.emailConfirmedField.type !== AdminForthDataTypes.BOOLEAN) {
|
|
42
|
-
throw new Error(`Field ${this.emailConfirmedField.name} must be of type boolean`);
|
|
43
|
-
}
|
|
44
46
|
}
|
|
45
|
-
// find field with name resourceConfig.emailField in adminforth.auth.usersResourceId and show error if it doesn't exist
|
|
46
|
-
const authResource = adminforth.config.resources.find(r => r.resourceId === adminforth.config.auth.usersResourceId);
|
|
47
|
-
if (!authResource) {
|
|
48
|
-
throw new Error(`Resource with id config.auth.usersResourceId=${adminforth.config.auth.usersResourceId} not found`);
|
|
49
|
-
}
|
|
50
|
-
this.authResource = authResource;
|
|
51
47
|
const emailField = authResource.columns.find(f => f.name === this.options.emailField);
|
|
52
48
|
if (!emailField) {
|
|
53
49
|
const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailField);
|
|
@@ -81,7 +77,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
81
77
|
minLength: passwordField.minLength,
|
|
82
78
|
maxLength: passwordField.maxLength,
|
|
83
79
|
validation: passwordField.validation
|
|
84
|
-
}
|
|
80
|
+
},
|
|
81
|
+
requestEmailConfirmation: !!this.options.confirmEmails
|
|
85
82
|
}
|
|
86
83
|
}
|
|
87
84
|
});
|
|
@@ -90,24 +87,23 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
90
87
|
if (!adminforth.config.auth.beforeLoginConfirmation) {
|
|
91
88
|
adminforth.config.auth.beforeLoginConfirmation = [];
|
|
92
89
|
}
|
|
93
|
-
// unshift because if e.g. 2fa set it's hook, this one should be first
|
|
94
|
-
adminforth.config.auth.beforeLoginConfirmation.unshift((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
95
|
-
if (!adminUser.dbUser[this.emailConfirmedField.name]) {
|
|
96
|
-
return { body: { allowedLogin: false }, error: 'You need to confirm your email to be able to login' };
|
|
97
|
-
}
|
|
98
|
-
}));
|
|
99
90
|
}
|
|
100
91
|
});
|
|
101
92
|
}
|
|
102
93
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
103
|
-
|
|
94
|
+
if (this.options.confirmEmails) {
|
|
95
|
+
const emailConfirmedColumn = this.resourceConfig.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
|
|
96
|
+
if (emailConfirmedColumn.type !== AdminForthDataTypes.BOOLEAN) {
|
|
97
|
+
throw new Error(`Field ${this.emailConfirmedField.name} must be of type boolean`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
104
100
|
}
|
|
105
101
|
instanceUniqueRepresentation(pluginOptions) {
|
|
106
102
|
// optional method to return unique string representation of plugin instance.
|
|
107
103
|
// Needed if plugin can have multiple instances on one resource
|
|
108
104
|
return `single`;
|
|
109
105
|
}
|
|
110
|
-
doLogin(email, response) {
|
|
106
|
+
doLogin(email, response, extra) {
|
|
111
107
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
108
|
const username = email;
|
|
113
109
|
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
@@ -117,7 +113,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
117
113
|
username,
|
|
118
114
|
};
|
|
119
115
|
const toReturn = { allowedLogin: true, error: '' };
|
|
120
|
-
yield this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response);
|
|
116
|
+
yield this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response, extra);
|
|
121
117
|
if (toReturn.allowedLogin) {
|
|
122
118
|
this.adminforth.auth.setAuthCookie({
|
|
123
119
|
response,
|
|
@@ -131,29 +127,36 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
131
127
|
setupEndpoints(server) {
|
|
132
128
|
server.endpoint({
|
|
133
129
|
method: 'POST',
|
|
134
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
130
|
+
path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
|
|
135
131
|
noAuth: true,
|
|
136
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
|
|
137
|
-
const { token } = body;
|
|
138
|
-
const { email } = yield this.adminforth.auth.verify(token, 'tempVerifyEmailToken');
|
|
132
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, headers, query, cookies }) {
|
|
133
|
+
const { token, password } = body;
|
|
134
|
+
const { email } = yield this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
|
|
139
135
|
if (!email) {
|
|
140
136
|
return { error: 'Invalid token', ok: false };
|
|
141
137
|
}
|
|
138
|
+
if (!password) {
|
|
139
|
+
return { error: 'Password is required', ok: false };
|
|
140
|
+
}
|
|
142
141
|
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
143
142
|
if (!userRecord) {
|
|
144
143
|
return { error: 'User not found', ok: false };
|
|
145
144
|
}
|
|
145
|
+
if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
|
|
146
|
+
return { error: 'Email already confirmed', ok: false };
|
|
147
|
+
}
|
|
146
148
|
yield this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
147
|
-
[this.options.confirmEmails.emailConfirmedField]: true
|
|
149
|
+
[this.options.confirmEmails.emailConfirmedField]: true,
|
|
150
|
+
[this.options.passwordHashField]: yield AdminForth.Utils.generatePasswordHash(password),
|
|
148
151
|
});
|
|
149
|
-
return yield this.doLogin(email, response);
|
|
152
|
+
return yield this.doLogin(email, response, { body, headers, query, cookies });
|
|
150
153
|
})
|
|
151
154
|
});
|
|
152
155
|
server.endpoint({
|
|
153
156
|
method: 'POST',
|
|
154
157
|
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
155
158
|
noAuth: true,
|
|
156
|
-
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response }) {
|
|
159
|
+
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response, headers, query, cookies }) {
|
|
157
160
|
const { email, url, password } = body;
|
|
158
161
|
// validate email
|
|
159
162
|
if (this.emailField.validation) {
|
|
@@ -164,51 +167,39 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
169
|
// validate password
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
if (!this.options.confirmEmails) {
|
|
171
|
+
if (password.length < this.passwordField.minLength) {
|
|
172
|
+
return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
|
|
173
|
+
}
|
|
174
|
+
if (password.length > this.passwordField.maxLength) {
|
|
175
|
+
return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
|
|
176
|
+
}
|
|
177
|
+
if (this.passwordField.validation) {
|
|
178
|
+
for (const { regExp, message } of this.passwordField.validation) {
|
|
179
|
+
if (!new RegExp(regExp).test(password)) {
|
|
180
|
+
return { error: message, ok: false };
|
|
181
|
+
}
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
}
|
|
180
185
|
// first check again if email already exists
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
186
|
+
const existingUser = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
187
|
+
if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && (existingUser === null || existingUser === void 0 ? void 0 : existingUser[this.emailConfirmedField.name]))) {
|
|
183
188
|
return { error: 'Email already exists', ok: false };
|
|
184
189
|
}
|
|
185
190
|
// create user
|
|
186
|
-
|
|
191
|
+
if (!existingUser) {
|
|
192
|
+
const created = yield this.adminforth.resource(this.authResource.resourceId).create(Object.assign(Object.assign(Object.assign({}, (this.options.defaultFieldValues || {})), (this.options.confirmEmails ? { [this.options.confirmEmails.emailConfirmedField]: false } : {})), { [this.emailField.name]: email, [this.options.passwordHashField]: password ? yield AdminForth.Utils.generatePasswordHash(password) : '' }));
|
|
193
|
+
}
|
|
187
194
|
if (!this.options.confirmEmails) {
|
|
188
|
-
const resp = yield this.doLogin(email, response);
|
|
195
|
+
const resp = yield this.doLogin(email, response, { body, headers, query, cookies });
|
|
189
196
|
return resp;
|
|
190
197
|
}
|
|
191
198
|
// send confirmation email
|
|
192
199
|
const brandName = this.adminforth.config.customization.brandName;
|
|
193
200
|
const verifyToken = this.adminforth.auth.issueJWT({ email, issuer: brandName }, 'tempVerifyEmailToken', '2h');
|
|
194
|
-
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
195
|
-
const ses = new SESClient({
|
|
196
|
-
region: this.options.confirmEmails.providerOptions.AWS_SES.region,
|
|
197
|
-
credentials: {
|
|
198
|
-
accessKeyId: this.options.confirmEmails.providerOptions.AWS_SES.accessKeyId,
|
|
199
|
-
secretAccessKey: this.options.confirmEmails.providerOptions.AWS_SES.secretAccessKey
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
201
|
process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
|
|
203
|
-
const
|
|
204
|
-
Destination: {
|
|
205
|
-
ToAddresses: [email]
|
|
206
|
-
},
|
|
207
|
-
Message: {
|
|
208
|
-
Body: {
|
|
209
|
-
Text: {
|
|
210
|
-
Charset: "UTF-8",
|
|
211
|
-
Data: `
|
|
202
|
+
const emailText = `
|
|
212
203
|
Dear user,
|
|
213
204
|
Welcome to ${brandName}!
|
|
214
205
|
|
|
@@ -222,11 +213,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
222
213
|
Thanks,
|
|
223
214
|
The ${brandName} Team
|
|
224
215
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
Html: {
|
|
228
|
-
Charset: "UTF-8",
|
|
229
|
-
Data: `
|
|
216
|
+
`;
|
|
217
|
+
const emailHtml = `
|
|
230
218
|
<html>
|
|
231
219
|
<head></head>
|
|
232
220
|
<body>
|
|
@@ -242,26 +230,10 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
242
230
|
</html>
|
|
243
231
|
|
|
244
232
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
Charset: "UTF-8",
|
|
250
|
-
Data: `Signup request at ${brandName}`
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
Source: `${this.options.confirmEmails.sendFrom}`
|
|
254
|
-
});
|
|
255
|
-
try {
|
|
256
|
-
const sRes = yield ses.send(emailCommand);
|
|
257
|
-
}
|
|
258
|
-
catch (e) {
|
|
259
|
-
console.error('Error sending email', e);
|
|
260
|
-
if (process.env.NODE_ENV === 'development') {
|
|
261
|
-
return { error: 'Some thing went wrong, please check the console', ok: false };
|
|
262
|
-
}
|
|
263
|
-
return { error: 'Something went wrong, please contact support', ok: false };
|
|
264
|
-
}
|
|
233
|
+
`;
|
|
234
|
+
const emailSubject = `Signup request at ${brandName}`;
|
|
235
|
+
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
236
|
+
this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
|
|
265
237
|
return { ok: true };
|
|
266
238
|
})
|
|
267
239
|
});
|
package/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
|
|
2
|
-
import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResourceColumn, AdminForthResource, BeforeLoginConfirmationFunction } from "adminforth";
|
|
2
|
+
import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResourceColumn, AdminForthResource, BeforeLoginConfirmationFunction, HttpExtra } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
|
-
import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses';
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
@@ -25,9 +24,16 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
25
24
|
throw new Error(`emailField is required and should be a name of field in auth resource`);
|
|
26
25
|
}
|
|
27
26
|
|
|
27
|
+
// find field with name resourceConfig.emailField in adminforth.auth.usersResourceId and show error if it doesn't exist
|
|
28
|
+
const authResource = adminforth.config.resources.find(r => r.resourceId === adminforth.config.auth.usersResourceId);
|
|
29
|
+
if (!authResource) {
|
|
30
|
+
throw new Error(`Resource with id config.auth.usersResourceId=${adminforth.config.auth.usersResourceId} not found`);
|
|
31
|
+
}
|
|
32
|
+
this.authResource = authResource;
|
|
33
|
+
|
|
28
34
|
if (this.options.confirmEmails) {
|
|
29
|
-
if (!this.options.confirmEmails.
|
|
30
|
-
throw new Error(`confirmEmails.
|
|
35
|
+
if (!this.options.confirmEmails.adapter) {
|
|
36
|
+
throw new Error(`confirmEmails.adapter is required and should be a name of field in auth resource`);
|
|
31
37
|
}
|
|
32
38
|
if (!this.options.confirmEmails.emailConfirmedField) {
|
|
33
39
|
throw new Error(`confirmEmails.emailConfirmedField is required and should be a name of field in auth resource`);
|
|
@@ -40,18 +46,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
40
46
|
`);
|
|
41
47
|
}
|
|
42
48
|
this.emailConfirmedField = emailConfirmedField;
|
|
43
|
-
if (this.emailConfirmedField.type !== AdminForthDataTypes.BOOLEAN) {
|
|
44
|
-
throw new Error(`Field ${this.emailConfirmedField.name} must be of type boolean`);
|
|
45
|
-
}
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
// find field with name resourceConfig.emailField in adminforth.auth.usersResourceId and show error if it doesn't exist
|
|
49
|
-
const authResource = adminforth.config.resources.find(r => r.resourceId === adminforth.config.auth.usersResourceId);
|
|
50
|
-
if (!authResource) {
|
|
51
|
-
throw new Error(`Resource with id config.auth.usersResourceId=${adminforth.config.auth.usersResourceId} not found`);
|
|
52
|
-
}
|
|
53
|
-
this.authResource = authResource;
|
|
54
|
-
|
|
55
51
|
const emailField = authResource.columns.find(f => f.name === this.options.emailField);
|
|
56
52
|
if (!emailField) {
|
|
57
53
|
const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailField);
|
|
@@ -89,7 +85,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
89
85
|
minLength: passwordField.minLength,
|
|
90
86
|
maxLength: passwordField.maxLength,
|
|
91
87
|
validation: passwordField.validation
|
|
92
|
-
}
|
|
88
|
+
},
|
|
89
|
+
requestEmailConfirmation: !!this.options.confirmEmails
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
});
|
|
@@ -99,19 +96,16 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
99
96
|
if (!adminforth.config.auth.beforeLoginConfirmation) {
|
|
100
97
|
adminforth.config.auth.beforeLoginConfirmation = [];
|
|
101
98
|
}
|
|
102
|
-
// unshift because if e.g. 2fa set it's hook, this one should be first
|
|
103
|
-
(adminforth.config.auth.beforeLoginConfirmation as BeforeLoginConfirmationFunction[]).unshift(
|
|
104
|
-
async ({ adminUser }) => {
|
|
105
|
-
if (!adminUser.dbUser[this.emailConfirmedField.name]) {
|
|
106
|
-
return { body: { allowedLogin: false }, error: 'You need to confirm your email to be able to login' };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
);
|
|
110
99
|
}
|
|
111
100
|
}
|
|
112
101
|
|
|
113
102
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
114
|
-
|
|
103
|
+
if(this.options.confirmEmails){
|
|
104
|
+
const emailConfirmedColumn = this.resourceConfig.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
|
|
105
|
+
if (emailConfirmedColumn.type !== AdminForthDataTypes.BOOLEAN) {
|
|
106
|
+
throw new Error(`Field ${this.emailConfirmedField.name} must be of type boolean`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
115
109
|
}
|
|
116
110
|
|
|
117
111
|
instanceUniqueRepresentation(pluginOptions: any) : string {
|
|
@@ -120,7 +114,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
120
114
|
return `single`;
|
|
121
115
|
}
|
|
122
116
|
|
|
123
|
-
async doLogin(email: string, response: any): Promise<{ error?: string; allowedLogin: boolean; redirectTo?: string; }> {
|
|
117
|
+
async doLogin(email: string, response: any, extra: HttpExtra): Promise<{ error?: string; allowedLogin: boolean; redirectTo?: string; }> {
|
|
124
118
|
|
|
125
119
|
const username = email;
|
|
126
120
|
const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
@@ -131,7 +125,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
131
125
|
};
|
|
132
126
|
const toReturn = { allowedLogin: true, error: '' };
|
|
133
127
|
|
|
134
|
-
await this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response);
|
|
128
|
+
await this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response, extra);
|
|
135
129
|
if (toReturn.allowedLogin) {
|
|
136
130
|
this.adminforth.auth.setAuthCookie({
|
|
137
131
|
response,
|
|
@@ -149,22 +143,32 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
149
143
|
setupEndpoints(server: IHttpServer) {
|
|
150
144
|
server.endpoint({
|
|
151
145
|
method: 'POST',
|
|
152
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
146
|
+
path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
|
|
153
147
|
noAuth: true,
|
|
154
|
-
handler: async ({ body, response }) => {
|
|
155
|
-
const { token } = body;
|
|
156
|
-
const { email } = await this.adminforth.auth.verify(token, 'tempVerifyEmailToken');
|
|
148
|
+
handler: async ({ body, response, headers, query, cookies }) => {
|
|
149
|
+
const { token, password } = body;
|
|
150
|
+
const { email } = await this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
|
|
157
151
|
if (!email) {
|
|
158
152
|
return { error: 'Invalid token', ok: false };
|
|
159
153
|
}
|
|
154
|
+
|
|
155
|
+
if(!password) {
|
|
156
|
+
return { error: 'Password is required', ok: false };
|
|
157
|
+
}
|
|
160
158
|
const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
161
159
|
if (!userRecord) {
|
|
162
160
|
return { error: 'User not found', ok: false };
|
|
163
161
|
}
|
|
162
|
+
|
|
163
|
+
if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
|
|
164
|
+
return { error: 'Email already confirmed', ok: false };
|
|
165
|
+
}
|
|
166
|
+
|
|
164
167
|
await this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
165
|
-
[this.options.confirmEmails.emailConfirmedField]: true
|
|
168
|
+
[this.options.confirmEmails.emailConfirmedField]: true,
|
|
169
|
+
[this.options.passwordHashField]: await AdminForth.Utils.generatePasswordHash(password),
|
|
166
170
|
});
|
|
167
|
-
return await this.doLogin(email, response);
|
|
171
|
+
return await this.doLogin(email, response, { body, headers, query, cookies });
|
|
168
172
|
}
|
|
169
173
|
});
|
|
170
174
|
|
|
@@ -172,7 +176,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
172
176
|
method: 'POST',
|
|
173
177
|
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
174
178
|
noAuth: true,
|
|
175
|
-
handler: async ({ body, response }) => {
|
|
179
|
+
handler: async ({ body, response, headers, query, cookies }) => {
|
|
176
180
|
const { email, url, password } = body;
|
|
177
181
|
|
|
178
182
|
// validate email
|
|
@@ -185,36 +189,40 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
185
189
|
}
|
|
186
190
|
|
|
187
191
|
// validate password
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
if (!this.options.confirmEmails) {
|
|
193
|
+
if (password.length < this.passwordField.minLength) {
|
|
194
|
+
return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
|
|
195
|
+
}
|
|
196
|
+
if (password.length > this.passwordField.maxLength) {
|
|
197
|
+
return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
|
|
198
|
+
}
|
|
199
|
+
if (this.passwordField.validation) {
|
|
200
|
+
for (const { regExp, message } of this.passwordField.validation) {
|
|
201
|
+
if (!new RegExp(regExp).test(password)) {
|
|
202
|
+
return { error: message, ok: false };
|
|
203
|
+
}
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
}
|
|
201
207
|
|
|
202
208
|
// first check again if email already exists
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
209
|
+
const existingUser = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
210
|
+
if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && existingUser?.[this.emailConfirmedField.name])) {
|
|
205
211
|
return { error: 'Email already exists', ok: false };
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
// create user
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
if (!existingUser) {
|
|
216
|
+
const created = await this.adminforth.resource(this.authResource.resourceId).create({
|
|
217
|
+
...(this.options.defaultFieldValues || {}),
|
|
218
|
+
...(this.options.confirmEmails ? { [this.options.confirmEmails.emailConfirmedField]: false } : {}),
|
|
219
|
+
[this.emailField.name]: email,
|
|
220
|
+
[this.options.passwordHashField]: password ? await AdminForth.Utils.generatePasswordHash(password) : '',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
215
223
|
|
|
216
224
|
if (!this.options.confirmEmails) {
|
|
217
|
-
const resp = await this.doLogin(email, response);
|
|
225
|
+
const resp = await this.doLogin(email, response, { body, headers, query, cookies });
|
|
218
226
|
return resp;
|
|
219
227
|
}
|
|
220
228
|
|
|
@@ -223,27 +231,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
223
231
|
const brandName = this.adminforth.config.customization.brandName;
|
|
224
232
|
|
|
225
233
|
const verifyToken = this.adminforth.auth.issueJWT({email, issuer: brandName }, 'tempVerifyEmailToken', '2h');
|
|
226
|
-
|
|
227
|
-
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
228
|
-
const ses = new SESClient({
|
|
229
|
-
region: this.options.confirmEmails.providerOptions.AWS_SES.region,
|
|
230
|
-
credentials: {
|
|
231
|
-
accessKeyId: this.options.confirmEmails.providerOptions.AWS_SES.accessKeyId,
|
|
232
|
-
secretAccessKey: this.options.confirmEmails.providerOptions.AWS_SES.secretAccessKey
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
234
|
process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
|
|
237
|
-
|
|
238
|
-
const emailCommand = new SendEmailCommand({
|
|
239
|
-
Destination: {
|
|
240
|
-
ToAddresses: [email]
|
|
241
|
-
},
|
|
242
|
-
Message: {
|
|
243
|
-
Body: {
|
|
244
|
-
Text: {
|
|
245
|
-
Charset: "UTF-8",
|
|
246
|
-
Data: `
|
|
235
|
+
const emailText = `
|
|
247
236
|
Dear user,
|
|
248
237
|
Welcome to ${brandName}!
|
|
249
238
|
|
|
@@ -257,11 +246,9 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
257
246
|
Thanks,
|
|
258
247
|
The ${brandName} Team
|
|
259
248
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Charset: "UTF-8",
|
|
264
|
-
Data: `
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
const emailHtml = `
|
|
265
252
|
<html>
|
|
266
253
|
<head></head>
|
|
267
254
|
<body>
|
|
@@ -277,26 +264,11 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
277
264
|
</html>
|
|
278
265
|
|
|
279
266
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
},
|
|
283
|
-
Subject: {
|
|
284
|
-
Charset: "UTF-8",
|
|
285
|
-
Data: `Signup request at ${brandName}`
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
Source: `${this.options.confirmEmails.sendFrom}`
|
|
289
|
-
});
|
|
267
|
+
`;
|
|
268
|
+
const emailSubject = `Signup request at ${brandName}`;
|
|
290
269
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
} catch (e) {
|
|
294
|
-
console.error('Error sending email', e);
|
|
295
|
-
if (process.env.NODE_ENV === 'development') {
|
|
296
|
-
return { error: 'Some thing went wrong, please check the console', ok: false };
|
|
297
|
-
}
|
|
298
|
-
return { error: 'Something went wrong, please contact support', ok: false };
|
|
299
|
-
}
|
|
270
|
+
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
271
|
+
this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
|
|
300
272
|
|
|
301
273
|
return { ok: true };
|
|
302
274
|
}
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EmailAdapter } from 'adminforth';
|
|
2
|
+
|
|
1
3
|
export interface PluginOptions {
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -40,11 +42,6 @@ export interface PluginOptions {
|
|
|
40
42
|
*/
|
|
41
43
|
emailConfirmedField: string;
|
|
42
44
|
|
|
43
|
-
/**
|
|
44
|
-
* Required to confirm email
|
|
45
|
-
*/
|
|
46
|
-
emailProvider: 'AWS_SES' ;
|
|
47
|
-
|
|
48
45
|
/**
|
|
49
46
|
* From which email to send password reset emails
|
|
50
47
|
* e.g. no-reply@example.com
|
|
@@ -53,15 +50,9 @@ export interface PluginOptions {
|
|
|
53
50
|
sendFrom: string;
|
|
54
51
|
|
|
55
52
|
/**
|
|
56
|
-
*
|
|
53
|
+
* Adapter to send emails
|
|
57
54
|
*/
|
|
58
|
-
|
|
59
|
-
AWS_SES: {
|
|
60
|
-
region: string;
|
|
61
|
-
accessKeyId: string;
|
|
62
|
-
secretAccessKey: string;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
55
|
+
adapter: EmailAdapter;
|
|
65
56
|
}
|
|
66
57
|
|
|
67
58
|
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800 relative w-screen h-screen"
|
|
3
|
-
:style="coreStore.config?.loginBackgroundImage && backgroundPosition === 'over' ? {
|
|
4
|
-
'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
|
|
5
|
-
'background-size': 'cover',
|
|
6
|
-
'background-position': 'center',
|
|
7
|
-
'background-blend-mode': 'darken'
|
|
8
|
-
}: {}"
|
|
9
|
-
>
|
|
10
|
-
|
|
11
|
-
<img v-if="coreStore.config?.loginBackgroundImage && backgroundPosition !== 'over'"
|
|
12
|
-
:src="loadFile(coreStore.config?.loginBackgroundImage)"
|
|
13
|
-
class="position-absolute top-0 left-0 h-screen object-cover w-0"
|
|
14
|
-
:class="{
|
|
15
|
-
'1/2': 'md:w-1/2',
|
|
16
|
-
'1/3': 'md:w-1/3',
|
|
17
|
-
'2/3': 'md:w-2/3',
|
|
18
|
-
'3/4': 'md:w-3/4',
|
|
19
|
-
'2/5': 'md:w-2/5',
|
|
20
|
-
'3/5': 'md:w-3/5',
|
|
21
|
-
}[backgroundPosition]"
|
|
22
|
-
/>
|
|
23
|
-
|
|
24
|
-
<!-- Main modal -->
|
|
25
|
-
<div id="authentication-modal" tabindex="-1"
|
|
26
|
-
class="overflow-y-auto flex flex-grow
|
|
27
|
-
overflow-x-hidden z-50 min-w-[350px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
28
|
-
<div class="relative p-4 w-full max-h-full max-w-[400px]">
|
|
29
|
-
<!-- Modal content -->
|
|
30
|
-
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
|
|
31
|
-
<!-- Modal header -->
|
|
32
|
-
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
|
33
|
-
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
34
|
-
Reset your password on {{ coreStore.config?.brandName }}
|
|
35
|
-
</h3>
|
|
36
|
-
</div>
|
|
37
|
-
<!-- Modal body -->
|
|
38
|
-
<div class="p-4 md:p-5">
|
|
39
|
-
<form v-if="enteringNew" class="space-y-4" role="alert" @submit.prevent>
|
|
40
|
-
<div class="relative">
|
|
41
|
-
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
|
|
42
|
-
<input
|
|
43
|
-
:type="unmasked ? 'text' : 'password'"
|
|
44
|
-
name="password" id="password"
|
|
45
|
-
v-model="password"
|
|
46
|
-
@keydown.enter="passwordConfirmationInput.focus()"
|
|
47
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
48
|
-
placeholder="New password" required
|
|
49
|
-
/>
|
|
50
|
-
|
|
51
|
-
<button
|
|
52
|
-
type="button"
|
|
53
|
-
@click="unmasked = !unmasked"
|
|
54
|
-
class="h-6 absolute inset-y-2 top-9 right-1 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
55
|
-
>
|
|
56
|
-
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked" />
|
|
57
|
-
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
58
|
-
</button>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<div class="relative">
|
|
62
|
-
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
|
|
63
|
-
<input
|
|
64
|
-
:type="unmasked ? 'text' : 'password'"
|
|
65
|
-
name="password_confirmation" id="password_confirmation"
|
|
66
|
-
v-model="passwordConfirmation"
|
|
67
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
68
|
-
placeholder="Confirm new password" required
|
|
69
|
-
/>
|
|
70
|
-
|
|
71
|
-
<button
|
|
72
|
-
type="button"
|
|
73
|
-
@click="unmasked = !unmasked"
|
|
74
|
-
class="h-6 absolute inset-y-2 top-9 right-1 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
75
|
-
>
|
|
76
|
-
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked" />
|
|
77
|
-
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
78
|
-
</button>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<div v-if="validationError || error" class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
|
82
|
-
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
83
|
-
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
|
84
|
-
</svg>
|
|
85
|
-
<span class="sr-only">Info</span>
|
|
86
|
-
<div>
|
|
87
|
-
{{ validationError || error }}
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<button
|
|
92
|
-
:disabled="inProgress || (validationRunning && validationError)"
|
|
93
|
-
@click="setNewPassword"
|
|
94
|
-
type="submit" class="flex items-center justify-center gap-1 w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 disabled:opacity-50">
|
|
95
|
-
<svg v-if="inProgress"
|
|
96
|
-
aria-hidden="true" class="w-4 h-4 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874
|
|
97
|
-
// 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
|
|
98
|
-
Set new password
|
|
99
|
-
</button>
|
|
100
|
-
</form>
|
|
101
|
-
<!-- END of set new paasord -->
|
|
102
|
-
<div v-if="!enteringNew && requestSent" class="flex items center justify-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-green-800 dark:text-green-400" role="alert">
|
|
103
|
-
If user with specified email exists in our db, then request was sent. Please check your email at {{ sentToEmail }} to reset your password.
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<form v-if="!enteringNew && !requestSent" class="space-y-4" @submit.prevent>
|
|
107
|
-
<div>
|
|
108
|
-
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
|
109
|
-
<input type="email" name="email" id="email"
|
|
110
|
-
@keydown.enter="reset()"
|
|
111
|
-
ref="emailInput"
|
|
112
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
|
113
|
-
placeholder="name@company.com"
|
|
114
|
-
required
|
|
115
|
-
/>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<div v-if="error" class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
|
119
|
-
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
120
|
-
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
|
121
|
-
</svg>
|
|
122
|
-
<span class="sr-only">Info</span>
|
|
123
|
-
<div>
|
|
124
|
-
{{ error }}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<button
|
|
130
|
-
@click="reset"
|
|
131
|
-
type="submit" class="flex items-center justify-center gap-1 w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
|
132
|
-
<svg v-if="inProgress"
|
|
133
|
-
aria-hidden="true" class="w-4 h-4 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
|
|
134
|
-
|
|
135
|
-
Reset Password
|
|
136
|
-
|
|
137
|
-
</button>
|
|
138
|
-
|
|
139
|
-
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
140
|
-
or <router-link class=" text-blue-600 underline dark:text-blue-500 hover:no-underline
|
|
141
|
-
cursor-pointer" to="/login">
|
|
142
|
-
Back to login
|
|
143
|
-
</router-link>
|
|
144
|
-
</p>
|
|
145
|
-
|
|
146
|
-
</form>
|
|
147
|
-
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
</div>
|
|
154
|
-
</template>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<script setup lang="ts">
|
|
158
|
-
|
|
159
|
-
import { onMounted, ref, computed, Ref } from 'vue';
|
|
160
|
-
import { useCoreStore } from '@/stores/core';
|
|
161
|
-
import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
|
|
162
|
-
import { useRoute, useRouter } from 'vue-router';
|
|
163
|
-
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const inProgress = ref(false);
|
|
167
|
-
|
|
168
|
-
const coreStore = useCoreStore();
|
|
169
|
-
const requestSent = ref(false);
|
|
170
|
-
|
|
171
|
-
const emailInput = ref(null);
|
|
172
|
-
const password = ref("");
|
|
173
|
-
const passwordConfirmation = ref("");
|
|
174
|
-
const unmasked = ref(false);
|
|
175
|
-
const sentToEmail: Ref<string> = ref('');
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const route = useRoute();
|
|
179
|
-
const router = useRouter();
|
|
180
|
-
|
|
181
|
-
function checkPassowrd() {
|
|
182
|
-
|
|
183
|
-
if (!password.value || !passwordConfirmation.value) {
|
|
184
|
-
return 'Please enter both password and password confirmation';
|
|
185
|
-
}
|
|
186
|
-
if (password.value !== passwordConfirmation.value) {
|
|
187
|
-
return 'Passwords do not match';
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (password.value.length < passwordField.value.minLength) {
|
|
191
|
-
return `Password must be at least ${passwordField.value.minLength} characters long`;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (password.value.length > passwordField.value.maxLength) {
|
|
195
|
-
return `Password must be at most ${passwordField.value.maxLength} characters long`;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (passwordField.value.validation) {
|
|
199
|
-
const valError = applyRegexValidation(password.value, passwordField.value.validation);
|
|
200
|
-
if (valError) {
|
|
201
|
-
return valError;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const validationRunning = ref(false);
|
|
209
|
-
|
|
210
|
-
const validationError = computed(() => {
|
|
211
|
-
console.log('validationRunning.value', validationRunning.value, 'aa', checkPassowrd());
|
|
212
|
-
if (validationRunning.value) {
|
|
213
|
-
return checkPassowrd();
|
|
214
|
-
}
|
|
215
|
-
return null;
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
const error = ref(null);
|
|
219
|
-
|
|
220
|
-
// if token query param is present, we are entering new password
|
|
221
|
-
const enteringNew = computed(() => route.query.token);
|
|
222
|
-
|
|
223
|
-
const backgroundPosition = computed(() => {
|
|
224
|
-
return coreStore.config?.loginBackgroundPosition || '1/2';
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const passwordField = computed(
|
|
228
|
-
() => route.meta.passwordField
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
onMounted(async () => {
|
|
232
|
-
await coreStore.getPublicConfig();
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
async function reset() {
|
|
236
|
-
error.value = null;
|
|
237
|
-
const email = emailInput.value.value;
|
|
238
|
-
if (!email) {
|
|
239
|
-
error.value = 'Please enter your email';
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
inProgress.value = true;
|
|
244
|
-
const resp = await callAdminForthApi({
|
|
245
|
-
path: `/plugin/${route.meta.pluginInstanceId}/reset-password`,
|
|
246
|
-
method: 'POST',
|
|
247
|
-
body: {
|
|
248
|
-
email,
|
|
249
|
-
url: window.location.origin + window.location.pathname,
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
inProgress.value = false;
|
|
253
|
-
if (resp.error) {
|
|
254
|
-
error.value = resp.error;
|
|
255
|
-
} else {
|
|
256
|
-
error.value = null;
|
|
257
|
-
requestSent.value = true;
|
|
258
|
-
sentToEmail.value = email;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async function setNewPassword() {
|
|
263
|
-
error.value = null;
|
|
264
|
-
|
|
265
|
-
if (checkPassowrd()) {
|
|
266
|
-
validationRunning.value = true;
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
inProgress.value = true;
|
|
271
|
-
const resp = await callAdminForthApi({
|
|
272
|
-
path: `/plugin/${route.meta.pluginInstanceId}/reset-password-confirm`,
|
|
273
|
-
method: 'POST',
|
|
274
|
-
body: {
|
|
275
|
-
token: route.query.token,
|
|
276
|
-
password: password.value,
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
inProgress.value = false;
|
|
280
|
-
if (resp.error) {
|
|
281
|
-
error.value = resp.error;
|
|
282
|
-
} else {
|
|
283
|
-
error.value = null;
|
|
284
|
-
router.push('/login');
|
|
285
|
-
window.adminforth.alert({
|
|
286
|
-
message: 'Password reset successfully. Please login with your new password',
|
|
287
|
-
variant: 'success',
|
|
288
|
-
timeout: 15,
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
</script>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3">
|
|
3
|
-
Forgot password? <router-link class=" text-blue-600 underline dark:text-blue-500 hover:no-underline
|
|
4
|
-
cursor-pointer" to="/reset-password">
|
|
5
|
-
reset it
|
|
6
|
-
</router-link>
|
|
7
|
-
</p>
|
|
8
|
-
</template>
|
|
9
|
-
|
|
10
|
-
<script setup>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</script>
|