@adminforth/two-factors-auth 5.0.0 → 7.0.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.
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
2
+ <div class="relative flex items-center justify-center min-h-screen bg-lightHtml dark:bg-darkHtml"
3
3
  :style="coreStore.config?.loginBackgroundImage && coreStore.config?.loginBackgroundPosition === 'over' ? {
4
4
  'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
@@ -16,37 +16,38 @@
16
16
  :error="codeError"
17
17
  >
18
18
  <div>
19
- <p class="mb-3 flex items-center gap-2.5 text-sm font-medium text-gray-900 dark:text-white">
19
+ <p class="mb-3 flex items-center gap-2.5 text-sm font-medium text-lightInputText dark:text-darkInputText">
20
20
  <span class="flex w-6 h-6 shrink-0 items-center justify-center rounded-full bg-lightPrimary/10 text-xs font-semibold text-lightPrimary dark:bg-darkPrimary/20 dark:text-darkPrimary">1</span>
21
21
  {{ $t('Scan the QR code with your authenticator app') }}
22
22
  </p>
23
- <div class="flex items-center gap-4 rounded-lg border border-lightDialogBreakLine bg-gray-50 p-3.5 dark:border-darkDialogBreakLine dark:bg-gray-800">
23
+ <div class="flex items-center gap-4 rounded-lg border border-lightDialogBreakLine bg-lightSecondaryDarken p-3.5 dark:border-darkDialogBreakLine dark:bg-darkSecondaryLighten">
24
+ <!-- a QR code needs a white ground to be scanned, in the dark theme too -->
24
25
  <div class="w-32 h-32 shrink-0 rounded-lg bg-white p-2">
25
26
  <img v-if="totpQrCode" :src="totpQrCode" class="af-qr-code w-full h-full" alt="QR code" />
26
27
  </div>
27
28
  <div class="flex min-w-0 flex-col gap-2">
28
- <p class="text-sm leading-5 text-gray-500 dark:text-gray-400">{{ $t('Setting up on this device?') }}</p>
29
- <a :href="totpUri" class="inline-flex items-center gap-1.5 text-sm font-medium text-lightPrimary hover:underline dark:text-darkPrimary">
29
+ <p class="text-sm leading-5 text-lightInputIconColor dark:text-darkInputIconColor">{{ $t('Setting up on this device?') }}</p>
30
+ <Link :to="totpUri" class="inline-flex items-center gap-1.5 text-sm">
30
31
  {{ $t('Open in authenticator app') }}
31
- <IconArrowUpRightFromSquareOutline class="w-4 h-4" />
32
- </a>
32
+ <IconExternalLink class="w-4 h-4" />
33
+ </Link>
33
34
  </div>
34
35
  </div>
35
36
  </div>
36
37
 
37
38
  <div>
38
- <p class="mb-2 text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ $t('Or enter the key manually') }}</p>
39
- <div class="flex items-center gap-2 rounded-lg border border-lightDialogBreakLine bg-gray-50 py-1.5 pl-3.5 pr-1.5 dark:border-darkDialogBreakLine dark:bg-gray-800">
40
- <span class="af-2fa-secret flex-1 min-w-0 select-all break-all font-mono text-sm tracking-wider text-gray-800 dark:text-gray-100">{{ totp.newSecret }}</span>
41
- <button type="button" :aria-label="$t('Copy key')" class="af-2fa-copy-btn inline-flex w-9 h-9 shrink-0 items-center justify-center rounded-lg text-gray-500 hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover dark:text-gray-400 dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover" @click="onCopyClick">
42
- <IconFileCopyOutline class="w-5 h-5" />
43
- </button>
39
+ <p class="mb-2 text-xs font-medium uppercase tracking-wider text-lightInputIconColor dark:text-darkInputIconColor">{{ $t('Or enter the key manually') }}</p>
40
+ <div class="flex items-center gap-2 rounded-lg border border-lightDialogBreakLine bg-lightSecondaryDarken py-1.5 pl-3.5 pr-1.5 dark:border-darkDialogBreakLine dark:bg-darkSecondaryLighten">
41
+ <span class="af-2fa-secret flex-1 min-w-0 select-all break-all font-mono text-sm tracking-wider text-lightInputText dark:text-darkInputText">{{ totp.newSecret }}</span>
42
+ <Button type="button" variant="ghost" iconOnly :aria-label="$t('Copy key')" class="af-2fa-copy-btn" @click="onCopyClick">
43
+ <IconCopy aria-hidden="true" />
44
+ </Button>
44
45
  </div>
45
46
  </div>
46
47
 
47
48
  <TwoFaCodeInput ref="codeInput" :invalid="Boolean(codeError)" @complete="handleOnComplete">
48
49
  <template #label>
49
- <p class="mb-3 flex items-center gap-2.5 text-sm font-medium text-gray-900 dark:text-white">
50
+ <p class="mb-3 flex items-center gap-2.5 text-sm font-medium text-lightInputText dark:text-darkInputText">
50
51
  <span class="flex w-6 h-6 shrink-0 items-center justify-center rounded-full bg-lightPrimary/10 text-xs font-semibold text-lightPrimary dark:bg-darkPrimary/20 dark:text-darkPrimary">2</span>
51
52
  {{ $t('Enter the 6-digit code from the app') }}
52
53
  </p>
@@ -54,9 +55,9 @@
54
55
  </TwoFaCodeInput>
55
56
 
56
57
  <template #footer>
57
- <button v-if="skipAllowed" type="button" class="af-2fa-skip-btn inline-flex h-11 items-center whitespace-nowrap text-sm font-medium text-gray-600 hover:underline dark:text-gray-300" @click="handleSkip">
58
+ <Button v-if="skipAllowed" type="button" variant="ghost" class="af-2fa-skip-btn -ms-3.5" @click="handleSkip">
58
59
  {{ $t('Skip for now') }}
59
- </button>
60
+ </Button>
60
61
  <LinkButton to="/login" variant="secondary" class="af-2fa-back-to-login ms-auto whitespace-nowrap">{{ $t('Back to login') }}</LinkButton>
61
62
  </template>
62
63
  </TwoFaCard>
@@ -70,19 +71,16 @@
70
71
  import { onMounted, ref, computed, onBeforeMount } from 'vue';
71
72
  import { useCoreStore } from '@/stores/core';
72
73
  import { useUserStore } from '@/stores/user';
73
- import { IconArrowUpRightFromSquareOutline, IconEyeSolid, IconEyeSlashSolid, IconFileCopyOutline } from '@iconify-prerendered/vue-flowbite';
74
+ import { IconCopy, IconExternalLink } from '@iconify-prerendered/vue-lucide';
74
75
  import { callAdminForthApi, loadFile } from '@/utils';
75
76
  import { useRouter } from 'vue-router';
76
- import { showErrorTost } from '@/composables/useFrontendApi';
77
- import { LinkButton } from '@/afcl';
78
- import Vue2FACodeInput from '@loltech/vue3-2fa-code-input';
77
+ import { Button, Link, LinkButton } from '@/afcl';
79
78
  import adminforth from '@/adminforth';
80
79
  import TwoFaCard from './TwoFaCard.vue';
81
80
  import TwoFaCodeInput from './TwoFaCodeInput.vue';
82
81
  import { useI18n } from 'vue-i18n';
83
82
  import { useRoute } from 'vue-router';
84
83
  import { handlePasskeyAlert } from './utils.js';
85
- import ErrorMessage from '@/components/ErrorMessage.vue';
86
84
  import { useQRCode } from '@vueuse/integrations/useQRCode'
87
85
 
88
86
  const { t } = useI18n();
@@ -113,9 +111,6 @@ const user = useUserStore();
113
111
 
114
112
  const skipAllowed = ref(false);
115
113
 
116
- // use this simple function to automatically focus on the next input
117
-
118
- const showPw = ref(false);
119
114
 
120
115
  const error = ref(null);
121
116
  const totp = ref<{ issuer?: string; userName?: string; newSecret?: string }>({});
@@ -9,12 +9,10 @@
9
9
  "version": "1.0.0",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "@loltech/vue3-2fa-code-input": "^1.1.0",
13
12
  "@vueuse/core": "^14.3.0",
14
13
  "@vueuse/integrations": "^14.4.0",
15
14
  "dayjs": "^1.11.18",
16
- "qrcode": "^1.5.4",
17
- "vue3-otp-input": "^0.5.21"
15
+ "qrcode": "^1.5.4"
18
16
  }
19
17
  },
20
18
  "node_modules/@babel/helper-string-parser": {
@@ -22,6 +20,7 @@
22
20
  "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
23
21
  "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
24
22
  "license": "MIT",
23
+ "peer": true,
25
24
  "engines": {
26
25
  "node": ">=6.9.0"
27
26
  }
@@ -31,6 +30,7 @@
31
30
  "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
32
31
  "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
33
32
  "license": "MIT",
33
+ "peer": true,
34
34
  "engines": {
35
35
  "node": ">=6.9.0"
36
36
  }
@@ -40,6 +40,7 @@
40
40
  "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
41
41
  "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
42
42
  "license": "MIT",
43
+ "peer": true,
43
44
  "dependencies": {
44
45
  "@babel/types": "^7.29.0"
45
46
  },
@@ -55,6 +56,7 @@
55
56
  "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
56
57
  "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
57
58
  "license": "MIT",
59
+ "peer": true,
58
60
  "dependencies": {
59
61
  "@babel/helper-string-parser": "^7.27.1",
60
62
  "@babel/helper-validator-identifier": "^7.28.5"
@@ -67,13 +69,8 @@
67
69
  "version": "1.5.5",
68
70
  "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
69
71
  "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
70
- "license": "MIT"
71
- },
72
- "node_modules/@loltech/vue3-2fa-code-input": {
73
- "version": "1.1.0",
74
- "resolved": "https://registry.npmjs.org/@loltech/vue3-2fa-code-input/-/vue3-2fa-code-input-1.1.0.tgz",
75
- "integrity": "sha512-styyoQjsARSeVf6E5qMvYIVkLRrwYtfL8xDl5W+g9wKF8qlr2XGU2gi9maEg0X4Us41kJryXBzfRMUcv5tKIBA==",
76
- "license": "MIT"
72
+ "license": "MIT",
73
+ "peer": true
77
74
  },
78
75
  "node_modules/@types/web-bluetooth": {
79
76
  "version": "0.0.21",
@@ -86,6 +83,7 @@
86
83
  "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
87
84
  "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
88
85
  "license": "MIT",
86
+ "peer": true,
89
87
  "dependencies": {
90
88
  "@babel/parser": "^7.29.0",
91
89
  "@vue/shared": "3.5.30",
@@ -99,6 +97,7 @@
99
97
  "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
100
98
  "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
101
99
  "license": "MIT",
100
+ "peer": true,
102
101
  "dependencies": {
103
102
  "@vue/compiler-core": "3.5.30",
104
103
  "@vue/shared": "3.5.30"
@@ -109,6 +108,7 @@
109
108
  "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
110
109
  "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
111
110
  "license": "MIT",
111
+ "peer": true,
112
112
  "dependencies": {
113
113
  "@babel/parser": "^7.29.0",
114
114
  "@vue/compiler-core": "3.5.30",
@@ -126,6 +126,7 @@
126
126
  "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
127
127
  "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
128
128
  "license": "MIT",
129
+ "peer": true,
129
130
  "dependencies": {
130
131
  "@vue/compiler-dom": "3.5.30",
131
132
  "@vue/shared": "3.5.30"
@@ -136,6 +137,7 @@
136
137
  "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
137
138
  "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
138
139
  "license": "MIT",
140
+ "peer": true,
139
141
  "dependencies": {
140
142
  "@vue/shared": "3.5.30"
141
143
  }
@@ -145,6 +147,7 @@
145
147
  "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
146
148
  "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
147
149
  "license": "MIT",
150
+ "peer": true,
148
151
  "dependencies": {
149
152
  "@vue/reactivity": "3.5.30",
150
153
  "@vue/shared": "3.5.30"
@@ -155,6 +158,7 @@
155
158
  "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
156
159
  "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
157
160
  "license": "MIT",
161
+ "peer": true,
158
162
  "dependencies": {
159
163
  "@vue/reactivity": "3.5.30",
160
164
  "@vue/runtime-core": "3.5.30",
@@ -167,6 +171,7 @@
167
171
  "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
168
172
  "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
169
173
  "license": "MIT",
174
+ "peer": true,
170
175
  "dependencies": {
171
176
  "@vue/compiler-ssr": "3.5.30",
172
177
  "@vue/shared": "3.5.30"
@@ -179,7 +184,8 @@
179
184
  "version": "3.5.30",
180
185
  "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
181
186
  "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
182
- "license": "MIT"
187
+ "license": "MIT",
188
+ "peer": true
183
189
  },
184
190
  "node_modules/@vueuse/core": {
185
191
  "version": "14.4.0",
@@ -351,7 +357,8 @@
351
357
  "version": "3.2.3",
352
358
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
353
359
  "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
354
- "license": "MIT"
360
+ "license": "MIT",
361
+ "peer": true
355
362
  },
356
363
  "node_modules/dayjs": {
357
364
  "version": "1.11.19",
@@ -385,6 +392,7 @@
385
392
  "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
386
393
  "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
387
394
  "license": "BSD-2-Clause",
395
+ "peer": true,
388
396
  "engines": {
389
397
  "node": ">=0.12"
390
398
  },
@@ -396,7 +404,8 @@
396
404
  "version": "2.0.2",
397
405
  "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
398
406
  "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
399
- "license": "MIT"
407
+ "license": "MIT",
408
+ "peer": true
400
409
  },
401
410
  "node_modules/find-up": {
402
411
  "version": "4.1.0",
@@ -446,6 +455,7 @@
446
455
  "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
447
456
  "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
448
457
  "license": "MIT",
458
+ "peer": true,
449
459
  "dependencies": {
450
460
  "@jridgewell/sourcemap-codec": "^1.5.5"
451
461
  }
@@ -461,6 +471,7 @@
461
471
  }
462
472
  ],
463
473
  "license": "MIT",
474
+ "peer": true,
464
475
  "bin": {
465
476
  "nanoid": "bin/nanoid.cjs"
466
477
  },
@@ -517,7 +528,8 @@
517
528
  "version": "1.1.1",
518
529
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
519
530
  "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
520
- "license": "ISC"
531
+ "license": "ISC",
532
+ "peer": true
521
533
  },
522
534
  "node_modules/pngjs": {
523
535
  "version": "5.0.0",
@@ -547,6 +559,7 @@
547
559
  }
548
560
  ],
549
561
  "license": "MIT",
562
+ "peer": true,
550
563
  "dependencies": {
551
564
  "nanoid": "^3.3.11",
552
565
  "picocolors": "^1.1.1",
@@ -599,6 +612,7 @@
599
612
  "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
600
613
  "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
601
614
  "license": "BSD-3-Clause",
615
+ "peer": true,
602
616
  "engines": {
603
617
  "node": ">=0.10.0"
604
618
  }
@@ -634,6 +648,7 @@
634
648
  "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
635
649
  "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
636
650
  "license": "MIT",
651
+ "peer": true,
637
652
  "dependencies": {
638
653
  "@vue/compiler-dom": "3.5.30",
639
654
  "@vue/compiler-sfc": "3.5.30",
@@ -650,18 +665,6 @@
650
665
  }
651
666
  }
652
667
  },
653
- "node_modules/vue3-otp-input": {
654
- "version": "0.5.40",
655
- "resolved": "https://registry.npmjs.org/vue3-otp-input/-/vue3-otp-input-0.5.40.tgz",
656
- "integrity": "sha512-3AMYHqNz9ZDa9y7ICwcEcsJG7XdZGaLAr6IRLIl3whvseFE95F5Duc9q963HcqEbu8CeMWilkmbAt/0eZOZxow==",
657
- "license": "MIT",
658
- "dependencies": {
659
- "vue": "^3.4.27"
660
- },
661
- "peerDependencies": {
662
- "vue": "^3.0.*"
663
- }
664
- },
665
668
  "node_modules/which-module": {
666
669
  "version": "2.0.1",
667
670
  "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
@@ -10,11 +10,9 @@
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "dependencies": {
13
- "@loltech/vue3-2fa-code-input": "^1.1.0",
14
13
  "@vueuse/core": "^14.3.0",
15
14
  "@vueuse/integrations": "^14.4.0",
16
15
  "dayjs": "^1.11.18",
17
- "qrcode": "^1.5.4",
18
- "vue3-otp-input": "^0.5.21"
16
+ "qrcode": "^1.5.4"
19
17
  }
20
18
  }
@@ -8,9 +8,6 @@ importers:
8
8
 
9
9
  .:
10
10
  dependencies:
11
- '@loltech/vue3-2fa-code-input':
12
- specifier: ^1.1.0
13
- version: 1.1.0
14
11
  '@vueuse/core':
15
12
  specifier: ^14.3.0
16
13
  version: 14.4.0(vue@3.5.29)
@@ -23,9 +20,6 @@ importers:
23
20
  qrcode:
24
21
  specifier: ^1.5.4
25
22
  version: 1.5.4
26
- vue3-otp-input:
27
- specifier: ^0.5.21
28
- version: 0.5.40(vue@3.5.29)
29
23
 
30
24
  packages:
31
25
 
@@ -49,9 +43,6 @@ packages:
49
43
  '@jridgewell/sourcemap-codec@1.5.5':
50
44
  resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
51
45
 
52
- '@loltech/vue3-2fa-code-input@1.1.0':
53
- resolution: {integrity: sha512-styyoQjsARSeVf6E5qMvYIVkLRrwYtfL8xDl5W+g9wKF8qlr2XGU2gi9maEg0X4Us41kJryXBzfRMUcv5tKIBA==}
54
-
55
46
  '@types/web-bluetooth@0.0.21':
56
47
  resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
57
48
 
@@ -262,11 +253,6 @@ packages:
262
253
  resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
263
254
  engines: {node: '>=8'}
264
255
 
265
- vue3-otp-input@0.5.40:
266
- resolution: {integrity: sha512-3AMYHqNz9ZDa9y7ICwcEcsJG7XdZGaLAr6IRLIl3whvseFE95F5Duc9q963HcqEbu8CeMWilkmbAt/0eZOZxow==}
267
- peerDependencies:
268
- vue: ^3.0.*
269
-
270
256
  vue@3.5.29:
271
257
  resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
272
258
  peerDependencies:
@@ -310,8 +296,6 @@ snapshots:
310
296
 
311
297
  '@jridgewell/sourcemap-codec@1.5.5': {}
312
298
 
313
- '@loltech/vue3-2fa-code-input@1.1.0': {}
314
-
315
299
  '@types/web-bluetooth@0.0.21': {}
316
300
 
317
301
  '@vue/compiler-core@3.5.29':
@@ -488,10 +472,6 @@ snapshots:
488
472
  dependencies:
489
473
  ansi-regex: 5.0.1
490
474
 
491
- vue3-otp-input@0.5.40(vue@3.5.29):
492
- dependencies:
493
- vue: 3.5.29
494
-
495
475
  vue@3.5.29:
496
476
  dependencies:
497
477
  '@vue/compiler-dom': 3.5.29
@@ -1,11 +1,12 @@
1
1
  <template>
2
2
 
3
+ <!-- right after the OAuth buttons it joins them with their 8px gap instead of the 16px between login page injections -->
3
4
  <Button
4
5
  variant="secondary"
5
- class="af-login-with-passkey-btn w-full gap-2"
6
+ class="af-login-with-passkey-btn w-full gap-2 [.af-oauth-login+&]:!mt-2"
6
7
  @click="handleLoginWithPasskey"
7
8
  >
8
- <IconFingerprintOutline class="w-5 h-5 text-lightPrimary dark:text-darkPrimary" />
9
+ <IconFingerprint class="w-4 h-4 text-lightPrimary dark:text-darkPrimary" />
9
10
  {{ $t('Continue with Passkey') }}
10
11
  </Button>
11
12
 
@@ -14,7 +15,7 @@
14
15
 
15
16
 
16
17
  <script setup lang="ts">
17
- import { IconFingerprintOutline } from '@iconify-prerendered/vue-flowbite';
18
+ import { IconFingerprint } from '@iconify-prerendered/vue-lucide';
18
19
  import { getPasskey } from "./utils.js";
19
20
  import { callAdminForthApi } from '@/utils';
20
21
  import { useUserStore } from '@/stores/user';
@@ -1,7 +1,9 @@
1
1
  <template>
2
2
  <div class="af-two-factors-modal fixed inset-0 z-[9999] flex items-center justify-center bg-lightScrim dark:bg-darkScrim top-0 bottom-0 left-0 right-0 px-4"
3
3
  v-if="modelShow && (isLoading === false)">
4
+ <!-- the card floats over the page here, so it takes the shadow of floating layers -->
4
5
  <TwoFaCard
6
+ class="shadow-popover dark:shadow-darkPopover"
5
7
  :class="`af-two-factor-modal-${modalMode}`"
6
8
  :title="dialogTitle"
7
9
  :subtitle="subtitles[modalMode]"
@@ -9,45 +11,45 @@
9
11
  closable
10
12
  @close="onCancel"
11
13
  >
12
- <pre v-if="dialogDetails" class="af-2fa-details max-h-60 overflow-auto rounded-lg border border-lightDialogBreakLine bg-gray-50 p-3 text-xs text-gray-800 dark:border-darkDialogBreakLine dark:bg-gray-800 dark:text-gray-100">{{ dialogDetails }}</pre>
14
+ <pre v-if="dialogDetails" class="af-2fa-details max-h-60 overflow-auto rounded-lg border border-lightDialogBreakLine bg-lightSecondaryDarken p-3 text-xs text-lightInputText dark:border-darkDialogBreakLine dark:bg-darkSecondaryLighten dark:text-darkInputText">{{ dialogDetails }}</pre>
13
15
 
14
16
  <div v-if="confirmItems.length" class="af-2fa-actions">
15
- <p class="mb-2 text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ $t('You are approving') }}</p>
16
- <ul class="af-2fa-actions-list max-h-60 overflow-auto divide-y divide-lightDialogBreakLine rounded-lg border border-lightDialogBreakLine bg-gray-50 dark:divide-darkDialogBreakLine dark:border-darkDialogBreakLine dark:bg-gray-800">
17
+ <p class="mb-2 text-xs font-medium uppercase tracking-wider text-lightInputIconColor dark:text-darkInputIconColor">{{ $t('You are approving') }}</p>
18
+ <ul class="af-2fa-actions-list max-h-60 overflow-auto divide-y divide-lightDialogBreakLine rounded-lg border border-lightDialogBreakLine bg-lightSecondaryDarken dark:divide-darkDialogBreakLine dark:border-darkDialogBreakLine dark:bg-darkSecondaryLighten">
17
19
  <li v-for="(item, i) in confirmItems" :key="i" class="flex gap-3 px-3.5 py-3">
18
20
  <span aria-hidden="true" class="mt-2 w-1.5 h-1.5 shrink-0 rounded-full bg-lightPrimary dark:bg-darkPrimary"></span>
19
- <span class="min-w-0 text-base leading-6 break-words text-gray-800 dark:text-gray-100">{{ item }}</span>
21
+ <span class="min-w-0 text-base leading-6 break-words text-lightInputText dark:text-darkInputText">{{ item }}</span>
20
22
  </li>
21
23
  </ul>
22
24
  </div>
23
25
 
24
26
  <TwoFaCodeInput v-if="modalMode === 'totp'" ref="codeInput" :invalid="Boolean(codeError)" @complete="sendConfirmationResult" />
25
27
  <TwoFaHint v-else-if="modalMode === 'passkey'" class="af-2fa-passkey-hint">
26
- <template #icon><IconFingerprintOutline class="w-6 h-6" /></template>
28
+ <template #icon><IconFingerprint class="w-5 h-5" /></template>
27
29
  {{ $t('Your browser will ask for Touch ID, Windows Hello or a security key.') }}
28
30
  </TwoFaHint>
29
- <p v-else role="status" class="af-2fa-grace-notice flex items-start gap-2.5 rounded-lg border border-green-200 bg-green-50 px-3.5 py-3 text-sm leading-5 text-green-800 dark:border-green-500/30 dark:bg-green-500/10 dark:text-green-300">
30
- <IconCheckCircleOutline class="w-5 h-5 shrink-0" />
31
+ <p v-else role="status" class="af-2fa-grace-notice flex items-start gap-2.5 rounded-lg border border-lightSuccessBorder bg-lightSuccessSoft px-3.5 py-3 text-sm leading-5 text-lightSuccess dark:border-darkSuccessBorder dark:bg-darkSuccessSoft dark:text-darkSuccess">
32
+ <IconCircleCheck class="w-5 h-5 shrink-0" />
31
33
  {{ $t('You verified recently, so no code is needed.') }}
32
34
  </p>
33
35
 
34
36
  <template #footer>
35
37
  <TwoFaLinkButton v-if="modalMode === 'totp' && doesUserHavePasskeys" class="af-2fa-switch-to-passkey" @click="modalMode = 'passkey'">
36
- <IconFingerprintOutline class="w-4 h-4" />
38
+ <IconFingerprint class="w-4 h-4" />
37
39
  {{ $t('Use passkey instead') }}
38
40
  </TwoFaLinkButton>
39
41
  <TwoFaLinkButton v-else-if="modalMode === 'passkey'" class="af-2fa-switch-to-totp" @click="modalMode = 'totp'">
40
- <IconMobilePhoneOutline class="w-4 h-4" />
42
+ <IconSmartphone class="w-4 h-4" />
41
43
  {{ $t('Use code instead') }}
42
44
  </TwoFaLinkButton>
43
45
  <div class="af-2fa-buttons ms-auto flex gap-2 whitespace-nowrap">
44
46
  <Button variant="secondary" class="af-2fa-cancel-btn" @click="onCancel">{{ $t('Cancel') }}</Button>
45
47
  <Button v-if="modalMode === 'passkey'" @click="usePasskeyButtonClick" :disabled="isFetchingPasskey" :loader="isFetchingPasskey" class="af-2fa-passkey-btn gap-2">
46
- <IconFingerprintOutline class="w-4 h-4" />
48
+ <IconFingerprint class="w-4 h-4" />
47
49
  {{ $t('Use passkey') }}
48
50
  </Button>
49
51
  <Button v-else-if="modalMode === 'grace'" class="af-2fa-approve-btn gap-2" @click="approveInGracePeriod">
50
- <IconCheckOutline class="w-4 h-4" />
52
+ <IconCheck class="w-4 h-4" />
51
53
  {{ approveLabel }}
52
54
  </Button>
53
55
  </div>
@@ -63,12 +65,7 @@
63
65
  import { useI18n } from 'vue-i18n';
64
66
  import { callAdminForthApi } from '@/utils';
65
67
  import { Link, Button } from '@/afcl';
66
- import {
67
- IconCheckCircleOutline,
68
- IconCheckOutline,
69
- IconFingerprintOutline,
70
- IconMobilePhoneOutline,
71
- } from '@iconify-prerendered/vue-flowbite';
68
+ import { IconCheck, IconCircleCheck, IconFingerprint, IconSmartphone } from '@iconify-prerendered/vue-lucide';
72
69
  import { getPasskey } from './utils.js'
73
70
  import TwoFaCard from './TwoFaCard.vue';
74
71
  import TwoFaCodeInput from './TwoFaCodeInput.vue';
@@ -1,34 +1,36 @@
1
1
  <template>
2
- <div class="af-2fa-card relative flex flex-col w-full max-w-md overflow-hidden rounded-xl border border-lightDialogBreakLine bg-lightDialogBackgorund shadow-2xl dark:border-darkDialogBreakLine dark:bg-darkDialogBackgorund">
3
- <div class="af-2fa-header flex items-start gap-3.5 border-b border-lightDialogBreakLine py-5 pl-6 dark:border-darkDialogBreakLine" :class="closable ? 'pr-4' : 'pr-6'">
4
- <div class="af-2fa-icon-wrap flex w-10 h-10 shrink-0 items-center justify-center rounded-lg bg-lightPrimary/10 text-lightPrimary dark:bg-darkPrimary/20 dark:text-darkPrimary">
5
- <IconShieldCheckOutline class="af-2fa-shield-icon w-6 h-6" />
6
- </div>
7
- <div class="af-2fa-title-wrap flex-1 min-w-0">
8
- <p class="af-2fa-title break-words text-lg leading-6 font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText">{{ title }}</p>
9
- <p class="af-2fa-subtitle mt-0.5 text-sm leading-5 text-gray-500 dark:text-gray-400">{{ subtitle }}</p>
10
- </div>
11
- <button v-if="closable" type="button" :aria-label="$t('Cancel')" class="af-2fa-close-btn -mt-1 inline-flex w-9 h-9 shrink-0 items-center justify-center rounded-lg text-lightDialogCloseButton hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover" @click="emit('close')">
12
- <IconCloseOutline class="w-5 h-5" />
13
- </button>
14
- </div>
15
-
16
- <div class="af-2fa-body flex flex-col gap-5 px-6 pt-5 pb-6">
17
- <slot />
18
- <p v-if="error" role="alert" class="af-2fa-code-error -mt-2.5 flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400">
19
- <IconExclamationCircleOutline class="w-4 h-4 shrink-0" />
20
- {{ error }}
21
- </p>
22
- </div>
23
-
24
- <div v-if="$slots.footer" class="af-2fa-footer flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-lightDialogBreakLine bg-gray-50 py-3 pl-6 pr-5 dark:border-darkDialogBreakLine dark:bg-gray-800/50">
2
+ <DialogPanel
3
+ class="af-2fa-card"
4
+ :title="title"
5
+ :subtitle="subtitle"
6
+ :error="error"
7
+ :closable="closable"
8
+ :classes="{
9
+ header: 'af-2fa-header',
10
+ icon: 'af-2fa-icon-wrap',
11
+ titleWrap: 'af-2fa-title-wrap',
12
+ title: 'af-2fa-title',
13
+ subtitle: 'af-2fa-subtitle',
14
+ close: 'af-2fa-close-btn',
15
+ body: 'af-2fa-body',
16
+ error: 'af-2fa-code-error',
17
+ footer: 'af-2fa-footer',
18
+ }"
19
+ @close="emit('close')"
20
+ >
21
+ <template #icon>
22
+ <IconShieldCheck class="af-2fa-shield-icon" />
23
+ </template>
24
+ <slot />
25
+ <template v-if="$slots.footer" #footer>
25
26
  <slot name="footer" />
26
- </div>
27
- </div>
27
+ </template>
28
+ </DialogPanel>
28
29
  </template>
29
30
 
30
31
  <script setup lang="ts">
31
- import { IconCloseOutline, IconExclamationCircleOutline, IconShieldCheckOutline } from '@iconify-prerendered/vue-flowbite';
32
+ import { IconShieldCheck } from '@iconify-prerendered/vue-lucide';
33
+ import { DialogPanel } from '@/afcl';
32
34
 
33
35
  defineProps<{
34
36
  title: string;