two_factor_modal.html 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. {{define "modals/twoFactorModal"}}
  2. <a-modal id="two-factor-modal" v-model="twoFactorModal.visible" :title="twoFactorModal.title" :closable="true"
  3. :class="themeSwitcher.currentTheme">
  4. <template v-if="twoFactorModal.type === 'set'">
  5. <p>{{ i18n "pages.settings.security.twoFactorModalSteps" }}</p>
  6. <a-divider></a-divider>
  7. <p>{{ i18n "pages.settings.security.twoFactorModalFirstStep" }}</p>
  8. <div :style="{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: '12px' }">
  9. <div class="qr-bg" :style="{ width: '180px', height: '180px' }">
  10. <canvas @click="copy(twoFactorModal.token)" id="twofactor-qrcode" class="qr-cv"></canvas>
  11. </div>
  12. <span :style="{ fontSize: '12px', fontFamily: 'monospace' }">[[ twoFactorModal.token ]]</span>
  13. </div>
  14. <a-divider></a-divider>
  15. <p>{{ i18n "pages.settings.security.twoFactorModalSecondStep" }}</p>
  16. <a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input>
  17. </template>
  18. <template v-if="twoFactorModal.type === 'remove'">
  19. <p>{{ i18n "pages.settings.security.twoFactorModalRemoveStep" }}</p>
  20. <a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input>
  21. </template>
  22. <template slot="footer">
  23. <a-button @click="twoFactorModal.cancel">
  24. <span>{{ i18n "cancel" }}</span>
  25. </a-button>
  26. <a-button type="primary" :disabled="twoFactorModal.enteredCode.length < 6" @click="twoFactorModal.ok">
  27. <span>{{ i18n "confirm" }}</span>
  28. </a-button>
  29. </template>
  30. </a-modal>
  31. <script>
  32. const twoFactorModal = {
  33. title: '',
  34. fileName: '',
  35. token: '',
  36. enteredCode: '',
  37. visible: false,
  38. type: 'set',
  39. confirm: null,
  40. totpObject: null,
  41. qrImage: "",
  42. ok() {
  43. if (twoFactorModal.totpObject.generate() === twoFactorModal.enteredCode) {
  44. ObjectUtil.execute(twoFactorModal.confirm, true)
  45. twoFactorModal.close()
  46. switch (twoFactorModal.type) {
  47. case 'set':
  48. Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalSetSuccess" }}')
  49. break;
  50. case 'remove':
  51. Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalDeleteSuccess" }}')
  52. break;
  53. default:
  54. break;
  55. }
  56. } else {
  57. Vue.prototype.$message['error']('{{ i18n "pages.settings.security.twoFactorModalError" }}')
  58. }
  59. },
  60. cancel() {
  61. ObjectUtil.execute(twoFactorModal.confirm, false)
  62. twoFactorModal.close()
  63. },
  64. show: function ({
  65. title = '',
  66. token = '',
  67. type = 'set',
  68. confirm = (success) => { }
  69. }) {
  70. this.title = title;
  71. this.token = token;
  72. this.visible = true;
  73. this.confirm = confirm;
  74. this.type = type;
  75. this.totpObject = new OTPAuth.TOTP({
  76. issuer: "3x-ui",
  77. label: "Administrator",
  78. algorithm: "SHA1",
  79. digits: 6,
  80. period: 30,
  81. secret: twoFactorModal.token,
  82. });
  83. },
  84. close: function () {
  85. twoFactorModal.enteredCode = "";
  86. twoFactorModal.visible = false;
  87. },
  88. };
  89. const twoFactorModalApp = new Vue({
  90. delimiters: ['[[', ']]'],
  91. el: '#two-factor-modal',
  92. data: {
  93. twoFactorModal: twoFactorModal,
  94. },
  95. updated() {
  96. if (
  97. this.twoFactorModal.visible &&
  98. this.twoFactorModal.type === 'set' &&
  99. document.getElementById('twofactor-qrcode')
  100. ) {
  101. this.setQrCode('twofactor-qrcode', this.twoFactorModal.totpObject.toString());
  102. }
  103. },
  104. methods: {
  105. setQrCode(elementId, content) {
  106. new QRious({
  107. element: document.getElementById(elementId),
  108. size: 200,
  109. value: content,
  110. background: 'white',
  111. backgroundAlpha: 0,
  112. foreground: 'black',
  113. padding: 2,
  114. level: 'L'
  115. });
  116. },
  117. copy(content) {
  118. ClipboardManager
  119. .copyText(content)
  120. .then(() => {
  121. app.$message.success('{{ i18n "copied" }}')
  122. })
  123. },
  124. }
  125. });
  126. </script>
  127. {{end}}