2
0
Эх сурвалжийг харах

fixes #941 - Rework password recovery and remove IsLocal checks

Luke Pulverenti 10 жил өмнө
parent
commit
40897bac14
34 өөрчлөгдсөн 444 нэмэгдсэн , 259 устгасан
  1. 2 5
      MediaBrowser.Api/Images/ImageRequest.cs
  2. 52 3
      MediaBrowser.Api/Images/ImageService.cs
  3. 6 1
      MediaBrowser.Api/Playback/Hls/MpegDashService.cs
  4. 2 1
      MediaBrowser.Api/System/SystemService.cs
  5. 41 28
      MediaBrowser.Api/UserService.cs
  6. 6 0
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  7. 2 6
      MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
  8. 23 0
      MediaBrowser.Controller/Library/IUserManager.cs
  9. 0 7
      MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
  10. 1 2
      MediaBrowser.Controller/Session/ISessionManager.cs
  11. 2 2
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  12. 8 3
      MediaBrowser.Dlna/PlayTo/Device.cs
  13. 0 2
      MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
  14. 0 76
      MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs
  15. 0 76
      MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs
  16. 9 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  17. 9 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  18. 3 4
      MediaBrowser.Model/Drawing/ImageOutputFormat.cs
  19. 1 4
      MediaBrowser.Model/Dto/ImageOptions.cs
  20. 3 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  21. 10 0
      MediaBrowser.Model/Users/ForgotPasswordAction.cs
  22. 23 0
      MediaBrowser.Model/Users/ForgotPasswordResult.cs
  23. 17 0
      MediaBrowser.Model/Users/PinRedeemResult.cs
  24. 1 1
      MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs
  25. 1 1
      MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
  26. 13 12
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  27. 6 9
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
  28. 169 2
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  29. 8 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  30. 7 1
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  31. 2 9
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  32. 3 3
      MediaBrowser.ServerApplication/MainStartup.cs
  33. 2 0
      MediaBrowser.WebDashboard/Api/PackageCreator.cs
  34. 12 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 2 - 5
MediaBrowser.Api/Images/ImageRequest.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
 using ServiceStack;
 
 namespace MediaBrowser.Api.Images
@@ -54,7 +53,7 @@ namespace MediaBrowser.Api.Images
         public bool EnableImageEnhancers { get; set; }
 
         [ApiMember(Name = "Format", Description = "Determines the output foramt of the image - original,gif,jpg,png", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public ImageOutputFormat Format { get; set; }
+        public string Format { get; set; }
 
         [ApiMember(Name = "AddPlayedIndicator", Description = "Optional. Add a played indicator", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool AddPlayedIndicator { get; set; }
@@ -71,8 +70,6 @@ namespace MediaBrowser.Api.Images
         public ImageRequest()
         {
             EnableImageEnhancers = true;
-
-            Format = ImageOutputFormat.Original;
         }
     }
 

+ 52 - 3
MediaBrowser.Api/Images/ImageService.cs

@@ -542,7 +542,8 @@ namespace MediaBrowser.Api.Images
 
             }).ToList() : new List<IImageEnhancer>();
 
-            var contentType = GetMimeType(request.Format, imageInfo.Path);
+            var format = GetOutputFormat(request, imageInfo, supportedImageEnhancers);
+            var contentType = GetMimeType(format, imageInfo.Path);
 
             var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers));
 
@@ -562,6 +563,7 @@ namespace MediaBrowser.Api.Images
             return GetImageResult(item,
                 request,
                 imageInfo,
+                format,
                 supportedImageEnhancers,
                 contentType,
                 cacheDuration,
@@ -573,6 +575,7 @@ namespace MediaBrowser.Api.Images
         private async Task<object> GetImageResult(IHasImages item,
             ImageRequest request,
             ItemImageInfo image,
+            ImageOutputFormat format,
             List<IImageEnhancer> enhancers,
             string contentType,
             TimeSpan? cacheDuration,
@@ -598,11 +601,11 @@ namespace MediaBrowser.Api.Images
                 MaxWidth = request.MaxWidth,
                 Quality = request.Quality,
                 Width = request.Width,
-                OutputFormat = request.Format,
                 AddPlayedIndicator = request.AddPlayedIndicator,
                 PercentPlayed = request.PercentPlayed ?? 0,
                 UnplayedCount = request.UnplayedCount,
-                BackgroundColor = request.BackgroundColor
+                BackgroundColor = request.BackgroundColor,
+                OutputFormat = format
             };
 
             var file = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
@@ -617,6 +620,52 @@ namespace MediaBrowser.Api.Images
             });
         }
 
+        private ImageOutputFormat GetOutputFormat(ImageRequest request, ItemImageInfo image, List<IImageEnhancer> enhancers)
+        {
+            if (!string.IsNullOrWhiteSpace(request.Format))
+            {
+                ImageOutputFormat format;
+                if (Enum.TryParse(request.Format, true, out format))
+                {
+                    return format;
+                }
+            }
+
+            var serverFormats = _imageProcessor.GetSupportedImageOutputFormats();
+
+            var clientFormats = GetClientSupportedFormats();
+
+            if (serverFormats.Contains(ImageOutputFormat.Webp) &&
+                clientFormats.Contains(ImageOutputFormat.Webp))
+            {
+                return ImageOutputFormat.Webp;
+            }
+
+            if (enhancers.Count > 0)
+            {
+                return ImageOutputFormat.Png;
+            }
+
+            if (string.Equals(Path.GetExtension(image.Path), ".jpg", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(Path.GetExtension(image.Path), ".jpeg", StringComparison.OrdinalIgnoreCase))
+            {
+                return ImageOutputFormat.Jpg;
+            }
+
+            // We can't predict if there will be transparency or not, so play it safe
+            return ImageOutputFormat.Png;
+        }
+
+        private ImageOutputFormat[] GetClientSupportedFormats()
+        {
+            if (Request.AcceptTypes.Contains("image/webp", StringComparer.OrdinalIgnoreCase))
+            {
+                return new[] { ImageOutputFormat.Webp, ImageOutputFormat.Jpg, ImageOutputFormat.Png };
+            }
+
+            return new[] { ImageOutputFormat.Jpg, ImageOutputFormat.Png };
+        }
+
         private string GetMimeType(ImageOutputFormat format, string path)
         {
             if (format == ImageOutputFormat.Bmp)

+ 6 - 1
MediaBrowser.Api/Playback/Hls/MpegDashService.cs

@@ -598,6 +598,8 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
 
+            args += " -r 24 -g 24";
+
             // Add resolution params, if specified
             if (!hasGraphicalSubs)
             {
@@ -615,6 +617,9 @@ namespace MediaBrowser.Api.Playback.Hls
 
         protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
         {
+            // test url http://192.168.1.2:8096/mediabrowser/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
+            // Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
+
             var threads = GetNumberOfThreads(state, false);
 
             var inputModifier = GetInputModifier(state);
@@ -624,7 +629,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var segmentFilename = Path.GetFileNameWithoutExtension(outputPath) + "%03d" + GetSegmentFileExtension(state);
 
-            var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f ssegment -segment_time {6} -segment_format_options movflags=+faststart -segment_list_size {8} -segment_list \"{9}\" {10}",
+            var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}",
                 inputModifier,
                 GetInputArgument(transcodingJobId, state),
                 threads,

+ 2 - 1
MediaBrowser.Api/System/SystemService.cs

@@ -44,9 +44,10 @@ namespace MediaBrowser.Api.System
     /// This is currently not authenticated because the uninstaller needs to be able to shutdown the server.
     /// </summary>
     [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")]
-    [Authenticated(AllowLocal = true)]
     public class ShutdownApplication
     {
+        // TODO: This is not currently authenticated due to uninstaller
+        // Improve later
     }
 
     [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]

+ 41 - 28
MediaBrowser.Api/UserService.cs

@@ -12,6 +12,7 @@ using ServiceStack;
 using ServiceStack.Text.Controller;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 
@@ -175,6 +176,20 @@ namespace MediaBrowser.Api
         public string Name { get; set; }
     }
 
+    [Route("/Users/ForgotPassword", "POST", Summary = "Initiates the forgot password process for a local user")]
+    public class ForgotPassword : IReturn<ForgotPasswordResult>
+    {
+        [ApiMember(Name = "EnteredUsername", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")]
+        public string EnteredUsername { get; set; }
+    }
+
+    [Route("/Users/ForgotPassword/Pin", "POST", Summary = "Redeems a forgot password pin")]
+    public class ForgotPasswordPin : IReturn<PinRedeemResult>
+    {
+        [ApiMember(Name = "Pin", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")]
+        public string Pin { get; set; }
+    }
+
     /// <summary>
     /// Class UsersService
     /// </summary>
@@ -217,35 +232,16 @@ namespace MediaBrowser.Api
                 });
             }
 
-            var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
-            var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase);
-
-            if (Request.IsLocal && isDashboard)
+            // TODO: Uncomment once clients can handle an empty user list (and below)
+            //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp))
             {
-                var users = _userManager.Users
-                    .Where(i => !i.Configuration.IsDisabled && !(i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest))
-                    .ToList();
-
-                return ToOptimizedResult(users);
+                return Get(new GetUsers
+                {
+                    IsHidden = false,
+                    IsDisabled = false
+                });
             }
 
-            // TODO: Uncomment this once all clients can handle an empty user list.
-            return Get(new GetUsers
-            {
-                IsHidden = false,
-                IsDisabled = false
-            });
-
-            //// TODO: Add or is authenticated
-            //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp))
-            //{
-            //    return Get(new GetUsers
-            //    {
-            //        IsHidden = false,
-            //        IsDisabled = false
-            //    });
-            //}
-
             //// Return empty when external
             //return ToOptimizedResult(new List<UserDto>());
         }
@@ -379,7 +375,7 @@ namespace MediaBrowser.Api
                 RemoteEndPoint = Request.RemoteIp,
                 Username = request.Username
 
-            }, Request.IsLocal).ConfigureAwait(false);
+            }).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
@@ -419,7 +415,7 @@ namespace MediaBrowser.Api
                 await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
             }
         }
-        
+
         /// <summary>
         /// Posts the specified request.
         /// </summary>
@@ -510,5 +506,22 @@ namespace MediaBrowser.Api
 
             return ToOptimizedResult(result);
         }
+
+        /// <summary>
+        /// Posts the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
+        public object Post(ForgotPassword request)
+        {
+            var isLocal = Request.IsLocal || _networkManager.IsInLocalNetwork(Request.RemoteIp);
+
+            return _userManager.StartForgotPasswordProcess(request.EnteredUsername, isLocal);
+        }
+
+        public object Post(ForgotPasswordPin request)
+        {
+            return _userManager.RedeemPasswordResetPin(request.Pin);
+        }
     }
 }

+ 6 - 0
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -89,5 +89,11 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="imageIndex">Index of the image.</param>
         /// <returns>Task{System.String}.</returns>
         Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex);
+
+        /// <summary>
+        /// Gets the supported image output formats.
+        /// </summary>
+        /// <returns>ImageOutputFormat[].</returns>
+        ImageOutputFormat[] GetSupportedImageOutputFormats();
     }
 }

+ 2 - 6
MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Drawing;
+using System;
 using System.Collections.Generic;
 using System.IO;
 
@@ -59,12 +60,7 @@ namespace MediaBrowser.Controller.Drawing
 
         private bool IsOutputFormatDefault(string originalImagePath)
         {
-            if (OutputFormat == ImageOutputFormat.Original)
-            {
-                return true;
-            }
-
-            return string.Equals(Path.GetExtension(originalImagePath), "." + OutputFormat);
+            return string.Equals(Path.GetExtension(originalImagePath), "." + OutputFormat, StringComparison.OrdinalIgnoreCase);
         }
     }
 }

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

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Library
 {
@@ -56,6 +57,13 @@ namespace MediaBrowser.Controller.Library
         /// <returns>User.</returns>
         User GetUserById(string id);
 
+        /// <summary>
+        /// Gets the name of the user by.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <returns>User.</returns>
+        User GetUserByName(string name);
+
         /// <summary>
         /// Authenticates a User and returns a result indicating whether or not it succeeded
         /// </summary>
@@ -141,5 +149,20 @@ namespace MediaBrowser.Controller.Library
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <returns>Task&lt;System.Boolean&gt;.</returns>
         Task<bool> AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint);
+
+        /// <summary>
+        /// Starts the forgot password process.
+        /// </summary>
+        /// <param name="enteredUsername">The entered username.</param>
+        /// <param name="isInNetwork">if set to <c>true</c> [is in network].</param>
+        /// <returns>ForgotPasswordResult.</returns>
+        ForgotPasswordResult StartForgotPasswordProcess(string enteredUsername, bool isInNetwork);
+
+        /// <summary>
+        /// Redeems the password reset pin.
+        /// </summary>
+        /// <param name="pin">The pin.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
     }
 }

+ 0 - 7
MediaBrowser.Controller/Net/AuthenticatedAttribute.cs

@@ -9,12 +9,6 @@ namespace MediaBrowser.Controller.Net
     {
         public IAuthService AuthService { get; set; }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether or not to allow local unauthenticated access.
-        /// </summary>
-        /// <value><c>true</c> if [allow local]; otherwise, <c>false</c>.</value>
-        public bool AllowLocal { get; set; }
-
         /// <summary>
         /// Gets or sets the roles.
         /// </summary>
@@ -70,7 +64,6 @@ namespace MediaBrowser.Controller.Net
     {
         bool EscapeParentalControl { get; }
 
-        bool AllowLocal { get; }
         IEnumerable<string> GetRoles();
     }
 }

+ 1 - 2
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -223,9 +223,8 @@ namespace MediaBrowser.Controller.Session
         /// Authenticates the new session.
         /// </summary>
         /// <param name="request">The request.</param>
-        /// <param name="isLocal">if set to <c>true</c> [is local].</param>
         /// <returns>Task{SessionInfo}.</returns>
-        Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request, bool isLocal);
+        Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request);
 
         /// <summary>
         /// Reports the capabilities.

+ 2 - 2
MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

@@ -1,5 +1,4 @@
-using System.Linq;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
@@ -16,6 +15,7 @@ using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;

+ 8 - 3
MediaBrowser.Dlna/PlayTo/Device.cs

@@ -124,7 +124,6 @@ namespace MediaBrowser.Dlna.PlayTo
                     {
                         _logger.Debug("RestartTimer");
                         _timer.Change(10, GetPlaybackTimerIntervalMs());
-
                         _volumeTimer.Change(100, GetVolumeTimerIntervalMs());
                     }
 
@@ -147,8 +146,14 @@ namespace MediaBrowser.Dlna.PlayTo
                         _logger.Debug("RestartTimerInactive");
                         var interval = GetInactiveTimerIntervalMs();
 
-                        _timer.Change(interval, interval);
-                        _volumeTimer.Change(Timeout.Infinite, Timeout.Infinite);
+                        if (_timer != null)
+                        {
+                            _timer.Change(interval, interval);
+                        }
+                        if (_volumeTimer != null)
+                        {
+                            _volumeTimer.Change(Timeout.Infinite, Timeout.Infinite);
+                        }
                     }
 
                     _timerActive = false;

+ 0 - 2
MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj

@@ -74,8 +74,6 @@
     <Compile Include="Providers\SeriesXmlProvider.cs" />
     <Compile Include="Providers\TrailerXmlProvider.cs" />
     <Compile Include="Providers\VideoXmlProvider.cs" />
-    <Compile Include="Savers\AlbumXmlSaver.cs" />
-    <Compile Include="Savers\ArtistXmlSaver.cs" />
     <Compile Include="Savers\BoxSetXmlSaver.cs" />
     <Compile Include="Savers\ChannelXmlSaver.cs" />
     <Compile Include="Savers\EpisodeXmlSaver.cs" />

+ 0 - 76
MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs

@@ -1,76 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-
-namespace MediaBrowser.LocalMetadata.Savers
-{
-    class AlbumXmlSaver : IMetadataFileSaver
-    {
-        public string Name
-        {
-            get
-            {
-                return "Media Browser Xml";
-            }
-        }
-
-        private readonly IServerConfigurationManager _config;
-
-        public AlbumXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
-        /// <summary>
-        /// Determines whether [is enabled for] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
-        {
-            if (!item.SupportsLocalMetadata)
-            {
-                return false;
-            }
-
-            return item is MusicAlbum && updateType >= ItemUpdateType.MetadataDownload;
-        }
-
-        /// <summary>
-        /// Saves the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public void Save(IHasMetadata item, CancellationToken cancellationToken)
-        {
-            var builder = new StringBuilder();
-
-            builder.Append("<Item>");
-
-            XmlSaverHelpers.AddCommonNodes((MusicAlbum)item, builder);
-
-            builder.Append("</Item>");
-
-            var xmlFilePath = GetSavePath(item);
-
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }, _config);
-        }
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetSavePath(IHasMetadata item)
-        {
-            return Path.Combine(item.Path, "album.xml");
-        }
-    }
-}

+ 0 - 76
MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs

@@ -1,76 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-
-namespace MediaBrowser.LocalMetadata.Savers
-{
-    class ArtistXmlSaver : IMetadataFileSaver
-    {
-        public string Name
-        {
-            get
-            {
-                return "Media Browser Xml";
-            }
-        }
-
-        private readonly IServerConfigurationManager _config;
-
-        public ArtistXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
-        /// <summary>
-        /// Determines whether [is enabled for] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
-        {
-            if (!item.SupportsLocalMetadata)
-            {
-                return false;
-            }
-
-            return item is MusicArtist && updateType >= ItemUpdateType.MetadataDownload;
-        }
-
-        /// <summary>
-        /// Saves the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public void Save(IHasMetadata item, CancellationToken cancellationToken)
-        {
-            var builder = new StringBuilder();
-
-            builder.Append("<Item>");
-
-            XmlSaverHelpers.AddCommonNodes((MusicArtist)item, builder);
-
-            builder.Append("</Item>");
-
-            var xmlFilePath = GetSavePath(item);
-
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }, _config);
-        }
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetSavePath(IHasMetadata item)
-        {
-            return Path.Combine(item.Path, "artist.xml");
-        }
-    }
-}

+ 9 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -1103,6 +1103,15 @@
     <Compile Include="..\MediaBrowser.Model\Users\AuthenticationResult.cs">
       <Link>Users\AuthenticationResult.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Users\ForgotPasswordAction.cs">
+      <Link>Users\ForgotPasswordAction.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Users\ForgotPasswordResult.cs">
+      <Link>Users\ForgotPasswordResult.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Users\PinRedeemResult.cs">
+      <Link>Users\PinRedeemResult.cs</Link>
+    </Compile>
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>

+ 9 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -1062,6 +1062,15 @@
     <Compile Include="..\MediaBrowser.Model\Users\AuthenticationResult.cs">
       <Link>Users\AuthenticationResult.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Users\ForgotPasswordAction.cs">
+      <Link>Users\ForgotPasswordAction.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Users\ForgotPasswordResult.cs">
+      <Link>Users\ForgotPasswordResult.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Users\PinRedeemResult.cs">
+      <Link>Users\PinRedeemResult.cs</Link>
+    </Compile>
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>

+ 3 - 4
MediaBrowser.Model/Drawing/ImageOutputFormat.cs

@@ -6,10 +6,6 @@ namespace MediaBrowser.Model.Drawing
     /// </summary>
     public enum ImageOutputFormat
     {
-        /// <summary>
-        /// The original
-        /// </summary>
-        Original,
         /// <summary>
         /// The BMP
         /// </summary>
@@ -26,6 +22,9 @@ namespace MediaBrowser.Model.Drawing
         /// The PNG
         /// </summary>
         Png,
+        /// <summary>
+        /// The webp
+        /// </summary>
         Webp
     }
 }

+ 1 - 4
MediaBrowser.Model/Dto/ImageOptions.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
-using System;
 
 namespace MediaBrowser.Model.Dto
 {
@@ -74,7 +73,7 @@ namespace MediaBrowser.Model.Dto
         /// Gets or sets the format.
         /// </summary>
         /// <value>The format.</value>
-        public ImageOutputFormat Format { get; set; }
+        public ImageOutputFormat? Format { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether [add played indicator].
@@ -100,8 +99,6 @@ namespace MediaBrowser.Model.Dto
         public ImageOptions()
         {
             EnableImageEnhancers = true;
-
-            Format = ImageOutputFormat.Original;
         }
     }
 }

+ 3 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -407,6 +407,9 @@
     <Compile Include="Updates\PackageInfo.cs" />
     <Compile Include="Updates\PackageVersionInfo.cs" />
     <Compile Include="Users\AuthenticationResult.cs" />
+    <Compile Include="Users\ForgotPasswordAction.cs" />
+    <Compile Include="Users\ForgotPasswordResult.cs" />
+    <Compile Include="Users\PinRedeemResult.cs" />
     <None Include="Fody.targets" />
     <None Include="FodyWeavers.xml" />
     <None Include="MediaBrowser.Model.snk" />

+ 10 - 0
MediaBrowser.Model/Users/ForgotPasswordAction.cs

@@ -0,0 +1,10 @@
+
+namespace MediaBrowser.Model.Users
+{
+    public enum ForgotPasswordAction
+    {
+        ContactAdmin = 0,
+        PinCode = 1,
+        InNetworkRequired = 2
+    }
+}

+ 23 - 0
MediaBrowser.Model/Users/ForgotPasswordResult.cs

@@ -0,0 +1,23 @@
+using System;
+
+namespace MediaBrowser.Model.Users
+{
+    public class ForgotPasswordResult
+    {
+        /// <summary>
+        /// Gets or sets the action.
+        /// </summary>
+        /// <value>The action.</value>
+        public ForgotPasswordAction Action { get; set; }
+        /// <summary>
+        /// Gets or sets the pin file.
+        /// </summary>
+        /// <value>The pin file.</value>
+        public string PinFile { get; set; }
+        /// <summary>
+        /// Gets or sets the pin expiration date.
+        /// </summary>
+        /// <value>The pin expiration date.</value>
+        public DateTime? PinExpirationDate { get; set; }
+    }
+}

+ 17 - 0
MediaBrowser.Model/Users/PinRedeemResult.cs

@@ -0,0 +1,17 @@
+
+namespace MediaBrowser.Model.Users
+{
+    public class PinRedeemResult
+    {
+        /// <summary>
+        /// Gets or sets a value indicating whether this <see cref="PinRedeemResult"/> is success.
+        /// </summary>
+        /// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
+        public bool Success { get; set; }
+        /// <summary>
+        /// Gets or sets the users reset.
+        /// </summary>
+        /// <value>The users reset.</value>
+        public string[] UsersReset { get; set; }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs

@@ -230,7 +230,7 @@ namespace MediaBrowser.Providers.Subtitles
             }
 
             Utilities.HttpClient = _httpClient;
-            OpenSubtitles.SetUserAgent("OS Test User Agent");
+            OpenSubtitles.SetUserAgent("mediabrowser.tv");
 
             await Login(cancellationToken).ConfigureAwait(false);
 

+ 1 - 1
MediaBrowser.Server.Implementations/Connect/ConnectManager.cs

@@ -1027,7 +1027,7 @@ namespace MediaBrowser.Server.Implementations.Connect
         {
             var user = e.Argument;
 
-            //await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
+            await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
         }
 
         private async Task TryUploadUserPreferences(User user, CancellationToken cancellationToken)

+ 13 - 12
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -129,6 +129,15 @@ namespace MediaBrowser.Server.Implementations.Drawing
             }
         }
 
+        public ImageOutputFormat[] GetSupportedImageOutputFormats()
+        {
+            if (_webpAvailable)
+            {
+                return new[] { ImageOutputFormat.Webp, ImageOutputFormat.Gif, ImageOutputFormat.Jpg, ImageOutputFormat.Png };
+            }
+            return new[] { ImageOutputFormat.Gif, ImageOutputFormat.Jpg, ImageOutputFormat.Png };
+        }
+
         public async Task<string> ProcessImage(ImageProcessingOptions options)
         {
             if (options == null)
@@ -212,9 +221,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
                             var newWidth = Convert.ToInt32(newSize.Width);
                             var newHeight = Convert.ToInt32(newSize.Height);
 
-                            var selectedOutputFormat = options.OutputFormat == ImageOutputFormat.Webp && !_webpAvailable
-                                ? GetFallbackImageFormat(originalImagePath)
-                                : options.OutputFormat;
+                            var selectedOutputFormat = options.OutputFormat;
+
+                            _logger.Debug("Processing image to {0}", selectedOutputFormat);
 
                             // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
                             // Also, Webp only supports Format32bppArgb and Format32bppRgb
@@ -279,11 +288,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
             }
         }
 
-        private ImageOutputFormat GetFallbackImageFormat(string originalImagePath)
-        {
-            return ImageOutputFormat.Png;
-        }
-
         private void SaveToWebP(Bitmap thumbnail, Stream toStream, int quality)
         {
             new SimpleEncoder().Encode(thumbnail, toStream, quality);
@@ -479,10 +483,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
             filename += "datemodified=" + dateModified.Ticks;
 
-            if (format != ImageOutputFormat.Original)
-            {
-                filename += "f=" + format;
-            }
+            filename += "f=" + format;
 
             var hasIndicator = false;
 

+ 6 - 9
MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -63,17 +63,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
             // This code is executed before the service
             var auth = AuthorizationContext.GetAuthorizationInfo(req);
 
-            if (!authAttribtues.AllowLocal || !req.IsLocal)
+            if (!string.IsNullOrWhiteSpace(auth.Token) ||
+                !_config.Configuration.InsecureApps6.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase))
             {
-                if (!string.IsNullOrWhiteSpace(auth.Token) ||
-                    !_config.Configuration.InsecureApps6.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-                {
-                    var valid = IsValidConnectKey(auth.Token);
+                var valid = IsValidConnectKey(auth.Token);
 
-                    if (!valid)
-                    {
-                        SessionManager.ValidateSecurityToken(auth.Token);
-                    }
+                if (!valid)
+                {
+                    SessionManager.ValidateSecurityToken(auth.Token);
                 }
             }
 

+ 169 - 2
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Drawing;
@@ -17,9 +18,11 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
 using MediaBrowser.Server.Implementations.Security;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Security.Cryptography;
@@ -65,7 +68,7 @@ namespace MediaBrowser.Server.Implementations.Library
         private readonly Func<IImageProcessor> _imageProcessorFactory;
         private readonly Func<IDtoService> _dtoServiceFactory;
         private readonly Func<IConnectManager> _connectFactory;
-        private readonly IApplicationHost _appHost;
+        private readonly IServerApplicationHost _appHost;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="UserManager" /> class.
@@ -73,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="logger">The logger.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userRepository">The user repository.</param>
-        public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IApplicationHost appHost)
+        public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost)
         {
             _logger = logger;
             UserRepository = userRepository;
@@ -85,6 +88,8 @@ namespace MediaBrowser.Server.Implementations.Library
             _appHost = appHost;
             ConfigurationManager = configurationManager;
             Users = new List<User>();
+
+            DeletePinFile();
         }
 
         #region UserUpdated Event
@@ -145,6 +150,16 @@ namespace MediaBrowser.Server.Implementations.Library
             return GetUserById(new Guid(id));
         }
 
+        public User GetUserByName(string name)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException("name");
+            }
+
+            return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
+        }
+
         public async Task Initialize()
         {
             Users = await LoadUsers().ConfigureAwait(false);
@@ -599,5 +614,157 @@ namespace MediaBrowser.Server.Implementations.Library
 
             EventHelper.FireEventIfNotNull(UserConfigurationUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
         }
+
+        private string PasswordResetFile
+        {
+            get { return Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); }
+        }
+
+        private string _lastPin;
+        private PasswordPinCreationResult _lastPasswordPinCreationResult;
+        private int _pinAttempts;
+
+        private PasswordPinCreationResult CreatePasswordResetPin()
+        {
+            var num = new Random().Next(1, 9999);
+
+            var path = PasswordResetFile;
+
+            var pin = num.ToString("0000", CultureInfo.InvariantCulture);
+            _lastPin = pin;
+
+            var time = TimeSpan.FromMinutes(5);
+            var expiration = DateTime.UtcNow.Add(time);
+
+            var text = new StringBuilder();
+
+            var info = _appHost.GetSystemInfo();
+            var localAddress = info.LocalAddress ?? string.Empty;
+
+            text.AppendLine("Use your web browser to visit:");
+            text.AppendLine(string.Empty);
+            text.AppendLine(localAddress + "/mediabrowser/web/forgotpasswordpin.html");
+            text.AppendLine(string.Empty);
+            text.AppendLine("Enter the following pin code:");
+            text.AppendLine(string.Empty);
+            text.AppendLine(pin);
+            text.AppendLine(string.Empty);
+            text.AppendLine("The pin code will expire at " + expiration.ToLocalTime().ToShortDateString() + " " + expiration.ToLocalTime().ToShortTimeString());
+
+            File.WriteAllText(path, text.ToString(), Encoding.UTF8);
+
+            var result = new PasswordPinCreationResult
+            {
+                PinFile = path,
+                ExpirationDate = expiration
+            };
+
+            _lastPasswordPinCreationResult = result;
+            _pinAttempts = 0;
+
+            return result;
+        }
+
+        public ForgotPasswordResult StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
+        {
+            DeletePinFile();
+
+            var user = string.IsNullOrWhiteSpace(enteredUsername) ?
+                null :
+                GetUserByName(enteredUsername);
+
+            if (user != null && user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest)
+            {
+                throw new ArgumentException("Unable to process forgot password request for guests.");
+            }
+
+            var action = ForgotPasswordAction.InNetworkRequired;
+            string pinFile = null;
+            DateTime? expirationDate = null;
+
+            if (user != null && !user.Configuration.IsAdministrator)
+            {
+                action = ForgotPasswordAction.ContactAdmin;
+            }
+            else
+            {
+                if (isInNetwork)
+                {
+                    action = ForgotPasswordAction.PinCode;
+                }
+
+                var result = CreatePasswordResetPin();
+                pinFile = result.PinFile;
+                expirationDate = result.ExpirationDate;
+            }
+
+            return new ForgotPasswordResult
+            {
+                Action = action,
+                PinFile = pinFile,
+                PinExpirationDate = expirationDate
+            };
+        }
+
+        public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+        {
+            DeletePinFile();
+
+            var usersReset = new List<string>();
+
+            var valid = !string.IsNullOrWhiteSpace(_lastPin) && 
+                string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) &&
+                _lastPasswordPinCreationResult != null &&
+                _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow;
+
+            if (valid)
+            {
+                _lastPin = null;
+                _lastPasswordPinCreationResult = null;
+
+                var users = Users.Where(i => !i.ConnectLinkType.HasValue || i.ConnectLinkType.Value != UserLinkType.Guest)
+                        .ToList();
+
+                foreach (var user in users)
+                {
+                    await ResetPassword(user).ConfigureAwait(false);
+                    usersReset.Add(user.Name);
+                }
+            }
+            else
+            {
+                _pinAttempts++;
+                if (_pinAttempts >= 3)
+                {
+                    _lastPin = null;
+                    _lastPasswordPinCreationResult = null;
+                }
+            }
+
+            return new PinRedeemResult
+            {
+                Success = valid,
+                UsersReset = usersReset.ToArray()
+            };
+        }
+
+        private void DeletePinFile()
+        {
+            try
+            {
+                File.Delete(PasswordResetFile);
+            }
+            catch
+            {
+
+            }
+        }
+
+        class PasswordPinCreationResult
+        {
+            public string PinFile { get; set; }
+            public DateTime ExpirationDate { get; set; }
+        }
+
     }
 }

+ 8 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -624,5 +624,12 @@
     "MessageLoggedOutParentalControl": "Access is currently restricted. Please try again later.",
     "DefaultErrorMessage": "There was an error processing the request. Please try again later.",
     "ButtonAccept": "Accept",
-    "ButtonReject": "Reject"
+    "ButtonReject": "Reject",
+    "HeaderForgotPassword": "Forgot Password",
+    "MessageContactAdminToResetPassword": "Please contact your system administrator to reset your password.",
+    "MessageForgotPasswordInNetworkRequired": "Please try again within your home network to initiate the password reset process.",
+    "MessageForgotPasswordFileCreated": "The following file has been created on your server and contains instructions on how to proceed:",
+    "MessageForgotPasswordFileExpiration": "The reset pin will expire at {0}.",
+    "MessageInvalidForgotPasswordPin": "An invalid or expired pin was entered. Please try again.",
+    "MessagePasswordResetForUsers":  "Passwords have been reset for the following users:"
 }

+ 7 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -1270,5 +1270,11 @@
     "LabelSelectLastestItemsFolders": "Include media from the following sections in Latest Items",
     "HeaderShareMediaFolders": "Share Media Folders",
     "MessageGuestSharingPermissionsHelp": "Most features are initially unavailable to guests but can be enabled as needed.",
-    "HeaderInvitations": "Invitations"
+    "HeaderInvitations": "Invitations",
+    "LabelForgotPasswordUsernameHelp": "Enter your username, if you remember it.",
+    "HeaderForgotPassword": "Forgot Password",
+    "TitleForgotPassword": "Forgot Password",
+    "TitlePasswordReset":  "Password Reset",
+    "LabelPasswordRecoveryPinCode": "Pin code:",
+    "HeaderPasswordReset":  "Password Reset"
 }

+ 2 - 9
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1300,23 +1300,16 @@ namespace MediaBrowser.Server.Implementations.Session
         /// Authenticates the new session.
         /// </summary>
         /// <param name="request">The request.</param>
-        /// <param name="isLocal">if set to <c>true</c> [is local].</param>
         /// <returns>Task{SessionInfo}.</returns>
         /// <exception cref="AuthenticationException">Invalid user or password entered.</exception>
         /// <exception cref="System.UnauthorizedAccessException">Invalid user or password entered.</exception>
         /// <exception cref="UnauthorizedAccessException">Invalid user or password entered.</exception>
-        public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request,
-            bool isLocal)
+        public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
         {
             var user = _userManager.Users
                 .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
 
-            var allowWithoutPassword = isLocal &&
-                                       string.Equals(request.App, "Dashboard", StringComparison.OrdinalIgnoreCase)
-                                       && !(user != null && user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest);
-
-            var result = allowWithoutPassword ||
-                await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
+            var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
 
             if (!result)
             {

+ 3 - 3
MediaBrowser.ServerApplication/MainStartup.cs

@@ -36,7 +36,9 @@ namespace MediaBrowser.ServerApplication
             var options = new StartupOptions();
             _isRunningAsService = options.ContainsOption("-service");
 
-            var applicationPath = Process.GetCurrentProcess().MainModule.FileName;
+            var currentProcess = Process.GetCurrentProcess();
+
+            var applicationPath = currentProcess.MainModule.FileName;
 
             var appPaths = CreateApplicationPaths(applicationPath, _isRunningAsService);
 
@@ -84,8 +86,6 @@ namespace MediaBrowser.ServerApplication
 
             RunServiceInstallationIfNeeded(applicationPath);
 
-            var currentProcess = Process.GetCurrentProcess();
-
             if (IsAlreadyRunning(applicationPath, currentProcess))
             {
                 logger.Info("Shutting down because another instance of Media Browser Server is already running.");

+ 2 - 0
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -374,6 +374,8 @@ namespace MediaBrowser.WebDashboard.Api
 
                                 "externalplayer.js",
                                 "favorites.js",
+                                "forgotpassword.js",
+                                "forgotpasswordpin.js",
                                 "gamesrecommendedpage.js",
                                 "gamesystemspage.js",
                                 "gamespage.js",

+ 12 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -90,6 +90,15 @@
     <Content Include="dashboard-ui\css\images\server.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\forgotpasswordpin.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="dashboard-ui\scripts\forgotpassword.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\selectserver.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -2232,6 +2241,9 @@
     <Content Include="dashboard-ui\index.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\forgotpassword.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
   </ItemGroup>
   <ItemGroup>
     <Content Include="dashboard-ui\advanced.html">