Pārlūkot izejas kodu

Merge branch 'master' into network-rewrite

Shadowghost 2 gadi atpakaļ
vecāks
revīzija
006b04dc0b
26 mainītis faili ar 922 papildinājumiem un 134 dzēšanām
  1. 4 4
      .github/workflows/codeql-analysis.yml
  2. 2 2
      .github/workflows/openapi.yml
  3. 3 3
      Directory.Packages.props
  4. 3 2
      Emby.Server.Implementations/ConfigurationOptions.cs
  5. 2 1
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  6. 8 2
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  7. 8 1
      Emby.Server.Implementations/Library/LibraryManager.cs
  8. 20 1
      Emby.Server.Implementations/Localization/Core/hi.json
  9. 3 3
      Emby.Server.Implementations/Localization/Core/lt-LT.json
  10. 1 1
      Emby.Server.Implementations/Playlists/PlaylistsFolder.cs
  11. 2 4
      Jellyfin.Api/Controllers/ItemsController.cs
  12. 4 0
      Jellyfin.Api/Controllers/StartupController.cs
  13. 3 23
      Jellyfin.Api/Controllers/UserController.cs
  14. 0 10
      Jellyfin.Data/Entities/User.cs
  15. 650 0
      Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.Designer.cs
  16. 164 0
      Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.cs
  17. 14 20
      Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
  18. 0 2
      Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
  19. 3 35
      Jellyfin.Server.Implementations/Users/UserManager.cs
  20. 0 1
      Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
  21. 2 2
      MediaBrowser.Controller/Entities/Folder.cs
  22. 13 0
      MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
  23. 0 16
      MediaBrowser.Controller/Library/IUserManager.cs
  24. 1 1
      MediaBrowser.Model/Dto/BaseItemDto.cs
  25. 1 0
      MediaBrowser.Model/Dto/UserDto.cs
  26. 11 0
      tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs

+ 4 - 4
.github/workflows/codeql-analysis.yml

@@ -22,16 +22,16 @@ jobs:
     - name: Checkout repository
     - name: Checkout repository
       uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
       uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
     - name: Setup .NET
     - name: Setup .NET
-      uses: actions/setup-dotnet@aa983c550dfda0d1722b6ac6aed55724ffacc6d3 # v3.1.0
+      uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
       with:
       with:
         dotnet-version: '7.0.x'
         dotnet-version: '7.0.x'
 
 
     - name: Initialize CodeQL
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@f0e3dfb30302f8a0881bb509b044e0de4f6ef589 # v2.3.4
+      uses: github/codeql-action/init@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
       with:
       with:
         languages: ${{ matrix.language }}
         languages: ${{ matrix.language }}
         queries: +security-extended
         queries: +security-extended
     - name: Autobuild
     - name: Autobuild
-      uses: github/codeql-action/autobuild@f0e3dfb30302f8a0881bb509b044e0de4f6ef589 # v2.3.4
+      uses: github/codeql-action/autobuild@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
     - name: Perform CodeQL Analysis
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@f0e3dfb30302f8a0881bb509b044e0de4f6ef589 # v2.3.4
+      uses: github/codeql-action/analyze@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6

+ 2 - 2
.github/workflows/openapi.yml

@@ -19,7 +19,7 @@ jobs:
           ref: ${{ github.event.pull_request.head.sha }}
           ref: ${{ github.event.pull_request.head.sha }}
           repository: ${{ github.event.pull_request.head.repo.full_name }}
           repository: ${{ github.event.pull_request.head.repo.full_name }}
       - name: Setup .NET
       - name: Setup .NET
-        uses: actions/setup-dotnet@aa983c550dfda0d1722b6ac6aed55724ffacc6d3 # v3.1.0
+        uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
         with:
         with:
           dotnet-version: '7.0.x'
           dotnet-version: '7.0.x'
       - name: Generate openapi.json
       - name: Generate openapi.json
@@ -51,7 +51,7 @@ jobs:
           ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }})
           ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }})
           git checkout --progress --force $ANCESTOR_REF
           git checkout --progress --force $ANCESTOR_REF
       - name: Setup .NET
       - name: Setup .NET
-        uses: actions/setup-dotnet@aa983c550dfda0d1722b6ac6aed55724ffacc6d3 # v3.1.0
+        uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
         with:
         with:
           dotnet-version: '7.0.x'
           dotnet-version: '7.0.x'
       - name: Generate openapi.json
       - name: Generate openapi.json

+ 3 - 3
Directory.Packages.props

@@ -17,7 +17,7 @@
     <PackageVersion Include="Diacritics" Version="3.3.18" />
     <PackageVersion Include="Diacritics" Version="3.3.18" />
     <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
     <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
     <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
     <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
-    <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.9.1" />
+    <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.9.2" />
     <PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
     <PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
     <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
     <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
     <PackageVersion Include="libse" Version="3.6.13" />
     <PackageVersion Include="libse" Version="3.6.13" />
@@ -46,14 +46,14 @@
     <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
     <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
     <PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
     <PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
-    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
+    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
     <PackageVersion Include="MimeTypes" Version="2.4.0" />
     <PackageVersion Include="MimeTypes" Version="2.4.0" />
     <PackageVersion Include="Mono.Nat" Version="3.0.4" />
     <PackageVersion Include="Mono.Nat" Version="3.0.4" />
     <PackageVersion Include="Moq" Version="4.18.4" />
     <PackageVersion Include="Moq" Version="4.18.4" />
     <PackageVersion Include="NEbml" Version="0.11.0" />
     <PackageVersion Include="NEbml" Version="0.11.0" />
     <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
     <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
-    <PackageVersion Include="PlaylistsNET" Version="1.3.2" />
+    <PackageVersion Include="PlaylistsNET" Version="1.4.0" />
     <PackageVersion Include="prometheus-net.AspNetCore" Version="8.0.0" />
     <PackageVersion Include="prometheus-net.AspNetCore" Version="8.0.0" />
     <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
     <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
     <PackageVersion Include="prometheus-net" Version="8.0.0" />
     <PackageVersion Include="prometheus-net" Version="8.0.0" />

+ 3 - 2
Emby.Server.Implementations/ConfigurationOptions.cs

@@ -11,14 +11,15 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// <summary>
         /// Gets a new copy of the default configuration options.
         /// Gets a new copy of the default configuration options.
         /// </summary>
         /// </summary>
-        public static Dictionary<string, string?> DefaultConfiguration => new Dictionary<string, string?>
+        public static Dictionary<string, string?> DefaultConfiguration => new()
         {
         {
             { HostWebClientKey, bool.TrueString },
             { HostWebClientKey, bool.TrueString },
             { DefaultRedirectKey, "web/" },
             { DefaultRedirectKey, "web/" },
             { FfmpegProbeSizeKey, "1G" },
             { FfmpegProbeSizeKey, "1G" },
             { FfmpegAnalyzeDurationKey, "200M" },
             { FfmpegAnalyzeDurationKey, "200M" },
             { PlaylistsAllowDuplicatesKey, bool.FalseString },
             { PlaylistsAllowDuplicatesKey, bool.FalseString },
-            { BindToUnixSocketKey, bool.FalseString }
+            { BindToUnixSocketKey, bool.FalseString },
+            { SqliteCacheSizeKey, "20000" }
         };
         };
     }
     }
 }
 }

+ 2 - 1
Emby.Server.Implementations/Data/BaseSqliteRepository.cs

@@ -82,9 +82,10 @@ namespace Emby.Server.Implementations.Data
 
 
         /// <summary>
         /// <summary>
         /// Gets the journal size limit. <see href="https://www.sqlite.org/pragma.html#pragma_journal_size_limit" />.
         /// Gets the journal size limit. <see href="https://www.sqlite.org/pragma.html#pragma_journal_size_limit" />.
+        /// The default (-1) is overriden to prevent unconstrained WAL size, as reported by users.
         /// </summary>
         /// </summary>
         /// <value>The journal size limit.</value>
         /// <value>The journal size limit.</value>
-        protected virtual int? JournalSizeLimit => 0;
+        protected virtual int? JournalSizeLimit => 134_217_728; // 128MiB
 
 
         /// <summary>
         /// <summary>
         /// Gets the page size.
         /// Gets the page size.

+ 8 - 2
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -25,6 +25,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
@@ -34,6 +35,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 using SQLitePCL.pretty;
 using SQLitePCL.pretty;
 
 
@@ -319,13 +321,15 @@ namespace Emby.Server.Implementations.Data
         /// <param name="logger">Instance of the <see cref="ILogger{SqliteItemRepository}"/> interface.</param>
         /// <param name="logger">Instance of the <see cref="ILogger{SqliteItemRepository}"/> interface.</param>
         /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
         /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
         /// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param>
         /// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param>
+        /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
         /// <exception cref="ArgumentNullException">config is null.</exception>
         /// <exception cref="ArgumentNullException">config is null.</exception>
         public SqliteItemRepository(
         public SqliteItemRepository(
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
             ILogger<SqliteItemRepository> logger,
             ILogger<SqliteItemRepository> logger,
             ILocalizationManager localization,
             ILocalizationManager localization,
-            IImageProcessor imageProcessor)
+            IImageProcessor imageProcessor,
+            IConfiguration configuration)
             : base(logger)
             : base(logger)
         {
         {
             _config = config;
             _config = config;
@@ -337,11 +341,13 @@ namespace Emby.Server.Implementations.Data
             _jsonOptions = JsonDefaults.Options;
             _jsonOptions = JsonDefaults.Options;
 
 
             DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
             DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
+
+            CacheSize = configuration.GetSqliteCacheSize();
             ReadConnectionsCount = Environment.ProcessorCount * 2;
             ReadConnectionsCount = Environment.ProcessorCount * 2;
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        protected override int? CacheSize => 20000;
+        protected override int? CacheSize { get; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
         protected override TempStoreMode TempStore => TempStoreMode.Memory;
         protected override TempStoreMode TempStore => TempStoreMode.Memory;

+ 8 - 1
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -1264,7 +1264,14 @@ namespace Emby.Server.Implementations.Library
                 AddUserToQuery(query, query.User, allowExternalContent);
                 AddUserToQuery(query, query.User, allowExternalContent);
             }
             }
 
 
-            return _itemRepository.GetItemList(query);
+            var itemList = _itemRepository.GetItemList(query);
+            var user = query.User;
+            if (user is not null)
+            {
+                return itemList.Where(i => i.IsVisible(user)).ToList();
+            }
+
+            return itemList;
         }
         }
 
 
         public List<BaseItem> GetItemList(InternalItemsQuery query)
         public List<BaseItem> GetItemList(InternalItemsQuery query)

+ 20 - 1
Emby.Server.Implementations/Localization/Core/hi.json

@@ -104,5 +104,24 @@
     "TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करें",
     "TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करें",
     "TasksChannelsCategory": "इंटरनेट प्रणाली",
     "TasksChannelsCategory": "इंटरनेट प्रणाली",
     "TasksApplicationCategory": "अनुप्रयोग",
     "TasksApplicationCategory": "अनुप्रयोग",
-    "TaskRefreshPeople": "लोगोकी जानकारी ताज़ी करें"
+    "TaskRefreshPeople": "लोगोकी जानकारी ताज़ी करें",
+    "TaskKeyframeExtractor": "कीफ़्रेम एक्सट्रैक्टर",
+    "TaskCleanActivityLogDescription": "कॉन्फ़िगर की गई आयु से पुरानी गतिविधि लॉग प्रविष्टियां हटाता है।",
+    "TaskRefreshChapterImagesDescription": "अध्याय वाले वीडियो के लिए थंबनेल बनाता है।",
+    "TaskRefreshLibraryDescription": "नई फ़ाइलों के लिए आपकी मीडिया लाइब्रेरी को स्कैन करता है और मेटाडेटा को ताज़ा करता है।",
+    "TaskCleanLogs": "स्वच्छ लॉग निर्देशिका",
+    "TaskUpdatePluginsDescription": "प्लगइन्स के लिए अपडेट डाउनलोड और इंस्टॉल करें जो स्वचालित रूप से अपडेट करने के लिए कॉन्फ़िगर किए गए हैं।",
+    "TaskCleanTranscode": "स्वच्छ ट्रांसकोड निर्देशिका",
+    "TaskCleanTranscodeDescription": "एक दिन से अधिक पुरानी ट्रांसकोड फ़ाइलें हटाता है.",
+    "TaskRefreshChannelsDescription": "इंटरनेट चैनल की जानकारी को ताज़ा करता है।",
+    "TaskOptimizeDatabaseDescription": "डेटाबेस को कॉम्पैक्ट करता है और मुक्त स्थान को छोटा करता है। लाइब्रेरी को स्कैन करने के बाद इस कार्य को चलाने या अन्य परिवर्तन करने से जो डेटाबेस संशोधनों को लागू करते हैं, प्रदर्शन में सुधार कर सकते हैं।",
+    "TaskRefreshChannels": "इंटरनेट चैनल की जानकारी को ताज़ा करता है",
+    "TaskRefreshChapterImages": "अध्याय छवियाँ निकालें",
+    "TaskCleanLogsDescription": "{0} दिन से अधिक पुरानी लॉग फ़ाइलें हटाता है।",
+    "TaskCleanCacheDescription": "उन कैश फ़ाइलों को हटाता है जिनकी अब सिस्टम को आवश्यकता नहीं है।",
+    "TaskUpdatePlugins": "अद्यतन प्लगइन्स",
+    "TaskRefreshPeopleDescription": "आपकी मीडिया लाइब्रेरी में अभिनेताओं और निर्देशकों के लिए मेटाडेटा अपडेट करता है।",
+    "TaskCleanCache": "स्वच्छ कैश निर्देशिका",
+    "TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कॉन्फ़िगरेशन के आधार पर लापता उपशीर्षक के लिए इंटरनेट खोजता है।",
+    "TaskKeyframeExtractorDescription": "अधिक सटीक एचएलएस प्लेलिस्ट बनाने के लिए वीडियो फ़ाइलों से मुख्य-फ़्रेम निकालता है। यह कार्य लंबे समय तक चल सकता है।"
 }
 }

+ 3 - 3
Emby.Server.Implementations/Localization/Core/lt-LT.json

@@ -20,9 +20,9 @@
     "HeaderFavoriteAlbums": "Mėgstami Albumai",
     "HeaderFavoriteAlbums": "Mėgstami Albumai",
     "HeaderFavoriteArtists": "Mėgstami Atlikėjai",
     "HeaderFavoriteArtists": "Mėgstami Atlikėjai",
     "HeaderFavoriteEpisodes": "Mėgstamiausios serijos",
     "HeaderFavoriteEpisodes": "Mėgstamiausios serijos",
-    "HeaderFavoriteShows": "Mėgstamiausi serialai",
-    "HeaderFavoriteSongs": "Mėgstamos dainos",
-    "HeaderLiveTV": "TV gyvai",
+    "HeaderFavoriteShows": "Mėgstamiausios TV Laidos",
+    "HeaderFavoriteSongs": "Mėgstamos Dainos",
+    "HeaderLiveTV": "Tiesioginė TV",
     "HeaderNextUp": "Toliau eilėje",
     "HeaderNextUp": "Toliau eilėje",
     "HeaderRecordingGroups": "Įrašų grupės",
     "HeaderRecordingGroups": "Įrašų grupės",
     "HomeVideos": "Namų vaizdo įrašai",
     "HomeVideos": "Namų vaizdo įrašai",

+ 1 - 1
Emby.Server.Implementations/Playlists/PlaylistsFolder.cs

@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Playlists
 
 
             query.Recursive = true;
             query.Recursive = true;
             query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
             query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
-            return LibraryManager.GetItemsResult(query);
+            return QueryWithPostFiltering2(query);
         }
         }
 
 
         public override string GetClientTypeName()
         public override string GetClientTypeName()

+ 2 - 4
Jellyfin.Api/Controllers/ItemsController.cs

@@ -512,12 +512,10 @@ public class ItemsController : BaseJellyfinApiController
             result = new QueryResult<BaseItem>(itemsArray);
             result = new QueryResult<BaseItem>(itemsArray);
         }
         }
 
 
-        // result might include items not accessible by the user, DtoService will remove them
-        var accessibleItems = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user);
         return new QueryResult<BaseItemDto>(
         return new QueryResult<BaseItemDto>(
             startIndex,
             startIndex,
-            accessibleItems.Count,
-            accessibleItems);
+            result.TotalRecordCount,
+            _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user));
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 4 - 0
Jellyfin.Api/Controllers/StartupController.cs

@@ -131,6 +131,10 @@ public class StartupController : BaseJellyfinApiController
     public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto)
     public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto)
     {
     {
         var user = _userManager.Users.First();
         var user = _userManager.Users.First();
+        if (string.IsNullOrWhiteSpace(startupUserDto.Password))
+        {
+            return BadRequest("Password must not be empty");
+        }
 
 
         if (startupUserDto.Name is not null)
         if (startupUserDto.Name is not null)
         {
         {

+ 3 - 23
Jellyfin.Api/Controllers/UserController.cs

@@ -323,36 +323,16 @@ public class UserController : BaseJellyfinApiController
     /// <response code="404">User not found.</response>
     /// <response code="404">User not found.</response>
     /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
     /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
     [HttpPost("{userId}/EasyPassword")]
     [HttpPost("{userId}/EasyPassword")]
+    [Obsolete("Use Quick Connect instead")]
     [Authorize]
     [Authorize]
     [ProducesResponseType(StatusCodes.Status204NoContent)]
     [ProducesResponseType(StatusCodes.Status204NoContent)]
     [ProducesResponseType(StatusCodes.Status403Forbidden)]
     [ProducesResponseType(StatusCodes.Status403Forbidden)]
     [ProducesResponseType(StatusCodes.Status404NotFound)]
     [ProducesResponseType(StatusCodes.Status404NotFound)]
-    public async Task<ActionResult> UpdateUserEasyPassword(
+    public ActionResult UpdateUserEasyPassword(
         [FromRoute, Required] Guid userId,
         [FromRoute, Required] Guid userId,
         [FromBody, Required] UpdateUserEasyPassword request)
         [FromBody, Required] UpdateUserEasyPassword request)
     {
     {
-        if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
-        {
-            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the easy password.");
-        }
-
-        var user = _userManager.GetUserById(userId);
-
-        if (user is null)
-        {
-            return NotFound("User not found");
-        }
-
-        if (request.ResetPassword)
-        {
-            await _userManager.ResetEasyPassword(user).ConfigureAwait(false);
-        }
-        else
-        {
-            await _userManager.ChangeEasyPassword(user, request.NewPw ?? string.Empty, request.NewPassword ?? string.Empty).ConfigureAwait(false);
-        }
-
-        return NoContent();
+        return Forbid();
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 0 - 10
Jellyfin.Data/Entities/User.cs

@@ -91,16 +91,6 @@ namespace Jellyfin.Data.Entities
         [StringLength(65535)]
         [StringLength(65535)]
         public string? Password { get; set; }
         public string? Password { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the user's easy password, or <c>null</c> if none is set.
-        /// </summary>
-        /// <remarks>
-        /// Max length = 65535.
-        /// </remarks>
-        [MaxLength(65535)]
-        [StringLength(65535)]
-        public string? EasyPassword { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether the user must update their password.
         /// Gets or sets a value indicating whether the user must update their password.
         /// </summary>
         /// </summary>

+ 650 - 0
Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.Designer.cs

@@ -0,0 +1,650 @@
+// <auto-generated />
+using System;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+    [DbContext(typeof(JellyfinDbContext))]
+    [Migration("20230526173516_RemoveEasyPassword")]
+    partial class RemoveEasyPassword
+    {
+        /// <inheritdoc />
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("DayOfWeek")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("EndHour")
+                        .HasColumnType("REAL");
+
+                    b.Property<double>("StartHour")
+                        .HasColumnType("REAL");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("AccessSchedules");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateCreated")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("ItemId")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property<int>("LogSeverity")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(512)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Overview")
+                        .HasMaxLength(512)
+                        .HasColumnType("TEXT");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ShortOverview")
+                        .HasMaxLength(512)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Type")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("DateCreated");
+
+                    b.ToTable("ActivityLogs");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Client")
+                        .IsRequired()
+                        .HasMaxLength(32)
+                        .HasColumnType("TEXT");
+
+                    b.Property<Guid>("ItemId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Key")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Value")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId", "ItemId", "Client", "Key")
+                        .IsUnique();
+
+                    b.ToTable("CustomItemDisplayPreferences");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("ChromecastVersion")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Client")
+                        .IsRequired()
+                        .HasMaxLength(32)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("DashboardTheme")
+                        .HasMaxLength(32)
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("EnableNextVideoInfoOverlay")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("IndexBy")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid>("ItemId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<int>("ScrollDirection")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("ShowBackdrop")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("ShowSidebar")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("SkipBackwardLength")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("SkipForwardLength")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("TvHome")
+                        .HasMaxLength(32)
+                        .HasColumnType("TEXT");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId", "ItemId", "Client")
+                        .IsUnique();
+
+                    b.ToTable("DisplayPreferences");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("DisplayPreferencesId")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Order")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("DisplayPreferencesId");
+
+                    b.ToTable("HomeSection");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("LastModified")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasMaxLength(512)
+                        .HasColumnType("TEXT");
+
+                    b.Property<Guid?>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId")
+                        .IsUnique();
+
+                    b.ToTable("ImageInfos");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Client")
+                        .IsRequired()
+                        .HasMaxLength(32)
+                        .HasColumnType("TEXT");
+
+                    b.Property<int?>("IndexBy")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid>("ItemId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("RememberIndexing")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("RememberSorting")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SortBy")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.Property<int>("SortOrder")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<int>("ViewType")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("ItemDisplayPreferences");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid?>("Permission_Permissions_Guid")
+                        .HasColumnType("TEXT");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid?>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("Value")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId", "Kind")
+                        .IsUnique()
+                        .HasFilter("[UserId] IS NOT NULL");
+
+                    b.ToTable("Permissions");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid?>("Preference_Preferences_Guid")
+                        .HasColumnType("TEXT");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid?>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Value")
+                        .IsRequired()
+                        .HasMaxLength(65535)
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId", "Kind")
+                        .IsUnique()
+                        .HasFilter("[UserId] IS NOT NULL");
+
+                    b.ToTable("Preferences");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("AccessToken")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateCreated")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateLastActivity")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("AccessToken")
+                        .IsUnique();
+
+                    b.ToTable("ApiKeys");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("AccessToken")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("AppName")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("AppVersion")
+                        .IsRequired()
+                        .HasMaxLength(32)
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateCreated")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateLastActivity")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateModified")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("DeviceId")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("DeviceName")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("DeviceId");
+
+                    b.HasIndex("AccessToken", "DateLastActivity");
+
+                    b.HasIndex("DeviceId", "DateLastActivity");
+
+                    b.HasIndex("UserId", "DeviceId");
+
+                    b.ToTable("Devices");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("CustomName")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("DeviceId")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("DeviceId")
+                        .IsUnique();
+
+                    b.ToTable("DeviceOptions");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("AudioLanguagePreference")
+                        .HasMaxLength(255)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("AuthenticationProviderId")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("DisplayCollectionsView")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("DisplayMissingEpisodes")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("EnableAutoLogin")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("EnableLocalPassword")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("EnableNextEpisodeAutoPlay")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("EnableUserPreferenceAccess")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("HidePlayedInLatest")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long>("InternalId")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("InvalidLoginAttemptCount")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime?>("LastActivityDate")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime?>("LastLoginDate")
+                        .HasColumnType("TEXT");
+
+                    b.Property<int?>("LoginAttemptsBeforeLockout")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("MaxActiveSessions")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MaxParentalAgeRating")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("MustUpdatePassword")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Password")
+                        .HasMaxLength(65535)
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("PasswordResetProviderId")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("PlayDefaultAudioTrack")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("RememberAudioSelections")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("RememberSubtitleSelections")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("RemoteClientBitrateLimit")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SubtitleLanguagePreference")
+                        .HasMaxLength(255)
+                        .HasColumnType("TEXT");
+
+                    b.Property<int>("SubtitleMode")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("SyncPlayAccess")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Username")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("TEXT")
+                        .UseCollation("NOCASE");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Username")
+                        .IsUnique();
+
+                    b.ToTable("Users");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("AccessSchedules")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("DisplayPreferences")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
+                        .WithMany("HomeSections")
+                        .HasForeignKey("DisplayPreferencesId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithOne("ProfileImage")
+                        .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("ItemDisplayPreferences")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Permissions")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Preferences")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", "User")
+                        .WithMany()
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("User");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+                {
+                    b.Navigation("HomeSections");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+                {
+                    b.Navigation("AccessSchedules");
+
+                    b.Navigation("DisplayPreferences");
+
+                    b.Navigation("ItemDisplayPreferences");
+
+                    b.Navigation("Permissions");
+
+                    b.Navigation("Preferences");
+
+                    b.Navigation("ProfileImage");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 164 - 0
Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.cs

@@ -0,0 +1,164 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+    /// <inheritdoc />
+    public partial class RemoveEasyPassword : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "EasyPassword",
+                schema: "jellyfin",
+                table: "Users");
+
+            migrationBuilder.RenameTable(
+                name: "Users",
+                schema: "jellyfin",
+                newName: "Users");
+
+            migrationBuilder.RenameTable(
+                name: "Preferences",
+                schema: "jellyfin",
+                newName: "Preferences");
+
+            migrationBuilder.RenameTable(
+                name: "Permissions",
+                schema: "jellyfin",
+                newName: "Permissions");
+
+            migrationBuilder.RenameTable(
+                name: "ItemDisplayPreferences",
+                schema: "jellyfin",
+                newName: "ItemDisplayPreferences");
+
+            migrationBuilder.RenameTable(
+                name: "ImageInfos",
+                schema: "jellyfin",
+                newName: "ImageInfos");
+
+            migrationBuilder.RenameTable(
+                name: "HomeSection",
+                schema: "jellyfin",
+                newName: "HomeSection");
+
+            migrationBuilder.RenameTable(
+                name: "DisplayPreferences",
+                schema: "jellyfin",
+                newName: "DisplayPreferences");
+
+            migrationBuilder.RenameTable(
+                name: "Devices",
+                schema: "jellyfin",
+                newName: "Devices");
+
+            migrationBuilder.RenameTable(
+                name: "DeviceOptions",
+                schema: "jellyfin",
+                newName: "DeviceOptions");
+
+            migrationBuilder.RenameTable(
+                name: "CustomItemDisplayPreferences",
+                schema: "jellyfin",
+                newName: "CustomItemDisplayPreferences");
+
+            migrationBuilder.RenameTable(
+                name: "ApiKeys",
+                schema: "jellyfin",
+                newName: "ApiKeys");
+
+            migrationBuilder.RenameTable(
+                name: "ActivityLogs",
+                schema: "jellyfin",
+                newName: "ActivityLogs");
+
+            migrationBuilder.RenameTable(
+                name: "AccessSchedules",
+                schema: "jellyfin",
+                newName: "AccessSchedules");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.EnsureSchema(
+                name: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "Users",
+                newName: "Users",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "Preferences",
+                newName: "Preferences",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "Permissions",
+                newName: "Permissions",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "ItemDisplayPreferences",
+                newName: "ItemDisplayPreferences",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "ImageInfos",
+                newName: "ImageInfos",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "HomeSection",
+                newName: "HomeSection",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "DisplayPreferences",
+                newName: "DisplayPreferences",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "Devices",
+                newName: "Devices",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "DeviceOptions",
+                newName: "DeviceOptions",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "CustomItemDisplayPreferences",
+                newName: "CustomItemDisplayPreferences",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "ApiKeys",
+                newName: "ApiKeys",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "ActivityLogs",
+                newName: "ActivityLogs",
+                newSchema: "jellyfin");
+
+            migrationBuilder.RenameTable(
+                name: "AccessSchedules",
+                newName: "AccessSchedules",
+                newSchema: "jellyfin");
+
+            migrationBuilder.AddColumn<string>(
+                name: "EasyPassword",
+                schema: "jellyfin",
+                table: "Users",
+                type: "TEXT",
+                maxLength: 65535,
+                nullable: true);
+        }
+    }
+}

+ 14 - 20
Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs

@@ -15,9 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
         protected override void BuildModel(ModelBuilder modelBuilder)
         protected override void BuildModel(ModelBuilder modelBuilder)
         {
         {
 #pragma warning disable 612, 618
 #pragma warning disable 612, 618
-            modelBuilder
-                .HasDefaultSchema("jellyfin")
-                .HasAnnotation("ProductVersion", "6.0.9");
+            modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
                 {
                 {
@@ -41,7 +39,7 @@ namespace Jellyfin.Server.Implementations.Migrations
 
 
                     b.HasIndex("UserId");
                     b.HasIndex("UserId");
 
 
-                    b.ToTable("AccessSchedules", "jellyfin");
+                    b.ToTable("AccessSchedules");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
@@ -89,7 +87,7 @@ namespace Jellyfin.Server.Implementations.Migrations
 
 
                     b.HasIndex("DateCreated");
                     b.HasIndex("DateCreated");
 
 
-                    b.ToTable("ActivityLogs", "jellyfin");
+                    b.ToTable("ActivityLogs");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
@@ -121,7 +119,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.HasIndex("UserId", "ItemId", "Client", "Key")
                     b.HasIndex("UserId", "ItemId", "Client", "Key")
                         .IsUnique();
                         .IsUnique();
 
 
-                    b.ToTable("CustomItemDisplayPreferences", "jellyfin");
+                    b.ToTable("CustomItemDisplayPreferences");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
@@ -178,7 +176,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.HasIndex("UserId", "ItemId", "Client")
                     b.HasIndex("UserId", "ItemId", "Client")
                         .IsUnique();
                         .IsUnique();
 
 
-                    b.ToTable("DisplayPreferences", "jellyfin");
+                    b.ToTable("DisplayPreferences");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
@@ -200,7 +198,7 @@ namespace Jellyfin.Server.Implementations.Migrations
 
 
                     b.HasIndex("DisplayPreferencesId");
                     b.HasIndex("DisplayPreferencesId");
 
 
-                    b.ToTable("HomeSection", "jellyfin");
+                    b.ToTable("HomeSection");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
@@ -225,7 +223,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.HasIndex("UserId")
                     b.HasIndex("UserId")
                         .IsUnique();
                         .IsUnique();
 
 
-                    b.ToTable("ImageInfos", "jellyfin");
+                    b.ToTable("ImageInfos");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
@@ -269,7 +267,7 @@ namespace Jellyfin.Server.Implementations.Migrations
 
 
                     b.HasIndex("UserId");
                     b.HasIndex("UserId");
 
 
-                    b.ToTable("ItemDisplayPreferences", "jellyfin");
+                    b.ToTable("ItemDisplayPreferences");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@@ -300,7 +298,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                         .IsUnique()
                         .IsUnique()
                         .HasFilter("[UserId] IS NOT NULL");
                         .HasFilter("[UserId] IS NOT NULL");
 
 
-                    b.ToTable("Permissions", "jellyfin");
+                    b.ToTable("Permissions");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
@@ -333,7 +331,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                         .IsUnique()
                         .IsUnique()
                         .HasFilter("[UserId] IS NOT NULL");
                         .HasFilter("[UserId] IS NOT NULL");
 
 
-                    b.ToTable("Preferences", "jellyfin");
+                    b.ToTable("Preferences");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
@@ -362,7 +360,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.HasIndex("AccessToken")
                     b.HasIndex("AccessToken")
                         .IsUnique();
                         .IsUnique();
 
 
-                    b.ToTable("ApiKeys", "jellyfin");
+                    b.ToTable("ApiKeys");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
@@ -420,7 +418,7 @@ namespace Jellyfin.Server.Implementations.Migrations
 
 
                     b.HasIndex("UserId", "DeviceId");
                     b.HasIndex("UserId", "DeviceId");
 
 
-                    b.ToTable("Devices", "jellyfin");
+                    b.ToTable("Devices");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
@@ -441,7 +439,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.HasIndex("DeviceId")
                     b.HasIndex("DeviceId")
                         .IsUnique();
                         .IsUnique();
 
 
-                    b.ToTable("DeviceOptions", "jellyfin");
+                    b.ToTable("DeviceOptions");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
@@ -465,10 +463,6 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.Property<bool>("DisplayMissingEpisodes")
                     b.Property<bool>("DisplayMissingEpisodes")
                         .HasColumnType("INTEGER");
                         .HasColumnType("INTEGER");
 
 
-                    b.Property<string>("EasyPassword")
-                        .HasMaxLength(65535)
-                        .HasColumnType("TEXT");
-
                     b.Property<bool>("EnableAutoLogin")
                     b.Property<bool>("EnableAutoLogin")
                         .HasColumnType("INTEGER");
                         .HasColumnType("INTEGER");
 
 
@@ -554,7 +548,7 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.HasIndex("Username")
                     b.HasIndex("Username")
                         .IsUnique();
                         .IsUnique();
 
 
-                    b.ToTable("Users", "jellyfin");
+                    b.ToTable("Users");
                 });
                 });
 
 
             modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
             modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>

+ 0 - 2
Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs

@@ -114,8 +114,6 @@ namespace Jellyfin.Server.Implementations.Users
                 await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
                 await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
             }
             }
 
 
-            user.EasyPassword = pin;
-
             return new ForgotPasswordResult
             return new ForgotPasswordResult
             {
             {
                 Action = ForgotPasswordAction.PinCode,
                 Action = ForgotPasswordAction.PinCode,

+ 3 - 35
Jellyfin.Server.Implementations/Users/UserManager.cs

@@ -268,37 +268,16 @@ namespace Jellyfin.Server.Implementations.Users
             return ChangePassword(user, string.Empty);
             return ChangePassword(user, string.Empty);
         }
         }
 
 
-        /// <inheritdoc/>
-        public Task ResetEasyPassword(User user)
-        {
-            return ChangeEasyPassword(user, string.Empty, null);
-        }
-
         /// <inheritdoc/>
         /// <inheritdoc/>
         public async Task ChangePassword(User user, string newPassword)
         public async Task ChangePassword(User user, string newPassword)
         {
         {
             ArgumentNullException.ThrowIfNull(user);
             ArgumentNullException.ThrowIfNull(user);
-
-            await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
-            await UpdateUserAsync(user).ConfigureAwait(false);
-
-            await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
-        }
-
-        /// <inheritdoc/>
-        public async Task ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1)
-        {
-            if (newPassword is not null)
-            {
-                newPasswordSha1 = _cryptoProvider.CreatePasswordHash(newPassword).ToString();
-            }
-
-            if (string.IsNullOrWhiteSpace(newPasswordSha1))
+            if (user.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword))
             {
             {
-                throw new ArgumentNullException(nameof(newPasswordSha1));
+                throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword));
             }
             }
 
 
-            user.EasyPassword = newPasswordSha1;
+            await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
             await UpdateUserAsync(user).ConfigureAwait(false);
             await UpdateUserAsync(user).ConfigureAwait(false);
 
 
             await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
             await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
@@ -315,7 +294,6 @@ namespace Jellyfin.Server.Implementations.Users
                 ServerId = _appHost.SystemId,
                 ServerId = _appHost.SystemId,
                 HasPassword = hasPassword,
                 HasPassword = hasPassword,
                 HasConfiguredPassword = hasPassword,
                 HasConfiguredPassword = hasPassword,
-                HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword),
                 EnableAutoLogin = user.EnableAutoLogin,
                 EnableAutoLogin = user.EnableAutoLogin,
                 LastLoginDate = user.LastLoginDate,
                 LastLoginDate = user.LastLoginDate,
                 LastActivityDate = user.LastActivityDate,
                 LastActivityDate = user.LastActivityDate,
@@ -832,16 +810,6 @@ namespace Jellyfin.Server.Implementations.Users
                 }
                 }
             }
             }
 
 
-            if (!success
-                && _networkManager.IsInLocalNetwork(remoteEndPoint)
-                && user?.EnableLocalPassword == true
-                && !string.IsNullOrEmpty(user.EasyPassword))
-            {
-                // Check easy password
-                var passwordHash = PasswordHash.Parse(user.EasyPassword);
-                success = _cryptoProvider.Verify(passwordHash, password);
-            }
-
             return (authenticationProvider, username, success);
             return (authenticationProvider, username, success);
         }
         }
 
 

+ 0 - 1
Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs

@@ -127,7 +127,6 @@ namespace Jellyfin.Server.Migrations.Routines
                         RememberSubtitleSelections = config.RememberSubtitleSelections,
                         RememberSubtitleSelections = config.RememberSubtitleSelections,
                         SubtitleLanguagePreference = config.SubtitleLanguagePreference,
                         SubtitleLanguagePreference = config.SubtitleLanguagePreference,
                         Password = mockup.Password,
                         Password = mockup.Password,
-                        EasyPassword = mockup.EasyPassword,
                         LastLoginDate = mockup.LastLoginDate,
                         LastLoginDate = mockup.LastLoginDate,
                         LastActivityDate = mockup.LastActivityDate
                         LastActivityDate = mockup.LastActivityDate
                     };
                     };

+ 2 - 2
MediaBrowser.Controller/Entities/Folder.cs

@@ -730,7 +730,7 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.GetItemsResult(query);
             return LibraryManager.GetItemsResult(query);
         }
         }
 
 
-        private QueryResult<BaseItem> QueryWithPostFiltering2(InternalItemsQuery query)
+        protected QueryResult<BaseItem> QueryWithPostFiltering2(InternalItemsQuery query)
         {
         {
             var startIndex = query.StartIndex;
             var startIndex = query.StartIndex;
             var limit = query.Limit;
             var limit = query.Limit;
@@ -1272,7 +1272,7 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             ArgumentNullException.ThrowIfNull(user);
             ArgumentNullException.ThrowIfNull(user);
 
 
-            return GetChildren(user, includeLinkedChildren, null);
+            return GetChildren(user, includeLinkedChildren, new InternalItemsQuery(user));
         }
         }
 
 
         public virtual List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
         public virtual List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)

+ 13 - 0
MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs

@@ -59,6 +59,11 @@ namespace MediaBrowser.Controller.Extensions
         /// </summary>
         /// </summary>
         public const string UnixSocketPermissionsKey = "kestrel:socketPermissions";
         public const string UnixSocketPermissionsKey = "kestrel:socketPermissions";
 
 
+        /// <summary>
+        /// The cache size of the SQL database, see cache_size.
+        /// </summary>
+        public const string SqliteCacheSizeKey = "sqlite:cacheSize";
+
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
         /// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
         /// </summary>
         /// </summary>
@@ -115,5 +120,13 @@ namespace MediaBrowser.Controller.Extensions
         /// <returns>The unix socket permissions.</returns>
         /// <returns>The unix socket permissions.</returns>
         public static string? GetUnixSocketPermissions(this IConfiguration configuration)
         public static string? GetUnixSocketPermissions(this IConfiguration configuration)
             => configuration[UnixSocketPermissionsKey];
             => configuration[UnixSocketPermissionsKey];
+
+        /// <summary>
+        /// Gets the cache_size from the <see cref="IConfiguration" />.
+        /// </summary>
+        /// <param name="configuration">The configuration to read the setting from.</param>
+        /// <returns>The sqlite cache size.</returns>
+        public static int? GetSqliteCacheSize(this IConfiguration configuration)
+            => configuration.GetValue<int?>(SqliteCacheSizeKey);
     }
     }
 }
 }

+ 0 - 16
MediaBrowser.Controller/Library/IUserManager.cs

@@ -96,13 +96,6 @@ namespace MediaBrowser.Controller.Library
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task ResetPassword(User user);
         Task ResetPassword(User user);
 
 
-        /// <summary>
-        /// Resets the easy password.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <returns>Task.</returns>
-        Task ResetEasyPassword(User user);
-
         /// <summary>
         /// <summary>
         /// Changes the password.
         /// Changes the password.
         /// </summary>
         /// </summary>
@@ -111,15 +104,6 @@ namespace MediaBrowser.Controller.Library
         /// <returns>Awaitable task.</returns>
         /// <returns>Awaitable task.</returns>
         Task ChangePassword(User user, string newPassword);
         Task ChangePassword(User user, string newPassword);
 
 
-        /// <summary>
-        /// Changes the easy password.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="newPassword">New password to use.</param>
-        /// <param name="newPasswordSha1">Hash of new password.</param>
-        /// <returns>Task.</returns>
-        Task ChangeEasyPassword(User user, string newPassword, string newPasswordSha1);
-
         /// <summary>
         /// <summary>
         /// Gets the user dto.
         /// Gets the user dto.
         /// </summary>
         /// </summary>

+ 1 - 1
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -783,7 +783,7 @@ namespace MediaBrowser.Model.Dto
         /// Gets or sets the LUFS value.
         /// Gets or sets the LUFS value.
         /// </summary>
         /// </summary>
         /// <value>The LUFS Value.</value>
         /// <value>The LUFS Value.</value>
-        public float LUFS { get; set; }
+        public float? LUFS { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the current program.
         /// Gets or sets the current program.

+ 1 - 0
MediaBrowser.Model/Dto/UserDto.cs

@@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Dto
         /// Gets or sets a value indicating whether this instance has configured easy password.
         /// Gets or sets a value indicating whether this instance has configured easy password.
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
+        [Obsolete("Easy Password has been replaced with Quick Connect")]
         public bool HasConfiguredEasyPassword { get; set; }
         public bool HasConfiguredEasyPassword { get; set; }
 
 
         /// <summary>
         /// <summary>

+ 11 - 0
tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs

@@ -6,6 +6,7 @@ using Emby.Server.Implementations.Data;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Configuration;
 using Moq;
 using Moq;
 using Xunit;
 using Xunit;
 
 
@@ -27,8 +28,18 @@ namespace Jellyfin.Server.Implementations.Tests.Data
             appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>()))
             appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>()))
                 .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal));
                 .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal));
 
 
+            var configSection = new Mock<IConfigurationSection>();
+            configSection
+                .SetupGet(x => x[It.Is<string>(s => s == MediaBrowser.Controller.Extensions.ConfigurationExtensions.SqliteCacheSizeKey)])
+                .Returns("0");
+            var config = new Mock<IConfiguration>();
+            config
+                .Setup(x => x.GetSection(It.Is<string>(s => s == MediaBrowser.Controller.Extensions.ConfigurationExtensions.SqliteCacheSizeKey)))
+                .Returns(configSection.Object);
+
             _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
             _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
             _fixture.Inject(appHost);
             _fixture.Inject(appHost);
+            _fixture.Inject(config);
             _sqliteItemRepository = _fixture.Create<SqliteItemRepository>();
             _sqliteItemRepository = _fixture.Create<SqliteItemRepository>();
         }
         }