Skip to content

Commit b603e73

Browse files
Revert "Don't return users with deleted column set to True"
This reverts commit 4ea6509.
1 parent e058660 commit b603e73

File tree

6 files changed

+27
-179
lines changed

6 files changed

+27
-179
lines changed

routers/authentication.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# auth.py
2-
from logging import getLogger, DEBUG
2+
from logging import getLogger
33
from typing import Optional
44
from datetime import datetime
55
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Form
@@ -22,7 +22,6 @@
2222
)
2323

2424
logger = getLogger("uvicorn.error")
25-
logger.setLevel(DEBUG)
2625

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

@@ -127,9 +126,7 @@ async def register(
127126
session: Session = Depends(get_session),
128127
) -> RedirectResponse:
129128
db_user = session.exec(select(User).where(
130-
User.email == user.email,
131-
User.deleted == False
132-
)).first()
129+
User.email == user.email)).first()
133130

134131
if db_user:
135132
raise HTTPException(status_code=400, detail="Email already registered")
@@ -159,9 +156,7 @@ async def login(
159156
session: Session = Depends(get_session),
160157
) -> RedirectResponse:
161158
db_user = session.exec(select(User).where(
162-
User.email == user.email,
163-
User.deleted == False
164-
)).first()
159+
User.email == user.email)).first()
165160
if not db_user or not verify_password(user.password, db_user.hashed_password):
166161
raise HTTPException(status_code=400, detail="Invalid credentials")
167162

@@ -210,9 +205,7 @@ async def refresh_token(
210205

211206
user_email = decoded_token.get("sub")
212207
db_user = session.exec(select(User).where(
213-
User.email == user_email,
214-
User.deleted == False
215-
)).first()
208+
User.email == user_email)).first()
216209
if not db_user:
217210
return RedirectResponse(url="/login", status_code=303)
218211

@@ -246,9 +239,7 @@ async def forgot_password(
246239
session: Session = Depends(get_session)
247240
):
248241
db_user = session.exec(select(User).where(
249-
User.email == user.email,
250-
User.deleted == False
251-
)).first()
242+
User.email == user.email)).first()
252243

253244
if db_user:
254245
background_tasks.add_task(send_reset_email, user.email, session)
@@ -264,13 +255,8 @@ async def reset_password(
264255
authorized_user, reset_token = get_user_from_reset_token(
265256
user.email, user.token, session)
266257

267-
logger.debug(f"authorized_user: {authorized_user}")
268-
logger.debug(f"reset_token: {reset_token}")
269-
270-
if not reset_token:
258+
if not authorized_user or not reset_token:
271259
raise HTTPException(status_code=400, detail="Invalid or expired token")
272-
elif not authorized_user:
273-
raise HTTPException(status_code=400, detail="User not found")
274260

275261
# Update password and mark token as used
276262
authorized_user.hashed_password = get_password_hash(user.new_password)

routers/user.py

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,6 @@ async def as_form(
3737
return cls(confirm_delete_password=confirm_delete_password)
3838

3939

40-
class UpdateProfile(BaseModel):
41-
"""Request model for updating user profile information"""
42-
name: str
43-
email: EmailStr
44-
avatar_url: str
45-
46-
@classmethod
47-
async def as_form(
48-
cls,
49-
name: str = Form(...),
50-
email: EmailStr = Form(...),
51-
avatar_url: str = Form(...),
52-
):
53-
return cls(name=name, email=email, avatar_url=avatar_url)
54-
55-
5640
# -- Routes --
5741

5842

@@ -64,38 +48,37 @@ async def view_profile(
6448
return {"user": current_user}
6549

6650

67-
@router.post("/update_profile", response_class=RedirectResponse)
68-
async def update_profile(
69-
profile_update: UpdateProfile = Depends(UpdateProfile.as_form),
51+
@router.post("/edit_profile", response_class=RedirectResponse)
52+
async def edit_profile(
53+
name: str = Form(...),
54+
email: str = Form(...),
55+
avatar_url: str = Form(...),
7056
current_user: User = Depends(get_authenticated_user),
7157
session: Session = Depends(get_session)
7258
):
7359
# Update user details
74-
current_user.name = profile_update.name
75-
current_user.email = profile_update.email
76-
current_user.avatar_url = profile_update.avatar_url
60+
current_user.name = name
61+
current_user.email = email
62+
current_user.avatar_url = avatar_url
7763
session.commit()
7864
session.refresh(current_user)
7965
return RedirectResponse(url="/profile", status_code=303)
8066

8167

8268
@router.post("/delete_account", response_class=RedirectResponse)
8369
async def delete_account(
84-
user_delete_account: UserDeleteAccount = Depends(
85-
UserDeleteAccount.as_form),
70+
confirm_delete_password: str = Form(...),
8671
current_user: User = Depends(get_authenticated_user),
8772
session: Session = Depends(get_session)
8873
):
89-
if not verify_password(user_delete_account.confirm_delete_password, current_user.hashed_password):
74+
if not verify_password(confirm_delete_password, current_user.hashed_password):
9075
raise HTTPException(status_code=400, detail="Password is incorrect")
9176

9277
# Mark the user as deleted
9378
current_user.deleted = True
9479
session.commit()
95-
96-
# Delete the user's access and refresh tokens to force a logout
97-
response = RedirectResponse(url="/", status_code=303)
98-
response.delete_cookie("access_token")
99-
response.delete_cookie("refresh_token")
100-
101-
return response
80+
#Logs Out
81+
router.get("/logout", response_class=RedirectResponse)
82+
# Deletes user
83+
session.delete(current_user)
84+
return RedirectResponse(url="/", status_code=303)

templates/users/profile.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ <h1 class="mb-4">User Profile</h1>
3434
Edit Profile
3535
</div>
3636
<div class="card-body">
37-
<form action="{{ url_for('update_profile') }}" method="post">
37+
<form action="{{ url_for('edit_profile') }}" method="post">
3838
<div class="mb-3">
3939
<label for="name" class="form-label">Name</label>
4040
<input type="text" class="form-control" id="name" name="name" value="{{ user.name }}">

tests/test_authentication.py

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,6 @@ def test_logout_endpoint(client: TestClient):
261261

262262

263263
def test_register_with_existing_email(client: TestClient, test_user: User):
264-
"""Test that registration fails with an existing non-deleted user's email"""
265-
# Ensure test user is not deleted
266-
assert not test_user.deleted
267-
268264
response = client.post(
269265
"/auth/register",
270266
data={
@@ -277,34 +273,6 @@ def test_register_with_existing_email(client: TestClient, test_user: User):
277273
assert response.status_code == 400
278274

279275

280-
def test_register_with_deleted_user_email(client: TestClient, test_user: User, session: Session):
281-
"""Test that registration succeeds with a deleted user's email"""
282-
# Mark test user as deleted
283-
test_user.deleted = True
284-
session.add(test_user)
285-
session.commit()
286-
287-
response = client.post(
288-
"/auth/register",
289-
data={
290-
"name": "New User",
291-
"email": test_user.email,
292-
"password": "Test123!@#",
293-
"confirm_password": "Test123!@#"
294-
},
295-
follow_redirects=False
296-
)
297-
assert response.status_code == 303
298-
299-
# Verify new user was created
300-
new_user = session.exec(select(User).where(
301-
User.email == test_user.email,
302-
User.deleted == False
303-
)).first()
304-
assert new_user is not None
305-
assert new_user.id != test_user.id
306-
307-
308276
def test_login_with_invalid_credentials(client: TestClient, test_user: User):
309277
response = client.post(
310278
"/auth/login",
@@ -393,74 +361,3 @@ def test_password_reset_email_url(client: TestClient, session: Session, test_use
393361
assert parsed.path == str(reset_password_path)
394362
assert query_params["email"][0] == test_user.email
395363
assert query_params["token"][0] == reset_token.token
396-
397-
398-
def test_deleted_user_cannot_login(client: TestClient, test_user: User, session: Session):
399-
"""Test that a deleted user cannot log in"""
400-
# First mark the user as deleted
401-
test_user.deleted = True
402-
session.add(test_user)
403-
session.commit()
404-
405-
response = client.post(
406-
"/auth/login",
407-
data={
408-
"email": test_user.email,
409-
"password": "Test123!@#"
410-
}
411-
)
412-
assert response.status_code == 400
413-
414-
415-
def test_deleted_user_cannot_use_tokens(client: TestClient, test_user: User, session: Session):
416-
"""Test that a deleted user's tokens become invalid"""
417-
# Create tokens before marking user as deleted
418-
access_token = create_access_token({"sub": test_user.email})
419-
refresh_token = create_refresh_token({"sub": test_user.email})
420-
421-
# Mark user as deleted
422-
test_user.deleted = True
423-
session.add(test_user)
424-
session.commit()
425-
426-
# Set tokens in cookies
427-
client.cookies.set("access_token", access_token)
428-
client.cookies.set("refresh_token", refresh_token)
429-
430-
# Try to refresh tokens
431-
response = client.post("/auth/refresh", follow_redirects=False)
432-
assert response.status_code == 303 # user is redirected to login
433-
434-
435-
def test_deleted_user_cannot_use_reset_token(client: TestClient, session: Session, test_user: User):
436-
"""Test that a deleted user cannot use a previously issued reset token"""
437-
# First create a reset token
438-
response = client.post(
439-
"/auth/forgot_password",
440-
data={"email": test_user.email},
441-
follow_redirects=False
442-
)
443-
assert response.status_code == 303
444-
445-
# Get the reset token
446-
reset_token = session.exec(select(PasswordResetToken)
447-
.where(PasswordResetToken.user_id == test_user.id)).first()
448-
assert reset_token is not None
449-
450-
# Now mark user as deleted
451-
test_user.deleted = True
452-
session.add(test_user)
453-
session.commit()
454-
455-
# Try to use the reset token
456-
response = client.post(
457-
"/auth/reset_password",
458-
data={
459-
"email": test_user.email,
460-
"token": reset_token.token,
461-
"new_password": "NewPass123!@#",
462-
"confirm_new_password": "NewPass123!@#"
463-
},
464-
follow_redirects=False
465-
)
466-
assert response.status_code == 400

utils/auth.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,7 @@ def validate_token_and_get_user(
180180
if decoded_token:
181181
user_email = decoded_token.get("sub")
182182
user = session.exec(select(User).where(
183-
User.email == user_email,
184-
User.deleted == False
185-
)).first()
183+
User.email == user_email)).first()
186184
if user:
187185
if token_type == "refresh":
188186
new_access_token = create_access_token(
@@ -277,10 +275,7 @@ def generate_password_reset_url(email: str, token: str) -> str:
277275

278276
def send_reset_email(email: str, session: Session):
279277
# Check for an existing unexpired token
280-
user = session.exec(select(User).where(
281-
User.email == email,
282-
User.deleted == False
283-
)).first()
278+
user = session.exec(select(User).where(User.email == email)).first()
284279
if user:
285280
existing_token = session.exec(
286281
select(PasswordResetToken)
@@ -332,11 +327,7 @@ def get_user_from_reset_token(email: str, token: str, session: Session) -> tuple
332327

333328
user = session.exec(select(User).where(
334329
User.email == email,
335-
User.id == reset_token.user_id,
336-
User.deleted == False
330+
User.id == reset_token.user_id
337331
)).first()
338332

339-
if not user:
340-
return None, None
341-
342333
return user, reset_token

utils/models.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import datetime, UTC, timedelta
44
from typing import Optional, List
55
from sqlmodel import SQLModel, Field, Relationship
6-
from sqlalchemy import Column, Enum as SQLAlchemyEnum, Index
6+
from sqlalchemy import Column, Enum as SQLAlchemyEnum
77

88

99
def utc_time():
@@ -89,7 +89,7 @@ class PasswordResetToken(SQLModel, table=True):
8989
class User(SQLModel, table=True):
9090
id: Optional[int] = Field(default=None, primary_key=True)
9191
name: str
92-
email: str = Field(index=True)
92+
email: str = Field(index=True, unique=True)
9393
hashed_password: str
9494
avatar_url: Optional[str] = None
9595
organization_id: Optional[int] = Field(
@@ -105,15 +105,6 @@ class User(SQLModel, table=True):
105105
password_reset_tokens: List["PasswordResetToken"] = Relationship(
106106
back_populates="user")
107107

108-
__table_args__ = (
109-
Index(
110-
'ix_user_email_unique_active',
111-
'email',
112-
unique=True,
113-
postgresql_where=(deleted == False)
114-
),
115-
)
116-
117108

118109
class UserOrganizationLink(SQLModel, table=True):
119110
id: Optional[int] = Field(default=None, primary_key=True)

0 commit comments

Comments
 (0)