Disable password login once a passkey is linked
- /token: reject password auth with a clear message if pocketid_sub is set on the account — passkey-linked users must sign in via PocketID - Link callback + auto-link-by-email: null out hashed_password when the passkey is attached so the old hash can't be used even if the check above were bypassed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,11 @@ async def login(
|
|||||||
user = result.scalar_one_or_none()
|
user = result.scalar_one_or_none()
|
||||||
if not user or not user.hashed_password:
|
if not user or not user.hashed_password:
|
||||||
raise HTTPException(status_code=400, detail="Invalid credentials")
|
raise HTTPException(status_code=400, detail="Invalid credentials")
|
||||||
|
if user.pocketid_sub is not None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Password login is disabled for this account — use your passkey to sign in.",
|
||||||
|
)
|
||||||
if not verify_password(form_data.password, user.hashed_password):
|
if not verify_password(form_data.password, user.hashed_password):
|
||||||
raise HTTPException(status_code=400, detail="Invalid credentials")
|
raise HTTPException(status_code=400, detail="Invalid credentials")
|
||||||
token = create_access_token({"sub": str(user.id)})
|
token = create_access_token({"sub": str(user.id)})
|
||||||
@@ -262,6 +267,7 @@ async def pocketid_callback(code: str, state: Optional[str] = None, db: AsyncSes
|
|||||||
if target is None:
|
if target is None:
|
||||||
return RedirectResponse(url="/login?auth_error=link_failed")
|
return RedirectResponse(url="/login?auth_error=link_failed")
|
||||||
target.pocketid_sub = sub
|
target.pocketid_sub = sub
|
||||||
|
target.hashed_password = None # disable password login once passkey is linked
|
||||||
if not target.email and email:
|
if not target.email and email:
|
||||||
dup = await db.execute(
|
dup = await db.execute(
|
||||||
select(User).where(User.email == email, User.id != target.id)
|
select(User).where(User.email == email, User.id != target.id)
|
||||||
@@ -293,6 +299,7 @@ async def pocketid_callback(code: str, state: Optional[str] = None, db: AsyncSes
|
|||||||
existing = result.scalar_one_or_none()
|
existing = result.scalar_one_or_none()
|
||||||
if existing and existing.pocketid_sub is None:
|
if existing and existing.pocketid_sub is None:
|
||||||
existing.pocketid_sub = sub
|
existing.pocketid_sub = sub
|
||||||
|
existing.hashed_password = None # disable password login once passkey is linked
|
||||||
user = existing
|
user = existing
|
||||||
|
|
||||||
# 3) Otherwise provision a new account with a collision-safe username.
|
# 3) Otherwise provision a new account with a collision-safe username.
|
||||||
|
|||||||
Reference in New Issue
Block a user