tiny error message fix
This commit is contained in:
parent
92489e5e8a
commit
820c01097c
2 changed files with 82 additions and 30 deletions
|
|
@ -125,23 +125,10 @@ async def check_invite(
|
||||||
)
|
)
|
||||||
invite = result.scalar_one_or_none()
|
invite = result.scalar_one_or_none()
|
||||||
|
|
||||||
if not invite:
|
# Return same error for not found, spent, and revoked to avoid information leakage
|
||||||
|
if not invite or invite.status in (InviteStatus.SPENT, InviteStatus.REVOKED):
|
||||||
return InviteCheckResponse(valid=False, error="Invite not found")
|
return InviteCheckResponse(valid=False, error="Invite not found")
|
||||||
|
|
||||||
if invite.status == InviteStatus.SPENT:
|
|
||||||
return InviteCheckResponse(
|
|
||||||
valid=False,
|
|
||||||
status=invite.status.value,
|
|
||||||
error="This invite has already been used"
|
|
||||||
)
|
|
||||||
|
|
||||||
if invite.status == InviteStatus.REVOKED:
|
|
||||||
return InviteCheckResponse(
|
|
||||||
valid=False,
|
|
||||||
status=invite.status.value,
|
|
||||||
error="This invite has been revoked"
|
|
||||||
)
|
|
||||||
|
|
||||||
return InviteCheckResponse(valid=True, status=invite.status.value)
|
return InviteCheckResponse(valid=True, status=invite.status.value)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -167,24 +154,13 @@ async def register(
|
||||||
)
|
)
|
||||||
invite = result.scalar_one_or_none()
|
invite = result.scalar_one_or_none()
|
||||||
|
|
||||||
if not invite:
|
# Return same error for not found, spent, and revoked to avoid information leakage
|
||||||
|
if not invite or invite.status in (InviteStatus.SPENT, InviteStatus.REVOKED):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Invalid invite code",
|
detail="Invalid invite code",
|
||||||
)
|
)
|
||||||
|
|
||||||
if invite.status == InviteStatus.SPENT:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail="This invite has already been used",
|
|
||||||
)
|
|
||||||
|
|
||||||
if invite.status == InviteStatus.REVOKED:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail="This invite has been revoked",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check email not already taken
|
# Check email not already taken
|
||||||
existing_user = await get_user_by_email(db, user_data.email)
|
existing_user = await get_user_by_email(db, user_data.email)
|
||||||
if existing_user:
|
if existing_user:
|
||||||
|
|
|
||||||
|
|
@ -508,6 +508,82 @@ async def test_check_invite_invalid_format(client_factory):
|
||||||
assert "format" in data["error"].lower()
|
assert "format" in data["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_invite_spent_returns_not_found(client_factory, admin_user, regular_user):
|
||||||
|
"""Check endpoint returns same error for spent invite as for non-existent (no info leakage)."""
|
||||||
|
# Create invite
|
||||||
|
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||||
|
async with client_factory.get_db_session() as db:
|
||||||
|
result = await db.execute(
|
||||||
|
select(User).where(User.email == regular_user["email"])
|
||||||
|
)
|
||||||
|
godfather = result.scalar_one()
|
||||||
|
|
||||||
|
create_resp = await client.post(
|
||||||
|
"/api/admin/invites",
|
||||||
|
json={"godfather_id": godfather.id},
|
||||||
|
)
|
||||||
|
identifier = create_resp.json()["identifier"]
|
||||||
|
|
||||||
|
# Use the invite
|
||||||
|
async with client_factory.create() as client:
|
||||||
|
await client.post(
|
||||||
|
"/api/auth/register",
|
||||||
|
json={
|
||||||
|
"email": unique_email("spentcheck"),
|
||||||
|
"password": "password123",
|
||||||
|
"invite_identifier": identifier,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check spent invite - should return same error as non-existent
|
||||||
|
async with client_factory.create() as client:
|
||||||
|
response = await client.get(f"/api/invites/{identifier}/check")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["valid"] is False
|
||||||
|
assert "not found" in data["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_invite_revoked_returns_not_found(client_factory, admin_user, regular_user):
|
||||||
|
"""Check endpoint returns same error for revoked invite as for non-existent (no info leakage)."""
|
||||||
|
from datetime import datetime, UTC
|
||||||
|
|
||||||
|
# Create invite
|
||||||
|
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||||
|
async with client_factory.get_db_session() as db:
|
||||||
|
result = await db.execute(
|
||||||
|
select(User).where(User.email == regular_user["email"])
|
||||||
|
)
|
||||||
|
godfather = result.scalar_one()
|
||||||
|
|
||||||
|
create_resp = await client.post(
|
||||||
|
"/api/admin/invites",
|
||||||
|
json={"godfather_id": godfather.id},
|
||||||
|
)
|
||||||
|
identifier = create_resp.json()["identifier"]
|
||||||
|
invite_id = create_resp.json()["id"]
|
||||||
|
|
||||||
|
# Revoke the invite
|
||||||
|
async with client_factory.get_db_session() as db:
|
||||||
|
result = await db.execute(select(Invite).where(Invite.id == invite_id))
|
||||||
|
invite = result.scalar_one()
|
||||||
|
invite.status = InviteStatus.REVOKED
|
||||||
|
invite.revoked_at = datetime.now(UTC)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
# Check revoked invite - should return same error as non-existent
|
||||||
|
async with client_factory.create() as client:
|
||||||
|
response = await client.get(f"/api/invites/{identifier}/check")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["valid"] is False
|
||||||
|
assert "not found" in data["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_check_invite_case_insensitive(client_factory, admin_user, regular_user):
|
async def test_check_invite_case_insensitive(client_factory, admin_user, regular_user):
|
||||||
"""Check endpoint handles case-insensitive identifiers."""
|
"""Check endpoint handles case-insensitive identifiers."""
|
||||||
|
|
@ -712,7 +788,7 @@ async def test_register_with_spent_invite(client_factory, admin_user, regular_us
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "already been used" in response.json()["detail"]
|
assert "invalid invite code" in response.json()["detail"].lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -758,7 +834,7 @@ async def test_register_with_revoked_invite(client_factory, admin_user, regular_
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "revoked" in response.json()["detail"].lower()
|
assert "invalid invite code" in response.json()["detail"].lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue