mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-12 21:20:07 +00:00
chore: replace two factor token input with qr-code
This commit is contained in:
parent
ca0cf0b8ea
commit
9e94aba649
3 changed files with 48 additions and 5 deletions
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue