26 changed files with 518 additions and 78 deletions
			
			
		| @ -0,0 +1,10 @@ | |||||
|  | -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | ||||
|  | BEGIN TRANSACTION; | ||||
|  | 
 | ||||
|  | ALTER TABLE user | ||||
|  |     ADD twofa_secret VARCHAR(64); | ||||
|  | 
 | ||||
|  | ALTER TABLE user | ||||
|  |     ADD twofa_status BOOLEAN default 0 NOT NULL; | ||||
|  | 
 | ||||
|  | COMMIT; | ||||
| @ -0,0 +1,178 @@ | |||||
|  | <template> | ||||
|  |     <form @submit.prevent="submit"> | ||||
|  |         <div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static"> | ||||
|  |             <div class="modal-dialog"> | ||||
|  |                 <div class="modal-content"> | ||||
|  |                     <div class="modal-header"> | ||||
|  |                         <h5 class="modal-title"> | ||||
|  |                             {{ $t("Setup 2FA") }} | ||||
|  |                             <span v-if="twoFAStatus == true" class="badge bg-primary">{{ $t("Active") }}</span> | ||||
|  |                             <span v-if="twoFAStatus == false" class="badge bg-primary">{{ $t("Inactive") }}</span> | ||||
|  |                         </h5> | ||||
|  |                         <button :disabled="processing" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> | ||||
|  |                     </div> | ||||
|  |                     <div class="modal-body"> | ||||
|  |                         <div class="mb-3"> | ||||
|  |                             <div v-if="uri && twoFAStatus == false" class="mx-auto text-center" style="width: 210px;"> | ||||
|  |                                 <vue-qrcode :key="uri" :value="uri" type="image/png" :quality="1" :color="{ light: '#ffffffff' }" /> | ||||
|  |                                 <button v-show="!showURI" type="button" class="btn btn-outline-primary btn-sm mt-2" @click="showURI = true">{{ $t("Show URI") }}</button> | ||||
|  |                             </div> | ||||
|  |                             <p v-if="showURI && twoFAStatus == false" class="text-break mt-2">{{ uri }}</p> | ||||
|  | 
 | ||||
|  |                             <button v-if="uri == null && twoFAStatus == false" class="btn btn-primary" type="button" @click="prepare2FA()"> | ||||
|  |                                 {{ $t("Enable 2FA") }} | ||||
|  |                             </button> | ||||
|  | 
 | ||||
|  |                             <button v-if="twoFAStatus == true" class="btn btn-danger" type="button" :disabled="processing" @click="confirmDisableTwoFA()"> | ||||
|  |                                 {{ $t("Disable 2FA") }} | ||||
|  |                             </button> | ||||
|  | 
 | ||||
|  |                             <div v-if="uri && twoFAStatus == false" class="mt-3"> | ||||
|  |                                 <label for="basic-url" class="form-label">{{ $t("twoFAVerifyLabel") }}</label> | ||||
|  |                                 <div class="input-group"> | ||||
|  |                                     <input v-model="token" type="text" maxlength="6" class="form-control"> | ||||
|  |                                     <button class="btn btn-outline-primary" type="button" @click="verifyToken()">{{ $t("Verify Token") }}</button> | ||||
|  |                                 </div> | ||||
|  |                                 <p v-show="tokenValid" class="mt-2" style="color: green">{{ $t("tokenValidSettingsMsg") }}</p> | ||||
|  |                             </div> | ||||
|  |                         </div> | ||||
|  |                     </div> | ||||
|  | 
 | ||||
|  |                     <div v-if="uri && twoFAStatus == false" class="modal-footer"> | ||||
|  |                         <button type="submit" class="btn btn-primary" :disabled="processing || tokenValid == false" @click="confirmEnableTwoFA()"> | ||||
|  |                             <div v-if="processing" class="spinner-border spinner-border-sm me-1"></div> | ||||
|  |                             {{ $t("Save") }} | ||||
|  |                         </button> | ||||
|  |                     </div> | ||||
|  |                 </div> | ||||
|  |             </div> | ||||
|  |         </div> | ||||
|  |     </form> | ||||
|  | 
 | ||||
|  |     <Confirm ref="confirmEnableTwoFA" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="save2FA"> | ||||
|  |         {{ $t("confirmEnableTwoFAMsg") }} | ||||
|  |     </Confirm> | ||||
|  | 
 | ||||
|  |     <Confirm ref="confirmDisableTwoFA" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="disable2FA"> | ||||
|  |         {{ $t("confirmDisableTwoFAMsg") }} | ||||
|  |     </Confirm> | ||||
|  | </template> | ||||
|  | 
 | ||||
|  | <script lang="ts"> | ||||
|  | import { Modal } from "bootstrap" | ||||
|  | import Confirm from "./Confirm.vue"; | ||||
|  | import VueQrcode from "vue-qrcode" | ||||
|  | import { useToast } from "vue-toastification" | ||||
|  | const toast = useToast() | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     components: { | ||||
|  |         Confirm, | ||||
|  |         VueQrcode, | ||||
|  |     }, | ||||
|  |     props: {}, | ||||
|  |     data() { | ||||
|  |         return { | ||||
|  |             processing: false, | ||||
|  |             uri: null, | ||||
|  |             tokenValid: false, | ||||
|  |             twoFAStatus: null, | ||||
|  |             token: null, | ||||
|  |             showURI: false, | ||||
|  |         } | ||||
|  |     }, | ||||
|  |     mounted() { | ||||
|  |         this.modal = new Modal(this.$refs.modal) | ||||
|  |         this.getStatus(); | ||||
|  |     }, | ||||
|  |     methods: { | ||||
|  |         show() { | ||||
|  |             this.modal.show() | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         confirmEnableTwoFA() { | ||||
|  |             this.$refs.confirmEnableTwoFA.show() | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         confirmDisableTwoFA() { | ||||
|  |             this.$refs.confirmDisableTwoFA.show() | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         prepare2FA() { | ||||
|  |             this.processing = true; | ||||
|  | 
 | ||||
|  |             this.$root.getSocket().emit("prepare2FA", (res) => { | ||||
|  |                 this.processing = false; | ||||
|  | 
 | ||||
|  |                 if (res.ok) { | ||||
|  |                     this.uri = res.uri; | ||||
|  |                 } else { | ||||
|  |                     toast.error(res.msg); | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         save2FA() { | ||||
|  |             this.processing = true; | ||||
|  | 
 | ||||
|  |             this.$root.getSocket().emit("save2FA", (res) => { | ||||
|  |                 this.processing = false; | ||||
|  | 
 | ||||
|  |                 if (res.ok) { | ||||
|  |                     this.$root.toastRes(res) | ||||
|  |                     this.getStatus(); | ||||
|  |                     this.modal.hide(); | ||||
|  |                 } else { | ||||
|  |                     toast.error(res.msg); | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         disable2FA() { | ||||
|  |             this.processing = true; | ||||
|  | 
 | ||||
|  |             this.$root.getSocket().emit("disable2FA", (res) => { | ||||
|  |                 this.processing = false; | ||||
|  | 
 | ||||
|  |                 if (res.ok) { | ||||
|  |                     this.$root.toastRes(res) | ||||
|  |                     this.getStatus(); | ||||
|  |                     this.modal.hide(); | ||||
|  |                 } else { | ||||
|  |                     toast.error(res.msg); | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         verifyToken() { | ||||
|  |             this.$root.getSocket().emit("verifyToken", this.token, (res) => { | ||||
|  |                 if (res.ok) { | ||||
|  |                     this.tokenValid = res.valid; | ||||
|  |                 } else { | ||||
|  |                     toast.error(res.msg); | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         }, | ||||
|  | 
 | ||||
|  |         getStatus() { | ||||
|  |             this.$root.getSocket().emit("twoFAStatus", (res) => { | ||||
|  |                 if (res.ok) { | ||||
|  |                     this.twoFAStatus = res.status; | ||||
|  |                 } else { | ||||
|  |                     toast.error(res.msg); | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         }, | ||||
|  |     }, | ||||
|  | } | ||||
|  | </script> | ||||
|  | 
 | ||||
|  | <style lang="scss" scoped> | ||||
|  | @import "../assets/vars.scss"; | ||||
|  | 
 | ||||
|  | .dark { | ||||
|  |     .modal-dialog .form-text, .modal-dialog p { | ||||
|  |         color: $dark-font-color; | ||||
|  |     } | ||||
|  | } | ||||
|  | </style> | ||||
					Loading…
					
					
				
		Reference in new issue