@adminforth/two-factors-auth 2.5.4 → 2.5.6

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
@@ -15,5 +15,5 @@ custom/package.json
15
15
  custom/tsconfig.json
16
16
  custom/utils.js
17
17
 
18
- sent 271,356 bytes received 210 bytes 543,132.00 bytes/sec
19
- total size is 270,529 speedup is 1.00
18
+ sent 272,054 bytes received 210 bytes 544,528.00 bytes/sec
19
+ total size is 271,238 speedup is 1.00
@@ -68,6 +68,7 @@
68
68
  </template>
69
69
 
70
70
  <script setup lang="ts">
71
+
71
72
  import VOtpInput from 'vue3-otp-input';
72
73
  import { ref, nextTick, watch, onMounted } from 'vue';
73
74
  import { useUserStore } from '@/stores/user';
@@ -98,27 +99,26 @@
98
99
  (e: 'closed'): void
99
100
  }>();
100
101
 
101
- function removeListeners() {
102
+ async function addEventListenerForOTPInput(){
103
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
104
+ focusFirstAvailableOtpInput();
105
+ isLoading.value = false;
106
+ await nextTick();
107
+ const rootEl = otpRoot.value;
108
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
109
+ }
110
+
111
+ function removeEventListenerForOTPInput() {
102
112
  window.removeEventListener('paste', handlePaste);
103
113
  document.removeEventListener('focusin', handleGlobalFocusIn, true);
104
114
  const rootEl = otpRoot.value;
105
115
  rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
116
+ // Abort any in-flight WebAuthn request when leaving the component
106
117
  }
107
118
 
108
- async function addListeners() {
109
- document.addEventListener('focusin', handleGlobalFocusIn, true);
110
-
111
- // Wait for DOM to be ready and OTP inputs to be rendered
112
- await nextTick();
113
- await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for v-otp-input to render
114
-
115
- focusFirstAvailableOtpInput();
116
- const rootEl = otpRoot.value;
117
- rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
118
- }
119
119
 
120
120
  const modelShow = ref(false);
121
- let resolveFn: ((confirmationResult: string) => void) | null = null;
121
+ let resolveFn: ((confirmationResult: any) => void) | null = null;
122
122
  let verifyingCallback: ((confirmationResult: string) => boolean) | null = null;
123
123
  let verifyFn: null | ((confirmationResult: string) => Promise<boolean> | boolean) = null;
124
124
  let rejectFn: ((err?: any) => void) | null = null;
@@ -133,7 +133,9 @@
133
133
  customDialogTitle.value = title;
134
134
  }
135
135
  modelShow.value = true;
136
- await addListeners();
136
+ if (modalMode.value === 'totp') {
137
+ await addEventListenerForOTPInput();
138
+ }
137
139
  resolveFn = resolve;
138
140
  rejectFn = reject;
139
141
  verifyFn = verifyingCallback ?? null;
@@ -163,15 +165,17 @@
163
165
  return null;
164
166
  } else if (name === 'InvalidStateError' || name === 'OperationError' || /pending/i.test(message)) {
165
167
  adminforth.alert({ message: t('Another security prompt is already open. Please try again.'), variant: 'warning' });
168
+ onCancel();
166
169
  return null;
167
170
  } else if (name === 'NotAllowedError') {
168
171
  adminforth.alert({ message: `The operation either timed out or was not allowed`, variant: 'warning' });
172
+ onCancel();
169
173
  return null;
170
174
  } else {
171
175
  adminforth.alert({message: `Error during authentication: ${error}`, variant: 'warning'});
176
+ onCancel();
172
177
  return null;
173
178
  }
174
- onCancel();
175
179
  }
176
180
  modelShow.value = false;
177
181
  const dataToReturn = {
@@ -179,7 +183,7 @@
179
183
  result: passkeyData
180
184
  }
181
185
  customDialogTitle.value = "";
182
- removeListeners();
186
+ removeEventListenerForOTPInput();
183
187
  resolveFn(dataToReturn);
184
188
  }
185
189
 
@@ -231,7 +235,7 @@
231
235
  result: value
232
236
  }
233
237
  customDialogTitle.value = "";
234
- removeListeners();
238
+ removeEventListenerForOTPInput();
235
239
  resolveFn(dataToReturn);
236
240
  }
237
241
 
@@ -240,19 +244,17 @@
240
244
  modelShow.value = false;
241
245
  bindValue.value = '';
242
246
  confirmationResult.value?.clearInput();
243
- removeListeners();
247
+ removeEventListenerForOTPInput();
244
248
  rejectFn("Cancel");
245
249
  emit('rejected', new Error('cancelled'));
246
250
  emit('closed');
247
251
  }
248
252
 
249
253
  watch(modalMode, async (newMode) => {
250
- if (newMode === 'totp' && modelShow.value && !isLoading.value) {
251
- await nextTick();
252
- setTimeout(() => {
253
- tagOtpInputs();
254
- focusFirstAvailableOtpInput();
255
- }, 100);
254
+ if (newMode === 'totp') {
255
+ await addEventListenerForOTPInput();
256
+ } else {
257
+ removeEventListenerForOTPInput();
256
258
  }
257
259
  });
258
260
 
@@ -287,22 +289,29 @@
287
289
 
288
290
  async function checkIfUserHasPasskeys() {
289
291
  isLoading.value = true;
290
- callAdminForthApi({
291
- method: 'GET',
292
- path: '/plugin/passkeys/getPasskeys',
293
- }).then((response) => {
292
+ try {
293
+ const response = await callAdminForthApi({
294
+ method: 'GET',
295
+ path: '/plugin/passkeys/getPasskeys',
296
+ });
297
+
294
298
  if (response.ok) {
295
299
  if (response.data.length >= 1) {
296
300
  doesUserHavePasskeys.value = true;
297
301
  modalMode.value = "passkey";
298
- isLoading.value = false;
299
302
  } else {
300
303
  doesUserHavePasskeys.value = false;
301
304
  modalMode.value = "totp";
302
- isLoading.value = false;
303
305
  }
304
306
  }
305
- });
307
+ } catch (error) {
308
+ console.error('Error checking passkeys:', error);
309
+ // Fallback to TOTP if there's an error
310
+ doesUserHavePasskeys.value = false;
311
+ modalMode.value = "totp";
312
+ } finally {
313
+ isLoading.value = false;
314
+ }
306
315
  }
307
316
 
308
317
  function getOtpInputs() {
@@ -181,14 +181,20 @@
181
181
  if (isPasskeysSupported.value === true) {
182
182
  await checkIfUserHasPasskeys();
183
183
  }
184
- document.addEventListener('focusin', handleGlobalFocusIn, true);
185
- focusFirstAvailableOtpInput();
186
- const rootEl = otpRoot.value;
187
- rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
184
+ if (confirmationMode.value === 'code') {
185
+ await addEventListenerForOTPInput();
186
+ }
188
187
  }
189
188
  isLoading.value = false;
190
189
  });
191
190
 
191
+ watch(confirmationMode, async (newMode) => {
192
+ if (newMode === 'code') {
193
+ await addEventListenerForOTPInput();
194
+ } else {
195
+ removeEventListenerForOTPInput();
196
+ }
197
+ });
192
198
  watch(route, (newRoute) => {
193
199
  codeError.value = null;
194
200
  if ( newRoute.hash === '#passkey' ) {
@@ -200,6 +206,24 @@
200
206
  }
201
207
  });
202
208
 
209
+ async function addEventListenerForOTPInput(){
210
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
211
+ focusFirstAvailableOtpInput();
212
+ isLoading.value = false;
213
+ await nextTick();
214
+ const rootEl = otpRoot.value;
215
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
216
+ }
217
+
218
+ function removeEventListenerForOTPInput() {
219
+ window.removeEventListener('paste', handlePaste);
220
+ document.removeEventListener('focusin', handleGlobalFocusIn, true);
221
+ const rootEl = otpRoot.value;
222
+ rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
223
+ // Abort any in-flight WebAuthn request when leaving the component
224
+ cancelPendingWebAuthn('component-unmount');
225
+ }
226
+
203
227
  async function isCMAAvailable() {
204
228
  if (window.PublicKeyCredential &&
205
229
  PublicKeyCredential.isConditionalMediationAvailable) {
@@ -211,12 +235,7 @@
211
235
  }
212
236
 
213
237
  onBeforeUnmount(() => {
214
- window.removeEventListener('paste', handlePaste);
215
- document.removeEventListener('focusin', handleGlobalFocusIn, true);
216
- const rootEl = otpRoot.value;
217
- rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
218
- // Abort any in-flight WebAuthn request when leaving the component
219
- cancelPendingWebAuthn('component-unmount');
238
+ removeEventListenerForOTPInput();
220
239
  });
221
240
 
222
241
  async function sendCode (value: any, factorMode: 'TOTP' | 'passkey', passkeyOptions: any) {
@@ -68,6 +68,7 @@
68
68
  </template>
69
69
 
70
70
  <script setup lang="ts">
71
+
71
72
  import VOtpInput from 'vue3-otp-input';
72
73
  import { ref, nextTick, watch, onMounted } from 'vue';
73
74
  import { useUserStore } from '@/stores/user';
@@ -98,27 +99,26 @@
98
99
  (e: 'closed'): void
99
100
  }>();
100
101
 
101
- function removeListeners() {
102
+ async function addEventListenerForOTPInput(){
103
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
104
+ focusFirstAvailableOtpInput();
105
+ isLoading.value = false;
106
+ await nextTick();
107
+ const rootEl = otpRoot.value;
108
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
109
+ }
110
+
111
+ function removeEventListenerForOTPInput() {
102
112
  window.removeEventListener('paste', handlePaste);
103
113
  document.removeEventListener('focusin', handleGlobalFocusIn, true);
104
114
  const rootEl = otpRoot.value;
105
115
  rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
116
+ // Abort any in-flight WebAuthn request when leaving the component
106
117
  }
107
118
 
108
- async function addListeners() {
109
- document.addEventListener('focusin', handleGlobalFocusIn, true);
110
-
111
- // Wait for DOM to be ready and OTP inputs to be rendered
112
- await nextTick();
113
- await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for v-otp-input to render
114
-
115
- focusFirstAvailableOtpInput();
116
- const rootEl = otpRoot.value;
117
- rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
118
- }
119
119
 
120
120
  const modelShow = ref(false);
121
- let resolveFn: ((confirmationResult: string) => void) | null = null;
121
+ let resolveFn: ((confirmationResult: any) => void) | null = null;
122
122
  let verifyingCallback: ((confirmationResult: string) => boolean) | null = null;
123
123
  let verifyFn: null | ((confirmationResult: string) => Promise<boolean> | boolean) = null;
124
124
  let rejectFn: ((err?: any) => void) | null = null;
@@ -133,7 +133,9 @@
133
133
  customDialogTitle.value = title;
134
134
  }
135
135
  modelShow.value = true;
136
- await addListeners();
136
+ if (modalMode.value === 'totp') {
137
+ await addEventListenerForOTPInput();
138
+ }
137
139
  resolveFn = resolve;
138
140
  rejectFn = reject;
139
141
  verifyFn = verifyingCallback ?? null;
@@ -163,15 +165,17 @@
163
165
  return null;
164
166
  } else if (name === 'InvalidStateError' || name === 'OperationError' || /pending/i.test(message)) {
165
167
  adminforth.alert({ message: t('Another security prompt is already open. Please try again.'), variant: 'warning' });
168
+ onCancel();
166
169
  return null;
167
170
  } else if (name === 'NotAllowedError') {
168
171
  adminforth.alert({ message: `The operation either timed out or was not allowed`, variant: 'warning' });
172
+ onCancel();
169
173
  return null;
170
174
  } else {
171
175
  adminforth.alert({message: `Error during authentication: ${error}`, variant: 'warning'});
176
+ onCancel();
172
177
  return null;
173
178
  }
174
- onCancel();
175
179
  }
176
180
  modelShow.value = false;
177
181
  const dataToReturn = {
@@ -179,7 +183,7 @@
179
183
  result: passkeyData
180
184
  }
181
185
  customDialogTitle.value = "";
182
- removeListeners();
186
+ removeEventListenerForOTPInput();
183
187
  resolveFn(dataToReturn);
184
188
  }
185
189
 
@@ -231,7 +235,7 @@
231
235
  result: value
232
236
  }
233
237
  customDialogTitle.value = "";
234
- removeListeners();
238
+ removeEventListenerForOTPInput();
235
239
  resolveFn(dataToReturn);
236
240
  }
237
241
 
@@ -240,19 +244,17 @@
240
244
  modelShow.value = false;
241
245
  bindValue.value = '';
242
246
  confirmationResult.value?.clearInput();
243
- removeListeners();
247
+ removeEventListenerForOTPInput();
244
248
  rejectFn("Cancel");
245
249
  emit('rejected', new Error('cancelled'));
246
250
  emit('closed');
247
251
  }
248
252
 
249
253
  watch(modalMode, async (newMode) => {
250
- if (newMode === 'totp' && modelShow.value && !isLoading.value) {
251
- await nextTick();
252
- setTimeout(() => {
253
- tagOtpInputs();
254
- focusFirstAvailableOtpInput();
255
- }, 100);
254
+ if (newMode === 'totp') {
255
+ await addEventListenerForOTPInput();
256
+ } else {
257
+ removeEventListenerForOTPInput();
256
258
  }
257
259
  });
258
260
 
@@ -287,22 +289,29 @@
287
289
 
288
290
  async function checkIfUserHasPasskeys() {
289
291
  isLoading.value = true;
290
- callAdminForthApi({
291
- method: 'GET',
292
- path: '/plugin/passkeys/getPasskeys',
293
- }).then((response) => {
292
+ try {
293
+ const response = await callAdminForthApi({
294
+ method: 'GET',
295
+ path: '/plugin/passkeys/getPasskeys',
296
+ });
297
+
294
298
  if (response.ok) {
295
299
  if (response.data.length >= 1) {
296
300
  doesUserHavePasskeys.value = true;
297
301
  modalMode.value = "passkey";
298
- isLoading.value = false;
299
302
  } else {
300
303
  doesUserHavePasskeys.value = false;
301
304
  modalMode.value = "totp";
302
- isLoading.value = false;
303
305
  }
304
306
  }
305
- });
307
+ } catch (error) {
308
+ console.error('Error checking passkeys:', error);
309
+ // Fallback to TOTP if there's an error
310
+ doesUserHavePasskeys.value = false;
311
+ modalMode.value = "totp";
312
+ } finally {
313
+ isLoading.value = false;
314
+ }
306
315
  }
307
316
 
308
317
  function getOtpInputs() {
@@ -181,14 +181,20 @@
181
181
  if (isPasskeysSupported.value === true) {
182
182
  await checkIfUserHasPasskeys();
183
183
  }
184
- document.addEventListener('focusin', handleGlobalFocusIn, true);
185
- focusFirstAvailableOtpInput();
186
- const rootEl = otpRoot.value;
187
- rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
184
+ if (confirmationMode.value === 'code') {
185
+ await addEventListenerForOTPInput();
186
+ }
188
187
  }
189
188
  isLoading.value = false;
190
189
  });
191
190
 
191
+ watch(confirmationMode, async (newMode) => {
192
+ if (newMode === 'code') {
193
+ await addEventListenerForOTPInput();
194
+ } else {
195
+ removeEventListenerForOTPInput();
196
+ }
197
+ });
192
198
  watch(route, (newRoute) => {
193
199
  codeError.value = null;
194
200
  if ( newRoute.hash === '#passkey' ) {
@@ -200,6 +206,24 @@
200
206
  }
201
207
  });
202
208
 
209
+ async function addEventListenerForOTPInput(){
210
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
211
+ focusFirstAvailableOtpInput();
212
+ isLoading.value = false;
213
+ await nextTick();
214
+ const rootEl = otpRoot.value;
215
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
216
+ }
217
+
218
+ function removeEventListenerForOTPInput() {
219
+ window.removeEventListener('paste', handlePaste);
220
+ document.removeEventListener('focusin', handleGlobalFocusIn, true);
221
+ const rootEl = otpRoot.value;
222
+ rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
223
+ // Abort any in-flight WebAuthn request when leaving the component
224
+ cancelPendingWebAuthn('component-unmount');
225
+ }
226
+
203
227
  async function isCMAAvailable() {
204
228
  if (window.PublicKeyCredential &&
205
229
  PublicKeyCredential.isConditionalMediationAvailable) {
@@ -211,12 +235,7 @@
211
235
  }
212
236
 
213
237
  onBeforeUnmount(() => {
214
- window.removeEventListener('paste', handlePaste);
215
- document.removeEventListener('focusin', handleGlobalFocusIn, true);
216
- const rootEl = otpRoot.value;
217
- rootEl && rootEl.removeEventListener('focusout', handleFocusOut, true);
218
- // Abort any in-flight WebAuthn request when leaving the component
219
- cancelPendingWebAuthn('component-unmount');
238
+ removeEventListenerForOTPInput();
220
239
  });
221
240
 
222
241
  async function sendCode (value: any, factorMode: 'TOTP' | 'passkey', passkeyOptions: any) {
package/dist/index.js CHANGED
@@ -418,7 +418,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
418
418
  }
419
419
  const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credential_id)]);
420
420
  if (!passkeyRecord) {
421
- return { error: 'Passkey not found' };
421
+ return { error: 'No registered passkey found, probably it was removed' };
422
422
  }
423
423
  const userPk = passkeyRecord[this.options.passkeys.credentialUserIdFieldName];
424
424
  if (!userPk) {
package/index.ts CHANGED
@@ -460,7 +460,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
460
460
 
461
461
  const passkeyRecord = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credential_id)]);
462
462
  if (!passkeyRecord) {
463
- return { error: 'Passkey not found' };
463
+ return { error: 'No registered passkey found, probably it was removed' };
464
464
  }
465
465
 
466
466
  const userPk = passkeyRecord[this.options.passkeys.credentialUserIdFieldName];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "2.5.4",
3
+ "version": "2.5.6",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",