diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs index 4debe3ea5..50bcfed11 100644 --- a/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Ntfy { var error = false; - var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? NtfyProxy.DEFAULT_PUSH_URL : settings.ServerUrl; + var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? DEFAULT_PUSH_URL : settings.ServerUrl; foreach (var topic in settings.Topics) { @@ -77,10 +77,22 @@ namespace NzbDrone.Core.Notifications.Ntfy } catch (HttpException ex) { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden) + if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { + if (!settings.AccessToken.IsNullOrWhiteSpace()) + { + _logger.Error(ex, "Invalid token"); + return new ValidationFailure("AccessToken", "Invalid token"); + } + + if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace()) + { + _logger.Error(ex, "Invalid username or password"); + return new ValidationFailure("UserName", "Invalid username or password"); + } + _logger.Error(ex, "Authorization is required"); - return new ValidationFailure("UserName", "Authorization is required"); + return new ValidationFailure("AccessToken", "Authorization is required"); } _logger.Error(ex, "Unable to send test message"); @@ -113,18 +125,22 @@ namespace NzbDrone.Core.Notifications.Ntfy requestBuilder.Headers.Add("X-Click", settings.ClickUrl); } - var request = requestBuilder.Build(); - - if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace()) + if (!settings.AccessToken.IsNullOrWhiteSpace()) { - request.Credentials = new BasicNetworkCredential(settings.UserName, settings.Password); + requestBuilder.Headers.Set("Authorization", $"Bearer {settings.AccessToken}"); } + else if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace()) + { + requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.UserName, settings.Password); + } + + var request = requestBuilder.Build(); _httpClient.Execute(request); } catch (HttpException ex) { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden) + if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { _logger.Error(ex, "Authorization is required"); throw; diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs index 94190458d..1c1cedec3 100644 --- a/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs @@ -16,8 +16,8 @@ namespace NzbDrone.Core.Notifications.Ntfy RuleFor(c => c.Priority).InclusiveBetween(1, 5); RuleFor(c => c.ServerUrl).IsValidUrl().When(c => !c.ServerUrl.IsNullOrWhiteSpace()); RuleFor(c => c.ClickUrl).IsValidUrl().When(c => !c.ClickUrl.IsNullOrWhiteSpace()); - RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace()); - RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace()); + RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace() && c.AccessToken.IsNullOrWhiteSpace()); + RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace() && c.AccessToken.IsNullOrWhiteSpace()); RuleForEach(c => c.Topics).NotEmpty().Matches("[a-zA-Z0-9_-]+").Must(c => !InvalidTopics.Contains(c)).WithMessage("Invalid topic"); } @@ -37,22 +37,25 @@ namespace NzbDrone.Core.Notifications.Ntfy [FieldDefinition(0, Label = "Server Url", Type = FieldType.Url, HelpLink = "https://ntfy.sh/docs/install/", HelpText = "Leave blank to use public server (https://ntfy.sh)")] public string ServerUrl { get; set; } - [FieldDefinition(1, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)] + [FieldDefinition(1, Label = "Access Token", Type = FieldType.Password, Privacy = PrivacyLevel.ApiKey, HelpText = "Optional token-based authorization. Takes priority over username/password", HelpLink = "https://docs.ntfy.sh/config/#access-tokens")] + public string AccessToken { get; set; } + + [FieldDefinition(2, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)] public string UserName { get; set; } - [FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)] + [FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))] + [FieldDefinition(4, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))] public int Priority { get; set; } - [FieldDefinition(4, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag)] + [FieldDefinition(5, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag)] public IEnumerable Topics { get; set; } - [FieldDefinition(5, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", HelpLink = "https://ntfy.sh/docs/emojis/")] + [FieldDefinition(6, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", HelpLink = "https://ntfy.sh/docs/emojis/")] public IEnumerable Tags { get; set; } - [FieldDefinition(6, Label = "Click Url", Type = FieldType.Url, HelpText = "Optional link when user clicks notification")] + [FieldDefinition(7, Label = "Click URL", Type = FieldType.Url, HelpText = "Optional link when user clicks notification")] public string ClickUrl { get; set; } public NzbDroneValidationResult Validate()