diff --git a/PlexRequests.Api/SonarrApi.cs b/PlexRequests.Api/SonarrApi.cs index 4ff51e2d7..148f27d23 100644 --- a/PlexRequests.Api/SonarrApi.cs +++ b/PlexRequests.Api/SonarrApi.cs @@ -64,7 +64,7 @@ namespace PlexRequests.Api }; var options = new SonarrAddSeries(); - if (episodes == true) + if (episodes) { options.addOptions = new AddOptions { diff --git a/PlexRequests.Core/Models/StatusModel.cs b/PlexRequests.Core/Models/StatusModel.cs index 33d8f0fe1..bea494dfe 100644 --- a/PlexRequests.Core/Models/StatusModel.cs +++ b/PlexRequests.Core/Models/StatusModel.cs @@ -31,7 +31,9 @@ namespace PlexRequests.Core.Models { public string Version { get; set; } public bool UpdateAvailable { get; set; } - public int ReleasesBehind { get; set; } public string UpdateUri { get; set; } + public string DownloadUri { get; set; } + public string ReleaseNotes { get; set; } + public string ReleaseTitle { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/CouchPotatoSettings.cs b/PlexRequests.Core/SettingModels/CouchPotatoSettings.cs index ff7ea01a5..030ff16b8 100644 --- a/PlexRequests.Core/SettingModels/CouchPotatoSettings.cs +++ b/PlexRequests.Core/SettingModels/CouchPotatoSettings.cs @@ -33,6 +33,7 @@ namespace PlexRequests.Core.SettingModels { public class CouchPotatoSettings : Settings { + public bool Enabled { get; set; } public string Ip { get; set; } public int Port { get; set; } public string ApiKey { get; set; } diff --git a/PlexRequests.Core/StatusChecker.cs b/PlexRequests.Core/StatusChecker.cs index 52b135312..94a745535 100644 --- a/PlexRequests.Core/StatusChecker.cs +++ b/PlexRequests.Core/StatusChecker.cs @@ -70,7 +70,11 @@ namespace PlexRequests.Core { model.UpdateAvailable = true; model.UpdateUri = latestRelease.Result.HtmlUrl; - } + } + + model.ReleaseNotes = latestRelease.Result.Body; + model.DownloadUri = latestRelease.Result.Assets[0].BrowserDownloadUrl; + model.ReleaseTitle = latestRelease.Result.Name; return model; } diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs index 35c5c3781..c073661b4 100644 --- a/PlexRequests.Core/UserMapper.cs +++ b/PlexRequests.Core/UserMapper.cs @@ -26,11 +26,13 @@ #endregion using System; using System.Linq; +using System.Security; using Nancy; using Nancy.Authentication.Forms; using Nancy.Security; +using PlexRequests.Helpers; using PlexRequests.Store; namespace PlexRequests.Core @@ -44,7 +46,7 @@ namespace PlexRequests.Core private static ISqliteConfiguration Db { get; set; } public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) { - var repo = new UserRepository(Db); + var repo = new UserRepository(Db); var user = repo.Get(identifier.ToString()); @@ -61,35 +63,65 @@ namespace PlexRequests.Core public static Guid? ValidateUser(string username, string password) { - var repo = new UserRepository(Db); + var repo = new UserRepository(Db); var users = repo.GetAll(); - var userRecord = users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase) && u.Password.Equals(password)); // TODO hashing - if (userRecord == null) + foreach (var u in users) { - return null; + if (username == u.UserName) + { + var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash); + if (passwordMatch) + { + return new Guid(u.UserGuid); + } + } } - - return new Guid(userRecord.User); + return null; } public static bool DoUsersExist() { - var repo = new UserRepository(Db); + var repo = new UserRepository(Db); var users = repo.GetAll(); + return users.Any(); } public static Guid? CreateUser(string username, string password) { - var repo = new UserRepository(Db); + var repo = new UserRepository(Db); + var salt = PasswordHasher.GenerateSalt(); - var userModel = new UserModel { UserName = username, User = Guid.NewGuid().ToString(), Password = password }; + var userModel = new UsersModel { UserName = username, UserGuid = Guid.NewGuid().ToString(), Salt = salt, Hash = PasswordHasher.ComputeHash(password, salt)}; repo.Insert(userModel); - var userRecord = repo.Get(userModel.User); + var userRecord = repo.Get(userModel.UserGuid); - return new Guid(userRecord.User); + return new Guid(userRecord.UserGuid); + } + + public static bool UpdateUser(string username, string oldPassword, string newPassword) + { + var repo = new UserRepository(Db); + var users = repo.GetAll(); + var userToChange = users.FirstOrDefault(x => x.UserName == username); + if (userToChange == null) + return false; + + var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash); + if (!passwordMatch) + { + throw new SecurityException("Password does not match"); + } + + var newSalt = PasswordHasher.GenerateSalt(); + var newHash = PasswordHasher.ComputeHash(newPassword, newSalt); + + userToChange.Hash = newHash; + userToChange.Salt = newSalt; + + return repo.Update(userToChange); } } } diff --git a/PlexRequests.Helpers.Tests/PasswordHasherTests.cs b/PlexRequests.Helpers.Tests/PasswordHasherTests.cs new file mode 100644 index 000000000..500d07534 --- /dev/null +++ b/PlexRequests.Helpers.Tests/PasswordHasherTests.cs @@ -0,0 +1,50 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AssemblyHelperTests.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System.Diagnostics; + +using NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class PasswordHasherTests + { + [Test] + public void TestHash() + { + var password = "abcdef"; + var salt = PasswordHasher.GenerateSalt(); + var hash = PasswordHasher.ComputeHash(password, salt); + + Assert.That(hash, Is.Not.EqualTo(password)); + + var match = PasswordHasher.VerifyPassword(password, salt, hash); + + Assert.That(match, Is.True); + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj index 4bacd1b68..bb9e7143b 100644 --- a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj +++ b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj @@ -70,6 +70,7 @@ + diff --git a/PlexRequests.Helpers/PasswordHasher.cs b/PlexRequests.Helpers/PasswordHasher.cs new file mode 100644 index 000000000..68d797ae8 --- /dev/null +++ b/PlexRequests.Helpers/PasswordHasher.cs @@ -0,0 +1,69 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PasswordHasher.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System.Collections.Generic; +using System.Security.Cryptography; +namespace PlexRequests.Helpers +{ + + public static class PasswordHasher + { + // 24 = 192 bits + private const int SaltByteSize = 24; + private const int HashByteSize = 24; + private const int HasingIterationsCount = 10101; + + public static byte[] ComputeHash(string password, byte[] salt, int iterations = HasingIterationsCount, int hashByteSize = HashByteSize) + { + var hashGenerator = new Rfc2898DeriveBytes(password, salt) { IterationCount = iterations }; + return hashGenerator.GetBytes(hashByteSize); + } + + public static byte[] GenerateSalt(int saltByteSize = SaltByteSize) + { + var saltGenerator = new RNGCryptoServiceProvider(); + var salt = new byte[saltByteSize]; + saltGenerator.GetBytes(salt); + return salt; + } + + public static bool VerifyPassword(string password, byte[] passwordSalt, byte[] passwordHash) + { + var computedHash = ComputeHash(password, passwordSalt); + return AreHashesEqual(computedHash, passwordHash); + } + + //Length constant verification - prevents timing attack + private static bool AreHashesEqual(IReadOnlyList firstHash, IReadOnlyList secondHash) + { + var minHashLength = firstHash.Count <= secondHash.Count ? firstHash.Count : secondHash.Count; + var xor = firstHash.Count ^ secondHash.Count; + for (var i = 0; i < minHashLength; i++) + xor |= firstHash[i] ^ secondHash[i]; + return 0 == xor; + } + } +} diff --git a/PlexRequests.Helpers/PlexRequests.Helpers.csproj b/PlexRequests.Helpers/PlexRequests.Helpers.csproj index 35d56a192..beaf7c117 100644 --- a/PlexRequests.Helpers/PlexRequests.Helpers.csproj +++ b/PlexRequests.Helpers/PlexRequests.Helpers.csproj @@ -53,6 +53,7 @@ + diff --git a/PlexRequests.Services/Notification/NotificationService.cs b/PlexRequests.Services/Notification/NotificationService.cs index 4ef7a8bd0..81968c28d 100644 --- a/PlexRequests.Services/Notification/NotificationService.cs +++ b/PlexRequests.Services/Notification/NotificationService.cs @@ -62,12 +62,9 @@ namespace PlexRequests.Services.Notification public static void Subscribe(INotification notification) { - Log.Trace("Subscribing Observer {0}", notification.NotificationName); INotification notificationValue; if (Observers.TryGetValue(notification.NotificationName, out notificationValue)) { - Log.Trace("Observer {0} already exists", notification.NotificationName); - // Observer already exists return; } diff --git a/PlexRequests.Store/PlexRequests.Store.csproj b/PlexRequests.Store/PlexRequests.Store.csproj index 3772c77a4..d9586aba3 100644 --- a/PlexRequests.Store/PlexRequests.Store.csproj +++ b/PlexRequests.Store/PlexRequests.Store.csproj @@ -70,6 +70,8 @@ + + True @@ -77,7 +79,6 @@ Sql.resx - diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 23d49a87c..1a2f5b32a 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -1,11 +1,12 @@ --Any DB changes need to be made in this file. -CREATE TABLE IF NOT EXISTS User +CREATE TABLE IF NOT EXISTS Users ( Id INTEGER PRIMARY KEY AUTOINCREMENT, - User varchar(50) NOT NULL , + UserGuid varchar(50) NOT NULL , UserName varchar(50) NOT NULL, - Password varchar(100) NOT NULL + Salt BLOB NOT NULL, + Hash BLOB NOT NULL ); diff --git a/PlexRequests.Store/UserModel.cs b/PlexRequests.Store/UserEntity.cs similarity index 87% rename from PlexRequests.Store/UserModel.cs rename to PlexRequests.Store/UserEntity.cs index 8bf3e0fc3..b3d53f8cc 100644 --- a/PlexRequests.Store/UserModel.cs +++ b/PlexRequests.Store/UserEntity.cs @@ -1,7 +1,7 @@ #region Copyright // /************************************************************************ // Copyright (c) 2016 Jamie Rees -// File: UserModel.cs +// File: UserEntity.cs // Created By: Jamie Rees // // Permission is hereby granted, free of charge, to any person obtaining @@ -28,11 +28,12 @@ using Dapper.Contrib.Extensions; namespace PlexRequests.Store { - [Table("User")] - public class UserModel : Entity + public class UserEntity { - public string User { get; set; } + [Key] + public int Id { get; set; } + public string UserName { get; set; } - public string Password { get; set; } + public string UserGuid { get; set; } } -} +} \ No newline at end of file diff --git a/PlexRequests.Store/UserRepository.cs b/PlexRequests.Store/UserRepository.cs index 4f2e6de90..3d431908e 100644 --- a/PlexRequests.Store/UserRepository.cs +++ b/PlexRequests.Store/UserRepository.cs @@ -32,14 +32,14 @@ using Dapper.Contrib.Extensions; namespace PlexRequests.Store { - public class UserRepository : IRepository where T : UserModel + public class UserRepository : IRepository where T : UserEntity { public UserRepository(ISqliteConfiguration config) { Config = config; } - private ISqliteConfiguration Config { get; set; } + private ISqliteConfiguration Config { get; } public long Insert(T entity) { using (var cnn = Config.DbConnection()) @@ -65,7 +65,7 @@ namespace PlexRequests.Store { db.Open(); var result = db.GetAll(); - var selected = result.FirstOrDefault(x => x.User == id); + var selected = result.FirstOrDefault(x => x.UserGuid == id); return selected; } } diff --git a/PlexRequests.Store/UsersModel.cs b/PlexRequests.Store/UsersModel.cs new file mode 100644 index 000000000..e0376b7ba --- /dev/null +++ b/PlexRequests.Store/UsersModel.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using Dapper.Contrib.Extensions; + +namespace PlexRequests.Store +{ + [Table("Users")] + public class UsersModel : UserEntity + { + public byte[] Hash { get; set; } + public byte[] Salt { get; set; } + } +} diff --git a/PlexRequests.UI/Content/requests.js b/PlexRequests.UI/Content/requests.js index 68b942bdb..1ce08c10b 100644 --- a/PlexRequests.UI/Content/requests.js +++ b/PlexRequests.UI/Content/requests.js @@ -114,7 +114,7 @@ $(".theNoteSaveButton").click(function (e) { var $form = $("#noteForm"); var data = $form.serialize(); - + $.ajax({ type: $form.prop("method"), @@ -198,7 +198,11 @@ $(document).on("click", ".approve", function (e) { success: function (response) { if (checkJsonResponse(response)) { - generateNotify("Success! Request Approved.", "success"); + if (response.message) { + generateNotify(response.message, "success"); + } else { + generateNotify("Success! Request Approved.", "success"); + } $("button[custom-button='" + buttonId + "']").remove(); $("#" + buttonId + "notapproved").prop("class", "fa fa-check"); @@ -227,7 +231,7 @@ $(document).on("click", ".clear", function (e) { if (checkJsonResponse(response)) { generateNotify("Success! Issues Cleared.", "info"); - $('#issueArea'+buttonId).html("
Issue: None
"); + $('#issueArea' + buttonId).html("
Issue: None
"); } }, error: function (e) { @@ -258,7 +262,7 @@ $(document).on("click", ".change", function (e) { if (checkJsonResponse(response)) { generateNotify("Success! Availibility changed.", "info"); var button = $("button[custom-availibility='" + buttonId + "']"); - var icon = $('#availableIcon'+buttonId); + var icon = $('#availableIcon' + buttonId); if (response.available) { button.text("Mark Unavailable"); @@ -333,7 +337,8 @@ function buildRequestContext(result, type) { issues: result.issues, otherMessage: result.otherMessage, requestId: result.id, - adminNote: result.adminNotes + adminNote: result.adminNotes, + imdb: result.imdbId }; return context; diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index caeaeb0a8..4e765594b 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -139,7 +139,8 @@ function buildMovieContext(result) { voteCount: result.voteCount, voteAverage: result.voteAverage, year: year, - type: "movie" + type: "movie", + imdb: result.imdbId }; return context; @@ -154,7 +155,8 @@ function buildTvShowContext(result) { title: result.seriesName, overview: result.overview, year: year, - type: "tv" + type: "tv", + imdb: result.imdbId }; return context; } diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index eeac628f0..7876aa41c 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -27,6 +27,8 @@ using System.Dynamic; using System.Linq; +using MarkdownSharp; + using Nancy; using Nancy.Extensions; using Nancy.ModelBinding; @@ -356,8 +358,15 @@ namespace PlexRequests.UI.Modules Log.Trace(settings.DumpJson()); var result = EmailService.SaveSettings(settings); - - NotificationService.Subscribe(new EmailMessageNotification(EmailService)); + + if (settings.Enabled) + { + NotificationService.Subscribe(new EmailMessageNotification(EmailService)); + } + else + { + NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + } Log.Info("Saved email settings, result: {0}", result); return Response.AsJson(result @@ -369,6 +378,8 @@ namespace PlexRequests.UI.Modules { var checker = new StatusChecker(); var status = checker.GetStatus(); + var md = new Markdown(); + status.ReleaseNotes = md.Transform(status.ReleaseNotes); return View["Status", status]; } @@ -389,8 +400,14 @@ namespace PlexRequests.UI.Modules Log.Trace(settings.DumpJson()); var result = PushbulletService.SaveSettings(settings); - - NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + if (settings.Enabled) + { + NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + else + { + NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } Log.Info("Saved email settings, result: {0}", result); return Response.AsJson(result diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index 4a468414b..10795ceaa 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -121,12 +121,12 @@ namespace PlexRequests.UI.Modules Log.Info("Sent successfully, Approving request now."); request.Approved = true; var requestResult = Service.UpdateRequest(request); - Log.Trace("Approval result: {0}",requestResult); + Log.Trace("Approval result: {0}", requestResult); if (requestResult) { return Response.AsJson(new JsonResponseModel { Result = true }); } - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated Sonarr but could not approve it in PlexRequests :("}); + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated Sonarr but could not approve it in PlexRequests :(" }); } return Response.AsJson(new JsonResponseModel { @@ -177,6 +177,21 @@ namespace PlexRequests.UI.Modules var cpSettings = CpService.GetSettings(); var cp = new CouchPotatoApi(); Log.Info("Adding movie to CP : {0}", request.Title); + if (!cpSettings.Enabled) + { + // Approve it + request.Approved = true; + + // Update the record + var inserted = Service.UpdateRequest(request); + return Response.AsJson(inserted + ? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to CouchPotato because it has not been configured." } + : new JsonResponseModel + { + Result = false, + Message = "We could not approve this request. Please try again or check the logs." + }); + } var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, cpSettings.ProfileId); Log.Trace("Adding movie to CP result {0}", result); if (result) @@ -188,7 +203,7 @@ namespace PlexRequests.UI.Modules var inserted = Service.UpdateRequest(request); return Response.AsJson(inserted - ? new JsonResponseModel {Result = true} + ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, @@ -239,7 +254,7 @@ namespace PlexRequests.UI.Modules } if (r.Type == RequestType.TvShow) { - var sender = new TvSender(SonarrApi,SickRageApi); + var sender = new TvSender(SonarrApi, SickRageApi); var sr = SickRageSettings.GetSettings(); var sonarr = SonarrSettings.GetSettings(); if (sr.Enabled) @@ -289,7 +304,7 @@ namespace PlexRequests.UI.Modules } private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp) - { + { Log.Info("Adding movie to CP : {0}", r.Title); var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri, settings.ProfileId); Log.Trace("Adding movie to CP result {0}", result); diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index d24bf19bf..1a10bcc77 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -30,6 +30,8 @@ using System.Dynamic; using Nancy; using Nancy.Authentication.Forms; using Nancy.Extensions; +using Nancy.Responses.Negotiation; +using Nancy.Security; using PlexRequests.Core; using PlexRequests.UI.Models; @@ -81,7 +83,6 @@ namespace PlexRequests.UI.Modules return View["Login/Register", model]; } - }; Post["/register"] = x => @@ -96,6 +97,30 @@ namespace PlexRequests.UI.Modules Session[SessionKeys.UsernameKey] = username; return this.LoginAndRedirect((Guid)userId); }; + + Get["/changepassword"] = _ => ChangePassword(); + Post["/changepassword"] = _ => ChangePasswordPost(); + } + + private Negotiator ChangePassword() + { + this.RequiresAuthentication(); + return View["ChangePassword"]; + } + + private Negotiator ChangePasswordPost() + { + var username = Context.CurrentUser.UserName; + var oldPass = Request.Form.OldPassword; + var newPassword = Request.Form.NewPassword; + var newPasswordAgain = Request.Form.NewPasswordAgain; + if (!newPassword.Equals(newPasswordAgain)) + { + + } + + var result = UserMapper.UpdateUser(username, oldPass, newPassword); + return View["ChangePassword"]; } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index b5e8b2e99..7d1d0dcab 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -39,6 +39,7 @@ using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; using PlexRequests.Services.Interfaces; using PlexRequests.Services.Notification; using PlexRequests.Store; @@ -176,27 +177,24 @@ namespace PlexRequests.UI.Modules } Log.Debug("movie with id {0} doesnt exists", movieId); - var cpSettings = CpService.GetSettings(); - if (cpSettings.ApiKey == null) - { - Log.Warn("CP apiKey is null"); - return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is not yet configured, If you are the Admin, please log in." }); - } - - Log.Trace("Settings: "); - Log.Trace(cpSettings.DumpJson); var movieApi = new TheMovieDbApi(); var movieInfo = movieApi.GetMovieInformation(movieId).Result; Log.Trace("Getting movie info from TheMovieDb"); Log.Trace(movieInfo.DumpJson); - - //#if !DEBUG - if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) +//#if !DEBUG + try + { + if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" }); + } + } + catch (ApplicationSettingsException) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" }); + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {movieInfo.Title} is in Plex, are you sure it's correctly setup?" }); } - //#endif +//#endif var model = new RequestedModel { @@ -219,6 +217,11 @@ namespace PlexRequests.UI.Modules Log.Trace(settings.DumpJson()); if (!settings.RequireApproval) { + var cpSettings = CpService.GetSettings(); + + Log.Trace("Settings: "); + Log.Trace(cpSettings.DumpJson); + Log.Info("Adding movie to CP (No approval required)"); var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettings.ProfileId); Log.Debug("Adding movie to CP result {0}", result); @@ -266,13 +269,19 @@ namespace PlexRequests.UI.Modules var tvApi = new TvMazeApi(); var showInfo = tvApi.ShowLookupByTheTvDbId(showId); - - //#if !DEBUG - if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01 +//#if !DEBUG + try + { + if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01 + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" }); + } + } + catch (ApplicationSettingsException) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" }); + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {showInfo.name} is in Plex, are you sure it's correctly setup?" }); } - //#endif +//#endif DateTime firstAir; DateTime.TryParse(showInfo.premiered, out firstAir); @@ -290,7 +299,8 @@ namespace PlexRequests.UI.Modules Approved = false, RequestedBy = Session[SessionKeys.UsernameKey].ToString(), Issues = IssueState.None, - LatestTv = latest + LatestTv = latest, + ImdbId = showInfo.externals?.imdb ?? string.Empty }; diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 33633179e..44ec9805c 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -73,6 +73,10 @@ ..\packages\Humanizer.Core.2.0.1\lib\dotnet\Humanizer.dll True + + ..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll + True + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll True @@ -328,6 +332,9 @@ Always + + Always + web.config diff --git a/PlexRequests.UI/Views/Admin/CouchPotato.cshtml b/PlexRequests.UI/Views/Admin/CouchPotato.cshtml index 9e0aff8ef..e90354a4a 100644 --- a/PlexRequests.UI/Views/Admin/CouchPotato.cshtml +++ b/PlexRequests.UI/Views/Admin/CouchPotato.cshtml @@ -14,7 +14,20 @@
CouchPotato Settings - +
+
+ +
+
diff --git a/PlexRequests.UI/Views/Admin/Status.cshtml b/PlexRequests.UI/Views/Admin/Status.cshtml index 057c16e44..05dbf1faf 100644 --- a/PlexRequests.UI/Views/Admin/Status.cshtml +++ b/PlexRequests.UI/Views/Admin/Status.cshtml @@ -14,7 +14,7 @@ @if (Model.UpdateAvailable) { - + } else { @@ -23,6 +23,16 @@
+ @if (Model.UpdateAvailable) + { +

+ @Model.ReleaseTitle +

+
+ + @Html.Raw(Model.ReleaseNotes) + } +
\ No newline at end of file diff --git a/PlexRequests.UI/Views/Login/ChangePassword.cshtml b/PlexRequests.UI/Views/Login/ChangePassword.cshtml new file mode 100644 index 000000000..f87dfa76e --- /dev/null +++ b/PlexRequests.UI/Views/Login/ChangePassword.cshtml @@ -0,0 +1,9 @@ + +
+ Old Password + New Password + New Password again +
+
+ +
diff --git a/PlexRequests.UI/Views/Requests/Index.cshtml b/PlexRequests.UI/Views/Requests/Index.cshtml index 5c1f38431..2b6653f0a 100644 --- a/PlexRequests.UI/Views/Requests/Index.cshtml +++ b/PlexRequests.UI/Views/Requests/Index.cshtml @@ -94,7 +94,7 @@
- +

{{title}} ({{year}})

{{status}} diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 309435839..7e41c6b4d 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -76,7 +76,7 @@
diff --git a/PlexRequests.UI/packages.config b/PlexRequests.UI/packages.config index f1e5e48fc..9794a322a 100644 --- a/PlexRequests.UI/packages.config +++ b/PlexRequests.UI/packages.config @@ -4,6 +4,7 @@ +