@adminforth/two-factors-auth 4.0.2 → 4.1.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
@@ -14,7 +14,8 @@ custom/package-lock.json
14
14
  custom/package.json
15
15
  custom/pnpm-lock.yaml
16
16
  custom/tsconfig.json
17
+ custom/use2faApi.ts
17
18
  custom/utils.js
18
19
 
19
- sent 286,356 bytes received 229 bytes 573,170.00 bytes/sec
20
- total size is 285,450 speedup is 1.00
20
+ sent 288,076 bytes received 248 bytes 576,648.00 bytes/sec
21
+ total size is 287,093 speedup is 1.00
@@ -101,7 +101,7 @@
101
101
  <script setup lang="ts">
102
102
 
103
103
  import VOtpInput from 'vue3-otp-input';
104
- import { ref, nextTick, watch, onMounted } from 'vue';
104
+ import { ref, nextTick, watch } from 'vue';
105
105
  import { useUserStore } from '@/stores/user';
106
106
  import { useI18n } from 'vue-i18n';
107
107
  import { callAdminForthApi } from '@/utils';
@@ -112,25 +112,14 @@
112
112
  import websocket from '@/websocket';
113
113
  import { getAdminForthClientId } from '@/utils/clientId';
114
114
  import type { AdminUser } from '@/types/Common';
115
-
116
- type TwoFaConfirmationResult = { mode: 'totp'; result: string } | { mode: 'passkey'; result: Record<string, any> };
117
-
118
- declare global {
119
- interface Window {
120
- adminforthTwoFaModal: {
121
- get2FaConfirmationResult: (
122
- title?: string,
123
- verifyingCallback?: (confirmationResult: string) => Promise<boolean>
124
- ) => Promise<TwoFaConfirmationResult>;
125
- };
126
- }
127
- }
115
+ import { useTwoFactorsAuthApi, type TwoFaConfirmationResult } from './use2faApi.js';
128
116
  const props = defineProps<{
129
117
  autoFinishLogin?: boolean
130
118
  adminUser?: AdminUser
131
119
  }>();
132
120
 
133
121
  const { alert } = useAdminforth();
122
+ const twoFactorsAuthApi = useTwoFactorsAuthApi();
134
123
 
135
124
  const isAwaiting2FAResult = ref(false);
136
125
  let allowAddNewSessions = true;
@@ -164,7 +153,7 @@
164
153
  }
165
154
  try {
166
155
  isAwaiting2FAResult.value = true;
167
- confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
156
+ confirmationResult = await twoFactorsAuthApi.get2FaConfirmationResult();
168
157
  } catch (error) {
169
158
  if (!isTwoFaCancel(error)) {
170
159
  console.error('[AdminForth 2FA] Error during 2FA confirmation', error);
@@ -226,7 +215,6 @@
226
215
 
227
216
  const modelShow = ref(false);
228
217
  let resolveFn: ((confirmationResult: any) => void) | null = null;
229
- let verifyingCallback: ((confirmationResult: string) => boolean) | null = null;
230
218
  let verifyFn: null | ((confirmationResult: string) => Promise<boolean> | boolean) = null;
231
219
  let rejectFn: ((err?: any) => void) | null = null;
232
220
 
@@ -235,37 +223,42 @@
235
223
  }
236
224
 
237
225
 
238
- window.adminforthTwoFaModal = {
239
- get2FaConfirmationResult: (title?: string, verifyingCallback?: (confirmationResult: string) => Promise<boolean>) =>
240
- new Promise((resolve, reject) => {
241
- (async () => {
242
- if (modelShow.value) {
243
- throw new Error(t('Modal is already open'));
244
- }
245
- const skipAllowModal = await checkIfSkipAllowModal();
246
- if (skipAllowModal) {
247
- resolve({ code: "123456" }); // dummy code
248
- return;
249
- }
250
- await checkIfUserHasPasskeys();
251
- if (title) {
252
- customDialogTitle.value = title;
253
- }
254
- resolveFn = resolve;
255
- rejectFn = reject;
256
- verifyFn = verifyingCallback ?? null;
257
- modelShow.value = true;
258
- if (modalMode.value === 'totp') {
259
- await addEventListenerForOTPInput();
260
- }
261
- })().catch((error) => {
262
- if (!isTwoFaCancel(error)) {
263
- console.error('[AdminForth 2FA] Failed to open 2FA modal', error);
264
- }
265
- reject(error);
266
- });
267
- }),
268
- };
226
+ function get2FaConfirmationResult(
227
+ title?: string,
228
+ verifyingCallback?: (confirmationResult: string) => Promise<boolean> | boolean
229
+ ): Promise<TwoFaConfirmationResult> {
230
+ return new Promise((resolve, reject) => {
231
+ (async () => {
232
+ if (modelShow.value) {
233
+ throw new Error(t('Modal is already open'));
234
+ }
235
+ const skipAllowModal = await checkIfSkipAllowModal();
236
+ if (skipAllowModal) {
237
+ resolve({ mode: 'totp', result: '123456' });
238
+ return;
239
+ }
240
+ await checkIfUserHasPasskeys();
241
+ if (title) {
242
+ customDialogTitle.value = title;
243
+ }
244
+ resolveFn = resolve;
245
+ rejectFn = reject;
246
+ verifyFn = verifyingCallback ?? null;
247
+ modelShow.value = true;
248
+ if (modalMode.value === 'totp') {
249
+ await addEventListenerForOTPInput();
250
+ }
251
+ })().catch((error) => {
252
+ if (!isTwoFaCancel(error)) {
253
+ console.error('[AdminForth 2FA] Failed to open 2FA modal', error);
254
+ }
255
+ reject(error);
256
+ });
257
+ });
258
+ }
259
+
260
+ twoFactorsAuthApi.setGet2FaConfirmationResultHandler(get2FaConfirmationResult);
261
+ twoFactorsAuthApi.initGlobalApi();
269
262
 
270
263
  const { t } = useI18n();
271
264
  const user = useUserStore();
@@ -276,15 +269,19 @@
276
269
  const doesUserHavePasskeys = ref(false);
277
270
  const modalMode = ref<"totp" | "passkey">("totp");
278
271
  const isLoading = ref(false);
272
+ const isFetchingPasskey = ref(false);
279
273
  const customDialogTitle = ref("");
280
274
 
281
275
  async function usePasskeyButtonClick() {
276
+ isFetchingPasskey.value = true;
282
277
  let passkeyData;
283
278
  try {
284
279
  passkeyData = await getPasskey();
285
280
  } catch (error) {
286
281
  onCancel();
287
282
  return null;
283
+ } finally {
284
+ isFetchingPasskey.value = false;
288
285
  }
289
286
  modelShow.value = false;
290
287
  const dataToReturn = {
@@ -293,7 +290,7 @@
293
290
  }
294
291
  customDialogTitle.value = "";
295
292
  removeEventListenerForOTPInput();
296
- resolveFn(dataToReturn);
293
+ resolveFn?.(dataToReturn);
297
294
  }
298
295
 
299
296
  function tagOtpInputs() {
@@ -354,7 +351,7 @@
354
351
  bindValue.value = '';
355
352
  confirmationResult.value?.clearInput();
356
353
  removeEventListenerForOTPInput();
357
- rejectFn("Cancel");
354
+ rejectFn?.("Cancel");
358
355
  emit('rejected', new Error('cancelled'));
359
356
  emit('closed');
360
357
  }
@@ -440,10 +437,10 @@
440
437
  ((firstEmpty || inputs[0]) as HTMLInputElement).focus();
441
438
  }
442
439
 
443
- function handleGlobalFocusIn(event) {
440
+ function handleGlobalFocusIn(event: FocusEvent) {
444
441
  const inputs = getOtpInputs();
445
442
  if (!inputs.length) return;
446
- const target = event.target;
443
+ const target = event.target as Element | null;
447
444
  if (!target) return;
448
445
  if (!inputs.includes(target)) {
449
446
  requestAnimationFrame(() => {
@@ -133,11 +133,13 @@
133
133
  import utc from 'dayjs/plugin/utc';
134
134
  import timezone from 'dayjs/plugin/timezone';
135
135
  import { useCoreStore } from '@/stores/core';
136
+ import { useTwoFactorsAuth } from './use2faApi';
136
137
 
137
138
  const { t } = useI18n();
138
139
  dayjs.extend(utc);
139
140
  dayjs.extend(timezone);
140
141
  const coreStore = useCoreStore();
142
+ const twoFactorsAuth = useTwoFactorsAuth();
141
143
 
142
144
  interface Passkey {
143
145
  id: string;
@@ -176,8 +178,9 @@
176
178
  async function startAddPasskey() {
177
179
  isFetchingPasskey.value = true;
178
180
  try {
179
- const confirmationResult = await window.adminforthTwoFaModal
180
- .get2FaConfirmationResult(t('To add passkey first verify yourself'));
181
+ const confirmationResult = await twoFactorsAuth.get2FaConfirmationResult(
182
+ t('To add passkey first verify yourself')
183
+ );
181
184
 
182
185
  if (!confirmationResult) return;
183
186
 
@@ -227,7 +230,9 @@
227
230
  body: { mode: addPasskeyMode.value, confirmationResult },
228
231
  });
229
232
  if (!response.ok) return { error: response.error ?? t('Verification failed') };
233
+ console.log('Passkey challenge response:', response.data);
230
234
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(response.data);
235
+ console.log('Parsed PublicKeyCredentialCreationOptions:', options);
231
236
  return { options, challengeId: response.challengeId };
232
237
  } catch (error) {
233
238
  console.error('Error requesting passkey challenge:', error);
@@ -0,0 +1,52 @@
1
+ import { ref } from 'vue';
2
+ import { defineStore } from 'pinia';
3
+
4
+ export type TwoFaConfirmationResult =
5
+ | { mode: 'totp'; result: string }
6
+ | { mode: 'passkey'; result: Record<string, any> };
7
+
8
+ type VerifyFn = (confirmationResult: string) => Promise<boolean> | boolean;
9
+ type Get2FaConfirmationResultHandler = (
10
+ title?: string,
11
+ verifyingCallback?: VerifyFn
12
+ ) => Promise<TwoFaConfirmationResult>;
13
+
14
+ declare global {
15
+ interface Window {
16
+ adminforthTwoFaModal: {
17
+ get2FaConfirmationResult: Get2FaConfirmationResultHandler;
18
+ };
19
+ }
20
+ }
21
+
22
+ export const useTwoFactorsAuthApi = defineStore('twoFactorsAuthApi', () => {
23
+ const get2FaConfirmationResultHandler = ref<Get2FaConfirmationResultHandler | null>(null);
24
+
25
+ function setGet2FaConfirmationResultHandler(handler: Get2FaConfirmationResultHandler | null) {
26
+ get2FaConfirmationResultHandler.value = handler;
27
+ }
28
+
29
+ async function get2FaConfirmationResult(
30
+ title?: string,
31
+ verifyingCallback?: VerifyFn
32
+ ): Promise<TwoFaConfirmationResult> {
33
+ if (!get2FaConfirmationResultHandler.value) {
34
+ throw new Error('Two factors auth modal API is not initialized');
35
+ }
36
+ return get2FaConfirmationResultHandler.value(title, verifyingCallback);
37
+ }
38
+
39
+ function initGlobalApi() {
40
+ window.adminforthTwoFaModal = {
41
+ get2FaConfirmationResult,
42
+ };
43
+ }
44
+
45
+ return {
46
+ get2FaConfirmationResult,
47
+ setGet2FaConfirmationResultHandler,
48
+ initGlobalApi,
49
+ };
50
+ });
51
+
52
+ export const useTwoFactorsAuth = useTwoFactorsAuthApi;
@@ -101,7 +101,7 @@
101
101
  <script setup lang="ts">
102
102
 
103
103
  import VOtpInput from 'vue3-otp-input';
104
- import { ref, nextTick, watch, onMounted } from 'vue';
104
+ import { ref, nextTick, watch } from 'vue';
105
105
  import { useUserStore } from '@/stores/user';
106
106
  import { useI18n } from 'vue-i18n';
107
107
  import { callAdminForthApi } from '@/utils';
@@ -112,25 +112,14 @@
112
112
  import websocket from '@/websocket';
113
113
  import { getAdminForthClientId } from '@/utils/clientId';
114
114
  import type { AdminUser } from '@/types/Common';
115
-
116
- type TwoFaConfirmationResult = { mode: 'totp'; result: string } | { mode: 'passkey'; result: Record<string, any> };
117
-
118
- declare global {
119
- interface Window {
120
- adminforthTwoFaModal: {
121
- get2FaConfirmationResult: (
122
- title?: string,
123
- verifyingCallback?: (confirmationResult: string) => Promise<boolean>
124
- ) => Promise<TwoFaConfirmationResult>;
125
- };
126
- }
127
- }
115
+ import { useTwoFactorsAuthApi, type TwoFaConfirmationResult } from './use2faApi.js';
128
116
  const props = defineProps<{
129
117
  autoFinishLogin?: boolean
130
118
  adminUser?: AdminUser
131
119
  }>();
132
120
 
133
121
  const { alert } = useAdminforth();
122
+ const twoFactorsAuthApi = useTwoFactorsAuthApi();
134
123
 
135
124
  const isAwaiting2FAResult = ref(false);
136
125
  let allowAddNewSessions = true;
@@ -164,7 +153,7 @@
164
153
  }
165
154
  try {
166
155
  isAwaiting2FAResult.value = true;
167
- confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
156
+ confirmationResult = await twoFactorsAuthApi.get2FaConfirmationResult();
168
157
  } catch (error) {
169
158
  if (!isTwoFaCancel(error)) {
170
159
  console.error('[AdminForth 2FA] Error during 2FA confirmation', error);
@@ -226,7 +215,6 @@
226
215
 
227
216
  const modelShow = ref(false);
228
217
  let resolveFn: ((confirmationResult: any) => void) | null = null;
229
- let verifyingCallback: ((confirmationResult: string) => boolean) | null = null;
230
218
  let verifyFn: null | ((confirmationResult: string) => Promise<boolean> | boolean) = null;
231
219
  let rejectFn: ((err?: any) => void) | null = null;
232
220
 
@@ -235,37 +223,42 @@
235
223
  }
236
224
 
237
225
 
238
- window.adminforthTwoFaModal = {
239
- get2FaConfirmationResult: (title?: string, verifyingCallback?: (confirmationResult: string) => Promise<boolean>) =>
240
- new Promise((resolve, reject) => {
241
- (async () => {
242
- if (modelShow.value) {
243
- throw new Error(t('Modal is already open'));
244
- }
245
- const skipAllowModal = await checkIfSkipAllowModal();
246
- if (skipAllowModal) {
247
- resolve({ code: "123456" }); // dummy code
248
- return;
249
- }
250
- await checkIfUserHasPasskeys();
251
- if (title) {
252
- customDialogTitle.value = title;
253
- }
254
- resolveFn = resolve;
255
- rejectFn = reject;
256
- verifyFn = verifyingCallback ?? null;
257
- modelShow.value = true;
258
- if (modalMode.value === 'totp') {
259
- await addEventListenerForOTPInput();
260
- }
261
- })().catch((error) => {
262
- if (!isTwoFaCancel(error)) {
263
- console.error('[AdminForth 2FA] Failed to open 2FA modal', error);
264
- }
265
- reject(error);
266
- });
267
- }),
268
- };
226
+ function get2FaConfirmationResult(
227
+ title?: string,
228
+ verifyingCallback?: (confirmationResult: string) => Promise<boolean> | boolean
229
+ ): Promise<TwoFaConfirmationResult> {
230
+ return new Promise((resolve, reject) => {
231
+ (async () => {
232
+ if (modelShow.value) {
233
+ throw new Error(t('Modal is already open'));
234
+ }
235
+ const skipAllowModal = await checkIfSkipAllowModal();
236
+ if (skipAllowModal) {
237
+ resolve({ mode: 'totp', result: '123456' });
238
+ return;
239
+ }
240
+ await checkIfUserHasPasskeys();
241
+ if (title) {
242
+ customDialogTitle.value = title;
243
+ }
244
+ resolveFn = resolve;
245
+ rejectFn = reject;
246
+ verifyFn = verifyingCallback ?? null;
247
+ modelShow.value = true;
248
+ if (modalMode.value === 'totp') {
249
+ await addEventListenerForOTPInput();
250
+ }
251
+ })().catch((error) => {
252
+ if (!isTwoFaCancel(error)) {
253
+ console.error('[AdminForth 2FA] Failed to open 2FA modal', error);
254
+ }
255
+ reject(error);
256
+ });
257
+ });
258
+ }
259
+
260
+ twoFactorsAuthApi.setGet2FaConfirmationResultHandler(get2FaConfirmationResult);
261
+ twoFactorsAuthApi.initGlobalApi();
269
262
 
270
263
  const { t } = useI18n();
271
264
  const user = useUserStore();
@@ -276,15 +269,19 @@
276
269
  const doesUserHavePasskeys = ref(false);
277
270
  const modalMode = ref<"totp" | "passkey">("totp");
278
271
  const isLoading = ref(false);
272
+ const isFetchingPasskey = ref(false);
279
273
  const customDialogTitle = ref("");
280
274
 
281
275
  async function usePasskeyButtonClick() {
276
+ isFetchingPasskey.value = true;
282
277
  let passkeyData;
283
278
  try {
284
279
  passkeyData = await getPasskey();
285
280
  } catch (error) {
286
281
  onCancel();
287
282
  return null;
283
+ } finally {
284
+ isFetchingPasskey.value = false;
288
285
  }
289
286
  modelShow.value = false;
290
287
  const dataToReturn = {
@@ -293,7 +290,7 @@
293
290
  }
294
291
  customDialogTitle.value = "";
295
292
  removeEventListenerForOTPInput();
296
- resolveFn(dataToReturn);
293
+ resolveFn?.(dataToReturn);
297
294
  }
298
295
 
299
296
  function tagOtpInputs() {
@@ -354,7 +351,7 @@
354
351
  bindValue.value = '';
355
352
  confirmationResult.value?.clearInput();
356
353
  removeEventListenerForOTPInput();
357
- rejectFn("Cancel");
354
+ rejectFn?.("Cancel");
358
355
  emit('rejected', new Error('cancelled'));
359
356
  emit('closed');
360
357
  }
@@ -440,10 +437,10 @@
440
437
  ((firstEmpty || inputs[0]) as HTMLInputElement).focus();
441
438
  }
442
439
 
443
- function handleGlobalFocusIn(event) {
440
+ function handleGlobalFocusIn(event: FocusEvent) {
444
441
  const inputs = getOtpInputs();
445
442
  if (!inputs.length) return;
446
- const target = event.target;
443
+ const target = event.target as Element | null;
447
444
  if (!target) return;
448
445
  if (!inputs.includes(target)) {
449
446
  requestAnimationFrame(() => {
@@ -133,11 +133,13 @@
133
133
  import utc from 'dayjs/plugin/utc';
134
134
  import timezone from 'dayjs/plugin/timezone';
135
135
  import { useCoreStore } from '@/stores/core';
136
+ import { useTwoFactorsAuth } from './use2faApi';
136
137
 
137
138
  const { t } = useI18n();
138
139
  dayjs.extend(utc);
139
140
  dayjs.extend(timezone);
140
141
  const coreStore = useCoreStore();
142
+ const twoFactorsAuth = useTwoFactorsAuth();
141
143
 
142
144
  interface Passkey {
143
145
  id: string;
@@ -176,8 +178,9 @@
176
178
  async function startAddPasskey() {
177
179
  isFetchingPasskey.value = true;
178
180
  try {
179
- const confirmationResult = await window.adminforthTwoFaModal
180
- .get2FaConfirmationResult(t('To add passkey first verify yourself'));
181
+ const confirmationResult = await twoFactorsAuth.get2FaConfirmationResult(
182
+ t('To add passkey first verify yourself')
183
+ );
181
184
 
182
185
  if (!confirmationResult) return;
183
186
 
@@ -227,7 +230,9 @@
227
230
  body: { mode: addPasskeyMode.value, confirmationResult },
228
231
  });
229
232
  if (!response.ok) return { error: response.error ?? t('Verification failed') };
233
+ console.log('Passkey challenge response:', response.data);
230
234
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(response.data);
235
+ console.log('Parsed PublicKeyCredentialCreationOptions:', options);
231
236
  return { options, challengeId: response.challengeId };
232
237
  } catch (error) {
233
238
  console.error('Error requesting passkey challenge:', error);
@@ -0,0 +1,52 @@
1
+ import { ref } from 'vue';
2
+ import { defineStore } from 'pinia';
3
+
4
+ export type TwoFaConfirmationResult =
5
+ | { mode: 'totp'; result: string }
6
+ | { mode: 'passkey'; result: Record<string, any> };
7
+
8
+ type VerifyFn = (confirmationResult: string) => Promise<boolean> | boolean;
9
+ type Get2FaConfirmationResultHandler = (
10
+ title?: string,
11
+ verifyingCallback?: VerifyFn
12
+ ) => Promise<TwoFaConfirmationResult>;
13
+
14
+ declare global {
15
+ interface Window {
16
+ adminforthTwoFaModal: {
17
+ get2FaConfirmationResult: Get2FaConfirmationResultHandler;
18
+ };
19
+ }
20
+ }
21
+
22
+ export const useTwoFactorsAuthApi = defineStore('twoFactorsAuthApi', () => {
23
+ const get2FaConfirmationResultHandler = ref<Get2FaConfirmationResultHandler | null>(null);
24
+
25
+ function setGet2FaConfirmationResultHandler(handler: Get2FaConfirmationResultHandler | null) {
26
+ get2FaConfirmationResultHandler.value = handler;
27
+ }
28
+
29
+ async function get2FaConfirmationResult(
30
+ title?: string,
31
+ verifyingCallback?: VerifyFn
32
+ ): Promise<TwoFaConfirmationResult> {
33
+ if (!get2FaConfirmationResultHandler.value) {
34
+ throw new Error('Two factors auth modal API is not initialized');
35
+ }
36
+ return get2FaConfirmationResultHandler.value(title, verifyingCallback);
37
+ }
38
+
39
+ function initGlobalApi() {
40
+ window.adminforthTwoFaModal = {
41
+ get2FaConfirmationResult,
42
+ };
43
+ }
44
+
45
+ return {
46
+ get2FaConfirmationResult,
47
+ setGet2FaConfirmationResultHandler,
48
+ initGlobalApi,
49
+ };
50
+ });
51
+
52
+ export const useTwoFactorsAuth = useTwoFactorsAuthApi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,4 @@
1
+ allowBuilds:
2
+ adminforth: true
3
+ minimumReleaseAgeExclude:
4
+ - adminforth@3.0.1