feat(subsonic): per-user encrypted app-password foundation
Subsonic auth (t=md5(password+salt), legacy p=) needs a recoverable secret, but login passwords are stored as a one-way argon2 hash. Add a separate, per-user app-password: high-entropy, random, and encrypted at rest with a Fernet key derived from SUBSONIC_SECRET_KEY (never stored in the DB). - SubsonicPasswordCipher + generate_subsonic_password in core.security - users.subsonic_password_enc column (+ Alembic migration), repo + port methods - SubsonicAuthService: verify (t+s / p / p=enc:) and rotate/reveal lifecycle - self-service GET/POST /users/me/subsonic-password + admin rotate endpoint - domain SubsonicCredentials + SubsonicCipher port; deps wiring Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ dependencies = [
|
||||
# auth
|
||||
"pyjwt>=2.10",
|
||||
"pwdlib[argon2]>=0.2.1",
|
||||
# symmetric encryption for the recoverable Subsonic app-password (Fernet)
|
||||
"cryptography>=44.0",
|
||||
# outbound http (ML client, MusicBrainz, AcoustID)
|
||||
"httpx>=0.28",
|
||||
# S3-compatible object storage
|
||||
@@ -71,6 +73,11 @@ select = [
|
||||
]
|
||||
ignore = ["B008"] # FastAPI Depends() in defaults is idiomatic
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
# Subsonic query params are camelCase by spec (artistCount, songId, …); the
|
||||
# handler arg names must match the wire names exactly.
|
||||
"app/api/rest/*" = ["N803"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.14"
|
||||
strict = true
|
||||
|
||||
Reference in New Issue
Block a user