@adminforth/open-signup 1.0.5 → 1.0.7
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/Changelog.md +7 -0
- package/custom/SignupPage.vue +62 -16
- package/custom/SignupUnderLogin.vue +4 -1
- package/dist/custom/SignupPage.vue +62 -16
- package/dist/custom/SignupUnderLogin.vue +4 -1
- package/dist/index.js +48 -37
- package/index.ts +55 -37
- package/package.json +1 -1
package/Changelog.md
ADDED
package/custom/SignupPage.vue
CHANGED
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
{{$t('Please check your email at')}} {{ sentToEmail }} {{$t('to confirm your email address.')}}
|
|
122
122
|
</div>
|
|
123
123
|
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
124
|
-
{{$t('
|
|
124
|
+
{{$t('Already have an account?')}} <Link :to="`/login${route.query.next ? `?next=${route.query.next}` : ''}`">{{ $t('login here') }}</Link>
|
|
125
125
|
</p>
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
<script setup lang="ts">
|
|
136
|
-
import { onMounted, ref, computed, Ref } from 'vue';
|
|
136
|
+
import { onMounted, ref, computed, Ref, onBeforeMount } from 'vue';
|
|
137
137
|
import { useCoreStore } from '@/stores/core';
|
|
138
138
|
import { useUserStore } from '@/stores/user';
|
|
139
139
|
import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
|
|
@@ -141,6 +141,9 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
141
141
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
142
142
|
import Button from '@/afcl/Button.vue';
|
|
143
143
|
import Link from '@/afcl/Link.vue';
|
|
144
|
+
import { useI18n } from 'vue-i18n';
|
|
145
|
+
|
|
146
|
+
const { t } = useI18n();
|
|
144
147
|
|
|
145
148
|
const inProgress = ref(false);
|
|
146
149
|
|
|
@@ -158,7 +161,6 @@ const sentToEmail: Ref<string> = ref('');
|
|
|
158
161
|
|
|
159
162
|
const requestEmailConfirmation = computed(() => route.meta.requestEmailConfirmation);
|
|
160
163
|
const verifyToken = computed(() => route.query.token);
|
|
161
|
-
const toLoginText = computed(() => `${verifyToken.value ? 'Go' : 'Back'} to login`);
|
|
162
164
|
const isPasswordNeeded = computed(() => !requestEmailConfirmation.value || (requestEmailConfirmation.value && verifyToken.value));
|
|
163
165
|
|
|
164
166
|
const user = useUserStore();
|
|
@@ -169,22 +171,25 @@ const router = useRouter();
|
|
|
169
171
|
function checkPassword() {
|
|
170
172
|
|
|
171
173
|
if (!password.value || !passwordConfirmation.value) {
|
|
172
|
-
return 'Please enter both password and password confirmation';
|
|
174
|
+
return t('Please enter both password and password confirmation');
|
|
173
175
|
}
|
|
174
176
|
if (password.value !== passwordConfirmation.value) {
|
|
175
|
-
return 'Passwords do not match';
|
|
177
|
+
return t('Passwords do not match');
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
180
|
+
if (!passwordConstraints.value) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (password.value.length < passwordConstraints.value.minLength) {
|
|
184
|
+
return t(`Password must be at least {minLength} characters long`, { minLength: passwordConstraints.value.minLength });
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
if (password.value.length >
|
|
183
|
-
return `Password must be at most
|
|
187
|
+
if (password.value.length > passwordConstraints.value.maxLength) {
|
|
188
|
+
return t(`Password must be at most {maxLength} characters long`, { maxLength: passwordConstraints.value.maxLength });
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
if (
|
|
187
|
-
const valError = applyRegexValidation(password.value,
|
|
191
|
+
if (passwordConstraints.value.validation) {
|
|
192
|
+
const valError = applyRegexValidation(password.value, passwordConstraints.value.validation);
|
|
188
193
|
if (valError) {
|
|
189
194
|
return valError;
|
|
190
195
|
}
|
|
@@ -213,19 +218,60 @@ const backgroundPosition = computed(() => {
|
|
|
213
218
|
return coreStore.config?.loginBackgroundPosition || '1/2';
|
|
214
219
|
});
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
|
|
222
|
+
const passwordConstraints: Ref<{
|
|
223
|
+
minLength: number;
|
|
224
|
+
maxLength: number;
|
|
225
|
+
validation: string;
|
|
226
|
+
}> = ref({
|
|
227
|
+
minLength: 8,
|
|
228
|
+
maxLength: 100,
|
|
229
|
+
validation: '',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// implement something similar to beforeEnter
|
|
233
|
+
// beforeEnter: async (to, from, next) => {
|
|
234
|
+
// if(localStorage.getItem('isAuthorized') === 'true') {
|
|
235
|
+
// // check if url has next=... and redirect to it
|
|
236
|
+
// console.log('to.query', to.query)
|
|
237
|
+
// if (to.query.next) {
|
|
238
|
+
// next(to.query.next.toString())
|
|
239
|
+
// } else {
|
|
240
|
+
// next({name: 'home'});
|
|
241
|
+
// }
|
|
242
|
+
// } else {
|
|
243
|
+
// next()
|
|
244
|
+
// }
|
|
245
|
+
// }
|
|
246
|
+
|
|
247
|
+
onBeforeMount(() => {
|
|
248
|
+
if (localStorage.getItem('isAuthorized') === 'true') {
|
|
249
|
+
// if route has next param, redirect
|
|
250
|
+
coreStore.fetchMenuAndResource();
|
|
251
|
+
if (route.query.next) {
|
|
252
|
+
router.push(route.query.next.toString());
|
|
253
|
+
} else {
|
|
254
|
+
router.push({ name: 'home' });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
})
|
|
219
258
|
|
|
220
259
|
onMounted(async () => {
|
|
260
|
+
|
|
261
|
+
|
|
221
262
|
await coreStore.getPublicConfig();
|
|
263
|
+
// getPasswordConstraints
|
|
264
|
+
passwordConstraints.value = await callAdminForthApi({
|
|
265
|
+
path: `/plugin/${route.meta.pluginInstanceId}/password-constraints`,
|
|
266
|
+
method: 'GET',
|
|
267
|
+
});
|
|
222
268
|
});
|
|
223
269
|
|
|
224
270
|
async function doSignup() {
|
|
225
271
|
error.value = null;
|
|
226
272
|
const email = emailInput.value!.value;
|
|
227
273
|
if (!email) {
|
|
228
|
-
error.value = 'Please enter your email';
|
|
274
|
+
error.value = t('Please enter your email');
|
|
229
275
|
return;
|
|
230
276
|
}
|
|
231
277
|
if (!requestEmailConfirmation.value && checkPassword()) {
|
|
@@ -274,7 +320,7 @@ const signupAfterEmailConfirmation = async () => {
|
|
|
274
320
|
});
|
|
275
321
|
if (resp.error) {
|
|
276
322
|
window.adminforth.alert({
|
|
277
|
-
message: `Error fetching data:
|
|
323
|
+
message: t(`Error fetching data: {error}`, { error: resp.error }),
|
|
278
324
|
variant: 'danger',
|
|
279
325
|
});
|
|
280
326
|
} else if (resp.redirectTo) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3">
|
|
3
3
|
{{$t('Have no account?')}}
|
|
4
|
-
<Link to="
|
|
4
|
+
<Link :to="`/signup${route.query.next ? `?next=${route.query.next}` : ''}`">
|
|
5
5
|
{{$t('sign up now')}}
|
|
6
6
|
</Link>
|
|
7
7
|
</p>
|
|
@@ -9,5 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
<script setup>
|
|
11
11
|
import Link from '@/afcl/Link.vue';
|
|
12
|
+
import { useRoute } from 'vue-router';
|
|
13
|
+
|
|
14
|
+
const route = useRoute();
|
|
12
15
|
|
|
13
16
|
</script>
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
{{$t('Please check your email at')}} {{ sentToEmail }} {{$t('to confirm your email address.')}}
|
|
122
122
|
</div>
|
|
123
123
|
<p class="text-gray-500 dark:text-gray-400 font-sm text-right mt-3">
|
|
124
|
-
{{$t('
|
|
124
|
+
{{$t('Already have an account?')}} <Link :to="`/login${route.query.next ? `?next=${route.query.next}` : ''}`">{{ $t('login here') }}</Link>
|
|
125
125
|
</p>
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
<script setup lang="ts">
|
|
136
|
-
import { onMounted, ref, computed, Ref } from 'vue';
|
|
136
|
+
import { onMounted, ref, computed, Ref, onBeforeMount } from 'vue';
|
|
137
137
|
import { useCoreStore } from '@/stores/core';
|
|
138
138
|
import { useUserStore } from '@/stores/user';
|
|
139
139
|
import { callAdminForthApi, loadFile, applyRegexValidation } from '@/utils';
|
|
@@ -141,6 +141,9 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
141
141
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
142
142
|
import Button from '@/afcl/Button.vue';
|
|
143
143
|
import Link from '@/afcl/Link.vue';
|
|
144
|
+
import { useI18n } from 'vue-i18n';
|
|
145
|
+
|
|
146
|
+
const { t } = useI18n();
|
|
144
147
|
|
|
145
148
|
const inProgress = ref(false);
|
|
146
149
|
|
|
@@ -158,7 +161,6 @@ const sentToEmail: Ref<string> = ref('');
|
|
|
158
161
|
|
|
159
162
|
const requestEmailConfirmation = computed(() => route.meta.requestEmailConfirmation);
|
|
160
163
|
const verifyToken = computed(() => route.query.token);
|
|
161
|
-
const toLoginText = computed(() => `${verifyToken.value ? 'Go' : 'Back'} to login`);
|
|
162
164
|
const isPasswordNeeded = computed(() => !requestEmailConfirmation.value || (requestEmailConfirmation.value && verifyToken.value));
|
|
163
165
|
|
|
164
166
|
const user = useUserStore();
|
|
@@ -169,22 +171,25 @@ const router = useRouter();
|
|
|
169
171
|
function checkPassword() {
|
|
170
172
|
|
|
171
173
|
if (!password.value || !passwordConfirmation.value) {
|
|
172
|
-
return 'Please enter both password and password confirmation';
|
|
174
|
+
return t('Please enter both password and password confirmation');
|
|
173
175
|
}
|
|
174
176
|
if (password.value !== passwordConfirmation.value) {
|
|
175
|
-
return 'Passwords do not match';
|
|
177
|
+
return t('Passwords do not match');
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
180
|
+
if (!passwordConstraints.value) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (password.value.length < passwordConstraints.value.minLength) {
|
|
184
|
+
return t(`Password must be at least {minLength} characters long`, { minLength: passwordConstraints.value.minLength });
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
if (password.value.length >
|
|
183
|
-
return `Password must be at most
|
|
187
|
+
if (password.value.length > passwordConstraints.value.maxLength) {
|
|
188
|
+
return t(`Password must be at most {maxLength} characters long`, { maxLength: passwordConstraints.value.maxLength });
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
if (
|
|
187
|
-
const valError = applyRegexValidation(password.value,
|
|
191
|
+
if (passwordConstraints.value.validation) {
|
|
192
|
+
const valError = applyRegexValidation(password.value, passwordConstraints.value.validation);
|
|
188
193
|
if (valError) {
|
|
189
194
|
return valError;
|
|
190
195
|
}
|
|
@@ -213,19 +218,60 @@ const backgroundPosition = computed(() => {
|
|
|
213
218
|
return coreStore.config?.loginBackgroundPosition || '1/2';
|
|
214
219
|
});
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
|
|
222
|
+
const passwordConstraints: Ref<{
|
|
223
|
+
minLength: number;
|
|
224
|
+
maxLength: number;
|
|
225
|
+
validation: string;
|
|
226
|
+
}> = ref({
|
|
227
|
+
minLength: 8,
|
|
228
|
+
maxLength: 100,
|
|
229
|
+
validation: '',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// implement something similar to beforeEnter
|
|
233
|
+
// beforeEnter: async (to, from, next) => {
|
|
234
|
+
// if(localStorage.getItem('isAuthorized') === 'true') {
|
|
235
|
+
// // check if url has next=... and redirect to it
|
|
236
|
+
// console.log('to.query', to.query)
|
|
237
|
+
// if (to.query.next) {
|
|
238
|
+
// next(to.query.next.toString())
|
|
239
|
+
// } else {
|
|
240
|
+
// next({name: 'home'});
|
|
241
|
+
// }
|
|
242
|
+
// } else {
|
|
243
|
+
// next()
|
|
244
|
+
// }
|
|
245
|
+
// }
|
|
246
|
+
|
|
247
|
+
onBeforeMount(() => {
|
|
248
|
+
if (localStorage.getItem('isAuthorized') === 'true') {
|
|
249
|
+
// if route has next param, redirect
|
|
250
|
+
coreStore.fetchMenuAndResource();
|
|
251
|
+
if (route.query.next) {
|
|
252
|
+
router.push(route.query.next.toString());
|
|
253
|
+
} else {
|
|
254
|
+
router.push({ name: 'home' });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
})
|
|
219
258
|
|
|
220
259
|
onMounted(async () => {
|
|
260
|
+
|
|
261
|
+
|
|
221
262
|
await coreStore.getPublicConfig();
|
|
263
|
+
// getPasswordConstraints
|
|
264
|
+
passwordConstraints.value = await callAdminForthApi({
|
|
265
|
+
path: `/plugin/${route.meta.pluginInstanceId}/password-constraints`,
|
|
266
|
+
method: 'GET',
|
|
267
|
+
});
|
|
222
268
|
});
|
|
223
269
|
|
|
224
270
|
async function doSignup() {
|
|
225
271
|
error.value = null;
|
|
226
272
|
const email = emailInput.value!.value;
|
|
227
273
|
if (!email) {
|
|
228
|
-
error.value = 'Please enter your email';
|
|
274
|
+
error.value = t('Please enter your email');
|
|
229
275
|
return;
|
|
230
276
|
}
|
|
231
277
|
if (!requestEmailConfirmation.value && checkPassword()) {
|
|
@@ -274,7 +320,7 @@ const signupAfterEmailConfirmation = async () => {
|
|
|
274
320
|
});
|
|
275
321
|
if (resp.error) {
|
|
276
322
|
window.adminforth.alert({
|
|
277
|
-
message: `Error fetching data:
|
|
323
|
+
message: t(`Error fetching data: {error}`, { error: resp.error }),
|
|
278
324
|
variant: 'danger',
|
|
279
325
|
});
|
|
280
326
|
} else if (resp.redirectTo) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3">
|
|
3
3
|
{{$t('Have no account?')}}
|
|
4
|
-
<Link to="
|
|
4
|
+
<Link :to="`/signup${route.query.next ? `?next=${route.query.next}` : ''}`">
|
|
5
5
|
{{$t('sign up now')}}
|
|
6
6
|
</Link>
|
|
7
7
|
</p>
|
|
@@ -9,5 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
<script setup>
|
|
11
11
|
import Link from '@/afcl/Link.vue';
|
|
12
|
+
import { useRoute } from 'vue-router';
|
|
13
|
+
|
|
14
|
+
const route = useRoute();
|
|
12
15
|
|
|
13
16
|
</script>
|
package/dist/index.js
CHANGED
|
@@ -73,11 +73,6 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
73
73
|
meta: {
|
|
74
74
|
customLayout: true,
|
|
75
75
|
pluginInstanceId: this.pluginInstanceId,
|
|
76
|
-
passwordField: {
|
|
77
|
-
minLength: passwordField.minLength,
|
|
78
|
-
maxLength: passwordField.maxLength,
|
|
79
|
-
validation: passwordField.validation
|
|
80
|
-
},
|
|
81
76
|
requestEmailConfirmation: !!this.options.confirmEmails
|
|
82
77
|
}
|
|
83
78
|
}
|
|
@@ -128,25 +123,37 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
128
123
|
});
|
|
129
124
|
}
|
|
130
125
|
setupEndpoints(server) {
|
|
126
|
+
server.endpoint({
|
|
127
|
+
method: 'GET',
|
|
128
|
+
path: `/plugin/${this.pluginInstanceId}/password-constraints`,
|
|
129
|
+
noAuth: true,
|
|
130
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ tr }) {
|
|
131
|
+
return {
|
|
132
|
+
minLength: this.passwordField.minLength,
|
|
133
|
+
maxLength: this.passwordField.maxLength,
|
|
134
|
+
validation: yield Promise.all(this.passwordField.validation.map((_b) => __awaiter(this, [_b], void 0, function* ({ regExp, message }) { return ({ regExp, message: yield tr(message, 'opensignup') }); }))),
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
});
|
|
131
138
|
server.endpoint({
|
|
132
139
|
method: 'POST',
|
|
133
140
|
path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
|
|
134
141
|
noAuth: true,
|
|
135
|
-
handler: (
|
|
142
|
+
handler: (_c) => __awaiter(this, [_c], void 0, function* ({ body, response, headers, query, cookies, tr }) {
|
|
136
143
|
const { token, password } = body;
|
|
137
144
|
const { email } = yield this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
|
|
138
145
|
if (!email) {
|
|
139
146
|
return { error: 'Invalid token', ok: false };
|
|
140
147
|
}
|
|
141
148
|
if (!password) {
|
|
142
|
-
return { error: 'Password is required', ok: false };
|
|
149
|
+
return { error: tr('Password is required', 'opensignup'), ok: false };
|
|
143
150
|
}
|
|
144
151
|
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
145
152
|
if (!userRecord) {
|
|
146
|
-
return { error: 'User not found', ok: false };
|
|
153
|
+
return { error: tr('User not found'), ok: false };
|
|
147
154
|
}
|
|
148
155
|
if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
|
|
149
|
-
return { error: 'Email already confirmed', ok: false };
|
|
156
|
+
return { error: tr('Email already confirmed'), ok: false };
|
|
150
157
|
}
|
|
151
158
|
yield this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
152
159
|
[this.options.confirmEmails.emailConfirmedField]: true,
|
|
@@ -159,7 +166,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
159
166
|
method: 'POST',
|
|
160
167
|
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
161
168
|
noAuth: true,
|
|
162
|
-
handler: (
|
|
169
|
+
handler: (_d) => __awaiter(this, [_d], void 0, function* ({ body, response, headers, query, cookies, tr }) {
|
|
163
170
|
const { email, url, password } = body;
|
|
164
171
|
// validate email
|
|
165
172
|
if (this.emailField.validation) {
|
|
@@ -172,15 +179,21 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
172
179
|
// validate password
|
|
173
180
|
if (!this.options.confirmEmails) {
|
|
174
181
|
if (password.length < this.passwordField.minLength) {
|
|
175
|
-
return {
|
|
182
|
+
return {
|
|
183
|
+
error: tr(`Password must be at least ${this.passwordField.minLength} characters long`, 'opensignup'),
|
|
184
|
+
ok: false
|
|
185
|
+
};
|
|
176
186
|
}
|
|
177
187
|
if (password.length > this.passwordField.maxLength) {
|
|
178
|
-
return {
|
|
188
|
+
return {
|
|
189
|
+
error: tr(`Password must be at most ${this.passwordField.maxLength} characters long`, 'opensignup'),
|
|
190
|
+
ok: false
|
|
191
|
+
};
|
|
179
192
|
}
|
|
180
193
|
if (this.passwordField.validation) {
|
|
181
194
|
for (const { regExp, message } of this.passwordField.validation) {
|
|
182
195
|
if (!new RegExp(regExp).test(password)) {
|
|
183
|
-
return { error: message, ok: false };
|
|
196
|
+
return { error: tr(message, 'opensignup'), ok: false };
|
|
184
197
|
}
|
|
185
198
|
}
|
|
186
199
|
}
|
|
@@ -188,7 +201,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
188
201
|
// first check again if email already exists
|
|
189
202
|
const existingUser = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
190
203
|
if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && (existingUser === null || existingUser === void 0 ? void 0 : existingUser[this.emailConfirmedField.name]))) {
|
|
191
|
-
return { error: 'Email already exists', ok: false };
|
|
204
|
+
return { error: tr('Email already exists', 'opensignup'), ok: false };
|
|
192
205
|
}
|
|
193
206
|
// create user
|
|
194
207
|
if (!existingUser) {
|
|
@@ -202,39 +215,37 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
202
215
|
const brandName = this.adminforth.config.customization.brandName;
|
|
203
216
|
const verifyToken = this.adminforth.auth.issueJWT({ email, issuer: brandName }, 'tempVerifyEmailToken', '2h');
|
|
204
217
|
process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
|
|
205
|
-
const emailText = `
|
|
218
|
+
const emailText = tr(`
|
|
206
219
|
Dear user,
|
|
207
|
-
Welcome to
|
|
220
|
+
Welcome to {brandName}!
|
|
208
221
|
|
|
209
222
|
To confirm your email, click the link below:\n\n
|
|
210
223
|
|
|
211
|
-
|
|
224
|
+
{url}?verifyToken={verifyToken}\n\n
|
|
212
225
|
|
|
213
226
|
If you didn't request this, please ignore this email.\n\n
|
|
214
227
|
Link is valid for 2 hours.\n\n
|
|
215
228
|
|
|
216
229
|
Thanks,
|
|
217
|
-
The
|
|
230
|
+
The {brandName} Team
|
|
218
231
|
|
|
219
|
-
|
|
220
|
-
const emailHtml = `
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`;
|
|
237
|
-
const emailSubject = `Signup request at ${brandName}`;
|
|
232
|
+
`, 'opensignup', { brandName, url, verifyToken });
|
|
233
|
+
const emailHtml = tr(`
|
|
234
|
+
<html>
|
|
235
|
+
<head></head>
|
|
236
|
+
<body>
|
|
237
|
+
<p>Dear user,</p>
|
|
238
|
+
<p>Welcome to {brandName}!</p>
|
|
239
|
+
<p>To confirm your email, click the link below:</p>
|
|
240
|
+
<a href="{url}?token={verifyToken}">Confirm email</a>
|
|
241
|
+
<p>If you didn't request this, please ignore this email.</p>
|
|
242
|
+
<p>Link is valid for 2 hours.</p>
|
|
243
|
+
<p>Thanks,</p>
|
|
244
|
+
<p>The {brandName} Team</p>
|
|
245
|
+
</body>
|
|
246
|
+
</html>
|
|
247
|
+
`, 'opensignup', { brandName, url, verifyToken });
|
|
248
|
+
const emailSubject = tr(`Signup request at {brandName}`, 'opensignup', { brandName });
|
|
238
249
|
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
239
250
|
this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
|
|
240
251
|
return { ok: true };
|
package/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
|
|
2
2
|
import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResourceColumn, AdminForthResource, BeforeLoginConfirmationFunction, HttpExtra } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
|
+
import { error } from "console";
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
@@ -81,11 +82,6 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
81
82
|
meta: {
|
|
82
83
|
customLayout: true,
|
|
83
84
|
pluginInstanceId: this.pluginInstanceId,
|
|
84
|
-
passwordField: {
|
|
85
|
-
minLength: passwordField.minLength,
|
|
86
|
-
maxLength: passwordField.maxLength,
|
|
87
|
-
validation: passwordField.validation
|
|
88
|
-
},
|
|
89
85
|
requestEmailConfirmation: !!this.options.confirmEmails
|
|
90
86
|
}
|
|
91
87
|
}
|
|
@@ -146,11 +142,27 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
146
142
|
|
|
147
143
|
|
|
148
144
|
setupEndpoints(server: IHttpServer) {
|
|
145
|
+
|
|
146
|
+
server.endpoint({
|
|
147
|
+
method: 'GET',
|
|
148
|
+
path: `/plugin/${this.pluginInstanceId}/password-constraints`,
|
|
149
|
+
noAuth: true,
|
|
150
|
+
handler: async ({tr}) => {
|
|
151
|
+
return {
|
|
152
|
+
minLength: this.passwordField.minLength,
|
|
153
|
+
maxLength: this.passwordField.maxLength,
|
|
154
|
+
validation: await Promise.all(
|
|
155
|
+
this.passwordField.validation.map(async ({ regExp, message }) => ({ regExp, message: await tr(message, 'opensignup') }))
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
149
161
|
server.endpoint({
|
|
150
162
|
method: 'POST',
|
|
151
163
|
path: `/plugin/${this.pluginInstanceId}/complete-verified-signup`,
|
|
152
164
|
noAuth: true,
|
|
153
|
-
handler: async ({ body, response, headers, query, cookies }) => {
|
|
165
|
+
handler: async ({ body, response, headers, query, cookies, tr }) => {
|
|
154
166
|
const { token, password } = body;
|
|
155
167
|
const { email } = await this.adminforth.auth.verify(token, 'tempVerifyEmailToken', false);
|
|
156
168
|
if (!email) {
|
|
@@ -158,15 +170,15 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
158
170
|
}
|
|
159
171
|
|
|
160
172
|
if(!password) {
|
|
161
|
-
return { error: 'Password is required', ok: false };
|
|
173
|
+
return { error: tr('Password is required', 'opensignup'), ok: false };
|
|
162
174
|
}
|
|
163
175
|
const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
164
176
|
if (!userRecord) {
|
|
165
|
-
return { error: 'User not found', ok: false };
|
|
177
|
+
return { error: tr('User not found'), ok: false };
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
if (userRecord[this.options.confirmEmails.emailConfirmedField]) {
|
|
169
|
-
return { error: 'Email already confirmed', ok: false };
|
|
181
|
+
return { error: tr('Email already confirmed'), ok: false };
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
await this.adminforth.resource(this.authResource.resourceId).update(userRecord[this.authResource.columns.find((col) => col.primaryKey).name], {
|
|
@@ -181,7 +193,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
181
193
|
method: 'POST',
|
|
182
194
|
path: `/plugin/${this.pluginInstanceId}/signup`,
|
|
183
195
|
noAuth: true,
|
|
184
|
-
handler: async ({ body, response, headers, query, cookies }) => {
|
|
196
|
+
handler: async ({ body, response, headers, query, cookies, tr }) => {
|
|
185
197
|
const { email, url, password } = body;
|
|
186
198
|
|
|
187
199
|
// validate email
|
|
@@ -196,15 +208,21 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
196
208
|
// validate password
|
|
197
209
|
if (!this.options.confirmEmails) {
|
|
198
210
|
if (password.length < this.passwordField.minLength) {
|
|
199
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
error: tr(`Password must be at least ${this.passwordField.minLength} characters long`, 'opensignup'),
|
|
213
|
+
ok: false
|
|
214
|
+
};
|
|
200
215
|
}
|
|
201
216
|
if (password.length > this.passwordField.maxLength) {
|
|
202
|
-
return {
|
|
217
|
+
return {
|
|
218
|
+
error: tr(`Password must be at most ${this.passwordField.maxLength} characters long`, 'opensignup'),
|
|
219
|
+
ok: false
|
|
220
|
+
};
|
|
203
221
|
}
|
|
204
222
|
if (this.passwordField.validation) {
|
|
205
223
|
for (const { regExp, message } of this.passwordField.validation) {
|
|
206
224
|
if (!new RegExp(regExp).test(password)) {
|
|
207
|
-
return { error: message, ok: false };
|
|
225
|
+
return { error: tr(message, 'opensignup'), ok: false };
|
|
208
226
|
}
|
|
209
227
|
}
|
|
210
228
|
}
|
|
@@ -213,7 +231,7 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
213
231
|
// first check again if email already exists
|
|
214
232
|
const existingUser = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, email));
|
|
215
233
|
if ((!this.options.confirmEmails && existingUser) || (this.options.confirmEmails && existingUser?.[this.emailConfirmedField.name])) {
|
|
216
|
-
return { error: 'Email already exists', ok: false };
|
|
234
|
+
return { error: tr('Email already exists', 'opensignup'), ok: false };
|
|
217
235
|
}
|
|
218
236
|
|
|
219
237
|
// create user
|
|
@@ -237,40 +255,40 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
237
255
|
|
|
238
256
|
const verifyToken = this.adminforth.auth.issueJWT({email, issuer: brandName }, 'tempVerifyEmailToken', '2h');
|
|
239
257
|
process.env.HEAVY_DEBUG && console.log('🐛Sending reset tok to', verifyToken);
|
|
240
|
-
const emailText = `
|
|
258
|
+
const emailText = tr(`
|
|
241
259
|
Dear user,
|
|
242
|
-
Welcome to
|
|
260
|
+
Welcome to {brandName}!
|
|
243
261
|
|
|
244
262
|
To confirm your email, click the link below:\n\n
|
|
245
263
|
|
|
246
|
-
|
|
264
|
+
{url}?verifyToken={verifyToken}\n\n
|
|
247
265
|
|
|
248
266
|
If you didn't request this, please ignore this email.\n\n
|
|
249
267
|
Link is valid for 2 hours.\n\n
|
|
250
268
|
|
|
251
269
|
Thanks,
|
|
252
|
-
The
|
|
270
|
+
The {brandName} Team
|
|
253
271
|
|
|
254
|
-
|
|
272
|
+
`, 'opensignup', { brandName, url, verifyToken }
|
|
273
|
+
);
|
|
255
274
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const emailSubject = `Signup request at ${brandName}`;
|
|
275
|
+
const emailHtml = tr(`
|
|
276
|
+
<html>
|
|
277
|
+
<head></head>
|
|
278
|
+
<body>
|
|
279
|
+
<p>Dear user,</p>
|
|
280
|
+
<p>Welcome to {brandName}!</p>
|
|
281
|
+
<p>To confirm your email, click the link below:</p>
|
|
282
|
+
<a href="{url}?token={verifyToken}">Confirm email</a>
|
|
283
|
+
<p>If you didn't request this, please ignore this email.</p>
|
|
284
|
+
<p>Link is valid for 2 hours.</p>
|
|
285
|
+
<p>Thanks,</p>
|
|
286
|
+
<p>The {brandName} Team</p>
|
|
287
|
+
</body>
|
|
288
|
+
</html>
|
|
289
|
+
`, 'opensignup', { brandName, url, verifyToken });
|
|
290
|
+
|
|
291
|
+
const emailSubject = tr(`Signup request at {brandName}`, 'opensignup', { brandName });
|
|
274
292
|
|
|
275
293
|
// send email with AWS SES this.options.providerOptions.AWS_SES
|
|
276
294
|
this.options.confirmEmails.adapter.sendEmail(this.options.confirmEmails.sendFrom, email, emailText, emailHtml, emailSubject);
|