@adminforth/two-factors-auth 1.8.10 → 1.9.1

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
@@ -11,5 +11,5 @@ custom/package-lock.json
11
11
  custom/package.json
12
12
  custom/tsconfig.json
13
13
 
14
- sent 31,567 bytes received 134 bytes 63,402.00 bytes/sec
15
- total size is 31,076 speedup is 0.98
14
+ sent 34,655 bytes received 134 bytes 69,578.00 bytes/sec
15
+ total size is 34,157 speedup is 0.98
@@ -28,14 +28,15 @@
28
28
  @on-complete="handleOnComplete"
29
29
  />
30
30
  </div>
31
- <div class="mt-6 flex justify-center">
32
- <LinkButton
33
- to="/login"
34
- class="w-full"
35
- >
36
- {{$t('Back to login')}}
37
- </LinkButton>
38
- </div>
31
+ <ErrorMessage :error="codeError" />
32
+ <div class="mt-6 flex justify-center">
33
+ <LinkButton
34
+ to="/login"
35
+ class="w-full"
36
+ >
37
+ {{$t('Back to login')}}
38
+ </LinkButton>
39
+ </div>
39
40
  </div>
40
41
  </div>
41
42
  </div>
@@ -57,11 +58,13 @@
57
58
  import { LinkButton } from '@/afcl';
58
59
  import VOtpInput from "vue3-otp-input";
59
60
  import { useI18n } from 'vue-i18n';
61
+ import ErrorMessage from '@/components/ErrorMessage.vue';
60
62
 
61
63
  const { t } = useI18n();
62
64
  const code = ref(null);
63
65
  const otpRoot = ref(null);
64
66
  const bindValue = ref('');
67
+ const codeError = ref(null);
65
68
 
66
69
  const handleOnComplete = (value) => {
67
70
  sendCode(value);
@@ -89,10 +92,17 @@
89
92
  onMounted(async () => {
90
93
  await nextTick();
91
94
  tagOtpInputs();
95
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
96
+ focusFirstAvailableOtpInput();
97
+ const rootEl = otpRoot.value;
98
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
92
99
  });
93
100
 
94
101
  onBeforeUnmount(() => {
95
102
  window.removeEventListener('paste', handlePaste);
103
+ document.removeEventListener('focusin', handleGlobalFocusIn, true);
104
+ const rootEl = otpRoot.value;
105
+ rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
96
106
  });
97
107
 
98
108
  async function sendCode (value) {
@@ -108,7 +118,7 @@
108
118
  if (resp.allowedLogin){
109
119
  await user.finishLogin();
110
120
  } else {
111
- showErrorTost(t('Invalid code'));
121
+ codeError.value = 'Invalid code';
112
122
  }
113
123
  }
114
124
 
@@ -128,6 +138,42 @@
128
138
  code.value?.fillInput(pastedText);
129
139
  }
130
140
  }
141
+
142
+ function getOtpInputs() {
143
+ const root = otpRoot.value;
144
+ if (!root) return [];
145
+ return Array.from(root.querySelectorAll('input.otp-input'));
146
+ }
147
+
148
+ function focusFirstAvailableOtpInput() {
149
+ const inputs = getOtpInputs();
150
+ if (!inputs.length) return;
151
+ const firstEmpty = inputs.find((i) => !i.value);
152
+ (firstEmpty || inputs[0]).focus();
153
+ }
154
+
155
+ function handleGlobalFocusIn(event) {
156
+ const inputs = getOtpInputs();
157
+ if (!inputs.length) return;
158
+ const target = event.target;
159
+ if (!target) return;
160
+ if (!inputs.includes(target)) {
161
+ requestAnimationFrame(() => {
162
+ focusFirstAvailableOtpInput();
163
+ });
164
+ }
165
+ }
166
+
167
+ function handleFocusOut() {
168
+ requestAnimationFrame(() => {
169
+ const inputs = getOtpInputs();
170
+ if (!inputs.length) return;
171
+ const active = document.activeElement;
172
+ if (!active || !inputs.includes(active)) {
173
+ focusFirstAvailableOtpInput();
174
+ }
175
+ });
176
+ }
131
177
  </script>
132
178
 
133
179
  <style scoped lang='scss'>
@@ -45,6 +45,7 @@
45
45
  @on-complete="handleOnComplete"
46
46
  />
47
47
  </div>
48
+ <ErrorMessage :error="codeError" />
48
49
  <!-- <Vue2FACodeInput v-model="code" autofocus /> -->
49
50
  <div class="flex flex-row gap-2.5 h-12">
50
51
  <LinkButton to="/login" class="w-full">
@@ -78,17 +79,20 @@ import Vue2FACodeInput from '@loltech/vue3-2fa-code-input';
78
79
  import VOtpInput from "vue3-otp-input";
79
80
  import adminforth from '@/adminforth';
80
81
  import { useI18n } from 'vue-i18n';
82
+ import ErrorMessage from '@/components/ErrorMessage.vue';
81
83
 
82
84
  const { t } = useI18n();
83
85
 
84
86
  const code = ref(null);
87
+ const codeError = ref(null);
88
+ const bindValue = ref('');
85
89
  const handleOnComplete = (value) => {
86
90
  sendCode(value);
87
91
  };
88
92
 
89
93
  const router = useRouter();
90
94
  const inProgress = ref(false);
91
- const otpRoot = ref(null);
95
+ const otpRoot = ref<HTMLElement | null>(null);
92
96
 
93
97
  const coreStore = useCoreStore();
94
98
  const user = useUserStore();
@@ -151,12 +155,19 @@ onMounted(async () => {
151
155
  }
152
156
 
153
157
  window.addEventListener('paste', handlePaste);
158
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
154
159
  await nextTick();
155
160
  tagOtpInputs();
161
+ focusFirstAvailableOtpInput();
162
+ const rootEl = otpRoot.value;
163
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
156
164
  });
157
165
 
158
166
  onBeforeUnmount(() => {
159
167
  window.removeEventListener('paste', handlePaste);
168
+ document.removeEventListener('focusin', handleGlobalFocusIn, true);
169
+ const rootEl = otpRoot.value;
170
+ rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
160
171
  });
161
172
 
162
173
  function tagOtpInputs() {
@@ -172,6 +183,42 @@ function tagOtpInputs() {
172
183
  });
173
184
  }
174
185
 
186
+ function getOtpInputs(): HTMLInputElement[] {
187
+ const root = otpRoot.value;
188
+ if (!root) return [];
189
+ return Array.from(root.querySelectorAll('input.otp-input')) as HTMLInputElement[];
190
+ }
191
+
192
+ function focusFirstAvailableOtpInput(): void {
193
+ const inputs = getOtpInputs();
194
+ if (!inputs.length) return;
195
+ const firstEmpty = inputs.find((i) => !i.value);
196
+ (firstEmpty || inputs[0]).focus();
197
+ }
198
+
199
+ function handleGlobalFocusIn(event: FocusEvent): void {
200
+ const inputs = getOtpInputs();
201
+ if (!inputs.length) return;
202
+ const target = event.target as HTMLElement | null;
203
+ if (!target) return;
204
+ if (!inputs.includes(target as HTMLInputElement)) {
205
+ requestAnimationFrame(() => {
206
+ focusFirstAvailableOtpInput();
207
+ });
208
+ }
209
+ }
210
+
211
+ function handleFocusOut(): void {
212
+ requestAnimationFrame(() => {
213
+ const inputs = getOtpInputs();
214
+ if (!inputs.length) return;
215
+ const active = document.activeElement as HTMLElement | null;
216
+ if (!active || !inputs.includes(active as HTMLInputElement)) {
217
+ focusFirstAvailableOtpInput();
218
+ }
219
+ });
220
+ }
221
+
175
222
  async function sendCode (value) {
176
223
  inProgress.value = true;
177
224
 
@@ -187,7 +234,7 @@ async function sendCode (value) {
187
234
  if (resp.allowedLogin){
188
235
  await user.finishLogin()
189
236
  } else {
190
- showErrorTost(t('Invalid code'));
237
+ codeError.value = 'Invalid code';
191
238
  }
192
239
  }
193
240
 
@@ -214,7 +261,7 @@ const handleSkip = async () => {
214
261
  if (resp.allowedLogin){
215
262
  await user.finishLogin()
216
263
  } else {
217
- showErrorTost(t('Something went wrong'));
264
+ codeError.value = 'Something went wrong';
218
265
  }
219
266
  }
220
267
  </script>
@@ -28,14 +28,15 @@
28
28
  @on-complete="handleOnComplete"
29
29
  />
30
30
  </div>
31
- <div class="mt-6 flex justify-center">
32
- <LinkButton
33
- to="/login"
34
- class="w-full"
35
- >
36
- {{$t('Back to login')}}
37
- </LinkButton>
38
- </div>
31
+ <ErrorMessage :error="codeError" />
32
+ <div class="mt-6 flex justify-center">
33
+ <LinkButton
34
+ to="/login"
35
+ class="w-full"
36
+ >
37
+ {{$t('Back to login')}}
38
+ </LinkButton>
39
+ </div>
39
40
  </div>
40
41
  </div>
41
42
  </div>
@@ -57,11 +58,13 @@
57
58
  import { LinkButton } from '@/afcl';
58
59
  import VOtpInput from "vue3-otp-input";
59
60
  import { useI18n } from 'vue-i18n';
61
+ import ErrorMessage from '@/components/ErrorMessage.vue';
60
62
 
61
63
  const { t } = useI18n();
62
64
  const code = ref(null);
63
65
  const otpRoot = ref(null);
64
66
  const bindValue = ref('');
67
+ const codeError = ref(null);
65
68
 
66
69
  const handleOnComplete = (value) => {
67
70
  sendCode(value);
@@ -89,10 +92,17 @@
89
92
  onMounted(async () => {
90
93
  await nextTick();
91
94
  tagOtpInputs();
95
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
96
+ focusFirstAvailableOtpInput();
97
+ const rootEl = otpRoot.value;
98
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
92
99
  });
93
100
 
94
101
  onBeforeUnmount(() => {
95
102
  window.removeEventListener('paste', handlePaste);
103
+ document.removeEventListener('focusin', handleGlobalFocusIn, true);
104
+ const rootEl = otpRoot.value;
105
+ rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
96
106
  });
97
107
 
98
108
  async function sendCode (value) {
@@ -108,7 +118,7 @@
108
118
  if (resp.allowedLogin){
109
119
  await user.finishLogin();
110
120
  } else {
111
- showErrorTost(t('Invalid code'));
121
+ codeError.value = 'Invalid code';
112
122
  }
113
123
  }
114
124
 
@@ -128,6 +138,42 @@
128
138
  code.value?.fillInput(pastedText);
129
139
  }
130
140
  }
141
+
142
+ function getOtpInputs() {
143
+ const root = otpRoot.value;
144
+ if (!root) return [];
145
+ return Array.from(root.querySelectorAll('input.otp-input'));
146
+ }
147
+
148
+ function focusFirstAvailableOtpInput() {
149
+ const inputs = getOtpInputs();
150
+ if (!inputs.length) return;
151
+ const firstEmpty = inputs.find((i) => !i.value);
152
+ (firstEmpty || inputs[0]).focus();
153
+ }
154
+
155
+ function handleGlobalFocusIn(event) {
156
+ const inputs = getOtpInputs();
157
+ if (!inputs.length) return;
158
+ const target = event.target;
159
+ if (!target) return;
160
+ if (!inputs.includes(target)) {
161
+ requestAnimationFrame(() => {
162
+ focusFirstAvailableOtpInput();
163
+ });
164
+ }
165
+ }
166
+
167
+ function handleFocusOut() {
168
+ requestAnimationFrame(() => {
169
+ const inputs = getOtpInputs();
170
+ if (!inputs.length) return;
171
+ const active = document.activeElement;
172
+ if (!active || !inputs.includes(active)) {
173
+ focusFirstAvailableOtpInput();
174
+ }
175
+ });
176
+ }
131
177
  </script>
132
178
 
133
179
  <style scoped lang='scss'>
@@ -45,6 +45,7 @@
45
45
  @on-complete="handleOnComplete"
46
46
  />
47
47
  </div>
48
+ <ErrorMessage :error="codeError" />
48
49
  <!-- <Vue2FACodeInput v-model="code" autofocus /> -->
49
50
  <div class="flex flex-row gap-2.5 h-12">
50
51
  <LinkButton to="/login" class="w-full">
@@ -78,17 +79,20 @@ import Vue2FACodeInput from '@loltech/vue3-2fa-code-input';
78
79
  import VOtpInput from "vue3-otp-input";
79
80
  import adminforth from '@/adminforth';
80
81
  import { useI18n } from 'vue-i18n';
82
+ import ErrorMessage from '@/components/ErrorMessage.vue';
81
83
 
82
84
  const { t } = useI18n();
83
85
 
84
86
  const code = ref(null);
87
+ const codeError = ref(null);
88
+ const bindValue = ref('');
85
89
  const handleOnComplete = (value) => {
86
90
  sendCode(value);
87
91
  };
88
92
 
89
93
  const router = useRouter();
90
94
  const inProgress = ref(false);
91
- const otpRoot = ref(null);
95
+ const otpRoot = ref<HTMLElement | null>(null);
92
96
 
93
97
  const coreStore = useCoreStore();
94
98
  const user = useUserStore();
@@ -151,12 +155,19 @@ onMounted(async () => {
151
155
  }
152
156
 
153
157
  window.addEventListener('paste', handlePaste);
158
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
154
159
  await nextTick();
155
160
  tagOtpInputs();
161
+ focusFirstAvailableOtpInput();
162
+ const rootEl = otpRoot.value;
163
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
156
164
  });
157
165
 
158
166
  onBeforeUnmount(() => {
159
167
  window.removeEventListener('paste', handlePaste);
168
+ document.removeEventListener('focusin', handleGlobalFocusIn, true);
169
+ const rootEl = otpRoot.value;
170
+ rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
160
171
  });
161
172
 
162
173
  function tagOtpInputs() {
@@ -172,6 +183,42 @@ function tagOtpInputs() {
172
183
  });
173
184
  }
174
185
 
186
+ function getOtpInputs(): HTMLInputElement[] {
187
+ const root = otpRoot.value;
188
+ if (!root) return [];
189
+ return Array.from(root.querySelectorAll('input.otp-input')) as HTMLInputElement[];
190
+ }
191
+
192
+ function focusFirstAvailableOtpInput(): void {
193
+ const inputs = getOtpInputs();
194
+ if (!inputs.length) return;
195
+ const firstEmpty = inputs.find((i) => !i.value);
196
+ (firstEmpty || inputs[0]).focus();
197
+ }
198
+
199
+ function handleGlobalFocusIn(event: FocusEvent): void {
200
+ const inputs = getOtpInputs();
201
+ if (!inputs.length) return;
202
+ const target = event.target as HTMLElement | null;
203
+ if (!target) return;
204
+ if (!inputs.includes(target as HTMLInputElement)) {
205
+ requestAnimationFrame(() => {
206
+ focusFirstAvailableOtpInput();
207
+ });
208
+ }
209
+ }
210
+
211
+ function handleFocusOut(): void {
212
+ requestAnimationFrame(() => {
213
+ const inputs = getOtpInputs();
214
+ if (!inputs.length) return;
215
+ const active = document.activeElement as HTMLElement | null;
216
+ if (!active || !inputs.includes(active as HTMLInputElement)) {
217
+ focusFirstAvailableOtpInput();
218
+ }
219
+ });
220
+ }
221
+
175
222
  async function sendCode (value) {
176
223
  inProgress.value = true;
177
224
 
@@ -187,7 +234,7 @@ async function sendCode (value) {
187
234
  if (resp.allowedLogin){
188
235
  await user.finishLogin()
189
236
  } else {
190
- showErrorTost(t('Invalid code'));
237
+ codeError.value = 'Invalid code';
191
238
  }
192
239
  }
193
240
 
@@ -214,7 +261,7 @@ const handleSkip = async () => {
214
261
  if (resp.allowedLogin){
215
262
  await user.finishLogin()
216
263
  } else {
217
- showErrorTost(t('Something went wrong'));
264
+ codeError.value = 'Something went wrong';
218
265
  }
219
266
  }
220
267
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "1.8.10",
3
+ "version": "1.9.1",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",