1+ import random
2+ from datetime import datetime , timedelta , timezone
3+ from fastapi import APIRouter , HTTPException , status
14from fastapi .security import OAuth2PasswordBearer
5+ from schemas .auth import LoginRequest , LoginResponse , RegisterRequest , VerifyCodeRequest , ForgotPasswordRequest , ResetPasswordRequest
6+ from services .email_service import enviar_email_verificacao
7+ from database .config import supabase
8+ from api .security import obter_hash_senha , verificar_senha , criar_token_acesso
29
3- oauth2_scheme = OAuth2PasswordBearer (tokenUrl = "auth/login" )
10+ oauth2_scheme = OAuth2PasswordBearer (tokenUrl = "api/v1/auth/login" )
11+
12+ router = APIRouter (tags = ["auth" ])
13+
14+ @router .post ("/login" , response_model = LoginResponse )
15+ async def login (data : LoginRequest ):
16+ response = supabase .table ("user" ).select ("*" ).eq ("email" , data .email ).execute ()
17+
18+ user_data = response .data
19+
20+ if not user_data :
21+ raise HTTPException (
22+ status_code = status .HTTP_401_UNAUTHORIZED ,
23+ detail = "E-mail ou senha incorretos"
24+ )
25+
26+ user = user_data [0 ]
27+
28+ if not verificar_senha (data .password , user ["senha" ]):
29+ raise HTTPException (
30+ status_code = status .HTTP_401_UNAUTHORIZED ,
31+ detail = "E-mail ou senha incorretos"
32+ )
33+
34+ if not user .get ("is_active" , False ):
35+ novo_codigo = f"{ random .randint (100000 , 999999 )} "
36+ novo_tempo_expiracao = (datetime .now (timezone .utc ) + timedelta (minutes = 15 )).isoformat ()
37+
38+ print ("\n " + "=" * 50 )
39+ print (f"🔑 [LOGIN - CONTA INATIVA] NOVO CÓDIGO GERADO PARA { data .email } : { novo_codigo } " )
40+ print ("=" * 50 + "\n " )
41+
42+
43+ supabase .table ("user" ).update ({
44+ "verification_code" : novo_codigo ,
45+ "verification_expires_at" : novo_tempo_expiracao
46+ }).eq ("id" , user ["id" ]).execute ()
47+
48+ await enviar_email_verificacao (email = user ["email" ], codigo = novo_codigo )
49+
50+ raise HTTPException (
51+ status_code = status .HTTP_403_FORBIDDEN ,
52+ detail = {
53+ "error" : "requires_verification" ,
54+ "email" : user ["email" ],
55+ "message" : "Esta conta ainda não foi ativada. Um novo código foi gerado."
56+ }
57+ )
58+
59+ payload_usuario = {
60+ "sub" : str (user ["id" ]),
61+ "email" : user ["email" ],
62+ "role" : user ["perfil" ]
63+ }
64+
65+ token_real = criar_token_acesso (data = payload_usuario )
66+
67+ return {
68+ "access_token" : token_real ,
69+ "token_type" : "bearer" ,
70+ "role" : user ["perfil" ],
71+ "name" : user ["nome" ]
72+ }
73+
74+ @router .post ("/register" , status_code = status .HTTP_201_CREATED )
75+ async def register (data : RegisterRequest ):
76+ if not getattr (data , "accepted_terms" , False ):
77+ raise HTTPException (
78+ status_code = status .HTTP_400_BAD_REQUEST ,
79+ detail = "Você precisa aceitar os Termos de Uso e a Política de Privacidade para se cadastrar."
80+ )
81+
82+ user_exists = supabase .table ("user" ).select ("id, is_active" ).eq ("email" , data .email ).execute ()
83+
84+ codigo_verificacao = f"{ random .randint (100000 , 999999 )} "
85+ tempo_expiracao = (datetime .now (timezone .utc ) + timedelta (minutes = 15 )).isoformat ()
86+ senha_criptografada = obter_hash_senha (data .senha )
87+ timestamp_aceite = datetime .now (timezone .utc ).isoformat ()
88+
89+ if user_exists .data :
90+ usuario_atual = user_exists .data [0 ]
91+
92+ if usuario_atual .get ("is_active" ) == True :
93+ raise HTTPException (
94+ status_code = status .HTTP_400_BAD_REQUEST ,
95+ detail = "Este e-mail já está cadastrado."
96+ )
97+
98+ print ("\n " + "=" * 50 )
99+ print (f"🔄 [REENVIO/ATUALIZAÇÃO] NOVO CÓDIGO PARA { data .email } : { codigo_verificacao } " )
100+ print ("=" * 50 + "\n " )
101+
102+ update_response = supabase .table ("user" ).update ({
103+ "nome" : data .nome ,
104+ "senha" : senha_criptografada ,
105+ "data_nascimento" : data .data_nascimento .isoformat (),
106+ "verification_code" : codigo_verificacao ,
107+ "verification_expires_at" : tempo_expiracao ,
108+ "accepted_terms" : True ,
109+ "accepted_terms_at" : timestamp_aceite
110+ }).eq ("id" , usuario_atual ["id" ]).execute ()
111+
112+ if not update_response .data :
113+ raise HTTPException (
114+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
115+ detail = "Erro ao atualizar código de verificação. Tente novamente."
116+ )
117+
118+ await enviar_email_verificacao (email = data .email , codigo = codigo_verificacao )
119+
120+ return {"message" : "Um novo código de verificação foi gerado." }
121+
122+ print ("\n " + "=" * 50 )
123+ print (f"📧 [NOVO CADASTRO] CÓDIGO PARA { data .email } : { codigo_verificacao } " )
124+ print ("=" * 50 + "\n " )
125+
126+ novo_usuario = {
127+ "nome" : data .nome ,
128+ "email" : data .email ,
129+ "senha" : senha_criptografada ,
130+ "data_nascimento" : data .data_nascimento .isoformat (),
131+ "perfil" : "user" ,
132+ "is_active" : False ,
133+ "verification_code" : codigo_verificacao ,
134+ "verification_expires_at" : tempo_expiracao ,
135+ "accepted_terms" : True ,
136+ "accepted_terms_at" : timestamp_aceite
137+ }
138+
139+ insert_response = supabase .table ("user" ).insert (novo_usuario ).execute ()
140+
141+ if not insert_response .data :
142+ raise HTTPException (
143+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
144+ detail = "Erro ao criar conta. Tente novamente mais tarde."
145+ )
146+
147+ await enviar_email_verificacao (email = data .email , codigo = codigo_verificacao )
148+
149+ return {"message" : "Cadastro realizado com sucesso! Verifique seu e-mail para ativar a conta." }
150+
151+ @router .post ("/verify-code" , status_code = status .HTTP_200_OK )
152+ async def verify_code (data : VerifyCodeRequest ):
153+ response = supabase .table ("user" ).select ("*" ).eq ("email" , data .email ).execute ()
154+
155+ if not response .data :
156+ raise HTTPException (
157+ status_code = status .HTTP_404_NOT_FOUND ,
158+ detail = "Usuário não encontrado."
159+ )
160+
161+ user = response .data [0 ]
162+
163+ if user .get ("is_active" , False ):
164+ return {"message" : "Esta conta já está ativa." }
165+
166+ if user .get ("verification_code" ) != data .code :
167+ raise HTTPException (
168+ status_code = status .HTTP_400_BAD_REQUEST ,
169+ detail = "Código de verificação incorreto."
170+ )
171+
172+ expires_at_str = user .get ("verification_expires_at" )
173+ if expires_at_str :
174+ expires_at = datetime .fromisoformat (expires_at_str .replace ("Z" , "+00:00" ))
175+
176+ if datetime .now (timezone .utc ) > expires_at :
177+ raise HTTPException (
178+ status_code = status .HTTP_400_BAD_REQUEST ,
179+ detail = "O código de verificação expirou. Solicite um novo código."
180+ )
181+
182+ update_response = supabase .table ("user" ).update ({
183+ "is_active" : True ,
184+ "verification_code" : None ,
185+ "verification_expires_at" : None
186+ }).eq ("id" , user ["id" ]).execute ()
187+
188+ if not update_response .data :
189+ raise HTTPException (
190+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
191+ detail = "Erro ao ativar a conta. Tente novamente."
192+ )
193+
194+ return {"message" : "Conta ativada com sucesso! Você já pode fazer login." }
195+
196+ @router .post ("/forgot-password" , status_code = status .HTTP_200_OK )
197+ async def forgot_password (data : ForgotPasswordRequest ):
198+ response = supabase .table ("user" ).select ("id, is_active" ).eq ("email" , data .email ).execute ()
199+
200+ if not response .data :
201+ return {"message" : "Se o e-mail estiver cadastrado, um código de recuperação será enviado." }
202+
203+ user = response .data [0 ]
204+
205+ codigo_recuperacao = f"{ random .randint (100000 , 999999 )} "
206+ tempo_expiracao = (datetime .now (timezone .utc ) + timedelta (minutes = 15 )).isoformat ()
207+
208+ print ("\n " + "=" * 50 )
209+ print (f"🔒 [RECUPERAÇÃO DE SENHA] CÓDIGO GERADO PARA { data .email } : { codigo_recuperacao } " )
210+ print ("=" * 50 + "\n " )
211+
212+ supabase .table ("user" ).update ({
213+ "reset_password_code" : codigo_recuperacao ,
214+ "reset_password_expires_at" : tempo_expiracao
215+ }).eq ("id" , user ["id" ]).execute ()
216+
217+ await enviar_email_verificacao (email = data .email , codigo = codigo_recuperacao )
218+
219+ return {"message" : "Código de recuperação gerado com sucesso. Verifique seu e-mail." }
220+
221+
222+ @router .post ("/reset-password" , status_code = status .HTTP_200_OK )
223+ async def reset_password (data : ResetPasswordRequest ):
224+ response = supabase .table ("user" ).select ("*" ).eq ("email" , data .email ).execute ()
225+
226+ if not response .data :
227+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = "Usuário não encontrado." )
228+
229+ user = response .data [0 ]
230+
231+ if not user .get ("reset_password_code" ) or user .get ("reset_password_code" ) != data .code :
232+ raise HTTPException (status_code = status .HTTP_400_BAD_REQUEST , detail = "Código de recuperação inválido." )
233+
234+ expires_at_str = user .get ("reset_password_expires_at" )
235+ if expires_at_str :
236+ expires_at = datetime .fromisoformat (expires_at_str .replace ("Z" , "+00:00" ))
237+ if datetime .now (timezone .utc ) > expires_at :
238+ raise HTTPException (status_code = status .HTTP_400_BAD_REQUEST , detail = "O código de recuperação expirou." )
239+
240+ nova_senha_criptografada = obter_hash_senha (data .nova_senha )
241+
242+ supabase .table ("user" ).update ({
243+ "senha" : nova_senha_criptografada ,
244+ "reset_password_code" : None ,
245+ "reset_password_expires_at" : None ,
246+ "is_active" : True
247+ }).eq ("id" , user ["id" ]).execute ()
248+
249+ return {"message" : "Senha redefinida com sucesso! Você já pode fazer login." }
0 commit comments