@adminforth/open-signup 1.0.3 → 1.0.5
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 +63 -55
- package/custom/SignupUnderLogin.vue +2 -2
- package/dist/custom/SignupPage.vue +63 -55
- package/dist/custom/SignupUnderLogin.vue +2 -2
- package/dist/index.js +60 -85
- package/index.ts +73 -96
- package/package.json +1 -1
- package/types.ts +4 -13
package/custom/SignupPage.vue
CHANGED
|
@@ -31,34 +31,36 @@
|
|
|
31
31
|
<!-- Modal header -->
|
|
32
32
|
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
|
33
33
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
34
|
-
Sign up into {{ coreStore.config?.brandName }}
|
|
34
|
+
{{$t('Sign up into')}} {{ coreStore.config?.brandName }}
|
|
35
35
|
</h3>
|
|
36
36
|
</div>
|
|
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">
|
|
41
|
-
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
|
40
|
+
<div v-if="!verifyToken" class="relative">
|
|
41
|
+
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('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
|
-
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
|
|
52
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
53
|
+
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('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
|
-
placeholder="New password" required
|
|
63
|
+
:placeholder="$t('New password')" required
|
|
62
64
|
/>
|
|
63
65
|
|
|
64
66
|
<button
|
|
@@ -71,16 +73,17 @@
|
|
|
71
73
|
</button>
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
|
-
<div class="relative">
|
|
75
|
-
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
|
|
76
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
77
|
+
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('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"
|
|
81
84
|
v-model="passwordConfirmation"
|
|
82
85
|
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"
|
|
83
|
-
placeholder="Confirm new password" required
|
|
86
|
+
:placeholder="$t('Confirm new password')" required
|
|
84
87
|
/>
|
|
85
88
|
|
|
86
89
|
<button
|
|
@@ -97,7 +100,7 @@
|
|
|
97
100
|
<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">
|
|
98
101
|
<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"/>
|
|
99
102
|
</svg>
|
|
100
|
-
<span class="sr-only">Info</span>
|
|
103
|
+
<span class="sr-only">{{$t('Info')}}</span>
|
|
101
104
|
<div>
|
|
102
105
|
{{ validationError || error }}
|
|
103
106
|
</div>
|
|
@@ -105,19 +108,21 @@
|
|
|
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"
|
|
112
115
|
>
|
|
113
|
-
Sign up
|
|
116
|
+
{{$t('Sign up')}}
|
|
114
117
|
</Button>
|
|
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
|
+
{{$t('Please check your email at')}} {{ sentToEmail }} {{$t('to confirm your email address.')}}
|
|
122
|
+
</div>
|
|
123
|
+
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
124
|
+
{{$t('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>
|
|
@@ -31,34 +31,36 @@
|
|
|
31
31
|
<!-- Modal header -->
|
|
32
32
|
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
|
33
33
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
34
|
-
Sign up into {{ coreStore.config?.brandName }}
|
|
34
|
+
{{$t('Sign up into')}} {{ coreStore.config?.brandName }}
|
|
35
35
|
</h3>
|
|
36
36
|
</div>
|
|
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">
|
|
41
|
-
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
|
40
|
+
<div v-if="!verifyToken" class="relative">
|
|
41
|
+
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('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
|
-
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New password</label>
|
|
52
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
53
|
+
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('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
|
-
placeholder="New password" required
|
|
63
|
+
:placeholder="$t('New password')" required
|
|
62
64
|
/>
|
|
63
65
|
|
|
64
66
|
<button
|
|
@@ -71,16 +73,17 @@
|
|
|
71
73
|
</button>
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
|
-
<div class="relative">
|
|
75
|
-
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm new password</label>
|
|
76
|
+
<div v-if="isPasswordNeeded" class="relative">
|
|
77
|
+
<label for="password_confirmation" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{$t('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"
|
|
81
84
|
v-model="passwordConfirmation"
|
|
82
85
|
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"
|
|
83
|
-
placeholder="Confirm new password" required
|
|
86
|
+
:placeholder="$t('Confirm new password')" required
|
|
84
87
|
/>
|
|
85
88
|
|
|
86
89
|
<button
|
|
@@ -97,7 +100,7 @@
|
|
|
97
100
|
<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">
|
|
98
101
|
<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"/>
|
|
99
102
|
</svg>
|
|
100
|
-
<span class="sr-only">Info</span>
|
|
103
|
+
<span class="sr-only">{{$t('Info')}}</span>
|
|
101
104
|
<div>
|
|
102
105
|
{{ validationError || error }}
|
|
103
106
|
</div>
|
|
@@ -105,19 +108,21 @@
|
|
|
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"
|
|
112
115
|
>
|
|
113
|
-
Sign up
|
|
116
|
+
{{$t('Sign up')}}
|
|
114
117
|
</Button>
|
|
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
|
+
{{$t('Please check your email at')}} {{ sentToEmail }} {{$t('to confirm your email address.')}}
|
|
122
|
+
</div>
|
|
123
|
+
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
124
|
+
{{$t('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,26 @@ 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
|
+
this.options.confirmEmails.adapter.validate();
|
|
96
|
+
}
|
|
97
|
+
if (this.options.confirmEmails) {
|
|
98
|
+
const emailConfirmedColumn = this.resourceConfig.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
|
|
99
|
+
if (emailConfirmedColumn.type !== AdminForthDataTypes.BOOLEAN) {
|
|
100
|
+
throw new Error(`Field ${this.emailConfirmedField.name} must be of type boolean`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
104
103
|
}
|
|
105
104
|
instanceUniqueRepresentation(pluginOptions) {
|
|
106
105
|
// optional method to return unique string representation of plugin instance.
|
|
107
106
|
// Needed if plugin can have multiple instances on one resource
|
|
108
107
|
return `single`;
|
|
109
108
|
}
|
|
110
|
-
doLogin(email, response) {
|
|
109
|
+
doLogin(email, response, extra) {
|
|
111
110
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
111
|
const username = email;
|
|
113
112
|
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
@@ -117,7 +116,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
117
116
|
username,
|
|
118
117
|
};
|
|
119
118
|
const toReturn = { allowedLogin: true, error: '' };
|
|
120
|
-
yield this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response);
|
|
119
|
+
yield this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response, extra);
|
|
121
120
|
if (toReturn.allowedLogin) {
|
|
122
121
|
this.adminforth.auth.setAuthCookie({
|
|
123
122
|
response,
|
|
@@ -131,29 +130,36 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
131
130
|
setupEndpoints(server) {
|
|
132
131
|
server.endpoint({
|
|
133
132
|
method: 'POST',
|
|
134
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
133
|
+
path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
|
|
135
134
|
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');
|
|
135
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, headers, query, cookies }) {
|
|
136
|
+
const { token, password } = body;
|
|
137
|
+
const { email } = yield this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
|
|
139
138
|
if (!email) {
|
|
140
139
|
return { error: 'Invalid token', ok: false };
|
|
141
140
|
}
|
|
141
|
+
if (!password) {
|
|
142
|
+
return { error: 'Password is required', ok: false };
|
|
143
|
+
}
|
|
142
144
|
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
143
145
|
if (!userRecord) {
|
|
144
146
|
return { error: 'User not found', ok: false };
|
|
145
147
|
}
|
|
148
|
+
if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
|
|
149
|
+
return { error: 'Email already confirmed', ok: false };
|
|
150
|
+
}
|
|
146
151
|
yield this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
147
|
-
[this.options.confirmEmails.emailConfirmedField]: true
|
|
152
|
+
[this.options.confirmEmails.emailConfirmedField]: true,
|
|
153
|
+
[this.options.passwordHashField]: yield AdminForth.Utils.generatePasswordHash(password),
|
|
148
154
|
});
|
|
149
|
-
return yield this.doLogin(email, response);
|
|
155
|
+
return yield this.doLogin(email, response, { body, headers, query, cookies });
|
|
150
156
|
})
|
|
151
157
|
});
|
|
152
158
|
server.endpoint({
|
|
153
159
|
method: 'POST',
|
|
154
160
|
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
155
161
|
noAuth: true,
|
|
156
|
-
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response }) {
|
|
162
|
+
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response, headers, query, cookies }) {
|
|
157
163
|
const { email, url, password } = body;
|
|
158
164
|
// validate email
|
|
159
165
|
if (this.emailField.validation) {
|
|
@@ -164,51 +170,39 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
164
170
|
}
|
|
165
171
|
}
|
|
166
172
|
// validate password
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
if (!this.options.confirmEmails) {
|
|
174
|
+
if (password.length < this.passwordField.minLength) {
|
|
175
|
+
return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
|
|
176
|
+
}
|
|
177
|
+
if (password.length > this.passwordField.maxLength) {
|
|
178
|
+
return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
|
|
179
|
+
}
|
|
180
|
+
if (this.passwordField.validation) {
|
|
181
|
+
for (const { regExp, message } of this.passwordField.validation) {
|
|
182
|
+
if (!new RegExp(regExp).test(password)) {
|
|
183
|
+
return { error: message, ok: false };
|
|
184
|
+
}
|
|
177
185
|
}
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
188
|
// first check again if email already exists
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
189
|
+
const existingUser = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
190
|
+
if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && (existingUser === null || existingUser === void 0 ? void 0 : existingUser[this.emailConfirmedField.name]))) {
|
|
183
191
|
return { error: 'Email already exists', ok: false };
|
|
184
192
|
}
|
|
185
193
|
// create user
|
|
186
|
-
|
|
194
|
+
if (!existingUser) {
|
|
195
|
+
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) : '' }));
|
|
196
|
+
}
|
|
187
197
|
if (!this.options.confirmEmails) {
|
|
188
|
-
const resp = yield this.doLogin(email, response);
|
|
198
|
+
const resp = yield this.doLogin(email, response, { body, headers, query, cookies });
|
|
189
199
|
return resp;
|
|
190
200
|
}
|
|
191
201
|
// send confirmation email
|
|
192
202
|
const brandName = this.adminforth.config.customization.brandName;
|
|
193
203
|
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
204
|
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: `
|
|
205
|
+
const emailText = `
|
|
212
206
|
Dear user,
|
|
213
207
|
Welcome to ${brandName}!
|
|
214
208
|
|
|
@@ -222,11 +216,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
222
216
|
Thanks,
|
|
223
217
|
The ${brandName} Team
|
|
224
218
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
Html: {
|
|
228
|
-
Charset: "UTF-8",
|
|
229
|
-
Data: `
|
|
219
|
+
`;
|
|
220
|
+
const emailHtml = `
|
|
230
221
|
<html>
|
|
231
222
|
<head></head>
|
|
232
223
|
<body>
|
|
@@ -242,26 +233,10 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
242
233
|
</html>
|
|
243
234
|
|
|
244
235
|
|
|
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
|
-
}
|
|
236
|
+
`;
|
|
237
|
+
const emailSubject = `Signup request at ${brandName}`;
|
|
238
|
+
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
239
|
+
this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
|
|
265
240
|
return { ok: true };
|
|
266
241
|
})
|
|
267
242
|
});
|
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,21 @@ 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
|
+
this.options.confirmEmails.adapter.validate();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if(this.options.confirmEmails){
|
|
109
|
+
const emailConfirmedColumn = this.resourceConfig.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
|
|
110
|
+
if (emailConfirmedColumn.type !== AdminForthDataTypes.BOOLEAN) {
|
|
111
|
+
throw new Error(`Field ${this.emailConfirmedField.name} must be of type boolean`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
instanceUniqueRepresentation(pluginOptions: any) : string {
|
|
@@ -120,7 +119,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
120
119
|
return `single`;
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
async doLogin(email: string, response: any): Promise<{ error?: string; allowedLogin: boolean; redirectTo?: string; }> {
|
|
122
|
+
async doLogin(email: string, response: any, extra: HttpExtra): Promise<{ error?: string; allowedLogin: boolean; redirectTo?: string; }> {
|
|
124
123
|
|
|
125
124
|
const username = email;
|
|
126
125
|
const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
@@ -131,7 +130,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
131
130
|
};
|
|
132
131
|
const toReturn = { allowedLogin: true, error: '' };
|
|
133
132
|
|
|
134
|
-
await this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response);
|
|
133
|
+
await this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response, extra);
|
|
135
134
|
if (toReturn.allowedLogin) {
|
|
136
135
|
this.adminforth.auth.setAuthCookie({
|
|
137
136
|
response,
|
|
@@ -149,22 +148,32 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
149
148
|
setupEndpoints(server: IHttpServer) {
|
|
150
149
|
server.endpoint({
|
|
151
150
|
method: 'POST',
|
|
152
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
151
|
+
path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
|
|
153
152
|
noAuth: true,
|
|
154
|
-
handler: async ({ body, response }) => {
|
|
155
|
-
const { token } = body;
|
|
156
|
-
const { email } = await this.adminforth.auth.verify(token, 'tempVerifyEmailToken');
|
|
153
|
+
handler: async ({ body, response, headers, query, cookies }) => {
|
|
154
|
+
const { token, password } = body;
|
|
155
|
+
const { email } = await this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
|
|
157
156
|
if (!email) {
|
|
158
157
|
return { error: 'Invalid token', ok: false };
|
|
159
158
|
}
|
|
159
|
+
|
|
160
|
+
if(!password) {
|
|
161
|
+
return { error: 'Password is required', ok: false };
|
|
162
|
+
}
|
|
160
163
|
const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
161
164
|
if (!userRecord) {
|
|
162
165
|
return { error: 'User not found', ok: false };
|
|
163
166
|
}
|
|
167
|
+
|
|
168
|
+
if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
|
|
169
|
+
return { error: 'Email already confirmed', ok: false };
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
await this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
165
|
-
[this.options.confirmEmails.emailConfirmedField]: true
|
|
173
|
+
[this.options.confirmEmails.emailConfirmedField]: true,
|
|
174
|
+
[this.options.passwordHashField]: await AdminForth.Utils.generatePasswordHash(password),
|
|
166
175
|
});
|
|
167
|
-
return await this.doLogin(email, response);
|
|
176
|
+
return await this.doLogin(email, response, { body, headers, query, cookies });
|
|
168
177
|
}
|
|
169
178
|
});
|
|
170
179
|
|
|
@@ -172,7 +181,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
172
181
|
method: 'POST',
|
|
173
182
|
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
174
183
|
noAuth: true,
|
|
175
|
-
handler: async ({ body, response }) => {
|
|
184
|
+
handler: async ({ body, response, headers, query, cookies }) => {
|
|
176
185
|
const { email, url, password } = body;
|
|
177
186
|
|
|
178
187
|
// validate email
|
|
@@ -185,36 +194,40 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
// validate password
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
if (!this.options.confirmEmails) {
|
|
198
|
+
if (password.length < this.passwordField.minLength) {
|
|
199
|
+
return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
|
|
200
|
+
}
|
|
201
|
+
if (password.length > this.passwordField.maxLength) {
|
|
202
|
+
return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
|
|
203
|
+
}
|
|
204
|
+
if (this.passwordField.validation) {
|
|
205
|
+
for (const { regExp, message } of this.passwordField.validation) {
|
|
206
|
+
if (!new RegExp(regExp).test(password)) {
|
|
207
|
+
return { error: message, ok: false };
|
|
208
|
+
}
|
|
198
209
|
}
|
|
199
210
|
}
|
|
200
211
|
}
|
|
201
212
|
|
|
202
213
|
// first check again if email already exists
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
214
|
+
const existingUser = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
215
|
+
if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && existingUser?.[this.emailConfirmedField.name])) {
|
|
205
216
|
return { error: 'Email already exists', ok: false };
|
|
206
217
|
}
|
|
207
218
|
|
|
208
219
|
// create user
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
if (!existingUser) {
|
|
221
|
+
const created = await this.adminforth.resource(this.authResource.resourceId).create({
|
|
222
|
+
...(this.options.defaultFieldValues || {}),
|
|
223
|
+
...(this.options.confirmEmails ? { [this.options.confirmEmails.emailConfirmedField]: false } : {}),
|
|
224
|
+
[this.emailField.name]: email,
|
|
225
|
+
[this.options.passwordHashField]: password ? await AdminForth.Utils.generatePasswordHash(password) : '',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
215
228
|
|
|
216
229
|
if (!this.options.confirmEmails) {
|
|
217
|
-
const resp = await this.doLogin(email, response);
|
|
230
|
+
const resp = await this.doLogin(email, response, { body, headers, query, cookies });
|
|
218
231
|
return resp;
|
|
219
232
|
}
|
|
220
233
|
|
|
@@ -223,27 +236,8 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
223
236
|
const brandName = this.adminforth.config.customization.brandName;
|
|
224
237
|
|
|
225
238
|
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
239
|
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: `
|
|
240
|
+
const emailText = `
|
|
247
241
|
Dear user,
|
|
248
242
|
Welcome to ${brandName}!
|
|
249
243
|
|
|
@@ -257,11 +251,9 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
257
251
|
Thanks,
|
|
258
252
|
The ${brandName} Team
|
|
259
253
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Charset: "UTF-8",
|
|
264
|
-
Data: `
|
|
254
|
+
`;
|
|
255
|
+
|
|
256
|
+
const emailHtml = `
|
|
265
257
|
<html>
|
|
266
258
|
<head></head>
|
|
267
259
|
<body>
|
|
@@ -277,26 +269,11 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
277
269
|
</html>
|
|
278
270
|
|
|
279
271
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
},
|
|
283
|
-
Subject: {
|
|
284
|
-
Charset: "UTF-8",
|
|
285
|
-
Data: `Signup request at ${brandName}`
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
Source: `${this.options.confirmEmails.sendFrom}`
|
|
289
|
-
});
|
|
272
|
+
`;
|
|
273
|
+
const emailSubject = `Signup request at ${brandName}`;
|
|
290
274
|
|
|
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
|
-
}
|
|
275
|
+
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
276
|
+
this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
|
|
300
277
|
|
|
301
278
|
return { ok: true };
|
|
302
279
|
}
|
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
|
|