diff --git a/backend/app/services/ssh.py b/backend/app/services/ssh.py index c5c8c9c..c1c2ff3 100644 --- a/backend/app/services/ssh.py +++ b/backend/app/services/ssh.py @@ -17,6 +17,7 @@ If the last_auth method fails it is cleared and the full list is tried. """ import asyncio +import hashlib import logging import time from dataclasses import dataclass, field @@ -84,7 +85,13 @@ def _opt_key(opt: _AuthOption) -> str: """Deduplication key for an auth option.""" if opt.type == "key": return f"key:{opt.key_id}:{opt.username}" - return f"password:{opt.cred_id}:{opt.username}" + # For DB-backed credentials we can deduplicate by (cred_id, username). + # For legacy/fallback credentials (cred_id is None), include a password + # fingerprint so different passwords for the same username are still tried. + if opt.cred_id is not None: + return f"password:{opt.cred_id}:{opt.username}" + pwd_fingerprint = hashlib.sha256(opt.password.encode("utf-8")).hexdigest()[:16] + return f"password:inline:{opt.username}:{pwd_fingerprint}" async def _load_key_path(db: AsyncSession, key_id: int) -> Optional[str]: