@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 +3 -2
- package/custom/TwoFAModal.vue +48 -51
- package/custom/TwoFactorsPasskeysSettings.vue +7 -2
- package/custom/use2faApi.ts +52 -0
- package/dist/custom/TwoFAModal.vue +48 -51
- package/dist/custom/TwoFactorsPasskeysSettings.vue +7 -2
- package/dist/custom/use2faApi.ts +52 -0
- package/package.json +1 -1
- package/pnpm-workspace.yaml +4 -0
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
|
|
20
|
-
total size is
|
|
20
|
+
sent 288,076 bytes received 248 bytes 576,648.00 bytes/sec
|
|
21
|
+
total size is 287,093 speedup is 1.00
|
package/custom/TwoFAModal.vue
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
180
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
180
|
-
|
|
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