mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-29 19:32:51 +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
	
	 Shishkevich D.
						Shishkevich D.