Merge pull request #80 from rede5/codex/integrate-next.js-with-go-backend
Integrate Go auth service with HttpOnly cookie flow
This commit is contained in:
commit
a735cd70d1
7 changed files with 186 additions and 237 deletions
|
|
@ -6,6 +6,8 @@ NEXT_PUBLIC_APPWRITE_PROJECT_ID=seu_projeto_id
|
||||||
NEXT_PUBLIC_APPWRITE_DATABASE_ID=seu_banco_de_dados_id
|
NEXT_PUBLIC_APPWRITE_DATABASE_ID=seu_banco_de_dados_id
|
||||||
# URL Frontend
|
# URL Frontend
|
||||||
NEXT_PUBLIC_FRONTEND_URL=https://seu-frontend.com
|
NEXT_PUBLIC_FRONTEND_URL=https://seu-frontend.com
|
||||||
|
# API Go SaveInMed
|
||||||
|
NEXT_PUBLIC_API_URL=http://localhost:8214
|
||||||
# API BFF SaveInMed
|
# API BFF SaveInMed
|
||||||
NEXT_PUBLIC_BFF_API_URL=https://bff_url/api/v1
|
NEXT_PUBLIC_BFF_API_URL=https://bff_url/api/v1
|
||||||
NEXT_PUBLIC_BFF_API_URL_MP=https://bff_url
|
NEXT_PUBLIC_BFF_API_URL_MP=https://bff_url
|
||||||
|
|
@ -41,4 +43,3 @@ MERCADO_PAGO_ACCESS_TOKEN=APP_USR-seu_token_de_acesso
|
||||||
MERCADO_PAGO_PUBLIC_KEY=APP_USR-seu_public_key
|
MERCADO_PAGO_PUBLIC_KEY=APP_USR-seu_public_key
|
||||||
NEXT_PUBLIC_MERCADO_PAGO_PUBLIC_KEY=APP_USR-seu_public_key
|
NEXT_PUBLIC_MERCADO_PAGO_PUBLIC_KEY=APP_USR-seu_public_key
|
||||||
# Em produção, use tokens do tipo APP_USR-... e mantenha esses segredos apenas no servidor (não no client)
|
# Em produção, use tokens do tipo APP_USR-... e mantenha esses segredos apenas no servidor (não no client)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { toast } from "react-hot-toast";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import { useCarrinho } from "@/contexts/CarrinhoContext";
|
import { useCarrinho } from "@/contexts/CarrinhoContext";
|
||||||
import { pedidoApiService } from "@/services/pedidoApiService";
|
import { pedidoApiService } from "@/services/pedidoApiService";
|
||||||
|
import { authService, GO_API_V1_BASE_URL } from "@/services/auth";
|
||||||
import { useEmpresa } from "@/contexts/EmpresaContext";
|
import { useEmpresa } from "@/contexts/EmpresaContext";
|
||||||
import { CheckCircle, Truck, CreditCard, ChevronLeft, MapPin } from "lucide-react";
|
import { CheckCircle, Truck, CreditCard, ChevronLeft, MapPin } from "lucide-react";
|
||||||
|
|
||||||
|
|
@ -38,40 +39,23 @@ export default function CheckoutPage() {
|
||||||
// Fetch user profile and addresses
|
// Fetch user profile and addresses
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const token = pedidoApiService.getAuthToken();
|
const userData = await authService.me();
|
||||||
if (!token) return;
|
if (!userData) return;
|
||||||
|
setUserProfile(userData);
|
||||||
// Fetch User
|
|
||||||
const meRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8214'}/api/v1/auth/me`, {
|
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (meRes.ok) {
|
|
||||||
const userData = await meRes.json();
|
|
||||||
setUserProfile(userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch Addresses
|
// Fetch Addresses
|
||||||
const addrRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8214'}/api/v1/enderecos`, {
|
const addrRes = await fetch(`${GO_API_V1_BASE_URL}/enderecos`, {
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { accept: 'application/json' },
|
||||||
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (addrRes.ok) {
|
if (addrRes.ok) {
|
||||||
const addrData = await addrRes.json();
|
const addrData = await addrRes.json();
|
||||||
if (Array.isArray(addrData) && addrData.length > 0) {
|
if (Array.isArray(addrData) && addrData.length > 0) {
|
||||||
setAddresses(addrData);
|
setAddresses(addrData);
|
||||||
setSelectedAddressId(addrData[0].id);
|
setSelectedAddressId(addrData[0].id);
|
||||||
} else if (!addrData || addrData.length === 0) {
|
} else {
|
||||||
const mock = {
|
setAddresses([]);
|
||||||
id: 'mock-1',
|
setSelectedAddressId(null);
|
||||||
logradouro: empresa?.endereco || "Rua Principal",
|
|
||||||
numero: empresa?.numero || "100",
|
|
||||||
bairro: empresa?.bairro || "Centro",
|
|
||||||
cidade: empresa?.cidade || "São Paulo",
|
|
||||||
uf: empresa?.estado || "SP",
|
|
||||||
cep: empresa?.cep || "01000-000",
|
|
||||||
titulo: "Endereço Padrão (Mock)"
|
|
||||||
};
|
|
||||||
setAddresses([mock]);
|
|
||||||
setSelectedAddressId('mock-1');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useState, useEffect, Suspense } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEmpresa } from "@/contexts/EmpresaContext";
|
import { useEmpresa } from "@/contexts/EmpresaContext";
|
||||||
import { translateError } from "@/lib/error-translator";
|
import { translateError } from "@/lib/error-translator";
|
||||||
|
import { authService } from "@/services/auth";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,7 +24,6 @@ const LoginPageContent = () => {
|
||||||
const [isLogin, setIsLogin] = useState<boolean>(true); // Alterna entre login e registro
|
const [isLogin, setIsLogin] = useState<boolean>(true); // Alterna entre login e registro
|
||||||
const [checkingAuth, setCheckingAuth] = useState<boolean>(true); // Verificação inicial de autenticação
|
const [checkingAuth, setCheckingAuth] = useState<boolean>(true); // Verificação inicial de autenticação
|
||||||
const [showPassword, setShowPassword] = useState<boolean>(false); // Controla visibilidade da senha
|
const [showPassword, setShowPassword] = useState<boolean>(false); // Controla visibilidade da senha
|
||||||
const [accessToken, setAccessToken] = useState<string>(""); // Token de acesso do BFF
|
|
||||||
const [showSuccessModal, setShowSuccessModal] = useState<boolean>(false); // Modal de sucesso do cadastro
|
const [showSuccessModal, setShowSuccessModal] = useState<boolean>(false); // Modal de sucesso do cadastro
|
||||||
const [showPendingModal, setShowPendingModal] = useState<boolean>(false); // Modal de cadastro pendente
|
const [showPendingModal, setShowPendingModal] = useState<boolean>(false); // Modal de cadastro pendente
|
||||||
|
|
||||||
|
|
@ -44,38 +44,9 @@ const LoginPageContent = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
try {
|
try {
|
||||||
// Verificar se há token armazenado
|
const userData = await authService.me();
|
||||||
const storedToken = localStorage.getItem('access_token');
|
if (userData) {
|
||||||
const headers: HeadersInit = {
|
localStorage.setItem('user', JSON.stringify(userData));
|
||||||
accept: "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (storedToken) {
|
|
||||||
headers.Authorization = `Bearer ${storedToken}`; // Usar Authorization ao invés de Cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verificar autenticação usando BFF com o token no header Authorization
|
|
||||||
const response = await fetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_BFF_API_URL}/auth/me`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers,
|
|
||||||
credentials: "include",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const userData = await response.json();
|
|
||||||
// ... (log logic)
|
|
||||||
} else {
|
|
||||||
const errorText = await response.text();
|
|
||||||
console.log("❌ Falha no /me:", errorText);
|
|
||||||
|
|
||||||
// Only remove token if explicitly unauthorized (401)
|
|
||||||
if (response.status === 401 && storedToken) {
|
|
||||||
localStorage.removeItem('access_token');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -95,44 +66,25 @@ const LoginPageContent = () => {
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 1. Fazer login no BFF
|
// 1. Fazer login na API Go (cookie HttpOnly)
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_BFF_API_URL!;
|
const loginData = await authService.login(email, password);
|
||||||
const response = await fetch(`${baseUrl}/auth/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include', // Permite que o browser receba e armazene cookies
|
|
||||||
mode: 'cors', // Habilita CORS explicitamente
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: email,
|
|
||||||
password: password
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ler e logar o corpo da resposta (usando clone para não consumir o body original)
|
if (!loginData || (loginData as any).error) {
|
||||||
const respClone = response.clone();
|
|
||||||
const respText = await respClone.text();
|
|
||||||
let respBody: any = respText;
|
|
||||||
try {
|
|
||||||
respBody = JSON.parse(respText);
|
|
||||||
} catch {
|
|
||||||
// corpo não é JSON, manter como texto
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
// Extrair mensagem de erro do backend
|
// Extrair mensagem de erro do backend
|
||||||
let errorMessage = respBody?.message || respBody?.error || respBody?.detail || respText;
|
let errorMessage =
|
||||||
|
(loginData as any)?.message ||
|
||||||
|
(loginData as any)?.error ||
|
||||||
|
(loginData as any)?.detail ||
|
||||||
|
"Falha no login";
|
||||||
|
|
||||||
// Tratar códigos de status específicos
|
// Tratar códigos de status específicos
|
||||||
if (response.status === 401) {
|
if ((loginData as any)?.status === 401) {
|
||||||
errorMessage = "Email ou senha incorretos. Verifique suas credenciais.";
|
errorMessage = "Email ou senha incorretos. Verifique suas credenciais.";
|
||||||
} else if (response.status === 403) {
|
} else if ((loginData as any)?.status === 403) {
|
||||||
errorMessage = "Acesso negado. Sua conta pode estar inativa.";
|
errorMessage = "Acesso negado. Sua conta pode estar inativa.";
|
||||||
} else if (response.status === 404) {
|
} else if ((loginData as any)?.status === 404) {
|
||||||
errorMessage = "Usuário não encontrado. Verifique o email informado.";
|
errorMessage = "Usuário não encontrado. Verifique o email informado.";
|
||||||
} else if (response.status >= 500) {
|
} else if ((loginData as any)?.status >= 500) {
|
||||||
errorMessage = "Erro interno do servidor. Tente novamente em alguns minutos.";
|
errorMessage = "Erro interno do servidor. Tente novamente em alguns minutos.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,37 +93,13 @@ const LoginPageContent = () => {
|
||||||
throw new Error(translatedError);
|
throw new Error(translatedError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginData = await response.json();
|
// 2. Verificar imediatamente se o /me funciona (cookie httpOnly)
|
||||||
|
const meData = await authService.me();
|
||||||
// 2. Capturar e armazenar o access_token
|
if (meData) {
|
||||||
if (loginData.access_token) {
|
localStorage.setItem('user', JSON.stringify(meData));
|
||||||
const token = loginData.access_token;
|
|
||||||
localStorage.setItem('access_token', token);
|
|
||||||
setAccessToken(token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Verificar imediatamente se o /me funciona (cookie httpOnly ou Authorization)
|
// 3. Armazenar informações do usuário no localStorage
|
||||||
const meHeaders: HeadersInit = {
|
|
||||||
accept: "application/json",
|
|
||||||
};
|
|
||||||
if (loginData.access_token) {
|
|
||||||
meHeaders.Authorization = `Bearer ${loginData.access_token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const meResponse = await fetch(`${baseUrl}/auth/me`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: meHeaders,
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (meResponse.ok) {
|
|
||||||
const userData = await meResponse.json();
|
|
||||||
} else {
|
|
||||||
console.log("❌ Falha no /me:", await meResponse.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Armazenar informações do usuário no localStorage
|
|
||||||
if (loginData.user) {
|
if (loginData.user) {
|
||||||
localStorage.setItem('user', JSON.stringify(loginData.user));
|
localStorage.setItem('user', JSON.stringify(loginData.user));
|
||||||
|
|
||||||
|
|
@ -195,12 +123,12 @@ const LoginPageContent = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Verificar e armazenar empresa ID se disponível
|
// 4. Verificar e armazenar empresa ID se disponível
|
||||||
if (loginData.user?.empresaId) {
|
if (loginData.user?.empresaId) {
|
||||||
setEmpresaId(loginData.user.empresaId);
|
setEmpresaId(loginData.user.empresaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Redirecionar para o dashboard
|
// 5. Redirecionar para o dashboard
|
||||||
router.push("/dashboard");
|
router.push("/dashboard");
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -208,6 +136,17 @@ const LoginPageContent = () => {
|
||||||
|
|
||||||
// Definir mensagem de erro amigável
|
// Definir mensagem de erro amigável
|
||||||
let friendlyMessage = error.message;
|
let friendlyMessage = error.message;
|
||||||
|
const status = error?.status;
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
|
friendlyMessage = "Email ou senha incorretos. Verifique suas credenciais.";
|
||||||
|
} else if (status === 403) {
|
||||||
|
friendlyMessage = "Acesso negado. Sua conta pode estar inativa.";
|
||||||
|
} else if (status === 404) {
|
||||||
|
friendlyMessage = "Usuário não encontrado. Verifique o email informado.";
|
||||||
|
} else if (status >= 500) {
|
||||||
|
friendlyMessage = "Erro interno do servidor. Tente novamente em alguns minutos.";
|
||||||
|
}
|
||||||
|
|
||||||
// Se for um erro de rede
|
// Se for um erro de rede
|
||||||
if (error.name === 'TypeError' || error.message.includes('fetch')) {
|
if (error.name === 'TypeError' || error.message.includes('fetch')) {
|
||||||
|
|
@ -218,7 +157,7 @@ const LoginPageContent = () => {
|
||||||
friendlyMessage = "Erro inesperado. Verifique suas credenciais e tente novamente.";
|
friendlyMessage = "Erro inesperado. Verifique suas credenciais e tente novamente.";
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(friendlyMessage);
|
setError(translateError(friendlyMessage));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -233,77 +172,24 @@ const LoginPageContent = () => {
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 1. Fazer registro no BFF com dados corretos
|
// 1. Fazer registro na API Go
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_BFF_API_URL!;
|
await authService.register({
|
||||||
const response = await fetch(`${baseUrl}/auth/register`, {
|
role: "Seller",
|
||||||
method: 'POST',
|
name: name,
|
||||||
headers: {
|
username: email,
|
||||||
'accept': 'application/json',
|
email: email,
|
||||||
'Content-Type': 'application/json',
|
password: password
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
role: "Seller",
|
|
||||||
name: name,
|
|
||||||
username: email,
|
|
||||||
email: email,
|
|
||||||
password: password
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 2. Fazer login automático após registro para obter cookie HttpOnly
|
||||||
|
const loginData = await authService.login(email, password);
|
||||||
|
|
||||||
if (!response.ok) {
|
// 3. Armazenar dados do usuário
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
|
|
||||||
// Tratar códigos de status específicos
|
|
||||||
let errorMessage = errorData.message || 'Falha no registro';
|
|
||||||
|
|
||||||
if (response.status === 409 || errorMessage.includes('already exists') || errorMessage.includes('já existe')) {
|
|
||||||
errorMessage = 'Este email já está cadastrado. Tente fazer login ou use outro email.';
|
|
||||||
} else if (response.status === 400) {
|
|
||||||
errorMessage = 'Dados inválidos. Verifique se todos os campos estão preenchidos corretamente.';
|
|
||||||
} else if (response.status >= 500) {
|
|
||||||
errorMessage = 'Erro interno do servidor. Tente novamente em alguns minutos.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aplicar tradução se disponível
|
|
||||||
const translatedError = translateError(errorMessage);
|
|
||||||
throw new Error(translatedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerData = await response.json();
|
|
||||||
|
|
||||||
// 2. Fazer login automático após registro para obter token
|
|
||||||
const loginResponse = await fetch(`${baseUrl}/auth/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
mode: 'cors',
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: email,
|
|
||||||
password: password
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!loginResponse.ok) {
|
|
||||||
console.error("❌ Erro no login automático:", loginResponse.status);
|
|
||||||
throw new Error('Erro ao fazer login automático após registro');
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginData = await loginResponse.json();
|
|
||||||
|
|
||||||
// 3. Armazenar token e dados do usuário
|
|
||||||
if (loginData.access_token) {
|
|
||||||
localStorage.setItem('access_token', loginData.access_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginData.user) {
|
if (loginData.user) {
|
||||||
localStorage.setItem('user', JSON.stringify(loginData.user));
|
localStorage.setItem('user', JSON.stringify(loginData.user));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Redirecionar para completar registro com token válido
|
// 4. Redirecionar para completar registro com sessão válida
|
||||||
router.push(`/completar-registro?nome=${encodeURIComponent(name)}&email=${encodeURIComponent(email)}`);
|
router.push(`/completar-registro?nome=${encodeURIComponent(name)}&email=${encodeURIComponent(email)}`);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -311,6 +197,15 @@ const LoginPageContent = () => {
|
||||||
|
|
||||||
// Definir mensagem de erro amigável
|
// Definir mensagem de erro amigável
|
||||||
let friendlyMessage = error.message;
|
let friendlyMessage = error.message;
|
||||||
|
const status = error?.status;
|
||||||
|
|
||||||
|
if (status === 409 || friendlyMessage.includes('already exists') || friendlyMessage.includes('já existe')) {
|
||||||
|
friendlyMessage = 'Este email já está cadastrado. Tente fazer login ou use outro email.';
|
||||||
|
} else if (status === 400) {
|
||||||
|
friendlyMessage = 'Dados inválidos. Verifique se todos os campos estão preenchidos corretamente.';
|
||||||
|
} else if (status >= 500) {
|
||||||
|
friendlyMessage = 'Erro interno do servidor. Tente novamente em alguns minutos.';
|
||||||
|
}
|
||||||
|
|
||||||
// Se for um erro de rede
|
// Se for um erro de rede
|
||||||
if (error.name === 'TypeError' || error.message.includes('fetch')) {
|
if (error.name === 'TypeError' || error.message.includes('fetch')) {
|
||||||
|
|
@ -321,7 +216,7 @@ const LoginPageContent = () => {
|
||||||
friendlyMessage = "Erro inesperado durante o cadastro. Tente novamente.";
|
friendlyMessage = "Erro inesperado durante o cadastro. Tente novamente.";
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(friendlyMessage);
|
setError(translateError(friendlyMessage));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -362,7 +257,6 @@ const LoginPageContent = () => {
|
||||||
// Fazer logout para limpar dados do usuário
|
// Fazer logout para limpar dados do usuário
|
||||||
localStorage.removeItem('access_token');
|
localStorage.removeItem('access_token');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
setAccessToken("");
|
|
||||||
// Redirecionar para página inicial
|
// Redirecionar para página inicial
|
||||||
router.push('/');
|
router.push('/');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useEffect } from "react";
|
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||||
import { UserRole } from "@/types/auth";
|
import { UserRole } from "@/types/auth";
|
||||||
|
import { authService } from "@/services/auth";
|
||||||
|
|
||||||
interface UserData {
|
interface UserData {
|
||||||
$id: string;
|
$id: string;
|
||||||
|
|
@ -32,22 +33,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
try {
|
try {
|
||||||
// Verificação simples baseada em token
|
const userData = await authService.me();
|
||||||
const storedToken = localStorage.getItem('access_token');
|
|
||||||
|
|
||||||
if (storedToken) {
|
if (userData) {
|
||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
const storedUser = localStorage.getItem('user');
|
localStorage.setItem('user', JSON.stringify(userData));
|
||||||
if (storedUser) {
|
const parsedUser = userData as UserData;
|
||||||
try {
|
setUser(parsedUser);
|
||||||
const parsedUser = JSON.parse(storedUser) as UserData;
|
setUserRole(parsedUser.nivel ?? null);
|
||||||
setUser(parsedUser);
|
|
||||||
setUserRole(parsedUser.nivel ?? null);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Erro ao fazer parse do usuário:", e);
|
|
||||||
// Opcional: tentar buscar da API se o parse falhar ou se não tiver no storage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { authService } from '@/services/auth';
|
||||||
import { UserRole, UserData, AuthContextType, ROLE_PERMISSIONS, ROLE_ROUTES } from '@/types/auth';
|
import { UserRole, UserData, AuthContextType, ROLE_PERMISSIONS, ROLE_ROUTES } from '@/types/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,10 +91,9 @@ export const useAuth = (): AuthContextType => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Verificar se há token BFF armazenado
|
const userData = await authService.me();
|
||||||
const storedToken = localStorage.getItem('access_token');
|
|
||||||
|
|
||||||
if (!storedToken) {
|
if (!userData) {
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setUserRole(null);
|
setUserRole(null);
|
||||||
|
|
@ -101,28 +101,15 @@ export const useAuth = (): AuthContextType => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marcar como autenticado se há token
|
|
||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
|
localStorage.setItem('user', JSON.stringify(userData));
|
||||||
const storedUser = localStorage.getItem('user');
|
const parsedUser = userData as UserData;
|
||||||
if (storedUser) {
|
setUser(parsedUser);
|
||||||
try {
|
const storedRole =
|
||||||
const parsedUser = JSON.parse(storedUser) as UserData;
|
parsedUser?.nivel ||
|
||||||
setUser(parsedUser);
|
(parsedUser as unknown as { role?: string; userRole?: string })?.role ||
|
||||||
const storedRole =
|
(parsedUser as unknown as { role?: string; userRole?: string })?.userRole;
|
||||||
parsedUser?.nivel ||
|
setUserRole(normalizeRole(storedRole));
|
||||||
(parsedUser as unknown as { role?: string; userRole?: string })?.role ||
|
|
||||||
(parsedUser as unknown as { role?: string; userRole?: string })?.userRole;
|
|
||||||
setUserRole(normalizeRole(storedRole));
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('Erro ao ler usuário salvo:', parseError);
|
|
||||||
setUser(null);
|
|
||||||
setUserRole(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setUser(null);
|
|
||||||
setUserRole(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro na verificação de autenticação:', error);
|
console.error('Erro na verificação de autenticação:', error);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { authService } from "@/services/auth";
|
||||||
|
|
||||||
// Hook desabilitado - sempre permite acesso para usuários com token
|
// Hook de autenticação usando sessão via cookie HttpOnly
|
||||||
export const useAuthGuard = () => {
|
export const useAuthGuard = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
|
|
@ -14,18 +15,17 @@ export const useAuthGuard = () => {
|
||||||
try {
|
try {
|
||||||
setAuthError(null);
|
setAuthError(null);
|
||||||
|
|
||||||
// Verificação simples baseada em token
|
const userData = await authService.me();
|
||||||
const storedToken = localStorage.getItem('access_token');
|
|
||||||
|
if (!userData) {
|
||||||
if (!storedToken) {
|
console.log("ℹ️ useAuthGuard: Nenhuma sessão encontrada, redirecionando para login");
|
||||||
console.log("ℹ️ useAuthGuard: Nenhum token encontrado, redirecionando para login");
|
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("✅ useAuthGuard: Token encontrado, usuário autenticado");
|
console.log("✅ useAuthGuard: Sessão encontrada, usuário autenticado");
|
||||||
setUser({ email: 'usuario@exemplo.com' }); // Mock user
|
setUser(userData);
|
||||||
setRegistroCompleto(true); // Sempre completo
|
setRegistroCompleto(userData["registro-completo"] !== false);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ useAuthGuard: Erro na verificação:', error);
|
console.error('❌ useAuthGuard: Erro na verificação:', error);
|
||||||
|
|
@ -40,4 +40,4 @@ export const useAuthGuard = () => {
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
return { user, loading, registroCompleto, authError };
|
return { user, loading, registroCompleto, authError };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
90
saveinmed-frontend/src/services/auth.ts
Normal file
90
saveinmed-frontend/src/services/auth.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
export const GO_API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8214';
|
||||||
|
export const GO_API_V1_BASE_URL = `${GO_API_BASE_URL}/api/v1`;
|
||||||
|
|
||||||
|
export interface AuthResponse {
|
||||||
|
user?: Record<string, any>;
|
||||||
|
access_token?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AUTH_BASE_URL = `${GO_API_V1_BASE_URL}/auth`;
|
||||||
|
|
||||||
|
const buildError = async (response: Response) => {
|
||||||
|
const message = await response.text();
|
||||||
|
const error = new Error(message || response.statusText);
|
||||||
|
(error as Error & { status?: number }).status = response.status;
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authService = {
|
||||||
|
login: async (email: string, password: string): Promise<AuthResponse> => {
|
||||||
|
const response = await fetch(`${AUTH_BASE_URL}/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ email, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await buildError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
register: async (payload: Record<string, any>): Promise<AuthResponse> => {
|
||||||
|
const response = await fetch(`${AUTH_BASE_URL}/register`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await buildError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
me: async (): Promise<Record<string, any> | null> => {
|
||||||
|
const response = await fetch(`${AUTH_BASE_URL}/me`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await buildError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.data || data;
|
||||||
|
},
|
||||||
|
|
||||||
|
logout: async (): Promise<void> => {
|
||||||
|
const response = await fetch(`${AUTH_BASE_URL}/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok && response.status !== 204) {
|
||||||
|
throw await buildError(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue