basyx commited on
Commit
b2d58fc
·
verified ·
1 Parent(s): 2062b26

Update auth/routes.py

Browse files
Files changed (1) hide show
  1. auth/routes.py +182 -47
auth/routes.py CHANGED
@@ -1,16 +1,15 @@
1
  from fastapi import APIRouter, Depends, HTTPException, status
2
  from sqlalchemy.orm import Session
 
3
  from datetime import datetime
4
 
5
  from auth.database import get_db
6
  from auth.models import User
7
-
8
  from auth.schemas import (
9
  SignupSchema,
10
  LoginSchema,
11
  TokenSchema,
12
  )
13
-
14
  from auth.security import (
15
  hash_password,
16
  verify_password,
@@ -18,46 +17,76 @@ from auth.security import (
18
  decode_token,
19
  )
20
 
21
- router = APIRouter(prefix="/api/auth", tags=["Authentication"])
22
-
23
-
24
- # =========================================================
25
- # SAFE USER SERIALIZER (avoids leaking password_hash)
26
- # =========================================================
27
- def serialize_user(user: User) -> dict:
28
- return {
29
- "id": str(user.id),
30
- "email": user.email,
31
- "username": user.username,
32
- "created_at": user.created_at.isoformat()
33
- if user.created_at else None,
34
- }
35
 
36
 
37
  # =========================================================
38
  # SIGNUP
39
  # =========================================================
40
- @router.post("/signup")
41
- def signup(data: SignupSchema, db: Session = Depends(get_db)):
 
 
 
 
42
 
43
  try:
44
- # normalize email (prevents duplicate edge cases)
45
- email = data.email.lower().strip()
46
 
47
- existing = db.query(User).filter(User.email == email).first()
 
 
48
 
49
- if existing:
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  raise HTTPException(
51
- status_code=status.HTTP_400_BAD_REQUEST,
52
  detail="Email already registered",
53
  )
54
 
55
- # IMPORTANT: enforce bcrypt-safe hashing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  password_hash = hash_password(data.password)
57
 
 
 
 
 
58
  user = User(
59
  email=email,
60
- username=data.username,
61
  password_hash=password_hash,
62
  created_at=datetime.utcnow(),
63
  )
@@ -66,18 +95,60 @@ def signup(data: SignupSchema, db: Session = Depends(get_db)):
66
  db.commit()
67
  db.refresh(user)
68
 
69
- token = create_access_token({"sub": str(user.id)})
 
 
 
 
 
 
 
 
 
70
 
71
  return {
72
  "status": "success",
73
- "user": serialize_user(user),
74
  "token": token,
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
  except HTTPException:
78
  raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  except Exception as e:
 
80
  db.rollback()
 
 
 
81
  raise HTTPException(
82
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
83
  detail=f"Signup failed: {str(e)}",
@@ -87,38 +158,70 @@ def signup(data: SignupSchema, db: Session = Depends(get_db)):
87
  # =========================================================
88
  # LOGIN
89
  # =========================================================
 
90
  @router.post("/login")
91
- def login(data: LoginSchema, db: Session = Depends(get_db)):
 
 
 
92
 
93
  try:
94
- email = data.email.lower().strip()
95
 
96
- user = db.query(User).filter(User.email == email).first()
 
 
 
 
 
 
97
 
98
  if not user:
 
99
  raise HTTPException(
100
  status_code=status.HTTP_401_UNAUTHORIZED,
101
- detail="Invalid credentials",
102
  )
103
 
104
- # bcrypt-safe verification
105
- if not verify_password(data.password, user.password_hash):
 
 
 
106
  raise HTTPException(
107
  status_code=status.HTTP_401_UNAUTHORIZED,
108
- detail="Invalid credentials",
109
  )
110
 
111
- token = create_access_token({"sub": str(user.id)})
 
 
 
 
 
112
 
113
  return {
114
  "status": "success",
115
- "user": serialize_user(user),
116
  "token": token,
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  except HTTPException:
120
  raise
 
121
  except Exception as e:
 
 
 
122
  raise HTTPException(
123
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
124
  detail=f"Login failed: {str(e)}",
@@ -128,13 +231,18 @@ def login(data: LoginSchema, db: Session = Depends(get_db)):
128
  # =========================================================
129
  # VERIFY TOKEN
130
  # =========================================================
 
131
  @router.post("/verify")
132
- def verify_token(data: TokenSchema):
 
 
133
 
134
  try:
 
135
  payload = decode_token(data.token)
136
 
137
  if not payload:
 
138
  raise HTTPException(
139
  status_code=status.HTTP_401_UNAUTHORIZED,
140
  detail="Invalid token",
@@ -147,38 +255,59 @@ def verify_token(data: TokenSchema):
147
 
148
  except HTTPException:
149
  raise
150
- except Exception:
 
 
 
 
151
  raise HTTPException(
152
- status_code=status.HTTP_401_UNAUTHORIZED,
153
- detail="Token verification failed",
154
  )
155
 
156
 
157
  # =========================================================
158
  # REFRESH TOKEN
159
  # =========================================================
 
160
  @router.post("/refresh")
161
- def refresh_token(data: TokenSchema, db: Session = Depends(get_db)):
 
 
 
162
 
163
  try:
 
164
  payload = decode_token(data.token)
165
 
166
  user_id = payload.get("sub")
 
167
  if not user_id:
 
168
  raise HTTPException(
169
  status_code=status.HTTP_401_UNAUTHORIZED,
170
  detail="Invalid token payload",
171
  )
172
 
173
- user = db.query(User).filter(User.id == user_id).first()
 
 
 
 
174
 
175
  if not user:
 
176
  raise HTTPException(
177
  status_code=status.HTTP_404_NOT_FOUND,
178
  detail="User not found",
179
  )
180
 
181
- new_token = create_access_token({"sub": str(user.id)})
 
 
 
 
 
182
 
183
  return {
184
  "status": "success",
@@ -187,19 +316,25 @@ def refresh_token(data: TokenSchema, db: Session = Depends(get_db)):
187
 
188
  except HTTPException:
189
  raise
190
- except Exception:
 
 
 
 
191
  raise HTTPException(
192
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
193
- detail="Token refresh failed",
194
  )
195
 
196
 
197
  # =========================================================
198
- # LOGOUT (JWT = CLIENT-SIDE INVALIDATION MODEL)
199
  # =========================================================
 
200
  @router.post("/logout")
201
  def logout():
 
202
  return {
203
  "status": "success",
204
- "message": "Token should be discarded client-side",
205
  }
 
1
  from fastapi import APIRouter, Depends, HTTPException, status
2
  from sqlalchemy.orm import Session
3
+ from sqlalchemy.exc import IntegrityError
4
  from datetime import datetime
5
 
6
  from auth.database import get_db
7
  from auth.models import User
 
8
  from auth.schemas import (
9
  SignupSchema,
10
  LoginSchema,
11
  TokenSchema,
12
  )
 
13
  from auth.security import (
14
  hash_password,
15
  verify_password,
 
17
  decode_token,
18
  )
19
 
20
+ router = APIRouter(
21
+ prefix="/api/auth",
22
+ tags=["Authentication"],
23
+ )
 
 
 
 
 
 
 
 
 
 
24
 
25
 
26
  # =========================================================
27
  # SIGNUP
28
  # =========================================================
29
+
30
+ @router.post("/signup", status_code=201)
31
+ def signup(
32
+ data: SignupSchema,
33
+ db: Session = Depends(get_db),
34
+ ):
35
 
36
  try:
 
 
37
 
38
+ # =================================================
39
+ # NORMALIZATION
40
+ # =================================================
41
 
42
+ email = data.email.strip().lower()
43
+ username = data.username.strip()
44
+
45
+ # =================================================
46
+ # EXISTING EMAIL CHECK
47
+ # =================================================
48
+
49
+ existing_email = (
50
+ db.query(User)
51
+ .filter(User.email == email)
52
+ .first()
53
+ )
54
+
55
+ if existing_email:
56
  raise HTTPException(
57
+ status_code=status.HTTP_409_CONFLICT,
58
  detail="Email already registered",
59
  )
60
 
61
+ # =================================================
62
+ # EXISTING USERNAME CHECK
63
+ # =================================================
64
+
65
+ existing_username = (
66
+ db.query(User)
67
+ .filter(User.username == username)
68
+ .first()
69
+ )
70
+
71
+ if existing_username:
72
+ raise HTTPException(
73
+ status_code=status.HTTP_409_CONFLICT,
74
+ detail="Username already taken",
75
+ )
76
+
77
+ # =================================================
78
+ # HASH PASSWORD
79
+ # =================================================
80
+
81
  password_hash = hash_password(data.password)
82
 
83
+ # =================================================
84
+ # CREATE USER
85
+ # =================================================
86
+
87
  user = User(
88
  email=email,
89
+ username=username,
90
  password_hash=password_hash,
91
  created_at=datetime.utcnow(),
92
  )
 
95
  db.commit()
96
  db.refresh(user)
97
 
98
+ # =================================================
99
+ # CREATE JWT
100
+ # =================================================
101
+
102
+ token = create_access_token(
103
+ {
104
+ "sub": str(user.id),
105
+ "email": user.email,
106
+ }
107
+ )
108
 
109
  return {
110
  "status": "success",
111
+ "message": "Account created successfully",
112
  "token": token,
113
+ "user": {
114
+ "id": str(user.id),
115
+ "email": user.email,
116
+ "username": user.username,
117
+ "created_at": (
118
+ user.created_at.isoformat()
119
+ if user.created_at
120
+ else None
121
+ ),
122
+ },
123
  }
124
 
125
  except HTTPException:
126
  raise
127
+
128
+ except IntegrityError as e:
129
+
130
+ db.rollback()
131
+
132
+ raise HTTPException(
133
+ status_code=status.HTTP_409_CONFLICT,
134
+ detail="User already exists",
135
+ )
136
+
137
+ except ValueError as e:
138
+
139
+ db.rollback()
140
+
141
+ raise HTTPException(
142
+ status_code=status.HTTP_400_BAD_REQUEST,
143
+ detail=str(e),
144
+ )
145
+
146
  except Exception as e:
147
+
148
  db.rollback()
149
+
150
+ print("SIGNUP ERROR:", str(e))
151
+
152
  raise HTTPException(
153
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
154
  detail=f"Signup failed: {str(e)}",
 
158
  # =========================================================
159
  # LOGIN
160
  # =========================================================
161
+
162
  @router.post("/login")
163
+ def login(
164
+ data: LoginSchema,
165
+ db: Session = Depends(get_db),
166
+ ):
167
 
168
  try:
 
169
 
170
+ email = data.email.strip().lower()
171
+
172
+ user = (
173
+ db.query(User)
174
+ .filter(User.email == email)
175
+ .first()
176
+ )
177
 
178
  if not user:
179
+
180
  raise HTTPException(
181
  status_code=status.HTTP_401_UNAUTHORIZED,
182
+ detail="Invalid email or password",
183
  )
184
 
185
+ if not verify_password(
186
+ data.password,
187
+ user.password_hash,
188
+ ):
189
+
190
  raise HTTPException(
191
  status_code=status.HTTP_401_UNAUTHORIZED,
192
+ detail="Invalid email or password",
193
  )
194
 
195
+ token = create_access_token(
196
+ {
197
+ "sub": str(user.id),
198
+ "email": user.email,
199
+ }
200
+ )
201
 
202
  return {
203
  "status": "success",
204
+ "message": "Login successful",
205
  "token": token,
206
+ "user": {
207
+ "id": str(user.id),
208
+ "email": user.email,
209
+ "username": user.username,
210
+ "created_at": (
211
+ user.created_at.isoformat()
212
+ if user.created_at
213
+ else None
214
+ ),
215
+ },
216
  }
217
 
218
  except HTTPException:
219
  raise
220
+
221
  except Exception as e:
222
+
223
+ print("LOGIN ERROR:", str(e))
224
+
225
  raise HTTPException(
226
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
227
  detail=f"Login failed: {str(e)}",
 
231
  # =========================================================
232
  # VERIFY TOKEN
233
  # =========================================================
234
+
235
  @router.post("/verify")
236
+ def verify_token(
237
+ data: TokenSchema,
238
+ ):
239
 
240
  try:
241
+
242
  payload = decode_token(data.token)
243
 
244
  if not payload:
245
+
246
  raise HTTPException(
247
  status_code=status.HTTP_401_UNAUTHORIZED,
248
  detail="Invalid token",
 
255
 
256
  except HTTPException:
257
  raise
258
+
259
+ except Exception as e:
260
+
261
+ print("VERIFY ERROR:", str(e))
262
+
263
  raise HTTPException(
264
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
265
+ detail=f"Token verification failed: {str(e)}",
266
  )
267
 
268
 
269
  # =========================================================
270
  # REFRESH TOKEN
271
  # =========================================================
272
+
273
  @router.post("/refresh")
274
+ def refresh_token(
275
+ data: TokenSchema,
276
+ db: Session = Depends(get_db),
277
+ ):
278
 
279
  try:
280
+
281
  payload = decode_token(data.token)
282
 
283
  user_id = payload.get("sub")
284
+
285
  if not user_id:
286
+
287
  raise HTTPException(
288
  status_code=status.HTTP_401_UNAUTHORIZED,
289
  detail="Invalid token payload",
290
  )
291
 
292
+ user = (
293
+ db.query(User)
294
+ .filter(User.id == user_id)
295
+ .first()
296
+ )
297
 
298
  if not user:
299
+
300
  raise HTTPException(
301
  status_code=status.HTTP_404_NOT_FOUND,
302
  detail="User not found",
303
  )
304
 
305
+ new_token = create_access_token(
306
+ {
307
+ "sub": str(user.id),
308
+ "email": user.email,
309
+ }
310
+ )
311
 
312
  return {
313
  "status": "success",
 
316
 
317
  except HTTPException:
318
  raise
319
+
320
+ except Exception as e:
321
+
322
+ print("REFRESH ERROR:", str(e))
323
+
324
  raise HTTPException(
325
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
326
+ detail=f"Refresh failed: {str(e)}",
327
  )
328
 
329
 
330
  # =========================================================
331
+ # LOGOUT
332
  # =========================================================
333
+
334
  @router.post("/logout")
335
  def logout():
336
+
337
  return {
338
  "status": "success",
339
+ "message": "Logout successful",
340
  }