Files
pgsql-jellyfin/docs/syncplay-authorization-error-handling.md
wjones c76853a442 PostgreSQL: Production fixes—UPSERT, remote backup, auth logs
- Fix: Atomic UPSERT for BaseItemProviders (resolves duplicate key errors during concurrent metadata refresh; clears navigation property to prevent EF Core tracking conflicts)
- Add: Remote PostgreSQL backup support (removes localhost-only restriction; works with pg_dump/pg_restore for both local and remote DBs)
- Add: Configurable backup disable option (`disable-backups`)
- Fix: Query timeout and performance (documented `command-timeout` config, added performance index scripts)
- Fix: Authentication errors now log as warnings with clear messages (ExceptionMiddleware), reducing log noise
- Fix: SyncPlay authorization handler validates user before lookup, logs warnings for unauthenticated/unknown users (returns 403/404)
- Fix: Database deadlock detection logs warnings and allows EF Core auto-retry
- Add: Configurable LibraryMonitorDelay (min 30s, default 60s)
- Fix: SQLite migration filtering—skip SQLite-only migrations on PostgreSQL
- Chore: Suppress StyleCop warnings (SA1137, etc.) for project consistency
- Docs: 21 documentation files added/updated (config, backup, performance, troubleshooting, session summary)
- All changes are backward compatible and production-ready
2026-03-03 16:35:27 -05:00

4.5 KiB

SyncPlay Authorization Error Handling Improvement

Problem

When unauthenticated users accessed SyncPlay endpoints, the application threw an unhandled exception:

System.ArgumentException: Guid can't be empty (Parameter 'id')
at UserManager.GetUserById(Guid id)

This made it appear as a bug to end users, when it's actually expected behavior (authentication required).

Solution

Added proper validation and user-friendly logging to SyncPlayAccessHandler.cs:

Changes Made

  1. Added Logger Dependency

    • Injected ILogger<SyncPlayAccessHandler> into the constructor
    • Enables proper logging of authorization failures
  2. Early Validation

    • Check for empty GUID before calling GetUserById()
    • Prevents exception from being thrown for expected case
  3. User-Friendly Messages

    • Empty user ID: "SyncPlay access denied: User authentication required. Unable to process request with empty user ID"
    • User not found: "SyncPlay access denied: User with ID {UserId} not found"

Before

var userId = context.User.GetUserId();
var user = _userManager.GetUserById(userId);  // ← Throws exception if userId is empty

Log Output:

[ERR] System.ArgumentException: Guid can't be empty (Parameter 'id')

After

var userId = context.User.GetUserId();

// Check if user is authenticated
if (userId.Equals(Guid.Empty))
{
    _logger.LogWarning("SyncPlay access denied: User authentication required. Unable to process request with empty user ID");
    return Task.CompletedTask;  // Results in 403 Forbidden
}

var user = _userManager.GetUserById(userId);
if (user is null)
{
    _logger.LogWarning("SyncPlay access denied: User with ID {UserId} not found", userId);
    throw new ResourceNotFoundException();
}

Log Output:

[WRN] SyncPlay access denied: User authentication required. Unable to process request with empty user ID

Impact

User Experience

Before:

  • Scary error message suggesting a bug
  • Stack trace in logs
  • HTTP 500 Internal Server Error

After:

  • Clear explanation of why access was denied
  • Clean warning message in logs
  • HTTP 403 Forbidden (correct status code)

Administrator Experience

Before:

[ERR] Error processing request. URL GET /SyncPlay/List.
System.ArgumentException: Guid can't be empty (Parameter 'id')
   at UserManager.GetUserById(Guid id)
   [... long stack trace ...]

Looks like a bug that needs fixing

After:

[WRN] SyncPlay access denied: User authentication required. Unable to process request with empty user ID

Clear, expected behavior - user needs to log in

Testing

To verify the fix:

  1. Unauthenticated Request:
curl http://localhost:8096/SyncPlay/List

Expected:

  • HTTP 403 Forbidden
  • Log: [WRN] SyncPlay access denied: User authentication required
  1. Invalid User ID:
curl -H "Authorization: MediaBrowser Token=invalid_token" http://localhost:8096/SyncPlay/List

Expected:

  • HTTP 404 Not Found
  • Log: [WRN] SyncPlay access denied: User with ID {guid} not found
  1. Valid Authenticated Request:
curl -H "Authorization: MediaBrowser Token=valid_token" http://localhost:8096/SyncPlay/List

Expected:

  • HTTP 200 OK (if user has permissions)
  • No error logs
  • Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
    • Added logger dependency
    • Added validation for empty user ID
    • Added user-friendly log messages

Code Style Notes

Guid Comparison

The project has a rule against using Guid.Empty with == operator:

// ❌ Banned
if (userId == Guid.Empty)

// ✅ Correct
if (userId.Equals(Guid.Empty))

This is enforced by analyzer rule RS0030.

Benefits

  1. Better Error Messages - Clear explanation of why access was denied
  2. Appropriate HTTP Status - 403 Forbidden instead of 500 Internal Server Error
  3. Less Noise - Warning instead of error for expected behavior
  4. Better Logging - Helps distinguish real bugs from authentication issues
  5. User-Friendly - Administrators can quickly understand what happened

Future Considerations

This pattern could be applied to other authorization handlers that might have similar issues with empty user IDs or invalid authentication.

Example Authorization Handlers to Review:

  • DefaultAuthorizationHandler
  • DownloadPolicy handlers
  • FirstTimeSetupOrElevatedPolicy handlers
  • LocalAccessPolicy handlers

Consider creating a base authorization handler class that includes this validation pattern.