Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShishkevich D. <135337715+shishkevichd@users.noreply.github.com>2025-05-08 17:20:58 +0300
committerGitHub <noreply@github.com>2025-05-08 17:20:58 +0300
commitfe3b1c9b52f584b0f045907585b206344fed55db (patch)
treeaeef0fa82a0355ee899ec3aecee23045a327a6dc /web/html/modals
parentd39ccf4b8f77f99d4468580085e9d89e8b5f0b1c (diff)
chore: implement 2fa auth (#2968)
* chore: implement 2fa auth from #2786 * chore: format code * chore: replace two factor token input with qr-code * chore: requesting confirmation of setting/removing two-factor authentication otpauth library was taken from cdnjs * chore: revert changes in `ClipboardManager` don't need it. * chore: removing twoFactor prop in settings page * chore: remove `twoFactorQr` object in `mounted` function
Diffstat (limited to 'web/html/modals')
-rw-r--r--web/html/modals/two_factor_modal.html118
1 files changed, 118 insertions, 0 deletions
diff --git a/web/html/modals/two_factor_modal.html b/web/html/modals/two_factor_modal.html
new file mode 100644
index 00000000..d2f8c442
--- /dev/null
+++ b/web/html/modals/two_factor_modal.html
@@ -0,0 +1,118 @@
+{{define "modals/twoFactorModal"}}
+<a-modal id="two-factor-modal" v-model="twoFactorModal.visible" :title="twoFactorModal.title" :closable="true"
+ :class="themeSwitcher.currentTheme">
+ <template v-if="twoFactorModal.type === 'set'">
+ <p>{{ i18n "pages.settings.security.twoFactorModalSteps" }}</p>
+ <a-divider></a-divider>
+ <p>{{ i18n "pages.settings.security.twoFactorModalFirstStep" }}</p>
+ <div :style="{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: '12px' }">
+ <div
+ :style="{ border: '1px solid', borderRadius: '1rem', borderColor: themeSwitcher.isDarkTheme ? 'var(--dark-color-surface-300)' : '#d9d9d9', padding: 0 }">
+ <img :src="twoFactorModal.qrImage"
+ :style="{ filter: themeSwitcher.isDarkTheme ? 'invert(1)' : 'none'}"
+ :alt="twoFactorModal.token">
+ </div>
+ <span :style="{ fontSize: '12px', fontFamily: 'monospace' }">[[ twoFactorModal.token ]]</span>
+ </div>
+ <a-divider></a-divider>
+ <p>{{ i18n "pages.settings.security.twoFactorModalSecondStep" }}</p>
+ <a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input>
+ </template>
+ <template v-if="twoFactorModal.type === 'remove'">
+ <p>{{ i18n "pages.settings.security.twoFactorModalRemoveStep" }}</p>
+ <a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input>
+ </template>
+ <template slot="footer">
+ <a-button @click="twoFactorModal.cancel">
+ <span>{{ i18n "cancel" }}</span>
+ </a-button>
+ <a-button type="primary" :disabled="twoFactorModal.enteredCode.length < 6" @click="twoFactorModal.ok">
+ <span>{{ i18n "confirm" }}</span>
+ </a-button>
+ </template>
+</a-modal>
+
+<script>
+ const twoFactorModal = {
+ title: '',
+ fileName: '',
+ token: '',
+ enteredCode: '',
+ visible: false,
+ type: 'set',
+ confirm: null,
+ totpObject: null,
+ qrImage: "",
+ ok() {
+ if (twoFactorModal.totpObject.generate() === twoFactorModal.enteredCode) {
+ ObjectUtil.execute(twoFactorModal.confirm, true)
+
+ twoFactorModal.close()
+
+ switch (twoFactorModal.type) {
+ case 'set':
+ Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalSetSuccess" }}')
+ break;
+ case 'remove':
+ Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalDeleteSuccess" }}')
+ break;
+ default:
+ break;
+ }
+ } else {
+ Vue.prototype.$message['error']('{{ i18n "pages.settings.security.twoFactorModalError" }}')
+ }
+ },
+ cancel() {
+ ObjectUtil.execute(twoFactorModal.confirm, false)
+
+ twoFactorModal.close()
+ },
+ show: function ({
+ title = '',
+ token = '',
+ type = 'set',
+ confirm = (success) => { }
+ }) {
+ this.title = title;
+ this.token = token;
+ this.visible = true;
+ this.confirm = confirm;
+ this.type = type;
+
+ this.totpObject = new OTPAuth.TOTP({
+ issuer: "3x-ui",
+ label: "Administrator",
+ algorithm: "SHA1",
+ digits: 6,
+ period: 30,
+ secret: twoFactorModal.token,
+ });
+
+ if (type === 'set') {
+ this.qrImage = new QRious({
+ size: 150,
+ value: twoFactorModal.totpObject.toString(),
+ background: 'white',
+ backgroundAlpha: 0,
+ foreground: 'black',
+ padding: 12,
+ level: 'L'
+ }).toDataURL()
+ }
+ },
+ close: function () {
+ twoFactorModal.enteredCode = "";
+ twoFactorModal.visible = false;
+ },
+ };
+
+ const twoFactorModalApp = new Vue({
+ delimiters: ['[[', ']]'],
+ el: '#two-factor-modal',
+ data: {
+ twoFactorModal: twoFactorModal,
+ },
+ });
+</script>
+{{end}} \ No newline at end of file