1
+ from datetime import datetime , timedelta
2
+ from typing import Optional
3
+ from fastapi import APIRouter , Depends , HTTPException , status , Response , Cookie , Header
4
+ from fastapi .security import OAuth2PasswordBearer , OAuth2PasswordRequestForm
5
+ from sqlalchemy .orm import Session
6
+
7
+ from app .db .database import get_db
8
+ from app .schemas .models import Token , User , UserCreate
9
+ from app .db .models import DBUser , DBAPIToken
10
+ from app .core .security import (
11
+ verify_password ,
12
+ get_password_hash ,
13
+ create_access_token ,
14
+ get_current_user ,
15
+ )
16
+ from app .core .config import settings
17
+
18
+ router = APIRouter ()
19
+
20
+ def authenticate_user (db : Session , email : str , password : str ) -> Optional [DBUser ]:
21
+ user = db .query (DBUser ).filter (DBUser .email == email ).first ()
22
+ if not user :
23
+ return None
24
+ if not verify_password (password , user .hashed_password ):
25
+ return None
26
+ return user
27
+
28
+ @router .post ("/login" , response_model = Token )
29
+ async def login (
30
+ response : Response ,
31
+ form_data : OAuth2PasswordRequestForm = Depends (),
32
+ db : Session = Depends (get_db )
33
+ ):
34
+ user = authenticate_user (db , form_data .username , form_data .password )
35
+ if not user :
36
+ raise HTTPException (
37
+ status_code = status .HTTP_401_UNAUTHORIZED ,
38
+ detail = "Incorrect email or password"
39
+ )
40
+
41
+ access_token = create_access_token (
42
+ data = {"sub" : user .email }
43
+ )
44
+
45
+ # Set cookie
46
+ response .set_cookie (
47
+ key = "access_token" ,
48
+ value = access_token ,
49
+ httponly = True ,
50
+ max_age = 1800 ,
51
+ expires = 1800 ,
52
+ )
53
+
54
+ return {"access_token" : access_token , "token_type" : "bearer" }
55
+
56
+ @router .post ("/logout" )
57
+ async def logout (response : Response ):
58
+ response .delete_cookie (
59
+ key = "access_token" ,
60
+ path = "/"
61
+ )
62
+ return {"message" : "Successfully logged out" }
63
+
64
+ async def get_current_user_from_token (
65
+ api_token : str = Header (None , alias = "X-API-Token" ),
66
+ db : Session = Depends (get_db )
67
+ ) -> DBUser :
68
+ if not api_token :
69
+ raise HTTPException (
70
+ status_code = status .HTTP_401_UNAUTHORIZED ,
71
+ detail = "API token is missing" ,
72
+ )
73
+
74
+ db_token = db .query (DBAPIToken ).filter (DBAPIToken .token == api_token ).first ()
75
+ if not db_token :
76
+ raise HTTPException (
77
+ status_code = status .HTTP_401_UNAUTHORIZED ,
78
+ detail = "Invalid API token" ,
79
+ )
80
+
81
+ # Update last used timestamp
82
+ db_token .last_used_at = datetime .utcnow ()
83
+ db .commit ()
84
+
85
+ return db_token .user
86
+
87
+ async def get_current_user_from_auth (
88
+ access_token : Optional [str ] = Cookie (None , alias = "access_token" ),
89
+ api_token : Optional [str ] = Header (None , alias = "X-API-Token" ),
90
+ db : Session = Depends (get_db )
91
+ ) -> DBUser :
92
+ if api_token :
93
+ return await get_current_user_from_token (api_token , db )
94
+
95
+ if not access_token :
96
+ raise HTTPException (
97
+ status_code = status .HTTP_401_UNAUTHORIZED ,
98
+ detail = "Could not validate credentials" ,
99
+ headers = {"WWW-Authenticate" : "Bearer" },
100
+ )
101
+
102
+ # Remove "Bearer " prefix if present
103
+ if access_token .startswith ("Bearer " ):
104
+ access_token = access_token [7 :]
105
+
106
+ return await get_current_user (token = access_token , db = db )
107
+
108
+ @router .get ("/me" , response_model = User )
109
+ async def read_users_me (current_user : DBUser = Depends (get_current_user_from_auth )):
110
+ return current_user
111
+
112
+ @router .post ("/register" , response_model = User )
113
+ async def register (user : UserCreate , db : Session = Depends (get_db )):
114
+ # Check if user with this email exists
115
+ db_user = db .query (DBUser ).filter (DBUser .email == user .email ).first ()
116
+ if db_user :
117
+ raise HTTPException (
118
+ status_code = status .HTTP_400_BAD_REQUEST ,
119
+ detail = "Email already registered"
120
+ )
121
+
122
+ # Create new user
123
+ hashed_password = get_password_hash (user .password )
124
+ db_user = DBUser (
125
+ email = user .email ,
126
+ hashed_password = hashed_password ,
127
+ is_admin = user .is_admin
128
+ )
129
+ db .add (db_user )
130
+ db .commit ()
131
+ db .refresh (db_user )
132
+ return db_user
0 commit comments