ScamDetect Bot commited on
Commit
396563b
·
1 Parent(s): 3d253e5

Auto-sync backend and fix configuration

Browse files
backend/api/routes/auth.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta
2
+ from typing import Optional
3
+ from fastapi import APIRouter, Depends, HTTPException, status
4
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
5
+ from jose import JWTError, jwt
6
+ from passlib.context import CryptContext
7
+ from sqlalchemy.orm import Session
8
+
9
+ from database import get_db
10
+ import models.db_models as db_models
11
+ import models.schemas as schemas
12
+ import os
13
+
14
+ # Configuration
15
+ SECRET_KEY = os.getenv("JWT_SECRET_KEY", "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7")
16
+ ALGORITHM = "HS256"
17
+ ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 1 week
18
+
19
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
20
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
21
+
22
+ router = APIRouter(prefix="/auth", tags=["Authentication"])
23
+
24
+ def verify_password(plain_password, hashed_password):
25
+ return pwd_context.verify(plain_password, hashed_password)
26
+
27
+ def get_password_hash(password):
28
+ return pwd_context.hash(password)
29
+
30
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
31
+ to_encode = data.copy()
32
+ if expires_delta:
33
+ expire = datetime.utcnow() + expires_delta
34
+ else:
35
+ expire = datetime.utcnow() + timedelta(minutes=15)
36
+ to_encode.update({"exp": expire})
37
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
38
+ return encoded_jwt
39
+
40
+ def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
41
+ if not token:
42
+ return None
43
+
44
+ credentials_exception = HTTPException(
45
+ status_code=status.HTTP_401_UNAUTHORIZED,
46
+ detail="Could not validate credentials",
47
+ headers={"WWW-Authenticate": "Bearer"},
48
+ )
49
+ try:
50
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
51
+ username: str = payload.get("sub")
52
+ if username is None:
53
+ raise credentials_exception
54
+ token_data = schemas.TokenData(username=username)
55
+ except JWTError:
56
+ raise credentials_exception
57
+
58
+ user = db.query(db_models.User).filter(db_models.User.username == token_data.username).first()
59
+ if user is None:
60
+ raise credentials_exception
61
+ return user
62
+
63
+ @router.post("/register", response_model=schemas.UserResponse)
64
+ def register_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
65
+ db_user = db.query(db_models.User).filter(
66
+ (db_models.User.username == user.username) | (db_models.User.email == user.email)
67
+ ).first()
68
+ if db_user:
69
+ raise HTTPException(status_code=400, detail="Username or email already registered")
70
+
71
+ hashed_password = get_password_hash(user.password)
72
+ db_user = db_models.User(
73
+ username=user.username,
74
+ email=user.email,
75
+ hashed_password=hashed_password
76
+ )
77
+ db.add(db_user)
78
+ db.commit()
79
+ db.refresh(db_user)
80
+ return db_user
81
+
82
+ @router.post("/login", response_model=schemas.Token)
83
+ def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
84
+ user = db.query(db_models.User).filter(db_models.User.username == form_data.username).first()
85
+ if not user or not verify_password(form_data.password, user.hashed_password):
86
+ raise HTTPException(
87
+ status_code=status.HTTP_401_UNAUTHORIZED,
88
+ detail="Incorrect username or password",
89
+ headers={"WWW-Authenticate": "Bearer"},
90
+ )
91
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
92
+ access_token = create_access_token(
93
+ data={"sub": user.username}, expires_delta=access_token_expires
94
+ )
95
+ return {"access_token": access_token, "token_type": "bearer"}
96
+
97
+ @router.get("/me", response_model=schemas.UserResponse)
98
+ def read_users_me(current_user: db_models.User = Depends(get_current_user)):
99
+ if not current_user:
100
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
101
+ return current_user
backend/api/routes/history.py CHANGED
@@ -1,17 +1,29 @@
1
- from fastapi import APIRouter, Depends
2
  from sqlalchemy.orm import Session
3
  from database import get_db
4
  import models.db_models as db_models
5
  import json
 
 
6
 
7
  router = APIRouter(prefix="/history", tags=["History"])
8
 
9
  @router.get("")
10
- async def get_scan_history(limit: int = 50, db: Session = Depends(get_db)):
 
 
 
 
 
11
  """Retrieve the most recent scan records from the database."""
12
- records = db.query(db_models.ScanRecord).order_by(
13
- db_models.ScanRecord.timestamp.desc()
14
- ).limit(limit).all()
 
 
 
 
 
15
 
16
  results = []
17
  for record in records:
@@ -24,6 +36,8 @@ async def get_scan_history(limit: int = 50, db: Session = Depends(get_db)):
24
  "threat_categories": json.loads(record.threat_categories) if record.threat_categories else [],
25
  "raw_text_extracted": record.raw_text_extracted,
26
  "behavioral_profile": json.loads(record.behavioral_profile) if getattr(record, "behavioral_profile", None) else None,
 
 
27
  "explanations": [
28
  {
29
  "feature": exp.feature,
 
1
+ from fastapi import APIRouter, Depends, Query
2
  from sqlalchemy.orm import Session
3
  from database import get_db
4
  import models.db_models as db_models
5
  import json
6
+ from typing import Optional
7
+ from api.routes.auth import get_current_user
8
 
9
  router = APIRouter(prefix="/history", tags=["History"])
10
 
11
  @router.get("")
12
+ async def get_scan_history(
13
+ limit: int = 50,
14
+ scope: str = Query("global", description="Scope of history: 'global' or 'my'"),
15
+ db: Session = Depends(get_db),
16
+ current_user: Optional[db_models.User] = Depends(get_current_user)
17
+ ):
18
  """Retrieve the most recent scan records from the database."""
19
+ query = db.query(db_models.ScanRecord)
20
+
21
+ if scope == "my":
22
+ if not current_user:
23
+ return [] # Or raise 401
24
+ query = query.filter(db_models.ScanRecord.user_id == current_user.id)
25
+
26
+ records = query.order_by(db_models.ScanRecord.timestamp.desc()).limit(limit).all()
27
 
28
  results = []
29
  for record in records:
 
36
  "threat_categories": json.loads(record.threat_categories) if record.threat_categories else [],
37
  "raw_text_extracted": record.raw_text_extracted,
38
  "behavioral_profile": json.loads(record.behavioral_profile) if getattr(record, "behavioral_profile", None) else None,
39
+ "source": getattr(record, "source", None),
40
+ "user_id": getattr(record, "user_id", None),
41
  "explanations": [
42
  {
43
  "feature": exp.feature,
backend/api/routes/scanner.py CHANGED
@@ -5,13 +5,15 @@ from services.scam_detection import process_image, process_video, process_url, p
5
  from sqlalchemy.orm import Session
6
  from database import get_db
7
  import models.db_models as db_models
 
 
8
  import json
9
  import asyncio
10
  from api.ws_manager import manager
11
 
12
  router = APIRouter(prefix="/scan", tags=["Scanner"])
13
 
14
- def save_scan_to_db(db: Session, result: ScanningResult):
15
  """Persist a ScanningResult into the SQLite database."""
16
  db_record = db_models.ScanRecord(
17
  id=result.id,
@@ -21,7 +23,9 @@ def save_scan_to_db(db: Session, result: ScanningResult):
21
  risk_level=result.risk_level,
22
  threat_categories=json.dumps(result.threat_categories),
23
  raw_text_extracted=result.raw_text_extracted,
24
- behavioral_profile=json.dumps(result.behavioral_profile) if result.behavioral_profile else None
 
 
25
  )
26
 
27
  for exp in result.explanations:
@@ -37,36 +41,68 @@ def save_scan_to_db(db: Session, result: ScanningResult):
37
  db.commit()
38
 
39
  @router.post("/image", response_model=ScanningResult)
40
- async def scan_image(file: UploadFile = File(...), ephemeral: str = Form("false"), db: Session = Depends(get_db)):
 
 
 
 
 
 
41
  contents = await file.read()
42
  result = process_image(contents)
 
 
43
  if ephemeral.lower() != "true":
44
- save_scan_to_db(db, result)
45
  broadcast_scan_result(result)
46
  return result
47
 
48
  @router.post("/video", response_model=ScanningResult)
49
- async def scan_video(file: UploadFile = File(...), ephemeral: str = Form("false"), db: Session = Depends(get_db)):
 
 
 
 
 
 
50
  contents = await file.read()
51
  result = process_video(contents)
 
 
52
  if ephemeral.lower() != "true":
53
- save_scan_to_db(db, result)
54
  broadcast_scan_result(result)
55
  return result
56
 
57
  @router.post("/url", response_model=ScanningResult)
58
- async def scan_url(url: str = Form(...), ephemeral: str = Form("false"), db: Session = Depends(get_db)):
 
 
 
 
 
 
59
  result = process_url(url)
 
 
60
  if ephemeral.lower() != "true":
61
- save_scan_to_db(db, result)
62
  broadcast_scan_result(result)
63
  return result
64
 
65
  @router.post("/text", response_model=ScanningResult)
66
- async def scan_text(text: str = Form(...), ephemeral: str = Form("false"), db: Session = Depends(get_db)):
 
 
 
 
 
 
67
  result = process_text(text)
 
 
68
  if ephemeral.lower() != "true":
69
- save_scan_to_db(db, result)
70
  broadcast_scan_result(result)
71
  return result
72
 
@@ -77,6 +113,9 @@ def broadcast_scan_result(result: ScanningResult):
77
  "risk_score": result.risk_score,
78
  "risk_level": result.risk_level,
79
  "threat_categories": result.threat_categories,
80
- "timestamp": result.timestamp
 
81
  }
 
 
82
  asyncio.create_task(manager.broadcast(minimal_payload))
 
5
  from sqlalchemy.orm import Session
6
  from database import get_db
7
  import models.db_models as db_models
8
+ from typing import Optional
9
+ from api.routes.auth import get_current_user
10
  import json
11
  import asyncio
12
  from api.ws_manager import manager
13
 
14
  router = APIRouter(prefix="/scan", tags=["Scanner"])
15
 
16
+ def save_scan_to_db(db: Session, result: ScanningResult, source: Optional[str] = None, user_id: Optional[int] = None):
17
  """Persist a ScanningResult into the SQLite database."""
18
  db_record = db_models.ScanRecord(
19
  id=result.id,
 
23
  risk_level=result.risk_level,
24
  threat_categories=json.dumps(result.threat_categories),
25
  raw_text_extracted=result.raw_text_extracted,
26
+ behavioral_profile=json.dumps(result.behavioral_profile) if result.behavioral_profile else None,
27
+ source=source,
28
+ user_id=user_id
29
  )
30
 
31
  for exp in result.explanations:
 
41
  db.commit()
42
 
43
  @router.post("/image", response_model=ScanningResult)
44
+ async def scan_image(
45
+ file: UploadFile = File(...),
46
+ ephemeral: str = Form("false"),
47
+ source: Optional[str] = Form(None),
48
+ db: Session = Depends(get_db),
49
+ current_user: Optional[db_models.User] = Depends(get_current_user)
50
+ ):
51
  contents = await file.read()
52
  result = process_image(contents)
53
+ result.source = source
54
+ result.user_id = current_user.id if current_user else None
55
  if ephemeral.lower() != "true":
56
+ save_scan_to_db(db, result, source, current_user.id if current_user else None)
57
  broadcast_scan_result(result)
58
  return result
59
 
60
  @router.post("/video", response_model=ScanningResult)
61
+ async def scan_video(
62
+ file: UploadFile = File(...),
63
+ ephemeral: str = Form("false"),
64
+ source: Optional[str] = Form(None),
65
+ db: Session = Depends(get_db),
66
+ current_user: Optional[db_models.User] = Depends(get_current_user)
67
+ ):
68
  contents = await file.read()
69
  result = process_video(contents)
70
+ result.source = source
71
+ result.user_id = current_user.id if current_user else None
72
  if ephemeral.lower() != "true":
73
+ save_scan_to_db(db, result, source, current_user.id if current_user else None)
74
  broadcast_scan_result(result)
75
  return result
76
 
77
  @router.post("/url", response_model=ScanningResult)
78
+ async def scan_url(
79
+ url: str = Form(...),
80
+ ephemeral: str = Form("false"),
81
+ source: Optional[str] = Form(None),
82
+ db: Session = Depends(get_db),
83
+ current_user: Optional[db_models.User] = Depends(get_current_user)
84
+ ):
85
  result = process_url(url)
86
+ result.source = source
87
+ result.user_id = current_user.id if current_user else None
88
  if ephemeral.lower() != "true":
89
+ save_scan_to_db(db, result, source, current_user.id if current_user else None)
90
  broadcast_scan_result(result)
91
  return result
92
 
93
  @router.post("/text", response_model=ScanningResult)
94
+ async def scan_text(
95
+ text: str = Form(...),
96
+ ephemeral: str = Form("false"),
97
+ source: Optional[str] = Form(None),
98
+ db: Session = Depends(get_db),
99
+ current_user: Optional[db_models.User] = Depends(get_current_user)
100
+ ):
101
  result = process_text(text)
102
+ result.source = source
103
+ result.user_id = current_user.id if current_user else None
104
  if ephemeral.lower() != "true":
105
+ save_scan_to_db(db, result, source, current_user.id if current_user else None)
106
  broadcast_scan_result(result)
107
  return result
108
 
 
113
  "risk_score": result.risk_score,
114
  "risk_level": result.risk_level,
115
  "threat_categories": result.threat_categories,
116
+ "timestamp": result.timestamp,
117
+ "source": result.source
118
  }
119
+
120
+ # Broadcast to all clients for real-time alerting
121
  asyncio.create_task(manager.broadcast(minimal_payload))
backend/main.py CHANGED
@@ -4,6 +4,7 @@ from api.routes import scanner
4
  from api.routes import history
5
  from api.routes import stats
6
  from api.routes import export
 
7
  from api.ws_manager import manager
8
  import os
9
 
@@ -41,6 +42,7 @@ app.include_router(scanner.router, prefix="/api")
41
  app.include_router(history.router, prefix="/api")
42
  app.include_router(stats.router, prefix="/api")
43
  app.include_router(export.router, prefix="/api")
 
44
 
45
  # WebSocket endpoint — mounted directly on app to bypass CORS middleware
46
  @app.websocket("/ws/notifications")
 
4
  from api.routes import history
5
  from api.routes import stats
6
  from api.routes import export
7
+ from api.routes import auth
8
  from api.ws_manager import manager
9
  import os
10
 
 
42
  app.include_router(history.router, prefix="/api")
43
  app.include_router(stats.router, prefix="/api")
44
  app.include_router(export.router, prefix="/api")
45
+ app.include_router(auth.router, prefix="/api")
46
 
47
  # WebSocket endpoint — mounted directly on app to bypass CORS middleware
48
  @app.websocket("/ws/notifications")