fix: wrap AsyncSession in context manager across all API blueprints

Replace all bare `session = AsyncSession(bind=conn)` assignments with
`async with AsyncSession(bind=conn) as session:` in downloads.py,
credentials.py, settings.py, sources.py, and subscriptions.py to
ensure sessions are properly closed on all code paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 23:38:58 -04:00
parent dc146f4a16
commit 080a1254c7
5 changed files with 771 additions and 800 deletions
+80 -84
View File
@@ -19,18 +19,17 @@ VALID_PLATFORMS = ["patreon", "subscribestar", "hentaifoundry", "discord", "pixi
async def list_credentials():
"""List all credential status (without sensitive data)."""
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
async with AsyncSession(bind=conn) as session:
result = await session.execute(select(Credential))
credentials = result.scalars().all()
result = await session.execute(select(Credential))
credentials = result.scalars().all()
# Also return a simple map of platform -> has_credentials for UI convenience
platforms_with_creds = {c.platform: True for c in credentials}
# Also return a simple map of platform -> has_credentials for UI convenience
platforms_with_creds = {c.platform: True for c in credentials}
return jsonify({
"items": [c.to_dict(include_data=False) for c in credentials],
"platforms": platforms_with_creds,
})
return jsonify({
"items": [c.to_dict(include_data=False) for c in credentials],
"platforms": platforms_with_creds,
})
@bp.route("", methods=["POST"])
@@ -48,15 +47,15 @@ async def upload_credentials():
if extension_key:
# Look up API key from database
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
result = await session.execute(
select(Setting).where(Setting.key == EXTENSION_API_KEY_SETTING)
)
setting = result.scalar()
stored_key = setting.value if setting else None
async with AsyncSession(bind=conn) as session:
result = await session.execute(
select(Setting).where(Setting.key == EXTENSION_API_KEY_SETTING)
)
setting = result.scalar()
stored_key = setting.value if setting else None
if not stored_key or extension_key != stored_key:
return jsonify({"error": "Invalid extension API key"}), 401
if not stored_key or extension_key != stored_key:
return jsonify({"error": "Invalid extension API key"}), 401
data = await request.get_json()
@@ -81,43 +80,42 @@ async def upload_credentials():
return jsonify({"error": "Invalid credential type. Must be 'cookies' or 'token'"}), 400
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
# Check for existing credential
result = await session.execute(
select(Credential).where(Credential.platform == platform)
)
credential = result.scalar()
# Encrypt the credential data
encrypted = encrypt_data(cred_data, settings.secret_key)
if credential:
# Update existing
credential.credential_type = cred_type
credential.data = encrypted
credential.expires_at = data.get("expires_at")
credential.last_verified = None # Reset verification
else:
# Create new
credential = Credential(
platform=platform,
credential_type=cred_type,
data=encrypted,
expires_at=data.get("expires_at"),
async with AsyncSession(bind=conn) as session:
# Check for existing credential
result = await session.execute(
select(Credential).where(Credential.platform == platform)
)
session.add(credential)
credential = result.scalar()
await session.commit()
await session.refresh(credential)
# Encrypt the credential data
encrypted = encrypt_data(cred_data, settings.secret_key)
current_app.logger.info(f"Updated credentials for platform: {platform}")
return jsonify({
"message": "Credentials updated",
"platform": platform,
"credential_type": cred_type,
"expires_at": credential.expires_at.isoformat() if credential.expires_at else None,
})
if credential:
# Update existing
credential.credential_type = cred_type
credential.data = encrypted
credential.expires_at = data.get("expires_at")
credential.last_verified = None # Reset verification
else:
# Create new
credential = Credential(
platform=platform,
credential_type=cred_type,
data=encrypted,
expires_at=data.get("expires_at"),
)
session.add(credential)
await session.commit()
await session.refresh(credential)
current_app.logger.info(f"Updated credentials for platform: {platform}")
return jsonify({
"message": "Credentials updated",
"platform": platform,
"credential_type": cred_type,
"expires_at": credential.expires_at.isoformat() if credential.expires_at else None,
})
@bp.route("/<platform>", methods=["DELETE"])
@@ -127,21 +125,20 @@ async def delete_credentials(platform: str):
return jsonify({"error": f"Invalid platform. Must be one of: {', '.join(VALID_PLATFORMS)}"}), 400
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
async with AsyncSession(bind=conn) as session:
result = await session.execute(
select(Credential).where(Credential.platform == platform)
)
credential = result.scalar()
result = await session.execute(
select(Credential).where(Credential.platform == platform)
)
credential = result.scalar()
if not credential:
return jsonify({"error": "Credentials not found for this platform"}), 404
if not credential:
return jsonify({"error": "Credentials not found for this platform"}), 404
await session.delete(credential)
await session.commit()
await session.delete(credential)
await session.commit()
current_app.logger.info(f"Deleted credentials for platform: {platform}")
return "", 204
current_app.logger.info(f"Deleted credentials for platform: {platform}")
return "", 204
@bp.route("/verify", methods=["POST"])
@@ -160,28 +157,27 @@ async def verify_credentials():
return jsonify({"error": f"Invalid platform. Must be one of: {', '.join(VALID_PLATFORMS)}"}), 400
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
async with AsyncSession(bind=conn) as session:
result = await session.execute(
select(Credential).where(Credential.platform == platform)
)
credential = result.scalar()
result = await session.execute(
select(Credential).where(Credential.platform == platform)
)
credential = result.scalar()
if not credential:
return jsonify({
"platform": platform,
"is_valid": False,
"error": "No credentials stored for this platform",
})
# TODO: Implement actual verification by making test request
# For now, just mark as verified
verified_at = utcnow()
credential.last_verified = verified_at
await session.commit()
if not credential:
return jsonify({
"platform": platform,
"is_valid": False,
"error": "No credentials stored for this platform",
"is_valid": True,
"last_verified": verified_at.isoformat(),
})
# TODO: Implement actual verification by making test request
# For now, just mark as verified
verified_at = utcnow()
credential.last_verified = verified_at
await session.commit()
return jsonify({
"platform": platform,
"is_valid": True,
"last_verified": verified_at.isoformat(),
})