mominah commited on
Commit
dc1eb58
·
verified ·
1 Parent(s): b5dda41

Update auth.py

Browse files
Files changed (1) hide show
  1. auth.py +61 -55
auth.py CHANGED
@@ -1,10 +1,13 @@
 
1
  import os
2
  import uuid
3
  import logging
4
  from datetime import datetime, timedelta
 
5
  from typing import Optional
6
 
7
- from fastapi import APIRouter, HTTPException, Request, UploadFile, File, Form, Query, Depends
 
8
  from fastapi.responses import StreamingResponse
9
  from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
10
  from jose import JWTError, jwt
@@ -15,14 +18,16 @@ import gridfs
15
  from models import User, UserUpdate, Token, LoginResponse
16
  from config import CONNECTION_STRING, SECRET_KEY, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS
17
 
18
- # Configure logger
 
19
  logger = logging.getLogger("uvicorn")
20
  logger.setLevel(logging.INFO)
21
 
22
- # Initialize MongoDB
23
  client = MongoClient(CONNECTION_STRING)
24
  db = client.users_database
25
  users_collection = db.users
 
26
  fs = gridfs.GridFS(db, collection="avatars")
27
 
28
  # OAuth2 setup
@@ -51,7 +56,8 @@ def create_token(data: dict, expires_delta: timedelta = None) -> str:
51
  to_encode = data.copy()
52
  expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
53
  to_encode.update({"exp": expire})
54
- return jwt.encode(to_encode, SECRET_KEY, algorithm="HS256")
 
55
 
56
  def create_access_token(email: str) -> str:
57
  return create_token({"sub": email}, timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
@@ -60,22 +66,16 @@ def create_refresh_token(email: str) -> str:
60
  return create_token({"sub": email}, timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS))
61
 
62
  def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
63
- logger.info(f"[AUTH] Received token: {token}")
64
  try:
65
  payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
66
- logger.info(f"[AUTH] Decoded payload: {payload}")
67
  email: str = payload.get("sub")
68
  if not email:
69
- logger.warning("[AUTH] Token payload missing 'sub'")
70
  raise HTTPException(status_code=401, detail="Invalid credentials")
71
  user = get_user(email)
72
  if not user:
73
- logger.warning(f"[AUTH] No user found for email: {email}")
74
  raise HTTPException(status_code=401, detail="User not found")
75
- logger.info(f"[AUTH] Authenticated user: {email}")
76
  return user
77
- except JWTError as e:
78
- logger.error(f"[AUTH] JWT decode failed: {e}")
79
  raise HTTPException(status_code=401, detail="Invalid token")
80
 
81
  async def save_avatar_file_to_gridfs(file: UploadFile) -> str:
@@ -86,10 +86,14 @@ async def save_avatar_file_to_gridfs(file: UploadFile) -> str:
86
  status_code=400,
87
  detail="Invalid image format. Only JPEG, PNG, and GIF are accepted."
88
  )
89
- contents = await file.read()
90
- file_id = fs.put(contents, filename=file.filename, contentType=file.content_type)
91
- logger.info(f"Avatar stored in GridFS with file_id: {file_id}")
92
- return str(file_id)
 
 
 
 
93
 
94
  @router.post("/signup", response_model=Token)
95
  async def signup(
@@ -99,7 +103,6 @@ async def signup(
99
  password: str = Form(...),
100
  avatar: Optional[UploadFile] = File(None)
101
  ):
102
- # Validate and create user
103
  try:
104
  _ = User(name=name, email=email, password=password)
105
  except Exception as e:
@@ -109,12 +112,22 @@ async def signup(
109
  logger.warning(f"Attempt to register already existing email: {email}")
110
  raise HTTPException(status_code=400, detail="Email already registered")
111
  hashed_password = get_password_hash(password)
112
- user_data = {"name": name, "email": email, "hashed_password": hashed_password, "chat_histories": []}
 
 
 
 
 
113
  if avatar:
114
- user_data["avatar"] = await save_avatar_file_to_gridfs(avatar)
 
115
  users_collection.insert_one(user_data)
116
  logger.info(f"New user registered: {email}")
117
- return {"access_token": create_access_token(email), "refresh_token": create_refresh_token(email), "token_type": "bearer"}
 
 
 
 
118
 
119
  @router.post("/login", response_model=LoginResponse)
120
  async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
@@ -122,37 +135,28 @@ async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends
122
  if not user:
123
  logger.warning(f"Failed login attempt for: {form_data.username}")
124
  raise HTTPException(status_code=401, detail="Incorrect username or password")
125
- avatar_url = f"/auth/avatar/{user['avatar']}" if user.get('avatar') else None
126
  logger.info(f"User logged in: {user['email']}")
127
- return {"access_token": create_access_token(user['email']), "refresh_token": create_refresh_token(user['email']), "token_type": "bearer", "name": user['name'], "avatar": avatar_url}
128
-
129
- @router.get("/user/data", response_model=User)
130
- async def get_user_data(
131
- request: Request,
132
- email: Optional[str] = Query(None, description="User email")
133
- ):
134
- # Determine if Authorization header is provided
135
- auth_header = request.headers.get("Authorization", "")
136
- user = None
137
- if auth_header.startswith("Bearer "):
138
- token = auth_header.split(" ", 1)[1]
139
- # Authenticated path
140
- user = get_current_user(token)
141
- elif email:
142
- # Public path
143
- user = get_user(email)
144
- if not user:
145
- raise HTTPException(status_code=404, detail="User not found")
146
- else:
147
- raise HTTPException(status_code=422, detail="Provide either Authorization header or 'email' query parameter")
148
 
149
- # Build response
150
- avatar_url = f"/auth/avatar/{user['avatar']}" if user.get('avatar') else None
 
 
 
151
  return {
152
- "name": user['name'],
153
- "email": user['email'],
154
  "avatar": avatar_url,
155
- "chat_histories": user.get('chat_histories', [])
156
  }
157
 
158
  @router.put("/user/update")
@@ -165,23 +169,24 @@ async def update_user(
165
  current_user: dict = Depends(get_current_user)
166
  ):
167
  update_data = {}
168
- if name:
169
- update_data['name'] = name
170
- if email:
171
- update_data['email'] = email
172
- if password:
173
  try:
174
- _ = User(name=current_user['name'], email=current_user['email'], password=password)
175
  except Exception as e:
176
  logger.error(f"Password validation error during update: {e}")
177
  raise HTTPException(status_code=400, detail=str(e))
178
- update_data['hashed_password'] = get_password_hash(password)
179
  if avatar:
180
- update_data['avatar'] = await save_avatar_file_to_gridfs(avatar)
 
181
  if not update_data:
182
  logger.info("No update parameters provided")
183
  raise HTTPException(status_code=400, detail="No update parameters provided")
184
- users_collection.update_one({'email': current_user['email']}, {'$set': update_data})
185
  logger.info(f"User updated: {current_user['email']}")
186
  return {"message": "User updated successfully"}
187
 
@@ -195,6 +200,7 @@ from bson import ObjectId
195
  @router.get("/avatar/{file_id}")
196
  async def get_avatar(file_id: str):
197
  try:
 
198
  file = fs.get(ObjectId(file_id))
199
  return StreamingResponse(file, media_type=file.content_type)
200
  except Exception as e:
 
1
+ # auth.py
2
  import os
3
  import uuid
4
  import logging
5
  from datetime import datetime, timedelta
6
+ from urllib.parse import quote_plus
7
  from typing import Optional
8
 
9
+ from dotenv import load_dotenv
10
+ from fastapi import APIRouter, HTTPException, Depends, Request, UploadFile, File, Form
11
  from fastapi.responses import StreamingResponse
12
  from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
13
  from jose import JWTError, jwt
 
18
  from models import User, UserUpdate, Token, LoginResponse
19
  from config import CONNECTION_STRING, SECRET_KEY, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS
20
 
21
+ load_dotenv()
22
+
23
  logger = logging.getLogger("uvicorn")
24
  logger.setLevel(logging.INFO)
25
 
26
+ # Updated MongoDB initialization: now using CONNECTION_STRING from config.py
27
  client = MongoClient(CONNECTION_STRING)
28
  db = client.users_database
29
  users_collection = db.users
30
+ # GridFS instance for storing avatars
31
  fs = gridfs.GridFS(db, collection="avatars")
32
 
33
  # OAuth2 setup
 
56
  to_encode = data.copy()
57
  expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
58
  to_encode.update({"exp": expire})
59
+ algorithm = "HS256"
60
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=algorithm)
61
 
62
  def create_access_token(email: str) -> str:
63
  return create_token({"sub": email}, timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
 
66
  return create_token({"sub": email}, timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS))
67
 
68
  def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
 
69
  try:
70
  payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
 
71
  email: str = payload.get("sub")
72
  if not email:
 
73
  raise HTTPException(status_code=401, detail="Invalid credentials")
74
  user = get_user(email)
75
  if not user:
 
76
  raise HTTPException(status_code=401, detail="User not found")
 
77
  return user
78
+ except JWTError:
 
79
  raise HTTPException(status_code=401, detail="Invalid token")
80
 
81
  async def save_avatar_file_to_gridfs(file: UploadFile) -> str:
 
86
  status_code=400,
87
  detail="Invalid image format. Only JPEG, PNG, and GIF are accepted."
88
  )
89
+ try:
90
+ contents = await file.read()
91
+ file_id = fs.put(contents, filename=file.filename, contentType=file.content_type)
92
+ logger.info(f"Avatar stored in GridFS with file_id: {file_id}")
93
+ return str(file_id)
94
+ except Exception as e:
95
+ logger.exception("Failed to store avatar in GridFS")
96
+ raise HTTPException(status_code=500, detail="Could not store avatar file in MongoDB.")
97
 
98
  @router.post("/signup", response_model=Token)
99
  async def signup(
 
103
  password: str = Form(...),
104
  avatar: Optional[UploadFile] = File(None)
105
  ):
 
106
  try:
107
  _ = User(name=name, email=email, password=password)
108
  except Exception as e:
 
112
  logger.warning(f"Attempt to register already existing email: {email}")
113
  raise HTTPException(status_code=400, detail="Email already registered")
114
  hashed_password = get_password_hash(password)
115
+ user_data = {
116
+ "name": name,
117
+ "email": email,
118
+ "hashed_password": hashed_password,
119
+ "chat_histories": []
120
+ }
121
  if avatar:
122
+ file_id = await save_avatar_file_to_gridfs(avatar)
123
+ user_data["avatar"] = file_id
124
  users_collection.insert_one(user_data)
125
  logger.info(f"New user registered: {email}")
126
+ return {
127
+ "access_token": create_access_token(email),
128
+ "refresh_token": create_refresh_token(email),
129
+ "token_type": "bearer"
130
+ }
131
 
132
  @router.post("/login", response_model=LoginResponse)
133
  async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
 
135
  if not user:
136
  logger.warning(f"Failed login attempt for: {form_data.username}")
137
  raise HTTPException(status_code=401, detail="Incorrect username or password")
 
138
  logger.info(f"User logged in: {user['email']}")
139
+ avatar_url = None
140
+ if "avatar" in user and user["avatar"]:
141
+ avatar_url = f"/auth/avatar/{user['avatar']}"
142
+ return {
143
+ "access_token": create_access_token(user["email"]),
144
+ "refresh_token": create_refresh_token(user["email"]),
145
+ "token_type": "bearer",
146
+ "name": user["name"],
147
+ "avatar": avatar_url
148
+ }
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ @router.get("/user/data")
151
+ async def get_user_data(request: Request, current_user: dict = Depends(get_current_user)):
152
+ avatar_url = None
153
+ if "avatar" in current_user and current_user["avatar"]:
154
+ avatar_url = f"/auth/avatar/{current_user['avatar']}"
155
  return {
156
+ "name": current_user["name"],
157
+ "email": current_user["email"],
158
  "avatar": avatar_url,
159
+ "chat_histories": current_user.get("chat_histories", [])
160
  }
161
 
162
  @router.put("/user/update")
 
169
  current_user: dict = Depends(get_current_user)
170
  ):
171
  update_data = {}
172
+ if name is not None:
173
+ update_data["name"] = name
174
+ if email is not None:
175
+ update_data["email"] = email
176
+ if password is not None:
177
  try:
178
+ _ = User(name=current_user["name"], email=current_user["email"], password=password)
179
  except Exception as e:
180
  logger.error(f"Password validation error during update: {e}")
181
  raise HTTPException(status_code=400, detail=str(e))
182
+ update_data["hashed_password"] = get_password_hash(password)
183
  if avatar:
184
+ file_id = await save_avatar_file_to_gridfs(avatar)
185
+ update_data["avatar"] = file_id
186
  if not update_data:
187
  logger.info("No update parameters provided")
188
  raise HTTPException(status_code=400, detail="No update parameters provided")
189
+ users_collection.update_one({"email": current_user["email"]}, {"$set": update_data})
190
  logger.info(f"User updated: {current_user['email']}")
191
  return {"message": "User updated successfully"}
192
 
 
200
  @router.get("/avatar/{file_id}")
201
  async def get_avatar(file_id: str):
202
  try:
203
+ # Convert the file_id string to an ObjectId before fetching
204
  file = fs.get(ObjectId(file_id))
205
  return StreamingResponse(file, media_type=file.content_type)
206
  except Exception as e: