diff --git a/Jellyfin.Plugin.ShareLinks/Models/ShareLinkRecord.cs b/Jellyfin.Plugin.ShareLinks/Models/ShareLinkRecord.cs
index f41e6f9..40d0964 100644
--- a/Jellyfin.Plugin.ShareLinks/Models/ShareLinkRecord.cs
+++ b/Jellyfin.Plugin.ShareLinks/Models/ShareLinkRecord.cs
@@ -56,9 +56,6 @@ public sealed class ShareLinkRecord
/// Gets or sets a value indicating whether the link may be used once only.
public bool OneUse { get; set; } = true;
- /// Gets or sets the encrypted guest password, if one has been generated.
- public string? GuestPasswordEncrypted { get; set; }
-
/// Gets or sets a value indicating whether metadata was touched during cleanup.
public bool MetadataTouched { get; set; }
diff --git a/Jellyfin.Plugin.ShareLinks/Services/ShareLinkRedemptionService.cs b/Jellyfin.Plugin.ShareLinks/Services/ShareLinkRedemptionService.cs
index 20be892..dfe296b 100644
--- a/Jellyfin.Plugin.ShareLinks/Services/ShareLinkRedemptionService.cs
+++ b/Jellyfin.Plugin.ShareLinks/Services/ShareLinkRedemptionService.cs
@@ -4,8 +4,10 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.ShareLinks.Models;
using Jellyfin.Plugin.ShareLinks.Storage;
+using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Session;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -20,6 +22,7 @@ public sealed class ShareLinkRedemptionService
private readonly ItemTagService _itemTagService;
private readonly JellyfinGuestUserService _guestUserService;
private readonly ShareLinkCleanupService _cleanupService;
+ private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;
/// Initializes a new instance of the class.
@@ -30,6 +33,7 @@ public sealed class ShareLinkRedemptionService
ItemTagService itemTagService,
JellyfinGuestUserService guestUserService,
ShareLinkCleanupService cleanupService,
+ ISessionManager sessionManager,
ILogger logger)
{
_libraryManager = libraryManager;
@@ -38,6 +42,7 @@ public sealed class ShareLinkRedemptionService
_itemTagService = itemTagService;
_guestUserService = guestUserService;
_cleanupService = cleanupService;
+ _sessionManager = sessionManager;
_logger = logger;
}
@@ -101,17 +106,33 @@ public sealed class ShareLinkRedemptionService
record.CleanupError = null;
await _store.UpdateAsync(record, cancellationToken).ConfigureAwait(false);
- var password = await GetOrCreatePasswordAsync(record, cancellationToken).ConfigureAwait(false);
+ // The account still needs a password so it can never be authenticated with a blank
+ // login; it is generated fresh on every redemption and is never stored or sent
+ // anywhere. The browser only ever receives a server-minted session token.
+ var password = JellyfinGuestUserService.GeneratePassword();
if (string.IsNullOrWhiteSpace(record.GuestUserName))
{
record.GuestUserName = JellyfinGuestUserService.BuildGuestUsername(record);
}
+ AuthenticationResult authResult;
try
{
var user = await _guestUserService.EnsureGuestUserAsync(record, password, cancellationToken).ConfigureAwait(false);
record.GuestUserId = user.Id;
record.GuestUserName = user.Username;
+
+ authResult = await _sessionManager.AuthenticateDirect(new AuthenticationRequest
+ {
+ Username = record.GuestUserName,
+ UserId = record.GuestUserId.Value,
+ App = "ShareLinks",
+ AppVersion = "1.0.0",
+ DeviceId = record.DeviceId,
+ DeviceName = "ShareLinks",
+ RemoteEndPoint = request.HttpContext.Connection.RemoteIpAddress?.ToString()
+ }).ConfigureAwait(false);
+
record.RedeemedAtUtc ??= now;
record.Status = ShareLinkStatus.Redeemed;
record.CleanupError = null;
@@ -127,29 +148,7 @@ public sealed class ShareLinkRedemptionService
return null;
}
- return BuildBootstrapHtml(request, record, password, itemId);
- }
-
- private async Task GetOrCreatePasswordAsync(ShareLinkRecord record, CancellationToken cancellationToken)
- {
- if (!string.IsNullOrWhiteSpace(record.GuestPasswordEncrypted))
- {
- try
- {
- return await _tokenService.UnprotectStringAsync(record.GuestPasswordEncrypted, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "ShareLinks: stored guest password could not be decrypted for record {RecordId}; generating a replacement.", record.Id);
- }
- }
-
- var password = JellyfinGuestUserService.GeneratePassword();
- record.GuestPasswordEncrypted = await _tokenService.ProtectStringAsync(password, cancellationToken).ConfigureAwait(false);
- record.Status = ShareLinkStatus.Redeeming;
- record.CleanupError = null;
- await _store.UpdateAsync(record, cancellationToken).ConfigureAwait(false);
- return password;
+ return BuildBootstrapHtml(request, authResult, itemId);
}
private async Task HandleTerminalRecordAsync(ShareLinkRecord record, ShareLinkStatus terminalStatus, string reason, CancellationToken cancellationToken)
@@ -180,24 +179,14 @@ public sealed class ShareLinkRedemptionService
}
}
- private static string BuildBootstrapHtml(HttpRequest request, ShareLinkRecord record, string password, Guid itemId)
+ private static string BuildBootstrapHtml(HttpRequest request, AuthenticationResult authResult, Guid itemId)
{
var pathBase = request.PathBase.Value ?? string.Empty;
- var authUrl = $"{pathBase}/Users/AuthenticateByName";
var redirectUrl = $"{pathBase}/web/index.html#/details?id={Uri.EscapeDataString(itemId.ToString("D"))}";
- var username = record.GuestUserName ?? JellyfinGuestUserService.BuildGuestUsername(record);
- var deviceId = record.DeviceId ?? string.Empty;
- var authJson = JsonSerializer.Serialize(new
- {
- Username = username,
- Pw = password
- });
-
- var authUrlJson = JsonSerializer.Serialize(authUrl);
+ var accessTokenJson = JsonSerializer.Serialize(authResult.AccessToken);
+ var userIdJson = JsonSerializer.Serialize(authResult.User.Id.ToString("N"));
var redirectUrlJson = JsonSerializer.Serialize(redirectUrl);
- var usernameJson = JsonSerializer.Serialize(username);
- var deviceIdJson = JsonSerializer.Serialize(deviceId);
var infoUrlJson = JsonSerializer.Serialize($"{pathBase}/System/Info/Public");
var pathBaseJson = JsonSerializer.Serialize(pathBase);
@@ -221,31 +210,11 @@ public sealed class ShareLinkRedemptionService