336 lines
8.4 KiB
TypeScript
336 lines
8.4 KiB
TypeScript
/**
|
|
* Serviço de Autenticação (Frontend)
|
|
* Chama Supabase diretamente
|
|
*/
|
|
|
|
import axios from "axios";
|
|
import { API_CONFIG } from "../api/config";
|
|
import type {
|
|
LoginInput,
|
|
LoginResponse,
|
|
AuthUser,
|
|
RefreshTokenResponse,
|
|
} from "./types";
|
|
|
|
class AuthService {
|
|
/**
|
|
* Faz login com email e senha
|
|
*/
|
|
async login(credentials: LoginInput): Promise<LoginResponse> {
|
|
try {
|
|
console.log("[authService] Tentando login com:", credentials.email);
|
|
const response = await axios.post<LoginResponse>(
|
|
`${API_CONFIG.AUTH_URL}/token?grant_type=password`,
|
|
{
|
|
email: credentials.email,
|
|
password: credentials.password,
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
},
|
|
}
|
|
);
|
|
|
|
console.log("[authService] Login bem-sucedido:", response.data);
|
|
|
|
// Salva tokens e user no localStorage
|
|
if (response.data.access_token) {
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN,
|
|
response.data.access_token
|
|
);
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.REFRESH_TOKEN,
|
|
response.data.refresh_token
|
|
);
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.USER,
|
|
JSON.stringify(response.data.user)
|
|
);
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error: any) {
|
|
console.error("[authService] Erro no login:", error);
|
|
console.error("[authService] Response data:", error.response?.data);
|
|
console.error("[authService] Response status:", error.response?.status);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registro público (signup) com email e senha
|
|
* POST /auth/v1/signup
|
|
* Não requer autenticação - permite auto-registro
|
|
*/
|
|
async signup(data: {
|
|
email: string;
|
|
password: string;
|
|
full_name: string;
|
|
phone?: string;
|
|
}): Promise<LoginResponse> {
|
|
try {
|
|
const response = await axios.post<LoginResponse>(
|
|
`${API_CONFIG.AUTH_URL}/signup`,
|
|
{
|
|
email: data.email,
|
|
password: data.password,
|
|
options: {
|
|
data: {
|
|
full_name: data.full_name,
|
|
phone: data.phone,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
},
|
|
}
|
|
);
|
|
|
|
// Salva tokens e user no localStorage
|
|
if (response.data.access_token) {
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN,
|
|
response.data.access_token
|
|
);
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.REFRESH_TOKEN,
|
|
response.data.refresh_token
|
|
);
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.USER,
|
|
JSON.stringify(response.data.user)
|
|
);
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("Erro no signup:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Envia magic link para o email do usuário
|
|
* POST /auth/v1/otp
|
|
*/
|
|
async sendMagicLink(
|
|
email: string,
|
|
redirectUrl?: string
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
await axios.post(
|
|
`${API_CONFIG.AUTH_URL}/otp`,
|
|
{
|
|
email,
|
|
options: {
|
|
emailRedirectTo:
|
|
redirectUrl || `${API_CONFIG.APP_URL}/auth/callback`,
|
|
},
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
},
|
|
}
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message: "Magic link enviado com sucesso",
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro ao enviar magic link:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Solicita reset de senha via email (público)
|
|
* POST /auth/v1/recover
|
|
*/
|
|
async requestPasswordReset(
|
|
email: string,
|
|
redirectUrl?: string
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
await axios.post(
|
|
`${API_CONFIG.AUTH_URL}/recover`,
|
|
{
|
|
email,
|
|
options: {
|
|
redirectTo: redirectUrl || `${API_CONFIG.APP_URL}/reset-password`,
|
|
},
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
},
|
|
}
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message:
|
|
"Email de recuperação de senha enviado com sucesso. Verifique sua caixa de entrada.",
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro ao solicitar reset de senha:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza a senha do usuário usando o token de recuperação
|
|
* PUT /auth/v1/user
|
|
*/
|
|
async updatePassword(
|
|
accessToken: string,
|
|
newPassword: string
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
await axios.put(
|
|
`${API_CONFIG.AUTH_URL}/user`,
|
|
{
|
|
password: newPassword,
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
}
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message: "Senha atualizada com sucesso",
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro ao atualizar senha:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Faz logout (invalida sessão no servidor e limpa localStorage)
|
|
*/
|
|
async logout(): Promise<void> {
|
|
try {
|
|
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
|
|
|
|
if (token) {
|
|
// Chama API para invalidar sessão no servidor
|
|
await axios.post(
|
|
`${API_CONFIG.AUTH_URL}/logout`,
|
|
{},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao invalidar sessão no servidor:", error);
|
|
// Continua mesmo com erro, para garantir limpeza local
|
|
} finally {
|
|
// Sempre limpa o localStorage
|
|
localStorage.removeItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
|
|
localStorage.removeItem(API_CONFIG.STORAGE_KEYS.REFRESH_TOKEN);
|
|
localStorage.removeItem(API_CONFIG.STORAGE_KEYS.USER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica se usuário está autenticado
|
|
*/
|
|
isAuthenticated(): boolean {
|
|
return !!localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
|
|
}
|
|
|
|
/**
|
|
* Retorna o usuário atual do localStorage
|
|
*/
|
|
getCurrentUser(): AuthUser | null {
|
|
const userStr = localStorage.getItem(API_CONFIG.STORAGE_KEYS.USER);
|
|
if (!userStr) return null;
|
|
|
|
try {
|
|
return JSON.parse(userStr);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retorna o access token
|
|
*/
|
|
getAccessToken(): string | null {
|
|
return localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
|
|
}
|
|
|
|
/**
|
|
* Renova o access token usando o refresh token
|
|
*/
|
|
async refreshToken(): Promise<RefreshTokenResponse> {
|
|
try {
|
|
const refreshToken = localStorage.getItem(
|
|
API_CONFIG.STORAGE_KEYS.REFRESH_TOKEN
|
|
);
|
|
|
|
if (!refreshToken) {
|
|
throw new Error("Refresh token não encontrado");
|
|
}
|
|
|
|
const response = await axios.post<RefreshTokenResponse>(
|
|
`${API_CONFIG.AUTH_URL}/token?grant_type=refresh_token`,
|
|
{
|
|
refresh_token: refreshToken,
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
|
},
|
|
}
|
|
);
|
|
|
|
// Atualiza tokens e user no localStorage
|
|
if (response.data.access_token) {
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN,
|
|
response.data.access_token
|
|
);
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.REFRESH_TOKEN,
|
|
response.data.refresh_token
|
|
);
|
|
localStorage.setItem(
|
|
API_CONFIG.STORAGE_KEYS.USER,
|
|
JSON.stringify(response.data.user)
|
|
);
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("Erro ao renovar token:", error);
|
|
// Se falhar, limpa tudo e força novo login
|
|
this.logout();
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const authService = new AuthService();
|