sachin commited on
Commit
944b400
·
1 Parent(s): 405f86d

add-new table

Browse files
Files changed (2) hide show
  1. src/server/main.py +6 -4
  2. src/server/utils/auth.py +86 -33
src/server/main.py CHANGED
@@ -184,13 +184,15 @@ async def refresh(credentials: HTTPAuthorizationCredentials = Depends(bearer_sch
184
 
185
  @app.post("/v1/register",
186
  response_model=TokenResponse,
187
- summary="Register New User (Admin)",
188
- description="Create a new user account and return an access token and refresh token. Requires admin access (use 'admin' user with password 'admin54321' initially).",
189
  tags=["Authentication"],
190
  responses={
191
  200: {"description": "User registered successfully", "model": TokenResponse},
192
  400: {"description": "Username already exists"},
193
- 403: {"description": "Admin access required"}
 
 
194
  })
195
  async def register_user(
196
  register_request: RegisterRequest,
@@ -201,7 +203,7 @@ async def register_user(
201
  @app.post("/v1/app/register",
202
  response_model=TokenResponse,
203
  summary="Register New App User",
204
- description="Create a new user account for the mobile app using an encrypted email and device token. Returns an access token and refresh token. Rate limited to 5 requests per minute per IP. Requires X-Session-Key header.",
205
  tags=["Authentication"],
206
  responses={
207
  200: {"description": "User registered successfully", "model": TokenResponse},
 
184
 
185
  @app.post("/v1/register",
186
  response_model=TokenResponse,
187
+ summary="Register New User (Admin Only)",
188
+ description="Create a new user account in the `users` table. Only admin accounts can register new users (use 'admin' user with password 'admin54321' initially). Non-admin users are forbidden from modifying the users table.",
189
  tags=["Authentication"],
190
  responses={
191
  200: {"description": "User registered successfully", "model": TokenResponse},
192
  400: {"description": "Username already exists"},
193
+ 401: {"description": "Unauthorized - Valid admin token required"},
194
+ 403: {"description": "Forbidden - Admin access required"},
195
+ 500: {"description": "Registration failed due to server error"}
196
  })
197
  async def register_user(
198
  register_request: RegisterRequest,
 
203
  @app.post("/v1/app/register",
204
  response_model=TokenResponse,
205
  summary="Register New App User",
206
+ description="Create a new user account for the mobile app in the `app_users` table using an encrypted email and device token. Returns an access token and refresh token. Rate limited to 5 requests per minute per IP. Requires X-Session-Key header.",
207
  tags=["Authentication"],
208
  responses={
209
  200: {"description": "User registered successfully", "model": TokenResponse},
src/server/utils/auth.py CHANGED
@@ -21,6 +21,7 @@ engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
21
  Base = declarative_base()
22
  SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
23
 
 
24
  class User(Base):
25
  __tablename__ = "users"
26
  username = Column(String, primary_key=True, index=True)
@@ -28,6 +29,13 @@ class User(Base):
28
  is_admin = Column(Boolean, default=False)
29
  session_key = Column(String, nullable=True) # Stores base64-encoded session key
30
 
 
 
 
 
 
 
 
31
  # Ensure the /data directory exists
32
  os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
33
 
@@ -61,7 +69,7 @@ class Settings(BaseSettings):
61
 
62
  settings = Settings()
63
 
64
- # Seed initial data
65
  def seed_initial_data():
66
  db = SessionLocal()
67
  try:
@@ -142,9 +150,11 @@ async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(b
142
  user_id = token_data.sub
143
 
144
  db = SessionLocal()
 
145
  user = db.query(User).filter_by(username=user_id).first()
 
146
  db.close()
147
- if user_id is None or not user:
148
  logger.warning(f"Invalid or unknown user: {user_id}")
149
  raise credentials_exception
150
 
@@ -170,14 +180,41 @@ async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(b
170
  raise credentials_exception
171
 
172
  async def get_current_user_with_admin(credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)) -> str:
173
- user_id = await get_current_user(credentials)
174
- db = SessionLocal()
175
- user = db.query(User).filter_by(username=user_id).first()
176
- db.close()
177
- if not user or not user.is_admin:
178
- logger.warning(f"User {user_id} is not authorized as admin")
179
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
180
- return user_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  async def login(login_request: LoginRequest, session_key_b64: str) -> TokenResponse:
183
  db = SessionLocal()
@@ -189,14 +226,23 @@ async def login(login_request: LoginRequest, session_key_b64: str) -> TokenRespo
189
  db.close()
190
  raise HTTPException(status_code=400, detail="Invalid encrypted data")
191
 
 
192
  user = db.query(User).filter_by(username=username).first()
193
- if not user or not pwd_context.verify(password, user.password):
 
 
 
 
 
 
 
 
194
  db.close()
195
  logger.warning(f"Login failed for user: {username}")
196
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or device token")
197
 
198
- if user.session_key != session_key_b64:
199
- user.session_key = session_key_b64
200
  db.commit()
201
  db.close()
202
 
@@ -205,21 +251,26 @@ async def login(login_request: LoginRequest, session_key_b64: str) -> TokenRespo
205
 
206
  async def register(register_request: RegisterRequest, current_user: str = Depends(get_current_user_with_admin)) -> TokenResponse:
207
  db = SessionLocal()
208
- existing_user = db.query(User).filter_by(username=register_request.username).first()
209
- if existing_user:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  db.close()
211
- logger.warning(f"Registration failed: Username {register_request.username} already exists")
212
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username already exists")
213
-
214
- hashed_password = pwd_context.hash(register_request.password)
215
- new_user = User(username=register_request.username, password=hashed_password, is_admin=False)
216
- db.add(new_user)
217
- db.commit()
218
- db.close()
219
-
220
- tokens = await create_access_token(user_id=register_request.username)
221
- logger.info(f"Registered and generated token for user: {register_request.username} by admin {current_user}")
222
- return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
223
 
224
  async def app_register(register_request: RegisterRequest, session_key_b64: str) -> TokenResponse:
225
  db = SessionLocal()
@@ -231,15 +282,17 @@ async def app_register(register_request: RegisterRequest, session_key_b64: str)
231
  db.close()
232
  raise HTTPException(status_code=400, detail="Invalid encrypted data")
233
 
 
234
  existing_user = db.query(User).filter_by(username=username).first()
235
- if existing_user:
 
236
  db.close()
237
  logger.warning(f"App registration failed: Email {username} already exists")
238
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
239
 
240
  hashed_password = pwd_context.hash(password)
241
- new_user = User(username=username, password=hashed_password, is_admin=False, session_key=session_key_b64)
242
- db.add(new_user)
243
  db.commit()
244
  db.close()
245
 
@@ -256,12 +309,12 @@ async def refresh_token(credentials: HTTPAuthorizationCredentials = Depends(bear
256
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type; refresh token required")
257
  user_id = token_data.sub
258
  db = SessionLocal()
 
259
  user = db.query(User).filter_by(username=user_id).first()
 
260
  db.close()
261
- if not user:
262
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
263
- if datetime.utcnow().timestamp() > token_data.exp:
264
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Refresh token has expired")
265
  tokens = await create_access_token(user_id=user_id)
266
  return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
267
  except jwt.InvalidTokenError:
 
21
  Base = declarative_base()
22
  SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
23
 
24
+ # Model for admin-related users
25
  class User(Base):
26
  __tablename__ = "users"
27
  username = Column(String, primary_key=True, index=True)
 
29
  is_admin = Column(Boolean, default=False)
30
  session_key = Column(String, nullable=True) # Stores base64-encoded session key
31
 
32
+ # Model for app users
33
+ class AppUser(Base):
34
+ __tablename__ = "app_users"
35
+ username = Column(String, primary_key=True, index=True)
36
+ password = Column(String) # Stores hashed passwords
37
+ session_key = Column(String, nullable=True) # Stores base64-encoded session key
38
+
39
  # Ensure the /data directory exists
40
  os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
41
 
 
69
 
70
  settings = Settings()
71
 
72
+ # Seed initial data for users table only
73
  def seed_initial_data():
74
  db = SessionLocal()
75
  try:
 
150
  user_id = token_data.sub
151
 
152
  db = SessionLocal()
153
+ # Check both users and app_users tables
154
  user = db.query(User).filter_by(username=user_id).first()
155
+ app_user = db.query(AppUser).filter_by(username=user_id).first()
156
  db.close()
157
+ if user_id is None or (not user and not app_user):
158
  logger.warning(f"Invalid or unknown user: {user_id}")
159
  raise credentials_exception
160
 
 
180
  raise credentials_exception
181
 
182
  async def get_current_user_with_admin(credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)) -> str:
183
+ token = credentials.credentials
184
+ credentials_exception = HTTPException(
185
+ status_code=status.HTTP_401_UNAUTHORIZED,
186
+ detail="Invalid authentication credentials",
187
+ headers={"WWW-Authenticate": "Bearer"},
188
+ )
189
+ try:
190
+ payload = jwt.decode(token, settings.api_key_secret, algorithms=["HS256"])
191
+ token_data = TokenPayload(**payload)
192
+ user_id = token_data.sub
193
+
194
+ db = SessionLocal()
195
+ user = db.query(User).filter_by(username=user_id).first()
196
+ db.close()
197
+ if not user:
198
+ logger.warning(f"User not found in users table: {user_id}")
199
+ raise credentials_exception
200
+ if not user.is_admin:
201
+ logger.warning(f"User {user_id} is not authorized as admin")
202
+ raise HTTPException(
203
+ status_code=status.HTTP_403_FORBIDDEN,
204
+ detail="Admin access required; only admin accounts can perform this action"
205
+ )
206
+
207
+ logger.info(f"Validated admin user: {user_id}")
208
+ return user_id
209
+ except jwt.InvalidSignatureError as e:
210
+ logger.error(f"Invalid signature error: {str(e)}")
211
+ raise credentials_exception
212
+ except jwt.InvalidTokenError as e:
213
+ logger.error(f"Other token error: {str(e)}")
214
+ raise credentials_exception
215
+ except Exception as e:
216
+ logger.error(f"Unexpected admin validation error: {str(e)}")
217
+ raise credentials_exception
218
 
219
  async def login(login_request: LoginRequest, session_key_b64: str) -> TokenResponse:
220
  db = SessionLocal()
 
226
  db.close()
227
  raise HTTPException(status_code=400, detail="Invalid encrypted data")
228
 
229
+ # Check both users and app_users tables
230
  user = db.query(User).filter_by(username=username).first()
231
+ app_user = db.query(AppUser).filter_by(username=username).first()
232
+
233
+ if not user and not app_user:
234
+ db.close()
235
+ logger.warning(f"Login failed for user: {username}")
236
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or device token")
237
+
238
+ target_user = user if user else app_user
239
+ if not pwd_context.verify(password, target_user.password):
240
  db.close()
241
  logger.warning(f"Login failed for user: {username}")
242
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or device token")
243
 
244
+ if target_user.session_key != session_key_b64:
245
+ target_user.session_key = session_key_b64
246
  db.commit()
247
  db.close()
248
 
 
251
 
252
  async def register(register_request: RegisterRequest, current_user: str = Depends(get_current_user_with_admin)) -> TokenResponse:
253
  db = SessionLocal()
254
+ try:
255
+ existing_user = db.query(User).filter_by(username=register_request.username).first()
256
+ if existing_user:
257
+ logger.warning(f"Registration failed: Username {register_request.username} already exists")
258
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username already exists")
259
+
260
+ hashed_password = pwd_context.hash(register_request.password)
261
+ new_user = User(username=register_request.username, password=hashed_password, is_admin=False)
262
+ db.add(new_user)
263
+ db.commit()
264
+ logger.info(f"Admin {current_user} successfully registered new user: {register_request.username}")
265
+
266
+ tokens = await create_access_token(user_id=register_request.username)
267
+ return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
268
+ except Exception as e:
269
+ db.rollback()
270
+ logger.error(f"Registration error by admin {current_user}: {str(e)}")
271
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Registration failed: {str(e)}")
272
+ finally:
273
  db.close()
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
  async def app_register(register_request: RegisterRequest, session_key_b64: str) -> TokenResponse:
276
  db = SessionLocal()
 
282
  db.close()
283
  raise HTTPException(status_code=400, detail="Invalid encrypted data")
284
 
285
+ # Check both tables to prevent duplicate usernames
286
  existing_user = db.query(User).filter_by(username=username).first()
287
+ existing_app_user = db.query(AppUser).filter_by(username=username).first()
288
+ if existing_user or existing_app_user:
289
  db.close()
290
  logger.warning(f"App registration failed: Email {username} already exists")
291
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
292
 
293
  hashed_password = pwd_context.hash(password)
294
+ new_app_user = AppUser(username=username, password=hashed_password, session_key=session_key_b64)
295
+ db.add(new_app_user)
296
  db.commit()
297
  db.close()
298
 
 
309
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type; refresh token required")
310
  user_id = token_data.sub
311
  db = SessionLocal()
312
+ # Check both users and app_users tables
313
  user = db.query(User).filter_by(username=user_id).first()
314
+ app_user = db.query(AppUser).filter_by(username=user_id).first()
315
  db.close()
316
+ if not user and not app_user:
317
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
 
 
318
  tokens = await create_access_token(user_id=user_id)
319
  return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
320
  except jwt.InvalidTokenError: