Bläddra i källkod

feat(financial): add password confirmation dialog for financial access

alvesantos 4 dagar sedan
förälder
incheckning
8ac50ff7f0

+ 5 - 0
src/api/auth.js

@@ -10,6 +10,11 @@ export const verifyPasswordCode = async (email, code) => {
   return data;
 };
 
+export const confirmPassword = async (password) => {
+  const { data } = await api.post("/confirm-password", { password });
+  return data;
+};
+
 export const resetPassword = async (email, code, password, passwordConfirmation) => {
   const { data } = await api.post("/reset-password", {
     email,

+ 97 - 0
src/components/financial/FinancialAuthDialog.vue

@@ -0,0 +1,97 @@
+<template>
+  <q-dialog ref="dialogRef" persistent @hide="onDialogHide">
+    <q-card
+      class="q-dialog-plugin overflow-hidden"
+      style="min-width: 400px"
+    >
+      <DefaultDialogHeader title="Acesso ao Financeiro" @close="onCancel" />
+
+      <DefaultForm @submit.prevent="onConfirm">
+        <q-card-section class="row q-col-gutter-md q-pt-none">
+          <div class="col-12 text-caption text-grey-6">
+            Confirme sua senha para acessar o módulo Financeiro.
+          </div>
+
+          <DefaultPasswordInput
+            ref="passwordInput"
+            v-model="password"
+            autofocus
+            class="col-12"
+            dense
+            label="Senha"
+            outlined
+            :error="hasError"
+            :error-message="errorMessage"
+            @update:model-value="hasError = false"
+          />
+        </q-card-section>
+
+        <q-separator />
+
+        <q-card-actions align="right" class="q-pa-md q-gutter-sm">
+          <q-btn
+            color="negative"
+            label="Cancelar"
+            no-caps
+            outline
+            :disable="loading"
+            @click="onCancel"
+          />
+
+          <q-btn
+            color="primary"
+            label="Confirmar"
+            no-caps
+            type="submit"
+            unelevated
+            :disable="!password"
+            :loading="loading"
+          />
+        </q-card-actions>
+      </DefaultForm>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { confirmPassword } from "src/api/auth";
+import { ref } from "vue";
+import { useDialogPluginComponent } from "quasar";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultPasswordInput from "src/components/defaults/DefaultPasswordInput.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
+  useDialogPluginComponent();
+
+const errorMessage = ref("");
+const hasError = ref(false);
+const loading = ref(false);
+const password = ref("");
+
+const onCancel = () => {
+  onDialogCancel();
+};
+
+const onConfirm = async () => {
+  if (!password.value || loading.value) return;
+
+  loading.value = true;
+  hasError.value = false;
+
+  try {
+    await confirmPassword(password.value);
+
+    onDialogOK();
+  } catch (error) {
+    hasError.value = true;
+
+    errorMessage.value =
+      error?.response?.data?.message || "A senha fornecida está incorreta.";
+  } finally {
+    loading.value = false;
+  }
+};
+</script>

+ 16 - 1
src/router/index.js

@@ -6,11 +6,12 @@ import {
   createWebHashHistory,
 } from "vue-router";
 import routes from "./routes";
-import { Notify } from "quasar";
+import { Notify, Dialog } from "quasar";
 import { permissionStore } from "src/stores/permission";
 import { i18n } from "src/boot/i18n";
 import { userStore } from "src/stores/user";
 import { useAuth } from "src/composables/useAuth";
+import FinancialAuthDialog from "src/components/financial/FinancialAuthDialog.vue";
 /*
  * If not building with SSR mode, you can
  * directly export the Router instantiation;
@@ -64,6 +65,20 @@ export default defineRouter(function (/* { store, ssrContext } */) {
         return next(from);
       }
     }
+    // Reautenticação do Financeiro exige confirmação de senha ao entrar
+    // em qualquer rota /financial/*. Liberado por sessão; limpo no logout.
+    if (to.meta.requireFinancialAuth && !userStore().financialUnlocked) {
+      const unlocked = await new Promise((resolve) => {
+        Dialog.create({ component: FinancialAuthDialog })
+          .onOk(() => resolve(true))
+          .onCancel(() => resolve(false))
+          .onDismiss(() => resolve(false));
+      });
+      if (!unlocked) {
+        return next(from.name ? false : { name: "DashboardPage" });
+      }
+      userStore().financialUnlocked = true;
+    }
     return next();
   });
 

+ 7 - 0
src/router/routes/financial.route.js

@@ -7,6 +7,7 @@ export default [
       title: { value: "Financeiro", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -21,6 +22,7 @@ export default [
       title: { value: "Contas a Pagar", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -36,6 +38,7 @@ export default [
       title: { value: "Contas a Receber", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -51,6 +54,7 @@ export default [
       title: { value: "Plano de Contas", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -66,6 +70,7 @@ export default [
       title: { value: "Tesouraria", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -81,6 +86,7 @@ export default [
       title: { value: "Emissão de Notas", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -96,6 +102,7 @@ export default [
       title: { value: "Integração Asaas", translate: false },
       requireAuth: true,
       requiredPermission: "franchisee_financial",
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },

+ 4 - 0
src/stores/user.js

@@ -9,6 +9,8 @@ export const userStore = defineStore("user", () => {
   const accessToken = ref(null);
   const isAdmin = ref(false);
   const selectedUnit = ref(null);
+  // Reautenticação do Financeiro: liberado por sessão, limpo no logout (#CB017).
+  const financialUnlocked = ref(false);
 
   /** Lista de unidades do usuário atual */
   const userUnits = computed(() => user.value?.units ?? []);
@@ -44,6 +46,7 @@ export const userStore = defineStore("user", () => {
     isAdmin.value = false;
     accessToken.value = false;
     selectedUnit.value = null;
+    financialUnlocked.value = false;
   };
 
   const fetchUser = async () => {
@@ -57,6 +60,7 @@ export const userStore = defineStore("user", () => {
     accessToken,
     selectedUnit,
     userUnits,
+    financialUnlocked,
     setUser,
     setSelectedUnit,
     resetUser,