@adminforth/open-signup 1.0.2
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 +278 -0
- package/custom/SignupUnderLogin.vue +13 -0
- package/dist/custom/ResetPassword.vue +295 -0
- package/dist/custom/ResetPasswordUnderLogin.vue +13 -0
- package/dist/custom/SignupPage.vue +278 -0
- package/dist/custom/SignupUnderLogin.vue +13 -0
- package/dist/index.js +269 -0
- package/dist/types.js +1 -0
- package/index.ts +309 -0
- package/package.json +19 -0
- package/tsconfig.json +12 -0
- package/types.ts +68 -0
|
@@ -0,0 +1,278 @@
|
|
|
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
|
+
Sign up into {{ coreStore.config?.brandName }}
|
|
35
|
+
</h3>
|
|
36
|
+
</div>
|
|
37
|
+
<!-- Modal body -->
|
|
38
|
+
<div class="p-4 md:p-5">
|
|
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>
|
|
42
|
+
<input type="email" name="email" id="email"
|
|
43
|
+
tabindex="1"
|
|
44
|
+
@keydown.enter="passwordInput.focus()"
|
|
45
|
+
ref="emailInput"
|
|
46
|
+
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
|
+
placeholder="name@company.com"
|
|
48
|
+
required
|
|
49
|
+
/>
|
|
50
|
+
</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>
|
|
53
|
+
<input
|
|
54
|
+
tabindex="2"
|
|
55
|
+
ref="passwordInput"
|
|
56
|
+
:type="unmasked ? 'text' : 'password'"
|
|
57
|
+
name="password" id="password"
|
|
58
|
+
v-model="password"
|
|
59
|
+
@keydown.enter="passwordConfirmationInput.focus()"
|
|
60
|
+
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
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
@click="unmasked = !unmasked"
|
|
67
|
+
class="h-6 absolute inset-y-2 top-9 right-1 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
68
|
+
>
|
|
69
|
+
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked" />
|
|
70
|
+
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
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
|
+
<input
|
|
77
|
+
ref="passwordConfirmationInput"
|
|
78
|
+
:type="unmasked ? 'text' : 'password'"
|
|
79
|
+
name="password_confirmation" id="password_confirmation"
|
|
80
|
+
tabindex="3"
|
|
81
|
+
v-model="passwordConfirmation"
|
|
82
|
+
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
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
@click="unmasked = !unmasked"
|
|
89
|
+
class="h-6 absolute inset-y-2 top-9 right-1 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
90
|
+
>
|
|
91
|
+
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked" />
|
|
92
|
+
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<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">
|
|
97
|
+
<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
|
+
<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
|
+
</svg>
|
|
100
|
+
<span class="sr-only">Info</span>
|
|
101
|
+
<div>
|
|
102
|
+
{{ validationError || error }}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<Button
|
|
107
|
+
:disabled="inProgress || (validationRunning && validationError)"
|
|
108
|
+
@click="doSignup"
|
|
109
|
+
type="submit"
|
|
110
|
+
class="w-full"
|
|
111
|
+
:loader="inProgress"
|
|
112
|
+
>
|
|
113
|
+
Sign up
|
|
114
|
+
</Button>
|
|
115
|
+
</form>
|
|
116
|
+
<!-- END of set new paasord -->
|
|
117
|
+
<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
|
+
If user with specified email exists in our db, then request was sent. Please check your email at {{ sentToEmail }} to reset your password.
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
<script setup lang="ts">
|
|
131
|
+
|
|
132
|
+
import { onMounted, ref, computed, Ref } from 'vue';
|
|
133
|
+
import { useCoreStore } from '@/stores/core';
|
|
134
|
+
import { useUserStore } from '@/stores/user';
|
|
135
|
+
import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
|
|
136
|
+
import { useRoute, useRouter } from 'vue-router';
|
|
137
|
+
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
138
|
+
import Button from '@/afcl/Button.vue';
|
|
139
|
+
|
|
140
|
+
const inProgress = ref(false);
|
|
141
|
+
|
|
142
|
+
const coreStore = useCoreStore();
|
|
143
|
+
const requestSent = ref(false);
|
|
144
|
+
|
|
145
|
+
const emailInput = ref(null);
|
|
146
|
+
const passwordInput = ref(null);
|
|
147
|
+
const passwordConfirmationInput = ref(null);
|
|
148
|
+
|
|
149
|
+
const password = ref("");
|
|
150
|
+
const passwordConfirmation = ref("");
|
|
151
|
+
const unmasked = ref(false);
|
|
152
|
+
const sentToEmail: Ref<string> = ref('');
|
|
153
|
+
|
|
154
|
+
const user = useUserStore();
|
|
155
|
+
|
|
156
|
+
const route = useRoute();
|
|
157
|
+
const router = useRouter();
|
|
158
|
+
|
|
159
|
+
function checkPassowrd() {
|
|
160
|
+
|
|
161
|
+
if (!password.value || !passwordConfirmation.value) {
|
|
162
|
+
return 'Please enter both password and password confirmation';
|
|
163
|
+
}
|
|
164
|
+
if (password.value !== passwordConfirmation.value) {
|
|
165
|
+
return 'Passwords do not match';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (password.value.length < passwordField.value.minLength) {
|
|
169
|
+
return `Password must be at least ${passwordField.value.minLength} characters long`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (password.value.length > passwordField.value.maxLength) {
|
|
173
|
+
return `Password must be at most ${passwordField.value.maxLength} characters long`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (passwordField.value.validation) {
|
|
177
|
+
const valError = applyRegexValidation(password.value, passwordField.value.validation);
|
|
178
|
+
if (valError) {
|
|
179
|
+
return valError;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const validationRunning = ref(false);
|
|
187
|
+
|
|
188
|
+
const validationError = computed(() => {
|
|
189
|
+
if (validationRunning.value) {
|
|
190
|
+
return checkPassowrd();
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const error: Ref<str | null> = ref(null);
|
|
196
|
+
|
|
197
|
+
onMounted(async () => {
|
|
198
|
+
if (route.query.token) {
|
|
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();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
const backgroundPosition = computed(() => {
|
|
227
|
+
return coreStore.config?.loginBackgroundPosition || '1/2';
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const passwordField = computed(
|
|
231
|
+
() => route.meta.passwordField
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
onMounted(async () => {
|
|
235
|
+
await coreStore.getPublicConfig();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
async function doSignup() {
|
|
239
|
+
error.value = null;
|
|
240
|
+
const email = emailInput.value!.value;
|
|
241
|
+
if (!email) {
|
|
242
|
+
error.value = 'Please enter your email';
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (checkPassowrd()) {
|
|
246
|
+
validationRunning.value = true;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
inProgress.value = true;
|
|
251
|
+
const resp = await callAdminForthApi({
|
|
252
|
+
path: `/plugin/${route.meta.pluginInstanceId}/signup`,
|
|
253
|
+
method: 'POST',
|
|
254
|
+
body: {
|
|
255
|
+
email,
|
|
256
|
+
url: window.location.origin + window.location.pathname,
|
|
257
|
+
password: password.value,
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
inProgress.value = false;
|
|
261
|
+
if (resp.error) {
|
|
262
|
+
error.value = resp.error;
|
|
263
|
+
} else {
|
|
264
|
+
error.value = null;
|
|
265
|
+
requestSent.value = true;
|
|
266
|
+
sentToEmail.value = email;
|
|
267
|
+
if (resp.redirectTo) {
|
|
268
|
+
router.push(resp.redirectTo);
|
|
269
|
+
} else {
|
|
270
|
+
error.value = null;
|
|
271
|
+
await user.finishLogin();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
</script>
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
|
|
11
|
+
import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses';
|
|
12
|
+
export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super(options, import.meta.url);
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
modifyResourceConfig(adminforth, resourceConfig) {
|
|
18
|
+
const _super = Object.create(null, {
|
|
19
|
+
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
20
|
+
});
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
_super.modifyResourceConfig.call(this, adminforth, resourceConfig);
|
|
23
|
+
if (!this.options.emailField) {
|
|
24
|
+
throw new Error(`emailField is required and should be a name of field in auth resource`);
|
|
25
|
+
}
|
|
26
|
+
if (this.options.confirmEmails) {
|
|
27
|
+
if (!this.options.confirmEmails.emailProvider) {
|
|
28
|
+
throw new Error(`confirmEmails.emailProvider is required and should be a name of field in auth resource`);
|
|
29
|
+
}
|
|
30
|
+
if (!this.options.confirmEmails.emailConfirmedField) {
|
|
31
|
+
throw new Error(`confirmEmails.emailConfirmedField is required and should be a name of field in auth resource`);
|
|
32
|
+
}
|
|
33
|
+
const emailConfirmedField = this.authResource.columns.find(f => f.name === this.options.confirmEmails.emailConfirmedField);
|
|
34
|
+
if (!emailConfirmedField) {
|
|
35
|
+
const similar = suggestIfTypo(this.authResource.columns.map(f => f.name), this.options.confirmEmails.emailConfirmedField);
|
|
36
|
+
throw new Error(`Field with name ${this.options.confirmEmails.emailConfirmedField} not found in resource ${this.authResource.resourceId}.
|
|
37
|
+
${similar ? `Did you mean ${similar}?` : ''}
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
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
|
+
}
|
|
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
|
+
const emailField = authResource.columns.find(f => f.name === this.options.emailField);
|
|
52
|
+
if (!emailField) {
|
|
53
|
+
const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailField);
|
|
54
|
+
throw new Error(`Field with name ${this.options.emailField} not found in resource ${authResource.resourceId}.
|
|
55
|
+
${similar ? `Did you mean ${similar}?` : ''}
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
this.emailField = emailField;
|
|
59
|
+
if (!this.options.passwordField) {
|
|
60
|
+
throw new Error(`passwordField is required to get password constraints and should be a name of virtual field in auth resource`);
|
|
61
|
+
}
|
|
62
|
+
const passwordField = authResource.columns.find(f => f.name === this.options.passwordField);
|
|
63
|
+
if (!passwordField) {
|
|
64
|
+
const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.passwordField);
|
|
65
|
+
throw new Error(`Field with name ${this.options.passwordField} not found in resource ${authResource.resourceId}.
|
|
66
|
+
${similar ? `Did you mean ${similar}?` : ''}
|
|
67
|
+
`);
|
|
68
|
+
}
|
|
69
|
+
this.passwordField = passwordField;
|
|
70
|
+
adminforth.config.customization.loginPageInjections.underInputs.push({
|
|
71
|
+
file: this.componentPath('SignupUnderLogin.vue'),
|
|
72
|
+
});
|
|
73
|
+
adminforth.config.customization.customPages.push({
|
|
74
|
+
path: '/signup',
|
|
75
|
+
component: {
|
|
76
|
+
file: this.componentPath('SignupPage.vue'),
|
|
77
|
+
meta: {
|
|
78
|
+
customLayout: true,
|
|
79
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
80
|
+
passwordField: {
|
|
81
|
+
minLength: passwordField.minLength,
|
|
82
|
+
maxLength: passwordField.maxLength,
|
|
83
|
+
validation: passwordField.validation
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// for confirmation disable login if email is not confirmed
|
|
89
|
+
if (this.options.confirmEmails) {
|
|
90
|
+
if (!adminforth.config.auth.beforeLoginConfirmation) {
|
|
91
|
+
adminforth.config.auth.beforeLoginConfirmation = [];
|
|
92
|
+
}
|
|
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
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
103
|
+
// optional method where you can safely check field types after database discovery was performed
|
|
104
|
+
}
|
|
105
|
+
instanceUniqueRepresentation(pluginOptions) {
|
|
106
|
+
// optional method to return unique string representation of plugin instance.
|
|
107
|
+
// Needed if plugin can have multiple instances on one resource
|
|
108
|
+
return `single`;
|
|
109
|
+
}
|
|
110
|
+
doLogin(email, response) {
|
|
111
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
112
|
+
const username = email;
|
|
113
|
+
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
114
|
+
const adminUser = {
|
|
115
|
+
dbUser: userRecord,
|
|
116
|
+
pk: userRecord[this.authResource.columns.find((col) => col.primaryKey).name],
|
|
117
|
+
username,
|
|
118
|
+
};
|
|
119
|
+
const toReturn = { allowedLogin: true, error: '' };
|
|
120
|
+
yield this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response);
|
|
121
|
+
if (toReturn.allowedLogin) {
|
|
122
|
+
this.adminforth.auth.setAuthCookie({
|
|
123
|
+
response,
|
|
124
|
+
username,
|
|
125
|
+
pk: userRecord[this.authResource.columns.find((col) => col.primaryKey).name]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return toReturn;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
setupEndpoints(server) {
|
|
132
|
+
server.endpoint({
|
|
133
|
+
method: 'POST',
|
|
134
|
+
path: `/plugin/${this.pluginInstanceId}/completeVerifiedSignup`,
|
|
135
|
+
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');
|
|
139
|
+
if (!email) {
|
|
140
|
+
return { error: 'Invalid token', ok: false };
|
|
141
|
+
}
|
|
142
|
+
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
143
|
+
if (!userRecord) {
|
|
144
|
+
return { error: 'User not found', ok: false };
|
|
145
|
+
}
|
|
146
|
+
yield this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
147
|
+
[this.options.confirmEmails.emailConfirmedField]: true
|
|
148
|
+
});
|
|
149
|
+
return yield this.doLogin(email, response);
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
server.endpoint({
|
|
153
|
+
method: 'POST',
|
|
154
|
+
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
155
|
+
noAuth: true,
|
|
156
|
+
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response }) {
|
|
157
|
+
const { email, url, password } = body;
|
|
158
|
+
// validate email
|
|
159
|
+
if (this.emailField.validation) {
|
|
160
|
+
for (const { regExp, message } of this.emailField.validation) {
|
|
161
|
+
if (!new RegExp(regExp).test(password)) {
|
|
162
|
+
return { error: message, ok: false };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// validate password
|
|
167
|
+
if (password.length < this.passwordField.minLength) {
|
|
168
|
+
return { error: `Password must be at least ${this.passwordField.minLength} characters long`, ok: false };
|
|
169
|
+
}
|
|
170
|
+
if (password.length > this.passwordField.maxLength) {
|
|
171
|
+
return { error: `Password must be at most ${this.passwordField.maxLength} characters long`, ok: false };
|
|
172
|
+
}
|
|
173
|
+
if (this.passwordField.validation) {
|
|
174
|
+
for (const { regExp, message } of this.passwordField.validation) {
|
|
175
|
+
if (!new RegExp(regExp).test(password)) {
|
|
176
|
+
return { error: message, ok: false };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// first check again if email already exists
|
|
181
|
+
const existing = yield this.adminforth.resource(this.authResource.resourceId).count(Filters.EQ(this.emailField.name, email));
|
|
182
|
+
if (existing > 0) {
|
|
183
|
+
return { error: 'Email already exists', ok: false };
|
|
184
|
+
}
|
|
185
|
+
// create user
|
|
186
|
+
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]: yield AdminForth.Utils.generatePasswordHash(password) }));
|
|
187
|
+
if (!this.options.confirmEmails) {
|
|
188
|
+
const resp = yield this.doLogin(email, response);
|
|
189
|
+
return resp;
|
|
190
|
+
}
|
|
191
|
+
// send confirmation email
|
|
192
|
+
const brandName = this.adminforth.config.customization.brandName;
|
|
193
|
+
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
|
+
process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
|
|
203
|
+
const emailCommand = new SendEmailCommand({
|
|
204
|
+
Destination: {
|
|
205
|
+
ToAddresses: [email]
|
|
206
|
+
},
|
|
207
|
+
Message: {
|
|
208
|
+
Body: {
|
|
209
|
+
Text: {
|
|
210
|
+
Charset: "UTF-8",
|
|
211
|
+
Data: `
|
|
212
|
+
Dear user,
|
|
213
|
+
Welcome to ${brandName}!
|
|
214
|
+
|
|
215
|
+
To confirm your email, click the link below:\n\n
|
|
216
|
+
|
|
217
|
+
${url}?verifyToken=${verifyToken}\n\n
|
|
218
|
+
|
|
219
|
+
If you didn't request this, please ignore this email.\n\n
|
|
220
|
+
Link is valid for 2 hours.\n\n
|
|
221
|
+
|
|
222
|
+
Thanks,
|
|
223
|
+
The ${brandName} Team
|
|
224
|
+
|
|
225
|
+
`
|
|
226
|
+
},
|
|
227
|
+
Html: {
|
|
228
|
+
Charset: "UTF-8",
|
|
229
|
+
Data: `
|
|
230
|
+
<html>
|
|
231
|
+
<head></head>
|
|
232
|
+
<body>
|
|
233
|
+
<p>Dear user,</p>
|
|
234
|
+
<p>Welcome to ${brandName}!</p>
|
|
235
|
+
<p>To confirm your email, click the link below:</p>
|
|
236
|
+
<a href="${url}?token=${verifyToken}">Confirm email</a>
|
|
237
|
+
<p>If you didn't request this, please ignore this email.</p>
|
|
238
|
+
<p>Link is valid for 2 hours.</p>
|
|
239
|
+
<p>Thanks,</p>
|
|
240
|
+
<p>The ${brandName} Team</p>
|
|
241
|
+
</body>
|
|
242
|
+
</html>
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
`
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
Subject: {
|
|
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
|
+
}
|
|
265
|
+
return { ok: true };
|
|
266
|
+
})
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|