Substitui o portal Vite+Vue puro por Nuxt 3 com BFF embutido (Nitro server
routes) e fluxo de autenticação Keycloak via token-handler pattern.
Server (BFF):
- server/api/auth/{login,callback,refresh,logout,me}.ts — Keycloak PKCE
- server/api/proxy/[...path].ts — proxy autenticado pro core-api com tenant
- server/utils/{session,keycloak,pkce,redis,tenant,prefeitura}.ts
- server/middleware/csrf.ts — Origin check + header X-Requested-With
Auth (token-handler pattern):
- JWT vive só server-side em Redis; cliente recebe cookie session-id opaco
- Refresh transparente quando access_token expira
- Multi-tenant via hostname → X-Municipio/X-Dominio injetados no proxy
- Realm dedicado: modumfiscal-portal-{env}
Frontend (Nuxt):
- src/pages/** (file-based routing) substitui src/views/
- Plugins SSR: prefeitura (bootstrap pré-hidratação) + auth (hidrata user via /api/auth/me)
- Composables useAuth, useApi, useLoginModal, useFocusLoginInput
- Modal global de login quando middleware /portal/** bloqueia
- Splash overlay no boot esconde flash do preset inicial pro tema dinâmico
- DocumentoInput bloqueia campo quando user autenticado (pré-preenche em certidão/IPTU)
Removidos:
- index.html, vite.config.js, src/main.js, src/router/
- src/config/apiClient.js (substituído por \$fetch via /api/proxy)
- src/services/{auth,prefeitura}Service.js (lógica migrada pra composables/plugins)
- src/mocks/ (não mais usado)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
import { computed } from 'vue'
|
|
import { useAuthStore } from '@/stores/authStore'
|
|
|
|
interface MeResponse {
|
|
name: string
|
|
documento: string
|
|
email: string
|
|
roles: string[]
|
|
}
|
|
|
|
const FETCH_HEADERS = { 'X-Requested-With': 'fetch' }
|
|
|
|
export function useAuth() {
|
|
const store = useAuthStore()
|
|
const router = useRouter()
|
|
|
|
async function login(documento?: string, returnTo?: string) {
|
|
const res = await $fetch<{ authUrl: string }>('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: FETCH_HEADERS,
|
|
body: { documento, returnTo },
|
|
})
|
|
if (import.meta.client) {
|
|
window.location.href = res.authUrl
|
|
}
|
|
}
|
|
|
|
async function fetchMe(): Promise<MeResponse | null> {
|
|
try {
|
|
const me = await $fetch<MeResponse>('/api/auth/me')
|
|
store.setUser(me)
|
|
return me
|
|
} catch {
|
|
store.clearUser()
|
|
return null
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
try {
|
|
const res = await $fetch<{ logoutUrl: string }>('/api/auth/logout', {
|
|
method: 'POST',
|
|
headers: FETCH_HEADERS,
|
|
})
|
|
store.clearUser()
|
|
if (import.meta.client) {
|
|
window.location.href = res.logoutUrl
|
|
}
|
|
} catch {
|
|
store.clearUser()
|
|
await router.push('/')
|
|
}
|
|
}
|
|
|
|
return {
|
|
user: computed(() => store.user),
|
|
isAuthenticated: computed(() => store.isAuthenticated),
|
|
nomeUsuario: computed(() => store.nomeUsuario),
|
|
documento: computed(() => store.documento),
|
|
roles: computed(() => store.roles),
|
|
login,
|
|
logout,
|
|
fetchMe,
|
|
}
|
|
}
|