You've already forked Sonarr
							
							
				mirror of
				https://github.com/Sonarr/Sonarr.git
				synced 2025-10-31 00:07:55 +02:00 
			
		
		
		
	New: Forms authentication
This commit is contained in:
		| @@ -9,5 +9,5 @@ require('./copy'); | ||||
|  | ||||
| gulp.task('build', function () { | ||||
|     return  runSequence('clean', | ||||
|         ['requireJs', 'less', 'handlebars', 'copyIndex', 'copyContent']); | ||||
| }); | ||||
|         ['requireJs', 'less', 'handlebars', 'copyHtml', 'copyContent']); | ||||
| }); | ||||
|   | ||||
| @@ -11,13 +11,13 @@ gulp.task('copyJs', function () { | ||||
|         .pipe(gulp.dest(paths.dest.root)); | ||||
| }); | ||||
|  | ||||
| gulp.task('copyIndex', function () { | ||||
|     return gulp.src(paths.src.index) | ||||
|         .pipe(cache('copyIndex')) | ||||
| gulp.task('copyHtml', function () { | ||||
|     return gulp.src(paths.src.html) | ||||
|         .pipe(cache('copyHtml')) | ||||
|         .pipe(gulp.dest(paths.dest.root)); | ||||
| }); | ||||
|  | ||||
| gulp.task('copyContent', function () { | ||||
|     return gulp.src([paths.src.content + '**/*.*', '!**/*.less']) | ||||
|         .pipe(gulp.dest(paths.dest.content)); | ||||
| }); | ||||
| }); | ||||
|   | ||||
| @@ -2,7 +2,7 @@ module.exports = { | ||||
|     src: { | ||||
|         root: './src/UI/', | ||||
|         templates: './src/UI/**/*.hbs', | ||||
|         index: './src/UI/index.html', | ||||
|         html: './src/UI/*.html', | ||||
|         partials: './src/UI/**/*Partial.hbs', | ||||
|         scripts: './src/UI/**/*.js', | ||||
|         less: ['./src/UI/**/*.less'], | ||||
|   | ||||
| @@ -10,11 +10,11 @@ require('./less.js'); | ||||
| require('./copy.js'); | ||||
|  | ||||
|  | ||||
| gulp.task('watch', ['jshint', 'handlebars', 'less', 'copyJs','copyIndex', 'copyContent'], function () { | ||||
| gulp.task('watch', ['jshint', 'handlebars', 'less', 'copyJs', 'copyHtml', 'copyContent'], function () { | ||||
|     gulp.watch([paths.src.scripts, paths.src.exclude.libs], ['jshint', 'copyJs']); | ||||
|     gulp.watch(paths.src.templates, ['handlebars']); | ||||
|     gulp.watch([paths.src.less, paths.src.exclude.libs], ['less']); | ||||
|     gulp.watch([paths.src.index], ['copyIndex']); | ||||
|     gulp.watch([paths.src.html], ['copyHtml']); | ||||
|     gulp.watch([paths.src.content + '**/*.*', '!**/*.less'], ['copyContent']); | ||||
| }); | ||||
|  | ||||
| @@ -23,8 +23,9 @@ gulp.task('liveReload', ['jshint', 'handlebars', 'less', 'copyJs'], function () | ||||
|     gulp.watch([ | ||||
|         'app/**/*.js', | ||||
|         'app/**/*.css', | ||||
|         'app/index.html' | ||||
|         'app/index.html', | ||||
|         'app/login.html' | ||||
|     ]).on('change', function (file) { | ||||
|         server.changed(file.path); | ||||
|     }); | ||||
| }); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										62
									
								
								src/NzbDrone.Api/Authentication/1tews5g3.gd1~
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/NzbDrone.Api/Authentication/1tews5g3.gd1~
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| using Nancy; | ||||
| using Nancy.Authentication.Basic; | ||||
| using Nancy.Authentication.Forms; | ||||
| using Nancy.Bootstrapper; | ||||
| using Nancy.Cryptography; | ||||
| using NzbDrone.Api.Extensions.Pipelines; | ||||
| using NzbDrone.Core.Configuration; | ||||
|  | ||||
| namespace NzbDrone.Api.Authentication | ||||
| { | ||||
|     public class EnableAuthInNancy : IRegisterNancyPipeline | ||||
|     { | ||||
|         private readonly IAuthenticationService _authenticationService; | ||||
|         private readonly IConfigService _configService; | ||||
|         private readonly IConfigFileProvider _configFileProvider; | ||||
|  | ||||
|         public EnableAuthInNancy(IAuthenticationService authenticationService, | ||||
|                                  IConfigService configService, | ||||
|                                  IConfigFileProvider configFileProvider) | ||||
|         { | ||||
|             _authenticationService = authenticationService; | ||||
|             _configService = configService; | ||||
|             _configFileProvider = configFileProvider; | ||||
|         } | ||||
|  | ||||
|         public void Register(IPipelines pipelines) | ||||
|         { | ||||
|             RegisterFormsAuth(pipelines); | ||||
|             pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr")); | ||||
|             pipelines.BeforeRequest.AddItemToEndOfPipeline(RequiresAuthentication); | ||||
|         } | ||||
|  | ||||
|         private Response RequiresAuthentication(NancyContext context) | ||||
|         { | ||||
|             Response response = null; | ||||
|  | ||||
|             if (!_authenticationService.IsAuthenticated(context)) | ||||
|             { | ||||
|                 response = new Response { StatusCode = HttpStatusCode.Unauthorized }; | ||||
|             } | ||||
|  | ||||
|             return response; | ||||
|         } | ||||
|  | ||||
|         private void RegisterFormsAuth(IPipelines pipelines) | ||||
|         { | ||||
|             var cryptographyConfiguration = new CryptographyConfiguration( | ||||
|                     new RijndaelEncryptionProvider(new PassphraseKeyGenerator(_configService.RijndaelPassphrase, | ||||
|                                                    new byte[] {1, 2, 3, 4, 5, 6, 7, 8})), | ||||
|                     new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, | ||||
|                                             new byte[] {1, 2, 3, 4, 5, 6, 7, 8})) | ||||
|                 ); | ||||
|  | ||||
|             FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration | ||||
|             { | ||||
|                 RedirectUrl = "~/login", | ||||
|                 UserMapper = _authenticationService, | ||||
|                 CryptographyConfiguration = cryptographyConfiguration | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,14 +2,16 @@ | ||||
| using System.Linq; | ||||
| using Nancy; | ||||
| using Nancy.Authentication.Basic; | ||||
| using Nancy.Authentication.Forms; | ||||
| using Nancy.Security; | ||||
| using NzbDrone.Api.Extensions; | ||||
| using NzbDrone.Common.Extensions; | ||||
| using NzbDrone.Core.Authentication; | ||||
| using NzbDrone.Core.Configuration; | ||||
| 
 | ||||
| namespace NzbDrone.Api.Authentication | ||||
| { | ||||
|     public interface IAuthenticationService : IUserValidator | ||||
|     public interface IAuthenticationService : IUserValidator, IUserMapper | ||||
|     { | ||||
|         bool IsAuthenticated(NancyContext context); | ||||
|     } | ||||
| @@ -17,37 +19,49 @@ namespace NzbDrone.Api.Authentication | ||||
|     public class AuthenticationService : IAuthenticationService | ||||
|     { | ||||
|         private readonly IConfigFileProvider _configFileProvider; | ||||
|         private readonly IUserService _userService; | ||||
|         private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" }; | ||||
|         private static String API_KEY; | ||||
| 
 | ||||
|         public AuthenticationService(IConfigFileProvider configFileProvider) | ||||
|         public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService) | ||||
|         { | ||||
|             _configFileProvider = configFileProvider; | ||||
|             _userService = userService; | ||||
|             API_KEY = configFileProvider.ApiKey; | ||||
|         } | ||||
| 
 | ||||
|         public IUserIdentity Validate(string username, string password) | ||||
|         { | ||||
|             if (!Enabled) | ||||
|             if (_configFileProvider.AuthenticationMethod == AuthenticationType.None) | ||||
|             { | ||||
|                 return AnonymousUser; | ||||
|             } | ||||
| 
 | ||||
|             if (_configFileProvider.Username.Equals(username) && | ||||
|                 _configFileProvider.Password.Equals(password)) | ||||
|             var user = _userService.FindUser(username, password); | ||||
| 
 | ||||
|             if (user != null) | ||||
|             { | ||||
|                 return new NzbDroneUser { UserName = username }; | ||||
|                 return new NzbDroneUser { UserName = user.Username }; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         private bool Enabled | ||||
|         public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) | ||||
|         { | ||||
|             get | ||||
|             if (_configFileProvider.AuthenticationMethod == AuthenticationType.None) | ||||
|             { | ||||
|                 return _configFileProvider.AuthenticationEnabled; | ||||
|                 return AnonymousUser; | ||||
|             } | ||||
| 
 | ||||
|             var user = _userService.FindUser(identifier); | ||||
| 
 | ||||
|             if (user != null) | ||||
|             { | ||||
|                 return new NzbDroneUser { UserName = user.Username }; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         public bool IsAuthenticated(NancyContext context) | ||||
| @@ -59,13 +73,13 @@ namespace NzbDrone.Api.Authentication | ||||
|                 return ValidApiKey(apiKey); | ||||
|             } | ||||
| 
 | ||||
|             if (_configFileProvider.AuthenticationMethod == AuthenticationType.None) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             if (context.Request.IsFeedRequest()) | ||||
|             { | ||||
|                 if (!Enabled) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
| 
 | ||||
|                 if (ValidUser(context) || ValidApiKey(apiKey)) | ||||
|                 { | ||||
|                     return true; | ||||
| @@ -74,7 +88,12 @@ namespace NzbDrone.Api.Authentication | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             if (!Enabled) | ||||
|             if (context.Request.IsLoginRequest()) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             if (context.Request.IsContentRequest()) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|   | ||||
| @@ -1,23 +1,46 @@ | ||||
| using Nancy; | ||||
| using System; | ||||
| using System.Text; | ||||
| using Nancy; | ||||
| using Nancy.Authentication.Basic; | ||||
| using Nancy.Authentication.Forms; | ||||
| using Nancy.Bootstrapper; | ||||
| using Nancy.Cryptography; | ||||
| using NzbDrone.Api.Extensions; | ||||
| using NzbDrone.Api.Extensions.Pipelines; | ||||
| using NzbDrone.Core.Authentication; | ||||
| using NzbDrone.Core.Configuration; | ||||
| 
 | ||||
| namespace NzbDrone.Api.Authentication | ||||
| { | ||||
|     public class EnableAuthInNancy : IRegisterNancyPipeline | ||||
|     { | ||||
|         private readonly IAuthenticationService _authenticationService; | ||||
|         private readonly IConfigService _configService; | ||||
|         private readonly IConfigFileProvider _configFileProvider; | ||||
| 
 | ||||
|         public EnableAuthInNancy(IAuthenticationService authenticationService) | ||||
|         public EnableAuthInNancy(IAuthenticationService authenticationService, | ||||
|                                  IConfigService configService, | ||||
|                                  IConfigFileProvider configFileProvider) | ||||
|         { | ||||
|             _authenticationService = authenticationService; | ||||
|             _configService = configService; | ||||
|             _configFileProvider = configFileProvider; | ||||
|         } | ||||
| 
 | ||||
|         public void Register(IPipelines pipelines) | ||||
|         { | ||||
|             pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr")); | ||||
|             if (_configFileProvider.AuthenticationMethod == AuthenticationType.Forms) | ||||
|             { | ||||
|                 RegisterFormsAuth(pipelines);                 | ||||
|             } | ||||
| 
 | ||||
|             else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic) | ||||
|             { | ||||
|                 pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));                 | ||||
|             } | ||||
| 
 | ||||
|             pipelines.BeforeRequest.AddItemToEndOfPipeline(RequiresAuthentication); | ||||
|             pipelines.AfterRequest.AddItemToEndOfPipeline(RemoveLoginHooksForApiCalls); | ||||
|         } | ||||
| 
 | ||||
|         private Response RequiresAuthentication(NancyContext context) | ||||
| @@ -31,5 +54,33 @@ namespace NzbDrone.Api.Authentication | ||||
| 
 | ||||
|             return response; | ||||
|         } | ||||
| 
 | ||||
|         private void RegisterFormsAuth(IPipelines pipelines) | ||||
|         { | ||||
|             var cryptographyConfiguration = new CryptographyConfiguration( | ||||
|                     new RijndaelEncryptionProvider(new PassphraseKeyGenerator(_configService.RijndaelPassphrase, Encoding.ASCII.GetBytes(_configService.RijndaelSalt))), | ||||
|                     new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt))) | ||||
|                 ); | ||||
| 
 | ||||
|             FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration | ||||
|             { | ||||
|                 RedirectUrl = "~/login", | ||||
|                 UserMapper = _authenticationService, | ||||
|                 CryptographyConfiguration = cryptographyConfiguration | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         private void RemoveLoginHooksForApiCalls(NancyContext context) | ||||
|         { | ||||
|             if (context.Request.IsApiRequest()) | ||||
|             { | ||||
|                 if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&  | ||||
|                      context.Response.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase)) || | ||||
|                     context.Response.StatusCode == HttpStatusCode.Unauthorized) | ||||
|                 { | ||||
|                     context.Response = new { Error = "Unauthorized" }.AsResponse(HttpStatusCode.Unauthorized); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/NzbDrone.Api/Authentication/LoginModule.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/NzbDrone.Api/Authentication/LoginModule.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| using System; | ||||
| using Nancy; | ||||
| using Nancy.Authentication.Forms; | ||||
| using Nancy.Extensions; | ||||
| using Nancy.ModelBinding; | ||||
| using NzbDrone.Core.Authentication; | ||||
| 
 | ||||
| namespace NzbDrone.Api.Authentication | ||||
| { | ||||
|     public class LoginModule : NancyModule | ||||
|     { | ||||
|         private readonly IUserService _userService; | ||||
| 
 | ||||
|         public LoginModule(IUserService userService) | ||||
|         { | ||||
|             _userService = userService; | ||||
|             Post["/login"] = x => Login(this.Bind<LoginResource>()); | ||||
|         } | ||||
| 
 | ||||
|         private Response Login(LoginResource resource) | ||||
|         { | ||||
|             var user = _userService.FindUser(resource.Username, resource.Password); | ||||
| 
 | ||||
|             if (user == null) | ||||
|             { | ||||
|                 return Context.GetRedirect("~/login?returnUrl=" + (string)Request.Query.returnUrl); | ||||
|             } | ||||
| 
 | ||||
|             DateTime? expiry = null; | ||||
| 
 | ||||
|             if (resource.RememberMe) | ||||
|             { | ||||
|                 expiry = DateTime.UtcNow.AddDays(7); | ||||
|             } | ||||
| 
 | ||||
|             return this.LoginAndRedirect(user.Identifier, expiry); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/NzbDrone.Api/Authentication/LoginResource.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/NzbDrone.Api/Authentication/LoginResource.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| namespace NzbDrone.Api.Authentication | ||||
| { | ||||
|     public class LoginResource | ||||
|     { | ||||
|         public string Username { get; set; } | ||||
|         public string Password { get; set; } | ||||
|         public bool RememberMe { get; set; } | ||||
|     } | ||||
| } | ||||
| @@ -2,6 +2,8 @@ | ||||
| using System.Reflection; | ||||
| using FluentValidation; | ||||
| using NzbDrone.Common.EnvironmentInfo; | ||||
| using NzbDrone.Common.Extensions; | ||||
| using NzbDrone.Core.Authentication; | ||||
| using NzbDrone.Core.Configuration; | ||||
| using NzbDrone.Core.Update; | ||||
| using NzbDrone.Core.Validation; | ||||
| @@ -13,11 +15,13 @@ namespace NzbDrone.Api.Config | ||||
|     public class HostConfigModule : NzbDroneRestModule<HostConfigResource> | ||||
|     { | ||||
|         private readonly IConfigFileProvider _configFileProvider; | ||||
|         private readonly IUserService _userService; | ||||
| 
 | ||||
|         public HostConfigModule(IConfigFileProvider configFileProvider) | ||||
|         public HostConfigModule(IConfigFileProvider configFileProvider, IUserService userService) | ||||
|             : base("/config/host") | ||||
|         { | ||||
|             _configFileProvider = configFileProvider; | ||||
|             _userService = userService; | ||||
| 
 | ||||
|             GetResourceSingle = GetHostConfig; | ||||
|             GetResourceById = GetHostConfig; | ||||
| @@ -26,8 +30,8 @@ namespace NzbDrone.Api.Config | ||||
|             SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");         | ||||
|             SharedValidator.RuleFor(c => c.Port).ValidPort(); | ||||
| 
 | ||||
|             SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled); | ||||
|             SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled); | ||||
|             SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None); | ||||
|             SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None); | ||||
| 
 | ||||
|             SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); | ||||
|             SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows); | ||||
| @@ -46,6 +50,14 @@ namespace NzbDrone.Api.Config | ||||
|             resource.InjectFrom(_configFileProvider); | ||||
|             resource.Id = 1; | ||||
| 
 | ||||
|             var user = _userService.FindUser(); | ||||
| 
 | ||||
|             if (user != null) | ||||
|             { | ||||
|                 resource.Username = user.Username; | ||||
|                 resource.Password = user.Password; | ||||
|             } | ||||
| 
 | ||||
|             return resource; | ||||
|         } | ||||
| 
 | ||||
| @@ -61,6 +73,11 @@ namespace NzbDrone.Api.Config | ||||
|                                      .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); | ||||
| 
 | ||||
|             _configFileProvider.SaveConfigDictionary(dictionary); | ||||
| 
 | ||||
|             if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace()) | ||||
|             { | ||||
|                 _userService.Upsert(resource.Username, resource.Password); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using NzbDrone.Api.REST; | ||||
| using NzbDrone.Core.Authentication; | ||||
| using NzbDrone.Core.Update; | ||||
| 
 | ||||
| namespace NzbDrone.Api.Config | ||||
| @@ -11,7 +12,7 @@ namespace NzbDrone.Api.Config | ||||
|         public Int32 SslPort { get; set; } | ||||
|         public Boolean EnableSsl { get; set; } | ||||
|         public Boolean LaunchBrowser { get; set; } | ||||
|         public bool AuthenticationEnabled { get; set; } | ||||
|         public AuthenticationType AuthenticationMethod { get; set; } | ||||
|         public Boolean AnalyticsEnabled { get; set; } | ||||
|         public String Username { get; set; } | ||||
|         public String Password { get; set; } | ||||
|   | ||||
| @@ -26,5 +26,15 @@ namespace NzbDrone.Api.Extensions | ||||
|                     request.UserHostAddress.Equals("127.0.0.1") || | ||||
|                     request.UserHostAddress.Equals("::1")); | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsLoginRequest(this Request request) | ||||
|         { | ||||
|             return request.Path.Equals("/login", StringComparison.InvariantCultureIgnoreCase); | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsContentRequest(this Request request) | ||||
|         { | ||||
|             return request.Path.StartsWith("/Content/", StringComparison.InvariantCultureIgnoreCase); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -49,7 +49,7 @@ namespace NzbDrone.Api.Frontend.Mappers | ||||
| 
 | ||||
|         public override bool CanHandle(string resourceUrl) | ||||
|         { | ||||
|             return !resourceUrl.Contains("."); | ||||
|             return !resourceUrl.Contains(".") && !resourceUrl.StartsWith("/login"); | ||||
|         } | ||||
| 
 | ||||
|         public override Response GetResponse(string resourceUrl) | ||||
|   | ||||
							
								
								
									
										88
									
								
								src/NzbDrone.Api/Frontend/Mappers/LoginHtmlMapper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/NzbDrone.Api/Frontend/Mappers/LoginHtmlMapper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text.RegularExpressions; | ||||
| using Nancy; | ||||
| using NLog; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Common.EnvironmentInfo; | ||||
| using NzbDrone.Core.Configuration; | ||||
| 
 | ||||
| namespace NzbDrone.Api.Frontend.Mappers | ||||
| { | ||||
|     public class LoginHtmlMapper : StaticResourceMapperBase | ||||
|     { | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory; | ||||
|         private readonly string _indexPath; | ||||
|         private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||||
| 
 | ||||
|         private static String URL_BASE; | ||||
|         private string _generatedContent; | ||||
| 
 | ||||
|         public LoginHtmlMapper(IAppFolderInfo appFolderInfo, | ||||
|                                IDiskProvider diskProvider, | ||||
|                                IConfigFileProvider configFileProvider, | ||||
|                                Func<ICacheBreakerProvider> cacheBreakProviderFactory, | ||||
|                                Logger logger) | ||||
|             : base(diskProvider, logger) | ||||
|         { | ||||
|             _diskProvider = diskProvider; | ||||
|             _cacheBreakProviderFactory = cacheBreakProviderFactory; | ||||
|             _indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "login.html"); | ||||
| 
 | ||||
|             URL_BASE = configFileProvider.UrlBase; | ||||
|         } | ||||
| 
 | ||||
|         public override string Map(string resourceUrl) | ||||
|         { | ||||
|             return _indexPath; | ||||
|         } | ||||
| 
 | ||||
|         public override bool CanHandle(string resourceUrl) | ||||
|         { | ||||
|             return resourceUrl.StartsWith("/login"); | ||||
|         } | ||||
| 
 | ||||
|         public override Response GetResponse(string resourceUrl) | ||||
|         { | ||||
|             var response = base.GetResponse(resourceUrl); | ||||
|             response.Headers["X-UA-Compatible"] = "IE=edge"; | ||||
| 
 | ||||
|             return response; | ||||
|         } | ||||
| 
 | ||||
|         protected override Stream GetContentStream(string filePath) | ||||
|         { | ||||
|             var text = GetLoginText(); | ||||
| 
 | ||||
|             var stream = new MemoryStream(); | ||||
|             var writer = new StreamWriter(stream); | ||||
|             writer.Write(text); | ||||
|             writer.Flush(); | ||||
|             stream.Position = 0; | ||||
|             return stream; | ||||
|         } | ||||
| 
 | ||||
|         private string GetLoginText() | ||||
|         { | ||||
|             if (RuntimeInfoBase.IsProduction && _generatedContent != null) | ||||
|             { | ||||
|                 return _generatedContent; | ||||
|             } | ||||
| 
 | ||||
|             var text = _diskProvider.ReadAllText(_indexPath); | ||||
| 
 | ||||
|             var cacheBreakProvider = _cacheBreakProviderFactory(); | ||||
| 
 | ||||
|             text = ReplaceRegex.Replace(text, match => | ||||
|             { | ||||
|                 var url = cacheBreakProvider.AddCacheBreakerToPath(match.Value); | ||||
|                 return URL_BASE + url; | ||||
|             }); | ||||
| 
 | ||||
|             _generatedContent = text; | ||||
| 
 | ||||
|             return _generatedContent; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -52,6 +52,9 @@ | ||||
|       <SpecificVersion>False</SpecificVersion> | ||||
|       <HintPath>..\packages\Nancy.Authentication.Basic.0.23.2\lib\net40\Nancy.Authentication.Basic.dll</HintPath> | ||||
|     </Reference> | ||||
|     <Reference Include="Nancy.Authentication.Forms"> | ||||
|       <HintPath>..\packages\Nancy.Authentication.Forms.0.23.2\lib\net40\Nancy.Authentication.Forms.dll</HintPath> | ||||
|     </Reference> | ||||
|     <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | ||||
|       <SpecificVersion>False</SpecificVersion> | ||||
|       <HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath> | ||||
| @@ -80,6 +83,8 @@ | ||||
|     </Compile> | ||||
|     <Compile Include="Authentication\AuthenticationService.cs" /> | ||||
|     <Compile Include="Authentication\EnableAuthInNancy.cs" /> | ||||
|     <Compile Include="Authentication\LoginModule.cs" /> | ||||
|     <Compile Include="Authentication\LoginResource.cs" /> | ||||
|     <Compile Include="Authentication\NzbDroneUser.cs" /> | ||||
|     <Compile Include="Blacklist\BlacklistModule.cs" /> | ||||
|     <Compile Include="Blacklist\BlacklistResource.cs" /> | ||||
| @@ -94,6 +99,7 @@ | ||||
|     <Compile Include="Commands\CommandResource.cs" /> | ||||
|     <Compile Include="Extensions\AccessControlHeaders.cs" /> | ||||
|     <Compile Include="Extensions\Pipelines\CorsPipeline.cs" /> | ||||
|     <Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" /> | ||||
|     <Compile Include="Profiles\Delay\DelayProfileModule.cs" /> | ||||
|     <Compile Include="Profiles\Delay\DelayProfileResource.cs" /> | ||||
|     <Compile Include="Profiles\Delay\DelayProfileValidator.cs" /> | ||||
|   | ||||
| @@ -57,7 +57,7 @@ namespace NzbDrone.Api.System | ||||
|                     IsOsx = OsInfo.IsOsx, | ||||
|                     IsWindows = OsInfo.IsWindows, | ||||
|                     Branch = _configFileProvider.Branch, | ||||
|                     Authentication = _configFileProvider.AuthenticationEnabled, | ||||
|                     Authentication = _configFileProvider.AuthenticationMethod, | ||||
|                     SqliteVersion = _database.Version, | ||||
|                     UrlBase = _configFileProvider.UrlBase, | ||||
|                     RuntimeVersion = _runtimeInfo.RuntimeVersion | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|   <package id="FluentValidation" version="5.5.0.0" targetFramework="net40" /> | ||||
|   <package id="Nancy" version="0.23.2" targetFramework="net40" /> | ||||
|   <package id="Nancy.Authentication.Basic" version="0.23.2" targetFramework="net40" /> | ||||
|   <package id="Nancy.Authentication.Forms" version="0.23.2" targetFramework="net40" /> | ||||
|   <package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" /> | ||||
|   <package id="NLog" version="2.1.0" targetFramework="net40" /> | ||||
|   <package id="ValueInjecter" version="2.3.3" targetFramework="net40" /> | ||||
|   | ||||
| @@ -3,6 +3,7 @@ using FluentAssertions; | ||||
| using NUnit.Framework; | ||||
| using NzbDrone.Common.EnvironmentInfo; | ||||
| using NzbDrone.Common.Extensions; | ||||
| using NzbDrone.Core.Authentication; | ||||
| using NzbDrone.Core.Configuration; | ||||
| using NzbDrone.Test.Common; | ||||
| 
 | ||||
| @@ -126,9 +127,9 @@ namespace NzbDrone.Common.Test | ||||
|         [Test] | ||||
|         public void GetAuthenticationType_No_Existing_Value() | ||||
|         { | ||||
|             var result = Subject.AuthenticationEnabled; | ||||
|             var result = Subject.AuthenticationMethod; | ||||
| 
 | ||||
|             result.Should().Be(false); | ||||
|             result.Should().Be(AuthenticationType.None); | ||||
|         } | ||||
| 
 | ||||
|         [Test] | ||||
|   | ||||
							
								
								
									
										9
									
								
								src/NzbDrone.Core/Authentication/AuthenticationType.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/NzbDrone.Core/Authentication/AuthenticationType.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| namespace NzbDrone.Core.Authentication | ||||
| { | ||||
|     public enum AuthenticationType | ||||
|     { | ||||
|         None = 0, | ||||
|         Basic = 1, | ||||
|         Forms = 2 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/NzbDrone.Core/Authentication/User.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/NzbDrone.Core/Authentication/User.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| using System; | ||||
| using NzbDrone.Core.Datastore; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Authentication | ||||
| { | ||||
|     public class User : ModelBase | ||||
|     { | ||||
|         public Guid Identifier { get; set; } | ||||
|         public string Username { get; set; } | ||||
|         public string Password { get; set; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										31
									
								
								src/NzbDrone.Core/Authentication/UserRepository.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/NzbDrone.Core/Authentication/UserRepository.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| using System; | ||||
| using System.Linq; | ||||
| using NzbDrone.Core.Datastore; | ||||
| using NzbDrone.Core.Messaging.Events; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Authentication | ||||
| { | ||||
|     public interface IUserRepository : IBasicRepository<User> | ||||
|     { | ||||
|         User FindUser(string username); | ||||
|         User FindUser(Guid identifier); | ||||
|     } | ||||
| 
 | ||||
|     public class UserRepository : BasicRepository<User>, IUserRepository | ||||
|     { | ||||
|         public UserRepository(IDatabase database, IEventAggregator eventAggregator) | ||||
|             : base(database, eventAggregator) | ||||
|         { | ||||
|         } | ||||
| 
 | ||||
|         public User FindUser(string username) | ||||
|         { | ||||
|             return Query.Where(u => u.Username == username).SingleOrDefault(); | ||||
|         } | ||||
| 
 | ||||
|         public User FindUser(Guid identifier) | ||||
|         { | ||||
|             return Query.Where(u => u.Identifier == identifier).SingleOrDefault(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										121
									
								
								src/NzbDrone.Core/Authentication/UserService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/NzbDrone.Core/Authentication/UserService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Xml.Linq; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Common.EnvironmentInfo; | ||||
| using NzbDrone.Common.Extensions; | ||||
| using NzbDrone.Core.Lifecycle; | ||||
| using NzbDrone.Core.Messaging.Events; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Authentication | ||||
| { | ||||
|     public interface IUserService | ||||
|     { | ||||
|         User Add(string username, string password); | ||||
|         User Update(User user); | ||||
|         User Upsert(string username, string password); | ||||
|         User FindUser(); | ||||
|         User FindUser(string username, string password); | ||||
|         User FindUser(Guid identifier); | ||||
|     } | ||||
| 
 | ||||
|     public class UserService : IUserService, IHandle<ApplicationStartedEvent> | ||||
|     { | ||||
|         private readonly IUserRepository _repo; | ||||
|         private readonly IAppFolderInfo _appFolderInfo; | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
| 
 | ||||
|         public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider) | ||||
|         { | ||||
|             _repo = repo; | ||||
|             _appFolderInfo = appFolderInfo; | ||||
|             _diskProvider = diskProvider; | ||||
|         } | ||||
| 
 | ||||
|         public User Add(string username, string password) | ||||
|         { | ||||
|             return _repo.Insert(new User | ||||
|                                 { | ||||
|                                     Identifier = Guid.NewGuid(), | ||||
|                                     Username = username.ToLowerInvariant(), | ||||
|                                     Password = password.SHA256Hash() | ||||
|                                 }); | ||||
|         } | ||||
| 
 | ||||
|         public User Update(User user) | ||||
|         { | ||||
|             return _repo.Update(user); | ||||
|         } | ||||
| 
 | ||||
|         public User Upsert(string username, string password) | ||||
|         { | ||||
|             var user = FindUser(); | ||||
| 
 | ||||
|             if (user == null) | ||||
|             { | ||||
|                 return Add(username, password); | ||||
|             } | ||||
| 
 | ||||
|             if (user.Password != password) | ||||
|             { | ||||
|                 user.Password = password.SHA256Hash(); | ||||
|             } | ||||
| 
 | ||||
|             user.Username = username.ToLowerInvariant(); | ||||
| 
 | ||||
|             return Update(user); | ||||
|         } | ||||
| 
 | ||||
|         public User FindUser() | ||||
|         { | ||||
|             return _repo.SingleOrDefault(); | ||||
|         } | ||||
| 
 | ||||
|         public User FindUser(string username, string password) | ||||
|         { | ||||
|             var user = _repo.FindUser(username.ToLowerInvariant()); | ||||
| 
 | ||||
|             if (user.Password == password.SHA256Hash()) | ||||
|             { | ||||
|                 return user; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         public User FindUser(Guid identifier) | ||||
|         { | ||||
|             return _repo.FindUser(identifier); | ||||
|         } | ||||
| 
 | ||||
|         public void Handle(ApplicationStartedEvent message) | ||||
|         { | ||||
|             if (_repo.All().Any()) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var configFile = _appFolderInfo.GetConfigPath(); | ||||
| 
 | ||||
|             if (!_diskProvider.FileExists(configFile)) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var xDoc = XDocument.Load(configFile); | ||||
|             var config = xDoc.Descendants("Config").Single(); | ||||
|             var usernameElement = config.Descendants("Username").FirstOrDefault(); | ||||
|             var passwordElement = config.Descendants("Password").FirstOrDefault(); | ||||
| 
 | ||||
|             if (usernameElement == null || passwordElement == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var username = usernameElement.Value; | ||||
|             var password = passwordElement.Value; | ||||
| 
 | ||||
|             Add(username, password); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -9,6 +9,7 @@ using NzbDrone.Common.Cache; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Common.EnvironmentInfo; | ||||
| using NzbDrone.Common.Extensions; | ||||
| using NzbDrone.Core.Authentication; | ||||
| using NzbDrone.Core.Configuration.Events; | ||||
| using NzbDrone.Core.Lifecycle; | ||||
| using NzbDrone.Core.Messaging.Commands; | ||||
| @@ -29,14 +30,11 @@ namespace NzbDrone.Core.Configuration | ||||
|         int SslPort { get; } | ||||
|         bool EnableSsl { get; } | ||||
|         bool LaunchBrowser { get; } | ||||
|         bool AuthenticationEnabled { get; } | ||||
|         AuthenticationType AuthenticationMethod { get; } | ||||
|         bool AnalyticsEnabled { get; } | ||||
|         string Username { get; } | ||||
|         string Password { get; } | ||||
|         string LogLevel { get; } | ||||
|         string Branch { get; } | ||||
|         string ApiKey { get; } | ||||
|         bool Torrent { get; } | ||||
|         string SslCertHash { get; } | ||||
|         string UrlBase { get; } | ||||
|         Boolean UpdateAutomatically { get; } | ||||
| @@ -163,14 +161,20 @@ namespace NzbDrone.Core.Configuration | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool Torrent | ||||
|         public AuthenticationType AuthenticationMethod | ||||
|         { | ||||
|             get { return GetValueBoolean("Torrent", false, persist: false); } | ||||
|         } | ||||
|             get | ||||
|             { | ||||
|                 var enabled = GetValueBoolean("AuthenticationEnabled", false, false); | ||||
| 
 | ||||
|         public bool AuthenticationEnabled | ||||
|         { | ||||
|             get { return GetValueBoolean("AuthenticationEnabled", false); } | ||||
|                 if (enabled) | ||||
|                 { | ||||
|                     SetValue("AuthenticationMethod", AuthenticationType.Basic); | ||||
|                     return AuthenticationType.Basic; | ||||
|                 } | ||||
|                  | ||||
|                 return GetValueEnum("AuthenticationMethod", AuthenticationType.None); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool AnalyticsEnabled | ||||
| @@ -186,16 +190,6 @@ namespace NzbDrone.Core.Configuration | ||||
|             get { return GetValue("Branch", "master").ToLowerInvariant(); } | ||||
|         } | ||||
| 
 | ||||
|         public string Username | ||||
|         { | ||||
|             get { return GetValue("Username", ""); } | ||||
|         } | ||||
| 
 | ||||
|         public string Password | ||||
|         { | ||||
|             get { return GetValue("Password", ""); } | ||||
|         } | ||||
| 
 | ||||
|         public string LogLevel | ||||
|         { | ||||
|             get { return GetValue("LogLevel", "Info"); } | ||||
|   | ||||
| @@ -289,6 +289,26 @@ namespace NzbDrone.Core.Configuration | ||||
|             set { SetValue("CleanupMetadataImages", value); } | ||||
|         } | ||||
| 
 | ||||
|         public String RijndaelPassphrase | ||||
|         { | ||||
|             get { return GetValue("RijndaelPassphrase", Guid.NewGuid().ToString(), true); } | ||||
|         } | ||||
| 
 | ||||
|         public String HmacPassphrase | ||||
|         { | ||||
|             get { return GetValue("HmacPassphrase", Guid.NewGuid().ToString(), true); } | ||||
|         } | ||||
| 
 | ||||
|         public String RijndaelSalt | ||||
|         { | ||||
|             get { return GetValue("RijndaelSalt", Guid.NewGuid().ToString(), true); } | ||||
|         } | ||||
| 
 | ||||
|         public String HmacSalt | ||||
|         { | ||||
|             get { return GetValue("HmacSalt", Guid.NewGuid().ToString(), true); } | ||||
|         } | ||||
| 
 | ||||
|         private string GetValue(string key) | ||||
|         { | ||||
|             return GetValue(key, String.Empty); | ||||
|   | ||||
| @@ -56,9 +56,14 @@ namespace NzbDrone.Core.Configuration | ||||
|         String TimeFormat { get; set; } | ||||
|         Boolean ShowRelativeDates { get; set; } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         //Internal | ||||
|         Boolean CleanupMetadataImages { get; set; } | ||||
| 
 | ||||
| 
 | ||||
|         //Forms Auth | ||||
|         string RijndaelPassphrase { get; } | ||||
|         string HmacPassphrase { get; } | ||||
|         string RijndaelSalt { get; } | ||||
|         string HmacSalt { get; } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										38
									
								
								src/NzbDrone.Core/Datastore/Converters/GuidConverter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/NzbDrone.Core/Datastore/Converters/GuidConverter.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| using System; | ||||
| using Marr.Data.Converters; | ||||
| using Marr.Data.Mapping; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Datastore.Converters | ||||
| { | ||||
|     public class GuidConverter : IConverter | ||||
|     { | ||||
|         public Object FromDB(ConverterContext context) | ||||
|         { | ||||
|             if (context.DbValue == DBNull.Value) | ||||
|             { | ||||
|                 return Guid.Empty; | ||||
|             } | ||||
| 
 | ||||
|             var value = (string)context.DbValue; | ||||
| 
 | ||||
|             return new Guid(value); | ||||
|         } | ||||
| 
 | ||||
|         public Object FromDB(ColumnMap map, Object dbValue) | ||||
|         { | ||||
|             return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); | ||||
|         } | ||||
| 
 | ||||
|         public Object ToDB(Object clrValue) | ||||
|         { | ||||
|             var value = clrValue; | ||||
| 
 | ||||
|             return value.ToString(); | ||||
|         } | ||||
| 
 | ||||
|         public Type DbType | ||||
|         { | ||||
|             get { return typeof(string); } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/NzbDrone.Core/Datastore/Migration/076_add_users_table.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/NzbDrone.Core/Datastore/Migration/076_add_users_table.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| using FluentMigrator; | ||||
| using NzbDrone.Core.Datastore.Migration.Framework; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Datastore.Migration | ||||
| { | ||||
|     [Migration(76)] | ||||
|     public class add_users_table : NzbDroneMigrationBase | ||||
|     { | ||||
|         protected override void MainDbUpgrade() | ||||
|         { | ||||
|             Create.TableForModel("Users") | ||||
|                   .WithColumn("Identifier").AsString().NotNullable().Unique() | ||||
|                   .WithColumn("Username").AsString().NotNullable().Unique() | ||||
|                   .WithColumn("Password").AsString().NotNullable(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -30,6 +30,7 @@ using NzbDrone.Core.Tags; | ||||
| using NzbDrone.Core.ThingiProvider; | ||||
| using NzbDrone.Core.Tv; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Core.Authentication; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Datastore | ||||
| { | ||||
| @@ -101,6 +102,7 @@ namespace NzbDrone.Core.Datastore | ||||
|             Mapper.Entity<Restriction>().RegisterModel("Restrictions"); | ||||
| 
 | ||||
|             Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles"); | ||||
|             Mapper.Entity<User>().RegisterModel("Users"); | ||||
|         } | ||||
| 
 | ||||
|         private static void RegisterMappers() | ||||
| @@ -122,6 +124,7 @@ namespace NzbDrone.Core.Datastore | ||||
|             MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter()); | ||||
|             MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<Int32>), new EmbeddedDocumentConverter()); | ||||
|             MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter()); | ||||
|             MapRepository.Instance.RegisterTypeConverter(typeof(Guid), new GuidConverter()); | ||||
|         } | ||||
| 
 | ||||
|         private static void RegisterProviderSettingConverter() | ||||
|   | ||||
| @@ -115,6 +115,10 @@ | ||||
|     </Compile> | ||||
|     <Compile Include="Analytics\AnalyticsService.cs" /> | ||||
|     <Compile Include="Annotations\FieldDefinitionAttribute.cs" /> | ||||
|     <Compile Include="Authentication\AuthenticationType.cs" /> | ||||
|     <Compile Include="Authentication\User.cs" /> | ||||
|     <Compile Include="Authentication\UserRepository.cs" /> | ||||
|     <Compile Include="Authentication\UserService.cs" /> | ||||
|     <Compile Include="Backup\Backup.cs" /> | ||||
|     <Compile Include="Backup\BackupCommand.cs" /> | ||||
|     <Compile Include="Backup\BackupService.cs" /> | ||||
| @@ -153,6 +157,7 @@ | ||||
|     <Compile Include="Datastore\Converters\EmbeddedDocumentConverter.cs" /> | ||||
|     <Compile Include="Datastore\Converters\EnumIntConverter.cs" /> | ||||
|     <Compile Include="Datastore\Converters\Int32Converter.cs" /> | ||||
|     <Compile Include="Datastore\Converters\GuidConverter.cs" /> | ||||
|     <Compile Include="Datastore\Converters\OsPathConverter.cs" /> | ||||
|     <Compile Include="Datastore\Converters\ProviderSettingConverter.cs" /> | ||||
|     <Compile Include="Datastore\Converters\QualityIntConverter.cs" /> | ||||
| @@ -236,6 +241,7 @@ | ||||
|     <Compile Include="Datastore\Migration\069_quality_proper.cs" /> | ||||
|     <Compile Include="Datastore\Migration\071_unknown_quality_in_profile.cs" /> | ||||
|     <Compile Include="Datastore\Migration\072_history_grabid.cs" /> | ||||
|     <Compile Include="Datastore\Migration\076_add_users_table.cs" /> | ||||
|     <Compile Include="Datastore\Migration\075_force_lib_update.cs" /> | ||||
|     <Compile Include="Datastore\Migration\074_disable_eztv.cs" /> | ||||
|     <Compile Include="Datastore\Migration\073_clear_ratings.cs" /> | ||||
|   | ||||
| @@ -284,3 +284,11 @@ dl.info { | ||||
|     background-color : #17B1D9; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .login { | ||||
|   color : #ececec; | ||||
|  | ||||
|   h2 { | ||||
|     vertical-align : bottom; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -35,7 +35,7 @@ define( | ||||
|             }, | ||||
|  | ||||
|             onRender: function(){ | ||||
|                 if(!this.ui.authToggle.prop('checked')){ | ||||
|                 if(this.ui.authToggle.val() === 'none'){ | ||||
|                     this.ui.authOptions.hide(); | ||||
|                 } | ||||
|  | ||||
| @@ -61,7 +61,7 @@ define( | ||||
|  | ||||
|             _setAuthOptionsVisibility: function () { | ||||
|  | ||||
|                 var showAuthOptions = this.ui.authToggle.prop('checked'); | ||||
|                 var showAuthOptions = this.ui.authToggle.val() !== 'none'; | ||||
|  | ||||
|                 if (showAuthOptions) { | ||||
|                     this.ui.authOptions.slideDown(); | ||||
|   | ||||
| @@ -112,21 +112,17 @@ | ||||
|         <div class="form-group"> | ||||
|             <label class="col-sm-3 control-label">Authentication</label> | ||||
|  | ||||
|             <div class="col-sm-8"> | ||||
|                 <div class="input-group"> | ||||
|                     <label class="checkbox toggle well"> | ||||
|                         <input type="checkbox" class="x-auth" name="authenticationEnabled"/> | ||||
|                         <p> | ||||
|                             <span>On</span> | ||||
|                             <span>Off</span> | ||||
|                         </p> | ||||
|                         <div class="btn btn-primary slide-button"/> | ||||
|                     </label> | ||||
|             <div class="col-sm-1 col-sm-push-4 help-inline"> | ||||
|                 <i class="icon-nd-form-warning" title="Requires restart to take effect"/> | ||||
|                 <i class="icon-nd-form-info" title="Require Username and Password to access Sonarr"/> | ||||
|             </div> | ||||
|  | ||||
|                     <span class="help-inline-checkbox"> | ||||
|                         <i class="icon-nd-form-info" title="Require Username and Password to access Sonarr"/> | ||||
|                     </span> | ||||
|                 </div> | ||||
|             <div class="col-sm-4 col-sm-pull-1"> | ||||
|                 <select name="authenticationMethod" class="form-control x-auth"> | ||||
|                     <option value="none">None</option> | ||||
|                     <option value="basic">Basic (Browser popup)</option> | ||||
|                     <option value="forms">Forms (Login page)</option> | ||||
|                 </select> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|   | ||||
| @@ -296,13 +296,14 @@ define( | ||||
|  | ||||
|         app.addInitializer(function () { | ||||
|  | ||||
|             var footerText = serverStatusModel.get('version'); | ||||
|             var version = serverStatusModel.get('version'); | ||||
|             var branch = serverStatusModel.get('branch'); | ||||
|  | ||||
|             if (serverStatusModel.get('branch') !== 'master') { | ||||
|                 footerText += '</br>' + serverStatusModel.get('branch'); | ||||
|             $('#footer-region .version').html(version); | ||||
|  | ||||
|             if (branch !== 'master') { | ||||
|                 $('#footer-region .branch').html(branch); | ||||
|             } | ||||
|  | ||||
|             $('#footer-region .version').html(footerText); | ||||
|         }); | ||||
|  | ||||
|         return app; | ||||
|   | ||||
| @@ -66,6 +66,7 @@ | ||||
|                 <div id="footer-region"> | ||||
|                     Sonarr Ver. | ||||
|                     <span class="version"></span> | ||||
|                     <div class="branch"></div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|   | ||||
							
								
								
									
										59
									
								
								src/UI/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/UI/login.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||||
| <html xmlns="http://www.w3.org/1999/xhtml"> | ||||
| <head runat="server"> | ||||
|     <title>Sonarr - Login</title> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"/> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="mobile-web-app-capable" content="yes"> | ||||
|     <meta name="apple-mobile-web-app-capable" content="yes"> | ||||
|  | ||||
|     <link href="/Content/bootstrap.css" rel='stylesheet' type='text/css'/> | ||||
|     <link href="/Content/theme.css" rel='stylesheet' type='text/css'/> | ||||
|  | ||||
|     <link rel="apple-touch-icon" href="/Content/Images/touch/57.png"/> | ||||
|     <link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png"/> | ||||
|     <link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png"/> | ||||
|     <link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png"/> | ||||
|     <link rel="icon" type="image/ico" href="/Content/Images/favicon.ico"/> | ||||
| </head> | ||||
| <body> | ||||
| <div class="container"> | ||||
|     <div id="nav-region"></div> | ||||
| </div> | ||||
| <div id="page"> | ||||
|     <div class="page-container"> | ||||
|         <div class="container-fluid"> | ||||
|             <div class="container-fluid main-region" id="main-region"> | ||||
|                 <div class="col-md-2 col-md-offset-5"> | ||||
|                     <form name="login" id="login" class="login" method="POST"> | ||||
|                         <h2><img src="/Content/Images/logos/32.png" alt=""/> Sonarr</h2> | ||||
|                         <div class="form-group"> | ||||
|                             <label for="username" class="sr-only">Email address</label> | ||||
|                             <input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="form-group"> | ||||
|                             <label for="password" class="sr-only">Password</label> | ||||
|                             <input type="password" id="password" name="password" class="form-control" placeholder="Password" required> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="checkbox"> | ||||
|                             <label> | ||||
|                                 <input type="checkbox" name="rememberMe" checked="checked"> Remember me | ||||
|                             </label> | ||||
|                         </div> | ||||
|  | ||||
|                         <input class="btn btn-lg btn-primary btn-block" type="submit" value="Log in" /> | ||||
|                     </form> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div id="modal-region"></div> | ||||
|             <div id="file-browser-modal-region"></div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <a id="scroll-up" title="Back to the top!"> | ||||
|         <i class="icon-circle-arrow-up"></i> | ||||
|     </a> | ||||
| </div> | ||||
| </body> | ||||
| </html> | ||||
		Reference in New Issue
	
	Block a user