@adminforth/two-factors-auth 1.3.0 → 1.4.0

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/build.log CHANGED
@@ -10,5 +10,5 @@ custom/package-lock.json
10
10
  custom/package.json
11
11
  custom/tsconfig.json
12
12
 
13
- sent 26,257 bytes received 115 bytes 52,744.00 bytes/sec
14
- total size is 25,834 speedup is 0.98
13
+ sent 26,506 bytes received 115 bytes 53,242.00 bytes/sec
14
+ total size is 26,077 speedup is 0.98
@@ -12,24 +12,30 @@
12
12
  <div class="relative p-4 w-full max-w-md max-h-full">
13
13
  <!-- Modal content -->
14
14
  <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black text-gray-500" >
15
- <div class="p-8 w-full max-w-md max-h-full" >
16
- <div class="m-3">{{$t('Please enter your authenticator code')}} </div>
17
- <div class="my-4 flex justify-center items-center">
15
+ <div class="p-8 w-full max-w-md max-h-full custom-auth-wrapper" >
16
+ <div id="mfaCode-label" class="m-4">{{$t('Please enter your authenticator code')}} </div>
17
+ <div class="my-4 w-full flex justify-center" ref="otpRoot">
18
18
  <v-otp-input
19
19
  ref="code"
20
+ container-class="grid grid-cols-6 gap-3 w-full"
20
21
  input-classes="bg-gray-50 text-center flex justify-center otp-input border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-10 h-10 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
21
- :conditionalClass="['one', 'two', 'three', 'four', 'five', 'six']"
22
+ :num-inputs="6"
22
23
  inputType="number"
23
24
  inputmode="numeric"
24
- :num-inputs="6"
25
- v-model:value="bindValue"
26
25
  :should-auto-focus="true"
27
26
  :should-focus-order="true"
27
+ v-model:value="bindValue"
28
28
  @on-complete="handleOnComplete"
29
29
  />
30
30
  </div>
31
- <!-- <Vue2FACodeInput v-model="code"/> -->
32
- <LinkButton to="/login" class="w-full">{{$t('Back to login')}}</LinkButton>
31
+ <div class="mt-6 flex justify-center">
32
+ <LinkButton
33
+ to="/login"
34
+ class="w-[290px] mx-4"
35
+ >
36
+ {{$t('Back to login')}}
37
+ </LinkButton>
38
+ </div>
33
39
  </div>
34
40
  </div>
35
41
  </div>
@@ -39,68 +45,50 @@
39
45
 
40
46
 
41
47
  </template>
42
-
43
-
48
+
49
+
44
50
  <script setup>
45
-
46
- import { onMounted, onBeforeUnmount, ref, watchEffect,computed,watch } from 'vue';
51
+
52
+ import { onMounted, nextTick, onBeforeUnmount, ref, watchEffect,computed,watch } from 'vue';
47
53
  import { useCoreStore } from '@/stores/core';
48
54
  import { useUserStore } from '@/stores/user';
49
- import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
50
55
  import { callAdminForthApi, loadFile } from '@/utils';
51
- import { useRouter } from 'vue-router';
52
56
  import { showErrorTost } from '@/composables/useFrontendApi';
53
57
  import { LinkButton } from '@/afcl';
54
- import Vue2FACodeInput from '@loltech/vue3-2fa-code-input';
55
58
  import VOtpInput from "vue3-otp-input";
56
59
  import { useI18n } from 'vue-i18n';
57
60
 
58
61
  const { t } = useI18n();
59
62
  const code = ref(null);
63
+ const otpRoot = ref(null);
64
+ const bindValue = ref('');
60
65
 
61
66
  const handleOnComplete = (value) => {
62
67
  sendCode(value);
63
68
  };
64
69
 
65
- const fillInput = (value) => {
66
- code.value?.fillInput(value);
67
- };
70
+ function tagOtpInputs() {
71
+ const root = otpRoot.value;
72
+ if (!root) return;
73
+ const inputs = root.querySelectorAll('input.otp-input');
74
+ inputs.forEach((el, idx) => {
75
+ el.setAttribute('name', 'mfaCode');
76
+ el.setAttribute('id', `mfaCode-${idx + 1}`);
77
+ el.setAttribute('autocomplete', 'one-time-code');
78
+ el.setAttribute('inputmode', 'numeric');
79
+ el.setAttribute('aria-labelledby', 'mfaCode-label');
80
+ });
81
+ }
68
82
 
69
- const router = useRouter();
70
83
  const inProgress = ref(false);
71
-
84
+
72
85
  const coreStore = useCoreStore();
73
86
  const user = useUserStore();
74
87
 
75
-
76
- // use this simple function to automatically focus on the next input
77
-
78
-
79
-
80
- const showPw = ref(false);
81
-
82
- const error = ref(null);
83
- const totp = ref({});
84
- const totpJWT = ref(null);
85
-
86
-
87
- function parseJwt(token) {
88
- // Split the token into its parts
89
- const base64Url = token.split('.')[1];
90
-
91
- // Base64-decode the payload
92
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
93
- const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
94
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
95
- }).join(''));
96
-
97
- // Parse the JSON payload
98
- return JSON.parse(jsonPayload);
99
- }
100
-
88
+
101
89
  onMounted(async () => {
102
- coreStore.getPublicConfig();
103
- window.addEventListener('paste', handlePaste);
90
+ await nextTick();
91
+ tagOtpInputs();
104
92
  });
105
93
 
106
94
  onBeforeUnmount(() => {
@@ -161,9 +149,9 @@
161
149
  }
162
150
 
163
151
  /**
164
- * This particular piece of code makes the last input have a gap in the middle.
165
- */
166
- .spaced-code-input {
152
+ * This particular piece of code makes the last input have a gap in the middle.
153
+ */
154
+ .spaced-code-input {
167
155
  & .vue3-2fa-code-input-box {
168
156
  &:nth-child(3) {
169
157
  @apply mr-4;
@@ -175,4 +163,3 @@
175
163
  }
176
164
  }
177
165
  </style>
178
-
@@ -12,14 +12,14 @@
12
12
  <div class="relative p-4 w-full max-w-md max-h-full">
13
13
  <!-- Modal content -->
14
14
  <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black text-gray-500" >
15
- <div class="p-10 w-full max-w-md max-h-full" >
15
+ <div class="p-10 w-full max-w-md max-h-full custom-auth-wrapper" >
16
16
  <div class="m-3" >{{$t('Scan this QR code with your authenticator app or open by')}} <a class="text-blue-600" :href="totpUri">{{$t('click')}}</a></div>
17
17
  <div class="flex justify-center m-3" >
18
18
  <img :src="totpQrCode" class="min-w-[200px], min-h-[200px]" alt="QR code" />
19
19
  </div>
20
20
  <div class="m-3 ">
21
21
  <div class="m-1">{{$t('Or copy this code to app manually:')}}</div>
22
- <div class="w-full max-w-[46rem]">
22
+ <div class="w-full">
23
23
  <div class="relative">
24
24
  <label for="npm-install-copy-text" class="sr-only">{{$t('Label')}}</label>
25
25
  <input id="npm-install-copy-text" type="text" class="col-span-10 bg-gray-50 border border-gray-300 text-gray-500 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full px-2.5 py-4 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 pr-12" :value="totp.newSecret" readonly>
@@ -33,21 +33,22 @@
33
33
  </div>
34
34
  </div>
35
35
  </div>
36
- <div class="my-4 flex justify-center items-center">
36
+ <div class="my-4 w-full flex justify-center p-2" ref="otpRoot">
37
37
  <v-otp-input
38
38
  ref="code"
39
- input-classes="bg-gray-50 text-center flex justify-center otp-input border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-11 h-11 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
40
- :conditionalClass="['one', 'two', 'three', 'four', 'five', 'six']"
41
- inputType="number"
42
- inputmode="numeric"
39
+ container-class="grid grid-cols-6 gap-3 w-full"
40
+ input-classes="otp-input bg-gray-50 text-center border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block h-[43.33px] w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
43
41
  :num-inputs="6"
42
+ inputType="number"
43
+ inputmode="numeric"
44
44
  :should-auto-focus="true"
45
45
  :should-focus-order="true"
46
+ v-model:value="bindValue"
46
47
  @on-complete="handleOnComplete"
47
48
  />
48
49
  </div>
49
50
  <!-- <Vue2FACodeInput v-model="code" autofocus /> -->
50
- <div class="flex flex-row gap-2.5 pl-3 pr-3 h-12">
51
+ <div class="flex flex-row gap-2.5 px-3 h-12">
51
52
  <LinkButton to="/login" class="w-full">
52
53
  {{$t('Back to login')}}
53
54
  </LinkButton>
@@ -67,7 +68,7 @@
67
68
 
68
69
  <script setup lang="ts">
69
70
 
70
- import { onMounted, onBeforeUnmount, ref, watchEffect,computed,watch } from 'vue';
71
+ import { onMounted, onBeforeUnmount, nextTick, ref, watchEffect,computed,watch } from 'vue';
71
72
  import { useCoreStore } from '@/stores/core';
72
73
  import { useUserStore } from '@/stores/user';
73
74
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
@@ -89,6 +90,7 @@ const handleOnComplete = (value) => {
89
90
 
90
91
  const router = useRouter();
91
92
  const inProgress = ref(false);
93
+ const otpRoot = ref(null);
92
94
 
93
95
  const coreStore = useCoreStore();
94
96
  const user = useUserStore();
@@ -151,12 +153,27 @@ onMounted(async () => {
151
153
  }
152
154
 
153
155
  window.addEventListener('paste', handlePaste);
156
+ await nextTick();
157
+ tagOtpInputs();
154
158
  });
155
159
 
156
160
  onBeforeUnmount(() => {
157
161
  window.removeEventListener('paste', handlePaste);
158
162
  });
159
163
 
164
+ function tagOtpInputs() {
165
+ const root = otpRoot.value;
166
+ if (!root) return;
167
+ const inputs = root.querySelectorAll('input.otp-input');
168
+ inputs.forEach((el, idx) => {
169
+ el.setAttribute('name', 'mfaCode');
170
+ el.setAttribute('id', `mfaCode-${idx + 1}`);
171
+ el.setAttribute('autocomplete', 'one-time-code');
172
+ el.setAttribute('inputmode', 'numeric');
173
+ el.setAttribute('aria-labelledby', 'mfaCode-label');
174
+ });
175
+ }
176
+
160
177
  async function sendCode (value) {
161
178
  inProgress.value = true;
162
179
 
@@ -206,5 +223,5 @@ const handleSkip = async () => {
206
223
  <style>
207
224
  .otp-input {
208
225
  margin: 0 5px;
209
- }
226
+ }
210
227
  </style>
@@ -12,24 +12,30 @@
12
12
  <div class="relative p-4 w-full max-w-md max-h-full">
13
13
  <!-- Modal content -->
14
14
  <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black text-gray-500" >
15
- <div class="p-8 w-full max-w-md max-h-full" >
16
- <div class="m-3">{{$t('Please enter your authenticator code')}} </div>
17
- <div class="my-4 flex justify-center items-center">
15
+ <div class="p-8 w-full max-w-md max-h-full custom-auth-wrapper" >
16
+ <div id="mfaCode-label" class="m-4">{{$t('Please enter your authenticator code')}} </div>
17
+ <div class="my-4 w-full flex justify-center" ref="otpRoot">
18
18
  <v-otp-input
19
19
  ref="code"
20
+ container-class="grid grid-cols-6 gap-3 w-full"
20
21
  input-classes="bg-gray-50 text-center flex justify-center otp-input border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-10 h-10 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
21
- :conditionalClass="['one', 'two', 'three', 'four', 'five', 'six']"
22
+ :num-inputs="6"
22
23
  inputType="number"
23
24
  inputmode="numeric"
24
- :num-inputs="6"
25
- v-model:value="bindValue"
26
25
  :should-auto-focus="true"
27
26
  :should-focus-order="true"
27
+ v-model:value="bindValue"
28
28
  @on-complete="handleOnComplete"
29
29
  />
30
30
  </div>
31
- <!-- <Vue2FACodeInput v-model="code"/> -->
32
- <LinkButton to="/login" class="w-full">{{$t('Back to login')}}</LinkButton>
31
+ <div class="mt-6 flex justify-center">
32
+ <LinkButton
33
+ to="/login"
34
+ class="w-[290px] mx-4"
35
+ >
36
+ {{$t('Back to login')}}
37
+ </LinkButton>
38
+ </div>
33
39
  </div>
34
40
  </div>
35
41
  </div>
@@ -39,68 +45,50 @@
39
45
 
40
46
 
41
47
  </template>
42
-
43
-
48
+
49
+
44
50
  <script setup>
45
-
46
- import { onMounted, onBeforeUnmount, ref, watchEffect,computed,watch } from 'vue';
51
+
52
+ import { onMounted, nextTick, onBeforeUnmount, ref, watchEffect,computed,watch } from 'vue';
47
53
  import { useCoreStore } from '@/stores/core';
48
54
  import { useUserStore } from '@/stores/user';
49
- import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
50
55
  import { callAdminForthApi, loadFile } from '@/utils';
51
- import { useRouter } from 'vue-router';
52
56
  import { showErrorTost } from '@/composables/useFrontendApi';
53
57
  import { LinkButton } from '@/afcl';
54
- import Vue2FACodeInput from '@loltech/vue3-2fa-code-input';
55
58
  import VOtpInput from "vue3-otp-input";
56
59
  import { useI18n } from 'vue-i18n';
57
60
 
58
61
  const { t } = useI18n();
59
62
  const code = ref(null);
63
+ const otpRoot = ref(null);
64
+ const bindValue = ref('');
60
65
 
61
66
  const handleOnComplete = (value) => {
62
67
  sendCode(value);
63
68
  };
64
69
 
65
- const fillInput = (value) => {
66
- code.value?.fillInput(value);
67
- };
70
+ function tagOtpInputs() {
71
+ const root = otpRoot.value;
72
+ if (!root) return;
73
+ const inputs = root.querySelectorAll('input.otp-input');
74
+ inputs.forEach((el, idx) => {
75
+ el.setAttribute('name', 'mfaCode');
76
+ el.setAttribute('id', `mfaCode-${idx + 1}`);
77
+ el.setAttribute('autocomplete', 'one-time-code');
78
+ el.setAttribute('inputmode', 'numeric');
79
+ el.setAttribute('aria-labelledby', 'mfaCode-label');
80
+ });
81
+ }
68
82
 
69
- const router = useRouter();
70
83
  const inProgress = ref(false);
71
-
84
+
72
85
  const coreStore = useCoreStore();
73
86
  const user = useUserStore();
74
87
 
75
-
76
- // use this simple function to automatically focus on the next input
77
-
78
-
79
-
80
- const showPw = ref(false);
81
-
82
- const error = ref(null);
83
- const totp = ref({});
84
- const totpJWT = ref(null);
85
-
86
-
87
- function parseJwt(token) {
88
- // Split the token into its parts
89
- const base64Url = token.split('.')[1];
90
-
91
- // Base64-decode the payload
92
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
93
- const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
94
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
95
- }).join(''));
96
-
97
- // Parse the JSON payload
98
- return JSON.parse(jsonPayload);
99
- }
100
-
88
+
101
89
  onMounted(async () => {
102
- coreStore.getPublicConfig();
103
- window.addEventListener('paste', handlePaste);
90
+ await nextTick();
91
+ tagOtpInputs();
104
92
  });
105
93
 
106
94
  onBeforeUnmount(() => {
@@ -161,9 +149,9 @@
161
149
  }
162
150
 
163
151
  /**
164
- * This particular piece of code makes the last input have a gap in the middle.
165
- */
166
- .spaced-code-input {
152
+ * This particular piece of code makes the last input have a gap in the middle.
153
+ */
154
+ .spaced-code-input {
167
155
  & .vue3-2fa-code-input-box {
168
156
  &:nth-child(3) {
169
157
  @apply mr-4;
@@ -175,4 +163,3 @@
175
163
  }
176
164
  }
177
165
  </style>
178
-
@@ -12,14 +12,14 @@
12
12
  <div class="relative p-4 w-full max-w-md max-h-full">
13
13
  <!-- Modal content -->
14
14
  <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black text-gray-500" >
15
- <div class="p-10 w-full max-w-md max-h-full" >
15
+ <div class="p-10 w-full max-w-md max-h-full custom-auth-wrapper" >
16
16
  <div class="m-3" >{{$t('Scan this QR code with your authenticator app or open by')}} <a class="text-blue-600" :href="totpUri">{{$t('click')}}</a></div>
17
17
  <div class="flex justify-center m-3" >
18
18
  <img :src="totpQrCode" class="min-w-[200px], min-h-[200px]" alt="QR code" />
19
19
  </div>
20
20
  <div class="m-3 ">
21
21
  <div class="m-1">{{$t('Or copy this code to app manually:')}}</div>
22
- <div class="w-full max-w-[46rem]">
22
+ <div class="w-full">
23
23
  <div class="relative">
24
24
  <label for="npm-install-copy-text" class="sr-only">{{$t('Label')}}</label>
25
25
  <input id="npm-install-copy-text" type="text" class="col-span-10 bg-gray-50 border border-gray-300 text-gray-500 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full px-2.5 py-4 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 pr-12" :value="totp.newSecret" readonly>
@@ -33,21 +33,22 @@
33
33
  </div>
34
34
  </div>
35
35
  </div>
36
- <div class="my-4 flex justify-center items-center">
36
+ <div class="my-4 w-full flex justify-center p-2" ref="otpRoot">
37
37
  <v-otp-input
38
38
  ref="code"
39
- input-classes="bg-gray-50 text-center flex justify-center otp-input border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-11 h-11 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
40
- :conditionalClass="['one', 'two', 'three', 'four', 'five', 'six']"
41
- inputType="number"
42
- inputmode="numeric"
39
+ container-class="grid grid-cols-6 gap-3 w-full"
40
+ input-classes="otp-input bg-gray-50 text-center border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block h-[43.33px] w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
43
41
  :num-inputs="6"
42
+ inputType="number"
43
+ inputmode="numeric"
44
44
  :should-auto-focus="true"
45
45
  :should-focus-order="true"
46
+ v-model:value="bindValue"
46
47
  @on-complete="handleOnComplete"
47
48
  />
48
49
  </div>
49
50
  <!-- <Vue2FACodeInput v-model="code" autofocus /> -->
50
- <div class="flex flex-row gap-2.5 pl-3 pr-3 h-12">
51
+ <div class="flex flex-row gap-2.5 px-3 h-12">
51
52
  <LinkButton to="/login" class="w-full">
52
53
  {{$t('Back to login')}}
53
54
  </LinkButton>
@@ -67,7 +68,7 @@
67
68
 
68
69
  <script setup lang="ts">
69
70
 
70
- import { onMounted, onBeforeUnmount, ref, watchEffect,computed,watch } from 'vue';
71
+ import { onMounted, onBeforeUnmount, nextTick, ref, watchEffect,computed,watch } from 'vue';
71
72
  import { useCoreStore } from '@/stores/core';
72
73
  import { useUserStore } from '@/stores/user';
73
74
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
@@ -89,6 +90,7 @@ const handleOnComplete = (value) => {
89
90
 
90
91
  const router = useRouter();
91
92
  const inProgress = ref(false);
93
+ const otpRoot = ref(null);
92
94
 
93
95
  const coreStore = useCoreStore();
94
96
  const user = useUserStore();
@@ -151,12 +153,27 @@ onMounted(async () => {
151
153
  }
152
154
 
153
155
  window.addEventListener('paste', handlePaste);
156
+ await nextTick();
157
+ tagOtpInputs();
154
158
  });
155
159
 
156
160
  onBeforeUnmount(() => {
157
161
  window.removeEventListener('paste', handlePaste);
158
162
  });
159
163
 
164
+ function tagOtpInputs() {
165
+ const root = otpRoot.value;
166
+ if (!root) return;
167
+ const inputs = root.querySelectorAll('input.otp-input');
168
+ inputs.forEach((el, idx) => {
169
+ el.setAttribute('name', 'mfaCode');
170
+ el.setAttribute('id', `mfaCode-${idx + 1}`);
171
+ el.setAttribute('autocomplete', 'one-time-code');
172
+ el.setAttribute('inputmode', 'numeric');
173
+ el.setAttribute('aria-labelledby', 'mfaCode-label');
174
+ });
175
+ }
176
+
160
177
  async function sendCode (value) {
161
178
  inProgress.value = true;
162
179
 
@@ -206,5 +223,5 @@ const handleSkip = async () => {
206
223
  <style>
207
224
  .otp-input {
208
225
  margin: 0 5px;
209
- }
226
+ }
210
227
  </style>
package/dist/index.js CHANGED
@@ -49,7 +49,8 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
49
49
  }
50
50
  const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
51
51
  const beforeLoginConfirmationArray = Array.isArray(beforeLoginConfirmation) ? beforeLoginConfirmation : [beforeLoginConfirmation];
52
- beforeLoginConfirmationArray.push((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, response }) {
52
+ beforeLoginConfirmationArray.push((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, response, extra }) {
53
+ var _b;
53
54
  const secret = adminUser.dbUser[this.options.twoFaSecretFieldName];
54
55
  const userName = adminUser.dbUser[adminforth.config.auth.usernameField];
55
56
  const brandName = adminforth.config.customization.brandName;
@@ -57,6 +58,8 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
57
58
  const authResource = adminforth.config.resources.find((res) => res.resourceId === adminforth.config.auth.usersResourceId);
58
59
  const authPk = authResource.columns.find((col) => col.primaryKey).name;
59
60
  const userPk = adminUser.dbUser[authPk];
61
+ const rememberMe = ((_b = extra === null || extra === void 0 ? void 0 : extra.body) === null || _b === void 0 ? void 0 : _b.rememberMe) || false;
62
+ const rememberMeDays = rememberMe ? adminforth.config.auth.rememberMeDays || 30 : 1;
60
63
  let newSecret = null;
61
64
  const userNeeds2FA = this.options.usersFilterToApply ? this.options.usersFilterToApply(adminUser) : true;
62
65
  if (!userNeeds2FA) {
@@ -68,7 +71,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
68
71
  newSecret = tempSecret.secret;
69
72
  }
70
73
  else {
71
- const value = this.adminforth.auth.issueJWT({ userName, issuer: brandName, pk: userPk, userCanSkipSetup }, 'tempTotp', '2h');
74
+ const value = this.adminforth.auth.issueJWT({ userName, issuer: brandName, pk: userPk, userCanSkipSetup, rememberMeDays }, 'tempTotp', '2h');
72
75
  response.setHeader('Set-Cookie', `adminforth_${brandNameSlug}_totpTemporaryJWT=${value}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; max-age=3600; `);
73
76
  return {
74
77
  body: {
@@ -78,7 +81,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
78
81
  ok: true
79
82
  };
80
83
  }
81
- const totpTemporaryJWT = this.adminforth.auth.issueJWT({ userName, newSecret, issuer: brandName, pk: userPk, userCanSkipSetup }, 'tempTotp', '2h');
84
+ const totpTemporaryJWT = this.adminforth.auth.issueJWT({ userName, newSecret, issuer: brandName, pk: userPk, userCanSkipSetup, rememberMeDays }, 'tempTotp', '2h');
82
85
  response.setHeader('Set-Cookie', `adminforth_${brandNameSlug}_totpTemporaryJWT=${totpTemporaryJWT}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${new Date(Date.now() + '1h').toUTCString()} `);
83
86
  return {
84
87
  body: {
@@ -125,7 +128,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
125
128
  yield connector.updateRecord({ resource: this.authResource, recordId: decoded.pk, newValues: { [this.options.twoFaSecretFieldName]: decoded.newSecret } });
126
129
  }
127
130
  this.adminforth.auth.removeCustomCookie({ response, name: 'totpTemporaryJWT' });
128
- this.adminforth.auth.setAuthCookie({ response, username: decoded.userName, pk: decoded.pk });
131
+ this.adminforth.auth.setAuthCookie({ expireInDays: decoded.rememberMeDays, response, username: decoded.userName, pk: decoded.pk });
129
132
  return { status: 'ok', allowedLogin: true };
130
133
  }
131
134
  else {
@@ -140,7 +143,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
140
143
  const verified = twofactor.verifyToken(user[this.options.twoFaSecretFieldName], body.code, this.options.timeStepWindow);
141
144
  if (verified) {
142
145
  this.adminforth.auth.removeCustomCookie({ response, name: 'totpTemporaryJWT' });
143
- this.adminforth.auth.setAuthCookie({ response, username: decoded.userName, pk: decoded.pk });
146
+ this.adminforth.auth.setAuthCookie({ expireInDays: decoded.rememberMeDays, response, username: decoded.userName, pk: decoded.pk });
144
147
  return { status: 'ok', allowedLogin: true };
145
148
  }
146
149
  else {
package/index.ts CHANGED
@@ -58,7 +58,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
58
58
  const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
59
59
  const beforeLoginConfirmationArray = Array.isArray(beforeLoginConfirmation) ? beforeLoginConfirmation : [beforeLoginConfirmation];
60
60
  beforeLoginConfirmationArray.push(
61
- async({ adminUser, response }: { adminUser: AdminUser, response: IAdminForthHttpResponse} )=> {
61
+ async({ adminUser, response, extra }: { adminUser: AdminUser, response: IAdminForthHttpResponse, extra?: any} )=> {
62
62
  const secret = adminUser.dbUser[this.options.twoFaSecretFieldName]
63
63
  const userName = adminUser.dbUser[adminforth.config.auth.usernameField]
64
64
  const brandName = adminforth.config.customization.brandName;
@@ -66,6 +66,8 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
66
66
  const authResource = adminforth.config.resources.find((res)=>res.resourceId === adminforth.config.auth.usersResourceId )
67
67
  const authPk = authResource.columns.find((col)=>col.primaryKey).name
68
68
  const userPk = adminUser.dbUser[authPk]
69
+ const rememberMe = extra?.body?.rememberMe || false;
70
+ const rememberMeDays = rememberMe ? adminforth.config.auth.rememberMeDays || 30 : 1;
69
71
  let newSecret = null;
70
72
 
71
73
  const userNeeds2FA = this.options.usersFilterToApply ? this.options.usersFilterToApply(adminUser) : true;
@@ -79,7 +81,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
79
81
  const tempSecret = twofactor.generateSecret({name: brandName,account: userName})
80
82
  newSecret = tempSecret.secret
81
83
  } else {
82
- const value = this.adminforth.auth.issueJWT({userName, issuer:brandName, pk:userPk, userCanSkipSetup }, 'tempTotp', '2h');
84
+ const value = this.adminforth.auth.issueJWT({userName, issuer:brandName, pk:userPk, userCanSkipSetup, rememberMeDays }, 'tempTotp', '2h');
83
85
  response.setHeader('Set-Cookie', `adminforth_${brandNameSlug}_totpTemporaryJWT=${value}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; max-age=3600; `);
84
86
 
85
87
  return {
@@ -90,7 +92,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
90
92
  ok: true
91
93
  }
92
94
  }
93
- const totpTemporaryJWT = this.adminforth.auth.issueJWT({userName, newSecret, issuer:brandName, pk:userPk, userCanSkipSetup }, 'tempTotp', '2h');
95
+ const totpTemporaryJWT = this.adminforth.auth.issueJWT({userName, newSecret, issuer:brandName, pk:userPk, userCanSkipSetup, rememberMeDays }, 'tempTotp', '2h');
94
96
  response.setHeader('Set-Cookie', `adminforth_${brandNameSlug}_totpTemporaryJWT=${totpTemporaryJWT}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${new Date(Date.now() + '1h').toUTCString() } `);
95
97
 
96
98
  return {
@@ -127,7 +129,6 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
127
129
  const brandNameSlug = this.adminforth.config.customization.brandNameSlug;
128
130
  const totpTemporaryJWT = cookies.find((cookie)=>cookie.key === `adminforth_${brandNameSlug}_totpTemporaryJWT`)?.value;
129
131
  const decoded = await this.adminforth.auth.verify(totpTemporaryJWT, 'tempTotp');
130
-
131
132
  if (!decoded)
132
133
  return {status:'error',message:'Invalid token'}
133
134
 
@@ -140,7 +141,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
140
141
  await connector.updateRecord({resource:this.authResource, recordId:decoded.pk, newValues:{[this.options.twoFaSecretFieldName]: decoded.newSecret}})
141
142
  }
142
143
  this.adminforth.auth.removeCustomCookie({response, name:'totpTemporaryJWT'})
143
- this.adminforth.auth.setAuthCookie({response, username:decoded.userName, pk:decoded.pk})
144
+ this.adminforth.auth.setAuthCookie({expireInDays: decoded.rememberMeDays, response, username:decoded.userName, pk:decoded.pk})
144
145
  return { status: 'ok', allowedLogin: true }
145
146
  } else {
146
147
  return {error: 'Wrong or expired OTP code'}
@@ -153,7 +154,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
153
154
  const verified = twofactor.verifyToken(user[this.options.twoFaSecretFieldName], body.code, this.options.timeStepWindow);
154
155
  if (verified) {
155
156
  this.adminforth.auth.removeCustomCookie({response, name:'totpTemporaryJWT'})
156
- this.adminforth.auth.setAuthCookie({response, username:decoded.userName, pk:decoded.pk})
157
+ this.adminforth.auth.setAuthCookie({expireInDays: decoded.rememberMeDays, response, username:decoded.userName, pk:decoded.pk})
157
158
  return { status: 'ok', allowedLogin: true }
158
159
  } else {
159
160
  return {error: 'Wrong or expired OTP code'}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",