Quellcode durchsuchen

feat: gate de reautenticação ao acessar o Financeiro (#CB017)

Guard requireFinancialAuth em todas as rotas /financial/* abre modal
de confirmação de senha (FinancialAuthDialog) antes de renderizar.
Flag financialUnlocked no store é por sessão e limpa no logout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee vor 2 Tagen
Ursprung
Commit
252a01c699

+ 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,

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

@@ -0,0 +1,78 @@
+<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" />
+
+      <q-card-section class="q-pt-none q-pb-sm">
+        <div class="text-caption text-grey-6 q-mb-sm">
+          Confirme sua senha para acessar o módulo Financeiro.
+        </div>
+        <q-form @submit.prevent="onConfirm">
+          <q-input
+            ref="passwordInput"
+            v-model="password"
+            type="password"
+            outlined
+            dense
+            autofocus
+            label="Senha"
+            :error="hasError"
+            :error-message="errorMessage"
+            @update:model-value="hasError = false"
+          >
+            <template #prepend>
+              <q-icon name="mdi-lock-outline" />
+            </template>
+          </q-input>
+        </q-form>
+      </q-card-section>
+
+      <q-card-actions align="right">
+        <q-btn outline color="primary" label="Cancelar" :disable="loading" @click="onCancel" />
+        <q-btn
+          color="primary"
+          label="Confirmar"
+          :loading="loading"
+          :disable="!password"
+          @click="onConfirm"
+        />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useDialogPluginComponent } from "quasar";
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import { confirmPassword } from "src/api/auth";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+
+const password = ref("");
+const loading = ref(false);
+const hasError = ref(false);
+const errorMessage = ref("");
+
+async function onConfirm() {
+  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;
+  }
+}
+
+function onCancel() {
+  onDialogCancel();
+}
+</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 (#CB017): 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();
   });
 

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

@@ -6,6 +6,7 @@ export default [
     meta: {
       title: { value: "Financeiro", translate: false },
       requireAuth: true,
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "FinancialPage", title: "Financeiro" },
@@ -19,6 +20,7 @@ export default [
     meta: {
       title: { value: "Tesouraria", translate: false },
       requireAuth: true,
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "TreasuryPage", title: "Tesouraria" },
@@ -32,6 +34,7 @@ export default [
     meta: {
       title: { value: "Contas a Pagar", translate: false },
       requireAuth: true,
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "AccountsPayablePage", title: "Contas a Pagar" },
@@ -45,6 +48,7 @@ export default [
     meta: {
       title: { value: "Contas a Receber", translate: false },
       requireAuth: true,
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "AccountsReceivablePage", title: "Contas a Receber" },
@@ -58,6 +62,7 @@ export default [
     meta: {
       title: { value: "Plano de Contas", translate: false },
       requireAuth: true,
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "ChartOfAccountsPage", title: "Plano de Contas" },
@@ -71,6 +76,7 @@ export default [
     meta: {
       title: { value: "Emissão de Notas", translate: false },
       requireAuth: true,
+      requireFinancialAuth: true,
       breadcrumbs: [
         { name: "DashboardPage", title: "Dashboard" },
         { name: "InvoiceIssuancePage", title: "Emissão de Notas" },

+ 4 - 0
src/stores/user.js

@@ -6,6 +6,8 @@ export const userStore = defineStore("user", () => {
   const user = ref(null);
   const accessToken = ref(null);
   const isAdmin = ref(false);
+  // Reautenticação do Financeiro: liberado por sessão, limpo no logout (#CB017).
+  const financialUnlocked = ref(false);
 
   const setUser = (userData) => {
     user.value = userData;
@@ -16,6 +18,7 @@ export const userStore = defineStore("user", () => {
     user.value = null;
     isAdmin.value = false;
     accessToken.value = false;
+    financialUnlocked.value = false;
   };
 
   const fetchUser = async () => {
@@ -30,5 +33,6 @@ export const userStore = defineStore("user", () => {
     resetUser,
     fetchUser,
     accessToken,
+    financialUnlocked,
   };
 });