chore: replace two factor token input with qr-code

This commit is contained in:
Shishkevich D. 2025-05-07 02:33:50 +00:00
parent ca0cf0b8ea
commit 9e94aba649
3 changed files with 48 additions and 5 deletions

View file

@ -512,7 +512,7 @@ class Wireguard {
} }
class ClipboardManager { class ClipboardManager {
static copyText(content = "") { static copyText(content = "", hasSuccessCopyMessage = false, successCopyMessage = '') {
// !! here old way of copying is used because not everyone can afford https connection // !! here old way of copying is used because not everyone can afford https connection
return new Promise((resolve) => { return new Promise((resolve) => {
try { try {
@ -535,6 +535,12 @@ class ClipboardManager {
window.document.body.removeChild(textarea); window.document.body.removeChild(textarea);
if (
hasSuccessCopyMessage
) {
Vue.prototype.$message['success'](successCopyMessage)
}
resolve(true) resolve(true)
} catch { } catch {
resolve(false) resolve(false)

View file

@ -122,6 +122,7 @@
</a-layout> </a-layout>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
{{template "component/aSidebar" .}} {{template "component/aSidebar" .}}
{{template "component/aThemeSwitch" .}} {{template "component/aThemeSwitch" .}}
@ -140,6 +141,7 @@
lang: LanguageManager.getLanguage(), lang: LanguageManager.getLanguage(),
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' }, remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'], remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
twoFactor: { qrElement: null },
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }], datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
remarkSample: '', remarkSample: '',
defaultFragment: { defaultFragment: {
@ -302,9 +304,22 @@
}, },
toggleTwoFactor(newValue) { toggleTwoFactor(newValue) {
if (newValue) { if (newValue) {
this.allSetting.twoFactorToken = RandomUtil.randomBase32String(); const newTwoFactorToken = RandomUtil.randomBase32String();
this.allSetting.twoFactorToken = newTwoFactorToken;
this.twoFactor.qrElement = new QRious({
size: 150,
value: `otpauth://totp/3x-ui:Administrator?secret=${newTwoFactorToken}&issuer=3x-ui`,
background: 'white',
backgroundAlpha: 0,
foreground: 'black',
padding: 12,
level: 'L'
})
} else { } else {
this.allSetting.twoFactorToken = ""; this.allSetting.twoFactorToken = "";
this.twoFactor.qrElement = null;
} }
}, },
addNoise() { addNoise() {
@ -497,6 +512,19 @@
}, },
async mounted() { async mounted() {
await this.getAllSetting(); await this.getAllSetting();
if (this.allSetting.twoFactorEnable) {
this.twoFactor.qrElement = new QRious({
size: 150,
value: `otpauth://totp/3x-ui:Administrator?secret=${this.allSetting.twoFactorToken}&issuer=3x-ui`,
background: 'white',
backgroundAlpha: 0,
foreground: 'black',
padding: 12,
level: 'L'
})
}
while (true) { while (true) {
await PromiseUtil.sleep(1000); await PromiseUtil.sleep(1000);
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting); this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);

View file

@ -43,9 +43,18 @@
<template #title>{{ i18n "pages.settings.security.twoFactorToken" }}</template> <template #title>{{ i18n "pages.settings.security.twoFactorToken" }}</template>
<template #description>{{ i18n "pages.settings.security.twoFactorTokenDesc" }}</template> <template #description>{{ i18n "pages.settings.security.twoFactorTokenDesc" }}</template>
<template #control> <template #control>
<a-input disabled="disabled" v-model="allSetting.twoFactorToken" :style="{ cursor: 'text' }"> <a-space direction="horizontal" size="middle">
<a-icon slot="addonAfter" type="copy" @click="ClipboardManager.copyText(allSetting.twoFactorToken)"/> <div
</a-input> :style="{ border: '1px solid', borderRadius: '1rem', borderColor: themeSwitcher.isDarkTheme ? 'var(--dark-color-surface-300)' : '#d9d9d9', padding: 0 }">
<img id="twoFactorQr" :src="twoFactor.qrElement.toDataURL()" :style="{ filter: themeSwitcher.isDarkTheme ? 'invert(1)' : 'none'}"></canvas>
</div>
<a-tooltip>
<template #title>
<span>{{ i18n "copy" }}</span>
</template>
<a-icon type="copy" @click='ClipboardManager.copyText(allSetting.twoFactorToken, true, `{{ i18n "copySuccess" }}`)'></a-icon>
</a-tooltip>
</a-space>
</template> </template>
</a-setting-list-item> </a-setting-list-item>
</a-collapse-panel> </a-collapse-panel>