aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs')
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs137
1 files changed, 137 insertions, 0 deletions
diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
new file mode 100644
index 000000000..6cb13cd23
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
@@ -0,0 +1,137 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Common;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Users;
+
+namespace Jellyfin.Server.Implementations.Users
+{
+ /// <summary>
+ /// The default password reset provider.
+ /// </summary>
+ public class DefaultPasswordResetProvider : IPasswordResetProvider
+ {
+ private const string BaseResetFileName = "passwordreset";
+
+ private readonly IApplicationHost _appHost;
+
+ private readonly string _passwordResetFileBase;
+ private readonly string _passwordResetFileBaseDir;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class.
+ /// </summary>
+ /// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="appHost">The application host.</param>
+ public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IApplicationHost appHost)
+ {
+ _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
+ _appHost = appHost;
+ // TODO: Remove the circular dependency on UserManager
+ }
+
+ /// <inheritdoc />
+ public string Name => "Default Password Reset Provider";
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
+ public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+ {
+ var userManager = _appHost.Resolve<IUserManager>();
+ var usersReset = new List<string>();
+ foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
+ {
+ SerializablePasswordReset spr;
+ await using (var str = File.OpenRead(resetFile))
+ {
+ spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
+ }
+
+ if (spr.ExpirationDate < DateTime.UtcNow)
+ {
+ File.Delete(resetFile);
+ }
+ else if (string.Equals(
+ spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
+ pin.Replace("-", string.Empty, StringComparison.Ordinal),
+ StringComparison.InvariantCultureIgnoreCase))
+ {
+ var resetUser = userManager.GetUserByName(spr.UserName)
+ ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
+
+ await userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ usersReset.Add(resetUser.Username);
+ File.Delete(resetFile);
+ }
+ }
+
+ if (usersReset.Count < 1)
+ {
+ throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+ }
+
+ return new PinRedeemResult
+ {
+ Success = true,
+ UsersReset = usersReset.ToArray()
+ };
+ }
+
+ /// <inheritdoc />
+ public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork)
+ {
+ string pin;
+ using (var cryptoRandom = RandomNumberGenerator.Create())
+ {
+ byte[] bytes = new byte[4];
+ cryptoRandom.GetBytes(bytes);
+ pin = BitConverter.ToString(bytes);
+ }
+
+ DateTime expireTime = DateTime.UtcNow.AddMinutes(30);
+ string filePath = _passwordResetFileBase + user.Id + ".json";
+ SerializablePasswordReset spr = new SerializablePasswordReset
+ {
+ ExpirationDate = expireTime,
+ Pin = pin,
+ PinFile = filePath,
+ UserName = user.Username
+ };
+
+ await using (FileStream fileStream = File.OpenWrite(filePath))
+ {
+ await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
+ await fileStream.FlushAsync().ConfigureAwait(false);
+ }
+
+ user.EasyPassword = pin;
+
+ return new ForgotPasswordResult
+ {
+ Action = ForgotPasswordAction.PinCode,
+ PinExpirationDate = expireTime,
+ };
+ }
+
+#nullable disable
+ private class SerializablePasswordReset : PasswordPinCreationResult
+ {
+ public string Pin { get; set; }
+
+ public string UserName { get; set; }
+ }
+ }
+}