Anish530 commited on
Commit
15785f4
·
1 Parent(s): aa27d2d

Added audit logs to be logged into database for staff ease

Browse files
backend/app/api/auth_routes.py CHANGED
@@ -10,9 +10,9 @@ from app.services.email_service import send_reset_password_email, send_verificat
10
  router = APIRouter(prefix="/auth", tags=["Auth"])
11
 
12
  @router.post("/login")
13
- def login(data: LoginRequest, db: Session = Depends(get_db)):
14
  try:
15
- token = login_user(db, data.email, data.password)
16
  if not token:
17
  raise HTTPException(status_code=401, detail="Invalid Credentials")
18
  return {"access_token": token}
@@ -77,8 +77,8 @@ def request_reset(
77
  return {"message": "If that email exists, a reset link has been sent."}
78
 
79
  @router.post("/reset-password")
80
- def reset_password(data: PasswordResetConfirm, db: Session = Depends(get_db)):
81
- success = confirm_password_reset(db, data.token, data.new_password)
82
  if not success:
83
  raise HTTPException(
84
  status_code=status.HTTP_400_BAD_REQUEST,
 
10
  router = APIRouter(prefix="/auth", tags=["Auth"])
11
 
12
  @router.post("/login")
13
+ def login(data: LoginRequest, request: Request, db: Session = Depends(get_db)):
14
  try:
15
+ token = login_user(db, data.email, data.password, request)
16
  if not token:
17
  raise HTTPException(status_code=401, detail="Invalid Credentials")
18
  return {"access_token": token}
 
77
  return {"message": "If that email exists, a reset link has been sent."}
78
 
79
  @router.post("/reset-password")
80
+ def reset_password(data: PasswordResetConfirm, request: Request, db: Session = Depends(get_db)):
81
+ success = confirm_password_reset(db, data.token, data.new_password, request)
82
  if not success:
83
  raise HTTPException(
84
  status_code=status.HTTP_400_BAD_REQUEST,
backend/app/models/audit_model.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, DateTime, JSON, ForeignKey
2
+ from app.db.database import Base
3
+ from datetime import datetime, UTC
4
+
5
+ class AuditLog(Base):
6
+ __tablename__ = "audit_logs"
7
+
8
+ id = Column(Integer, primary_key=True, index=True)
9
+ user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
10
+ action = Column(String, index=True, nullable=False)
11
+ timestamp = Column(DateTime, default=datetime.now(UTC), index=True)
12
+ ip = Column(String, nullable=True)
13
+ metadata_info = Column(JSON, nullable=True)
backend/app/services/audit_service.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from app.models.audit_model import AuditLog
3
+ from fastapi import Request
4
+ from app.core.request_id import get_request_id
5
+
6
+ def log_audit_event(
7
+ db: Session,
8
+ action: str,
9
+ user_id: int | None = None,
10
+ request: Request | None = None,
11
+ details: dict | None = None
12
+ ):
13
+ ip_address = None
14
+ if request and request.client:
15
+ ip_address = request.client.host
16
+
17
+ # ip_address = request.headers.get("X-Forwarded-For", request.client.host)
18
+ meta = details or {}
19
+ meta["request_id"] = get_request_id()
20
+
21
+ audit_entry = AuditLog(
22
+ user_id=user_id,
23
+ action=action,
24
+ ip=ip_address,
25
+ metadata_info=meta
26
+ )
27
+
28
+ db.add(audit_entry)
29
+ db.commit()
backend/app/services/auth_service.py CHANGED
@@ -4,17 +4,21 @@ from sqlalchemy.orm import Session
4
  from app.models.user_model import User
5
  from app.utils.security import verify_password, hash_password
6
  from app.utils.jwt_handler import create_access_token
 
 
7
 
8
- def login_user(db: Session, email: str, password: str):
9
  user = db.query(User).filter(User.email == email).first()
10
 
11
  if not user:
 
12
  return None
13
 
14
  if user.locked_until:
15
  if datetime.now(UTC).replace(tzinfo=None) < user.locked_until:
16
  time_left = user.locked_until - datetime.now(UTC).replace(tzinfo=None)
17
  minutes_left = int(time_left.total_seconds() / 60) + 1
 
18
  raise ValueError(f"locked_{minutes_left}")
19
  else:
20
  user.locked_until = None
@@ -27,10 +31,12 @@ def login_user(db: Session, email: str, password: str):
27
  if user.failed_attempts >= 5:
28
  user.locked_until = datetime.now(UTC).replace(tzinfo=None) + timedelta(minutes=15)
29
  db.commit()
 
30
  raise ValueError("locked_15")
31
  db.commit()
32
 
33
  attempts_left = 5 - user.failed_attempts
 
34
  raise ValueError(f"attempt_{attempts_left}")
35
 
36
  user.failed_attempts = 0
@@ -38,9 +44,11 @@ def login_user(db: Session, email: str, password: str):
38
  db.commit()
39
 
40
  if not user.is_verified:
 
41
  raise ValueError("unverified")
42
 
43
  token = create_access_token({"user_id": user.id})
 
44
  return token
45
 
46
  def request_password_reset(db: Session, email: str) -> str | None:
@@ -58,7 +66,7 @@ def request_password_reset(db: Session, email: str) -> str | None:
58
 
59
  return raw_token
60
 
61
- def confirm_password_reset(db: Session, token: str, new_password: str) -> bool:
62
  active_reset_users = db.query(User).filter(User.reset_token_hash.isnot(None)).all()
63
 
64
  target_user = None
@@ -79,6 +87,7 @@ def confirm_password_reset(db: Session, token: str, new_password: str) -> bool:
79
 
80
  db.commit()
81
 
 
82
  return True
83
 
84
  def confirm_email_verification(db: Session, token: str) -> bool:
 
4
  from app.models.user_model import User
5
  from app.utils.security import verify_password, hash_password
6
  from app.utils.jwt_handler import create_access_token
7
+ from fastapi import Request
8
+ from app.services.audit_service import log_audit_event
9
 
10
+ def login_user(db: Session, email: str, password: str, request: Request):
11
  user = db.query(User).filter(User.email == email).first()
12
 
13
  if not user:
14
+ log_audit_event(db, "login_failed", None, request, {"email_attempted": email, "reason": "user_not_found"})
15
  return None
16
 
17
  if user.locked_until:
18
  if datetime.now(UTC).replace(tzinfo=None) < user.locked_until:
19
  time_left = user.locked_until - datetime.now(UTC).replace(tzinfo=None)
20
  minutes_left = int(time_left.total_seconds() / 60) + 1
21
+ log_audit_event(db, "login_blocked", user.id, request, {"reason": "account_locked", "minutes_left": minutes_left})
22
  raise ValueError(f"locked_{minutes_left}")
23
  else:
24
  user.locked_until = None
 
31
  if user.failed_attempts >= 5:
32
  user.locked_until = datetime.now(UTC).replace(tzinfo=None) + timedelta(minutes=15)
33
  db.commit()
34
+ log_audit_event(db, "account_locked", user.id, request, {"reason": "max_failed_attempts"})
35
  raise ValueError("locked_15")
36
  db.commit()
37
 
38
  attempts_left = 5 - user.failed_attempts
39
+ log_audit_event(db, "login_failed", user.id, request, {"reason": "bad_password", "attempts_left": attempts_left})
40
  raise ValueError(f"attempt_{attempts_left}")
41
 
42
  user.failed_attempts = 0
 
44
  db.commit()
45
 
46
  if not user.is_verified:
47
+ log_audit_event(db, "login_blocked", user.id, request, {"reason": "unverified_email"})
48
  raise ValueError("unverified")
49
 
50
  token = create_access_token({"user_id": user.id})
51
+ log_audit_event(db, "login_success", user.id, request, {"provider": "local"})
52
  return token
53
 
54
  def request_password_reset(db: Session, email: str) -> str | None:
 
66
 
67
  return raw_token
68
 
69
+ def confirm_password_reset(db: Session, token: str, new_password: str, request: Request) -> bool:
70
  active_reset_users = db.query(User).filter(User.reset_token_hash.isnot(None)).all()
71
 
72
  target_user = None
 
87
 
88
  db.commit()
89
 
90
+ log_audit_event(db, "password_reset", target_user.id, request)
91
  return True
92
 
93
  def confirm_email_verification(db: Session, token: str) -> bool: