hisham-cmd commited on
Commit
d3e97f9
·
verified ·
1 Parent(s): adbbd7a

Upload app/api/auth.py

Browse files
Files changed (1) hide show
  1. app/api/auth.py +206 -0
app/api/auth.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from sqlalchemy.orm import Session
3
+ from google.oauth2 import id_token
4
+ from google.auth.transport import requests as google_requests
5
+
6
+ from app.db.base import get_db
7
+ from app.models.user import User, AuthProvider, UserRole
8
+ from app.schemas.auth import (
9
+ RegisterRequest,
10
+ LoginRequest,
11
+ GoogleLoginRequest,
12
+ RefreshRequest,
13
+ TokenResponse,
14
+ UserResponse,
15
+ AuthResponse,
16
+ )
17
+ from app.core.security import (
18
+ hash_password,
19
+ verify_password,
20
+ create_access_token,
21
+ create_refresh_token,
22
+ decode_token,
23
+ get_current_user,
24
+ )
25
+ from app.core.config import settings
26
+
27
+ router = APIRouter(prefix="/auth", tags=["Authentication"])
28
+
29
+
30
+ def _generate_tokens(user_id: int) -> dict:
31
+ access_token = create_access_token({"sub": str(user_id)})
32
+ refresh_token = create_refresh_token({"sub": str(user_id)})
33
+ return {
34
+ "access_token": access_token,
35
+ "refresh_token": refresh_token,
36
+ "token_type": "bearer",
37
+ }
38
+
39
+
40
+ @router.post("/register", response_model=AuthResponse, status_code=status.HTTP_201_CREATED)
41
+ async def register(request: RegisterRequest, db: Session = Depends(get_db)):
42
+ existing = db.query(User).filter(User.email == request.email).first()
43
+ if existing:
44
+ raise HTTPException(
45
+ status_code=status.HTTP_409_CONFLICT,
46
+ detail="Email already registered",
47
+ )
48
+
49
+ user = User(
50
+ name=request.name,
51
+ email=request.email,
52
+ password_hash=hash_password(request.password),
53
+ phone=request.phone,
54
+ role=UserRole.CUSTOMER,
55
+ auth_provider=AuthProvider.LOCAL,
56
+ )
57
+ db.add(user)
58
+ db.commit()
59
+ db.refresh(user)
60
+
61
+ tokens = _generate_tokens(user.id)
62
+ user_data = UserResponse.model_validate(user).model_dump()
63
+ user_data["created_at"] = user_data["created_at"].isoformat()
64
+
65
+ return AuthResponse(
66
+ isSuccess=True,
67
+ value={"tokens": tokens, "user": user_data},
68
+ statusCode=201,
69
+ )
70
+
71
+
72
+ @router.post("/login", response_model=AuthResponse)
73
+ async def login(request: LoginRequest, db: Session = Depends(get_db)):
74
+ user = db.query(User).filter(User.email == request.email).first()
75
+ if not user or not user.password_hash:
76
+ raise HTTPException(
77
+ status_code=status.HTTP_401_UNAUTHORIZED,
78
+ detail="Invalid email or password",
79
+ )
80
+
81
+ if not verify_password(request.password, user.password_hash):
82
+ raise HTTPException(
83
+ status_code=status.HTTP_401_UNAUTHORIZED,
84
+ detail="Invalid email or password",
85
+ )
86
+
87
+ tokens = _generate_tokens(user.id)
88
+ user_data = UserResponse.model_validate(user).model_dump()
89
+ user_data["created_at"] = user_data["created_at"].isoformat()
90
+
91
+ return AuthResponse(
92
+ isSuccess=True,
93
+ value={"tokens": tokens, "user": user_data},
94
+ statusCode=200,
95
+ )
96
+
97
+
98
+ @router.post("/google", response_model=AuthResponse)
99
+ async def google_login(request: GoogleLoginRequest, db: Session = Depends(get_db)):
100
+ """Authenticate user with Google ID token."""
101
+ if not settings.GOOGLE_CLIENT_ID:
102
+ raise HTTPException(
103
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
104
+ detail="Google authentication is not configured",
105
+ )
106
+
107
+ try:
108
+ idinfo = id_token.verify_oauth2_token(
109
+ request.credential,
110
+ google_requests.Request(),
111
+ settings.GOOGLE_CLIENT_ID,
112
+ )
113
+ except ValueError:
114
+ raise HTTPException(
115
+ status_code=status.HTTP_401_UNAUTHORIZED,
116
+ detail="Invalid Google token",
117
+ )
118
+
119
+ google_id = idinfo.get("sub")
120
+ email = idinfo.get("email")
121
+ name = idinfo.get("name", "")
122
+ avatar_url = idinfo.get("picture", "")
123
+
124
+ if not email:
125
+ raise HTTPException(
126
+ status_code=status.HTTP_400_BAD_REQUEST,
127
+ detail="Google account does not have an email",
128
+ )
129
+
130
+ # Check if user exists by google_id
131
+ user = db.query(User).filter(User.google_id == google_id).first()
132
+
133
+ if not user:
134
+ # Check if user exists by email (link accounts)
135
+ user = db.query(User).filter(User.email == email).first()
136
+ if user:
137
+ # Link existing account to Google
138
+ user.google_id = google_id
139
+ user.auth_provider = AuthProvider.GOOGLE
140
+ if not user.avatar_url:
141
+ user.avatar_url = avatar_url
142
+ else:
143
+ # Create new user
144
+ user = User(
145
+ name=name,
146
+ email=email,
147
+ google_id=google_id,
148
+ avatar_url=avatar_url,
149
+ auth_provider=AuthProvider.GOOGLE,
150
+ role=UserRole.CUSTOMER,
151
+ )
152
+ db.add(user)
153
+
154
+ db.commit()
155
+ db.refresh(user)
156
+
157
+ tokens = _generate_tokens(user.id)
158
+ user_data = UserResponse.model_validate(user).model_dump()
159
+ user_data["created_at"] = user_data["created_at"].isoformat()
160
+
161
+ return AuthResponse(
162
+ isSuccess=True,
163
+ value={"tokens": tokens, "user": user_data},
164
+ statusCode=200,
165
+ )
166
+
167
+
168
+ @router.post("/refresh", response_model=AuthResponse)
169
+ async def refresh_token(request: RefreshRequest):
170
+ payload = decode_token(request.refresh_token)
171
+ if payload.get("type") != "refresh":
172
+ raise HTTPException(
173
+ status_code=status.HTTP_401_UNAUTHORIZED,
174
+ detail="Invalid token type — expected refresh token",
175
+ )
176
+
177
+ user_id = payload.get("sub")
178
+ tokens = _generate_tokens(int(user_id))
179
+
180
+ return AuthResponse(
181
+ isSuccess=True,
182
+ value={"tokens": tokens},
183
+ statusCode=200,
184
+ )
185
+
186
+
187
+ @router.get("/me", response_model=AuthResponse)
188
+ async def get_me(
189
+ user_id: int = Depends(get_current_user),
190
+ db: Session = Depends(get_db),
191
+ ):
192
+ user = db.query(User).filter(User.id == user_id).first()
193
+ if not user:
194
+ raise HTTPException(
195
+ status_code=status.HTTP_404_NOT_FOUND,
196
+ detail="User not found",
197
+ )
198
+
199
+ user_data = UserResponse.model_validate(user).model_dump()
200
+ user_data["created_at"] = user_data["created_at"].isoformat()
201
+
202
+ return AuthResponse(
203
+ isSuccess=True,
204
+ value={"user": user_data},
205
+ statusCode=200,
206
+ )