Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Backend/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
"""Handles application configuration using environment variables."""
# This will now throw an error if SECRET_KEY is missing from .env
SECRET_KEY: str
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24

model_config = {"env_file": ".env"}

settings = Settings()
26 changes: 13 additions & 13 deletions Backend/app/db/seed.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from datetime import datetime
from datetime import datetime, timezone
from app.db.db import AsyncSessionLocal
from app.models.models import User

from ..services.auth_service import hash_password # Import the hashing utility

async def seed_db():
"""
Seeds the database with initial creator and brand users.
Includes hashed passwords to satisfy database constraints.
"""
users = [
{
"id": "aabb1fd8-ba93-4e8c-976e-35e5c40b809c",
Expand All @@ -13,7 +17,7 @@ async def seed_db():
"role": "creator",
"bio": "Lifestyle and travel content creator",
"profile_image": None,
"created_at": datetime.utcnow()
"created_at": datetime.now(timezone.utc) # Timezone-aware
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

datetime.now(timezone.utc) passed to a TIMESTAMP WITHOUT TIME ZONE column will crash the seed under asyncpg

User.created_at is Column(TIMESTAMP, default=datetime.utcnow) — bare TIMESTAMP in SQLAlchemy maps to TIMESTAMP WITHOUT TIME ZONE in PostgreSQL. asyncpg raises a DataError because timezone-aware Python datetime objects are being passed to TIMESTAMP WITHOUT TIME ZONE columns. The specific error is: asyncpg.exceptions.DataError: invalid input for query argument: datetime.datetime(..., tzinfo=datetime.timezone.utc) (can't subtract offset-naive and offset-aware datetimes). This is triggered when code changes from database-generated timestamps to explicit Python datetime.now(timezone.utc) datetimes on a TIMESTAMP WITHOUT TIME ZONE column.

The seed will fail at session.commit() on every fresh run.

Two consistent fixes — pick one and apply it everywhere created_at is written:

Option A — keep columns naive (minimal change in seed only):

🐛 Proposed fix (Option A)
-            "created_at": datetime.now(timezone.utc) # Timezone-aware
+            "created_at": datetime.now(timezone.utc).replace(tzinfo=None)

Option B — make the column timezone-aware throughout (preferred long-term): Apply this to Backend/app/models/models.py so the column type and seed value are consistent:

-    created_at = Column(TIMESTAMP, default=datetime.utcnow)
+    created_at = Column(TIMESTAMP(timezone=True), default=lambda: datetime.now(timezone.utc))

With Option B the seed values (datetime.now(timezone.utc)) are already correct and no change to seed.py is needed. This also eliminates the use of the deprecated datetime.utcnow in the model.

Also applies to: 30-30, 51-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/db/seed.py` at line 20, The seed fails because seed.py is writing
timezone-aware datetimes into a TIMESTAMP WITHOUT TIME ZONE column
(User.created_at); fix by choosing one consistent approach and applying it
everywhere you set created_at: either convert all seed datetimes to naive UTC
(e.g., use datetime.utcnow() or call .replace(tzinfo=None) before assigning) to
match the existing Column(TIMESTAMP, default=...) in models.py, or (recommended)
update the model definition for User.created_at in models.py to use
TIMESTAMP(timezone=True)/DateTime(timezone=True) and replace deprecated
datetime.utcnow defaults so the column accepts timezone-aware
datetime.now(timezone.utc); pick one option and make the corresponding change
across seed.py and models.py wherever created_at is written.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and read the User model definition
find . -name "models.py" -path "*/Backend/app/models/*" | head -5

Repository: AOSSIE-Org/InPactAI

Length of output: 93


🏁 Script executed:

# Read the seed.py file to confirm the timezone-aware datetime usage
find . -name "seed.py" -path "*/Backend/app/db/*" | head -5

Repository: AOSSIE-Org/InPactAI

Length of output: 87


🌐 Web query:

asyncpg timezone-aware datetime TIMESTAMP WITHOUT TIME ZONE error

💡 Result:

In asyncpg, PostgreSQL type mappings are strict:

  • TIMESTAMP WITHOUT TIME ZONE (timestamp) ↔ offset-naïve datetime (no tzinfo)
  • TIMESTAMP WITH TIME ZONE (timestamptz) ↔ offset-aware datetime (has tzinfo)
    [1]

So you’ll get an error when you pass a timezone-aware Python datetime (e.g. tzinfo=UTC) into a SQL parameter/column typed TIMESTAMP WITHOUT TIME ZONE, because asyncpg refuses to silently drop timezone info. This is commonly described as asyncpg raising if you try to store a timezone-aware datetime into a “no timezone” column. [2]

Fix options (pick one)

  1. Use the right DB type (recommended): change the column to timestamptz
ALTER TABLE my_table
  ALTER COLUMN created_at TYPE timestamptz
  USING created_at AT TIME ZONE 'UTC';

Postgres conversions between timestamp types typically assume the session timezone, and AT TIME ZONE lets you specify it explicitly. [3]

  1. If the column must stay timestamp, pass a naïve datetime (commonly “UTC but naïve”)
from datetime import timezone

dt_aware = dt  # tz-aware
dt_naive_utc = dt_aware.astimezone(timezone.utc).replace(tzinfo=None)
await conn.execute("INSERT INTO t(ts) VALUES($1)", dt_naive_utc)
  1. Convert in SQL at insert time (store a UTC “wall clock” timestamp in a timestamp column)
INSERT INTO t(ts)
VALUES (($1::timestamptz AT TIME ZONE 'UTC'));

(AT TIME ZONE is the standard Postgres way to control the interpretation/conversion.) [3]

Rule of thumb: if you want an “instant in time”, use timestamptz; if you want a “local calendar time” (no timezone meaning), use timestamp. [3]

Sources: asyncpg type mapping table [1], discussion of asyncpg refusing tz-aware datetimes for “no timezone” columns [2], PostgreSQL timestamp/timezone semantics and AT TIME ZONE behavior [3].

[1] (magicstack.github.io)
[2] (safir.lsst.io)
[3] (postgresql.org)

Citations:


🏁 Script executed:

# Read the User model definition
cat -n ./Backend/app/models/models.py | grep -A 20 "class User"

Repository: AOSSIE-Org/InPactAI

Length of output: 1925


🏁 Script executed:

# Read seed.py to verify the timezone-aware datetime usage at lines 20, 30, 51
cat -n ./Backend/app/db/seed.py | head -60

Repository: AOSSIE-Org/InPactAI

Length of output: 2613


Timezone-aware datetime passed to TIMESTAMP WITHOUT TIME ZONE column will cause asyncpg to fail

User.created_at is defined as Column(TIMESTAMP, default=datetime.utcnow) — SQLAlchemy maps bare TIMESTAMP to TIMESTAMP WITHOUT TIME ZONE in PostgreSQL. The seed is passing datetime.now(timezone.utc) (timezone-aware) to this column at lines 20, 30, and 51, which will cause asyncpg to raise asyncpg.exceptions.DataError at session.commit().

Recommended fix — strip timezone from seed values (matches your preference for naive datetimes):

Proposed fix
-            "created_at": datetime.now(timezone.utc) # Timezone-aware
+            "created_at": datetime.now(timezone.utc).replace(tzinfo=None)

This keeps the column naive (consistent with the model default datetime.utcnow and your broader preference for naive datetimes in the codebase).

Alternatively, you could make the column timezone-aware throughout (TIMESTAMP(timezone=True) in the model), but this would require migrating the column type and updating other naive timestamp columns in the schema.

Also applies to: 30-30, 51-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/db/seed.py` at line 20, Seed values are passing timezone-aware
datetimes to a TIMESTAMP WITHOUT TIME ZONE column: replace calls to
datetime.now(timezone.utc) used for User.created_at in Backend/app/db/seed.py
with naive datetimes (e.g., use datetime.utcnow() or strip tzinfo) so they match
the model's User.created_at (Column(TIMESTAMP, default=datetime.utcnow)) and
avoid asyncpg DataError on session.commit(). Ensure all occurrences (the three
places setting "created_at") are updated.

},
{
"id": "6dbfcdd5-795f-49c1-8f7a-a5538b8c6f6f",
Expand All @@ -23,27 +27,24 @@ async def seed_db():
"role": "brand",
"bio": "Sustainable fashion brand looking for influencers",
"profile_image": None,
"created_at": datetime.utcnow()
"created_at": datetime.now(timezone.utc) # Timezone-aware
},
]

# Insert or update the users
async with AsyncSessionLocal() as session:
for user_data in users:
# Check if user exists
existing_user = await session.execute(
User.__table__.select().where(User.email == user_data["email"])
)
existing_user = existing_user.scalar_one_or_none()
# Correct check for user existence
existing_user = await session.get(User, user_data["id"])

if existing_user:
continue
else:
# Create new user
# Create new user with hashed_password
user = User(
id=user_data["id"],
username=user_data["username"],
email=user_data["email"],
hashed_password=hash_password(user_data["password"]), # Fixes seed failure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded weak seed credentials are flagged by secret scanners

user_data["password"] (lines 16 and 26) holds the literal "password123", which is committed to source control and will be flagged by secret-scanning tools. For a seed file, this is low risk, but externalising it is a simple improvement:

🔒 Suggested improvement
+import os
 ...
-        "password": "password123",
+        "password": os.getenv("SEED_USER_PASSWORD", "changeme_dev_only"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/db/seed.py` at line 47, The seed file currently uses a hardcoded
weak password via user_data["password"] which gets passed to hash_password when
creating users; replace this by reading the seed password from a configurable
source (e.g., an environment variable like SEED_USER_PASSWORD) or generate a
secure random password at runtime and store it in user_data before calling
hash_password so no literal "password123" is committed; update any references to
user_data and ensure the fallback behavior is explicit (fail fast if env var is
required or log/save the generated password securely) and keep the call to
hash_password(hashed_password=...) unchanged.

role=user_data["role"],
profile_image=user_data["profile_image"],
bio=user_data["bio"],
Expand All @@ -52,6 +53,5 @@ async def seed_db():
session.add(user)
print(f"Created user: {user_data['email']}")

# Commit the session
await session.commit()
print("✅ Users seeded successfully.")
print("✅ Users seeded successfully.")
2 changes: 2 additions & 0 deletions Backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .routes.chat import router as chat_router
from .routes.match import router as match_router
from sqlalchemy.exc import SQLAlchemyError
from .routes import auth
import logging
import os
from dotenv import load_dotenv
Expand Down Expand Up @@ -56,6 +57,7 @@ async def lifespan(app: FastAPI):
app.include_router(match_router)
app.include_router(ai.router)
app.include_router(ai.youtube_router)
app.include_router(auth.router)


@app.get("/")
Expand Down
33 changes: 19 additions & 14 deletions Backend/app/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,22 @@ class User(Base):
__tablename__ = "users"

id = Column(String, primary_key=True, default=generate_uuid)
username = Column(String, unique=True, nullable=False)
email = Column(String, unique=True, nullable=False)
# password_hash = Column(Text, nullable=False) # Removed as Supabase handles auth
role = Column(String, nullable=False) # 'creator' or 'brand'
# Added index=True to username and email for faster login lookups
username = Column(String, unique=True, index=True, nullable=False)
email = Column(String, unique=True, index=True, nullable=False)

# Restored hashed_password for custom JWT authentication
hashed_password = Column(String, nullable=False)

role = Column(String, nullable=False, default="creator") # 'creator' or 'brand'
profile_image = Column(Text, nullable=True)
bio = Column(Text, nullable=True)
created_at = Column(TIMESTAMP, default=datetime.utcnow)

is_online = Column(Boolean, default=False) # ✅ Track if user is online
is_online = Column(Boolean, default=False)
last_seen = Column(TIMESTAMP, default=datetime.utcnow)

# Existing Relationships
audience = relationship("AudienceInsights", back_populates="user", uselist=False)
sponsorships = relationship("Sponsorship", back_populates="brand")
posts = relationship("UserPost", back_populates="user")
Expand Down Expand Up @@ -66,7 +71,7 @@ class AudienceInsights(Base):
time_of_attention = Column(Integer) # in seconds
price_expectation = Column(DECIMAL(10, 2))
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)
Comment on lines 73 to 75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

DateTime(timezone=True) paired with datetime.utcnow produces naive datetimes — use datetime.now(timezone.utc) across all updated timestamp columns.

datetime.utcnow() returns a naive datetime (no tzinfo). Pairing it with DateTime(timezone=True) means the default value inserted into the DB lacks timezone information, which is inconsistent with the declared column type and causes silent data integrity issues on timezone-aware DB backends (e.g. PostgreSQL TIMESTAMPTZ). This affects all the updated DateTime(timezone=True) columns: AudienceInsights.created_at, Sponsorship.created_at, UserPost.created_at, SponsorshipApplication.applied_at, Collaboration.created_at, and SponsorshipPayment.transaction_date.

Additionally, datetime.utcnow is deprecated as of Python 3.12.

♻️ Proposed fix (representative — apply to all 6 columns)
-from datetime import datetime
+from datetime import datetime, timezone
 ...
-        DateTime(timezone=True), default=datetime.utcnow
+        DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/models/models.py` around lines 73 - 75, Change the naive UTC
defaults to timezone-aware datetimes for all DateTime(timezone=True) timestamp
columns: replace uses of datetime.utcnow (or datetime.utcnow()) with
datetime.now(timezone.utc) as the default factory for
AudienceInsights.created_at, Sponsorship.created_at, UserPost.created_at,
SponsorshipApplication.applied_at, Collaboration.created_at, and
SponsorshipPayment.transaction_date; ensure you import timezone from datetime
and apply the same change consistently across those column definitions so the DB
receives tz-aware timestamps.


user = relationship("User", back_populates="audience")
Expand All @@ -80,12 +85,12 @@ class Sponsorship(Base):
brand_id = Column(String, ForeignKey("users.id"), nullable=False)
title = Column(String, nullable=False)
description = Column(Text, nullable=False)
required_audience = Column(JSON) # {"age": ["18-24"], "location": ["USA", "UK"]}
required_audience = Column(JSON)
budget = Column(DECIMAL(10, 2))
engagement_minimum = Column(Float)
status = Column(String, default="open")
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

brand = relationship("User", back_populates="sponsorships")
Expand All @@ -102,9 +107,9 @@ class UserPost(Base):
content = Column(Text, nullable=False)
post_url = Column(Text, nullable=True)
category = Column(String, nullable=True)
engagement_metrics = Column(JSON) # {"likes": 500, "comments": 100, "shares": 50}
engagement_metrics = Column(JSON)
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

user = relationship("User", back_populates="posts")
Expand All @@ -121,7 +126,7 @@ class SponsorshipApplication(Base):
proposal = Column(Text, nullable=False)
status = Column(String, default="pending")
applied_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

creator = relationship("User", back_populates="applications")
Expand All @@ -138,7 +143,7 @@ class Collaboration(Base):
collaboration_details = Column(Text, nullable=False)
status = Column(String, default="pending")
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)


Expand All @@ -153,10 +158,10 @@ class SponsorshipPayment(Base):
amount = Column(DECIMAL(10, 2), nullable=False)
status = Column(String, default="pending")
transaction_date = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

creator = relationship("User", foreign_keys=[creator_id], back_populates="payments")
brand = relationship(
"User", foreign_keys=[brand_id], back_populates="brand_payments"
)
)
Empty file removed Backend/app/models/users.py
Empty file.
63 changes: 58 additions & 5 deletions Backend/app/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,60 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from ..db.db import get_db
from ..models.models import User
from ..services.auth_service import hash_password, verify_password, create_access_token
from pydantic import BaseModel, EmailStr
from ..services.get_current_user import get_current_user

router = APIRouter()
router = APIRouter(prefix="/auth", tags=["Authentication"])

@router.get("/auth/ping")
def ping():
return {"message": "Auth route is working!"}
# Pydantic schemas for data validation
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
Comment on lines +13 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

UserCreate has no role field — all users silently default to "creator", locking out brand signups.

The User model supports "creator" and "brand" roles, but there is no way for a brand to register through this endpoint. If brand-specific signup is intentional (e.g. a separate flow), document it; otherwise add the field.

♻️ Proposed fix
 class UserCreate(BaseModel):
     username: str
     email: EmailStr
     password: str
+    role: str = "creator"  # 'creator' or 'brand'

And in the signup handler:

     new_user = User(
         username=user_data.username,
         email=user_data.email,
         hashed_password=hash_password(user_data.password),
+        role=user_data.role,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
role: str = "creator" # 'creator' or 'brand'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/routes/auth.py` around lines 13 - 16, UserCreate currently lacks
a role field so all signups default to "creator"; add a role attribute to the
UserCreate Pydantic model (e.g., role: Optional[Literal["creator","brand"]] =
"creator" or a required Literal if you want explicit choice) and update the
signup handler (the function that consumes UserCreate and creates the User
record) to pass this role into user creation; also validate allowed values
(Literal or Enum) to prevent invalid inputs and adjust any downstream logic or
docs if brand signups should be restricted to a separate flow.


class UserLogin(BaseModel):
email: EmailStr
password: str

@router.post("/signup")
async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
# Check if user already exists
result = await db.execute(select(User).where(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Email already registered")

new_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hash_password(user_data.password)
)
db.add(new_user)
await db.commit()
await db.refresh(new_user)
return {"message": "User created successfully"}
Comment on lines +22 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Signup silently raises a 500 on duplicate username — check uniqueness before inserting.

The endpoint checks for duplicate email (line 25) but not username. Since username is declared unique=True in the User model, submitting a duplicate username will surface as an unhandled IntegrityError (HTTP 500) rather than a clean HTTP 400.

🐛 Proposed fix
 `@router.post`("/signup")
 async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
     # Check if user already exists
     result = await db.execute(select(User).where(User.email == user_data.email))
     if result.scalars().first():
         raise HTTPException(status_code=400, detail="Email already registered")

+    result = await db.execute(select(User).where(User.username == user_data.username))
+    if result.scalars().first():
+        raise HTTPException(status_code=400, detail="Username already taken")
+
     new_user = User(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@router.post("/signup")
async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
# Check if user already exists
result = await db.execute(select(User).where(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Email already registered")
new_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hash_password(user_data.password)
)
db.add(new_user)
await db.commit()
await db.refresh(new_user)
return {"message": "User created successfully"}
`@router.post`("/signup")
async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
# Check if user already exists
result = await db.execute(select(User).where(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Email already registered")
result = await db.execute(select(User).where(User.username == user_data.username))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Username already taken")
new_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hash_password(user_data.password)
)
db.add(new_user)
await db.commit()
await db.refresh(new_user)
return {"message": "User created successfully"}
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 23-23: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/routes/auth.py` around lines 22 - 37, The signup endpoint
currently only checks email uniqueness and can raise an unhandled IntegrityError
for duplicate usernames; update the signup function to explicitly check for
existing username by querying select(User).where(User.username ==
user_data.username) and raise HTTPException(status_code=400, detail="Username
already registered") before creating the User, and additionally wrap the
db.commit()/db.refresh(new_user) in a try/except catching
sqlalchemy.exc.IntegrityError to convert any remaining constraint violations
into HTTP 400 errors for robustness.


@router.post("/login")
async def login(credentials: UserLogin, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.email == credentials.email))
user = result.scalars().first()

if not user or not verify_password(credentials.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password"
)

access_token = create_access_token(data={"sub": str(user.id)})
return {"access_token": access_token, "token_type": "bearer"}

@router.get("/me")
async def get_me(current_user: User = Depends(get_current_user)):
return {
"id": str(current_user.id),
"username": current_user.username,
"email": current_user.email,
"role": current_user.role
}
22 changes: 22 additions & 0 deletions Backend/app/services/auth_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from datetime import datetime, timedelta, timezone
from jose import jwt
from passlib.context import CryptContext
from ..config import settings

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str):
"""Hashes a plain text password using bcrypt."""
return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str):
"""Verifies a plain text password against a stored hash."""
return pwd_context.verify(plain_password, hashed_password)

def create_access_token(data: dict):
"""Generates a secure JWT access token with a timezone-aware expiry."""
to_encode = data.copy()
# Using timezone-aware UTC to fix CodeRabbit's 'Minor' issue
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
37 changes: 37 additions & 0 deletions Backend/app/services/get_current_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

SQLAlchemy 2.0 sqlalchemy.future deprecated removed

💡 Result:

In SQLAlchemy 1.4, sqlalchemy.future was a transitional package (e.g., from sqlalchemy.future import select, sqlalchemy.future.Engine/Connection) used to run in “2.0 mode” while still on 1.4. (docs.sqlalchemy.org)

In SQLAlchemy 2.0, those “future” APIs are effectively the default way of working, so you should stop importing from sqlalchemy.future and import from the normal locations instead (e.g., from sqlalchemy import select, create_engine). The SQLAlchemy 2.0 docs describe the old “future mode” page as a 1.4→2.0 transition artifact. (docs.sqlalchemy.org)

Also in 2.0, the future= parameter on create_engine() / Session is kept only for backwards compatibility; if you specify it, it must be True (so you can usually just remove it). (docs.sqlalchemy.org)

Citations:


🏁 Script executed:

# Check if the files contain the imports mentioned
find Backend -name "get_current_user.py" -o -name "auth.py" | head -20

Repository: AOSSIE-Org/InPactAI

Length of output: 130


🏁 Script executed:

# Find and check the requirements/setup files for SQLAlchemy version
fd -e txt -e cfg -e toml -e ini -e lock | xargs grep -l -i "sqlalchemy" 2>/dev/null | head -10

Repository: AOSSIE-Org/InPactAI

Length of output: 87


🏁 Script executed:

# Check the imports in both files
cat -n Backend/app/services/get_current_user.py | head -10

Repository: AOSSIE-Org/InPactAI

Length of output: 513


🏁 Script executed:

cat -n Backend/app/routes/auth.py | head -10

Repository: AOSSIE-Org/InPactAI

Length of output: 586


🏁 Script executed:

# Check SQLAlchemy version in requirements
cat Backend/requirements.txt | grep -i sqlalchemy

Repository: AOSSIE-Org/InPactAI

Length of output: 81


Replace from sqlalchemy.future import select with from sqlalchemy import select.

sqlalchemy.future was a forward-compatibility shim in SQLAlchemy 1.4. In SQLAlchemy 2.0 (in use here: SQLAlchemy==2.0.40), the 2.0-style select() is the standard API, and imports should come directly from sqlalchemy. The same fix applies to Backend/app/routes/auth.py line 3.

♻️ Fix
-from sqlalchemy.future import select
+from sqlalchemy import select
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from sqlalchemy.future import select
from sqlalchemy import select
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/services/get_current_user.py` at line 5, Replace the deprecated
import "from sqlalchemy.future import select" with the SQLAlchemy 2.0 import
"from sqlalchemy import select" in the modules that use it (e.g.,
get_current_user.py and auth.py); locate the top-level import statement that
references sqlalchemy.future and change it to import select directly from
sqlalchemy so the code uses the 2.0-style API.

from ..db.db import get_db
from ..models.models import User
from ..config import settings

# This tells FastAPI where to look for the token (the /auth/login route)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

OAuth2PasswordBearer(tokenUrl="auth/login") points to a JSON-based endpoint — Swagger "Authorize" will not work.

OAuth2PasswordBearer expects the tokenUrl endpoint to accept application/x-www-form-urlencoded (OAuth2 standard with username/password form fields). The /auth/login endpoint instead accepts a JSON body (UserLogin). The token URL in the scheme is only used for Swagger UI's "Authorize" dialog, so this won't cause a runtime auth failure, but the interactive docs will be broken and misleading for API consumers.

Either change /auth/login to accept OAuth2PasswordRequestForm (form data), or use a plain HTTPBearer scheme and document the JSON login contract separately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/services/get_current_user.py` at line 11, The OAuth2 scheme
declaration oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login") points
to /auth/login which accepts JSON, breaking Swagger's "Authorize"; fix by either
(A) changing the /auth/login endpoint handler to accept an
OAuth2PasswordRequestForm (replace JSON UserLogin input with
OAuth2PasswordRequestForm and extract username/password to issue the token) so
Swagger form-based auth works, or (B) if you must keep JSON, replace the scheme
with an HTTPBearer scheme (e.g., use fastapi.security.HTTPBearer) and document
the JSON login contract separately; update any uses of oauth2_scheme
accordingly.


async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db)
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# Decode the JWT token
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception

# Fetch the user from the DB
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalars().first()

if user is None:
raise credentials_exception
return user
5 changes: 4 additions & 1 deletion Backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ postgrest==1.0.1
propcache==0.3.1
pydantic==2.11.1
pydantic_core==2.33.0
PyJWT==2.10.1
pytest==8.3.5
pytest-mock==3.14.0
python-dateutil==2.9.0.post0
Expand All @@ -53,3 +52,7 @@ urllib3==2.3.0
uvicorn==0.34.0
websockets==14.2
yarl==1.18.3
passlib[bcrypt]==1.7.4
bcrypt==4.0.1
python-jose[cryptography]==3.4.0
email-validator==2.1.0.post1pydantic-settings==2.7.1