Skip to content

Commit 2086b16

Browse files
committed
Add API authentication for user management
Protect user endpoints with a bearer-token authentication filter. Generate and hash an initial API password on startup when the user UI is enabled. Register the new authentication services, bump the app version to 1.6.0.0, and refresh bundled web assets.
1 parent c97df07 commit 2086b16

19 files changed

Lines changed: 2199 additions & 2102 deletions

Controller/UsersController.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ namespace iGotify_Notification_Assist.Controller;
1010
public class UsersController : ControllerBase
1111
{
1212
[HttpGet]
13+
[ServiceFilter(typeof(AuthenticationFilter))]
1314
public async Task<IActionResult> GetAllUsers()
1415
{
1516
List<Users> userList = await DatabaseService.GetUsers();
1617
return Ok(new { Message = "Users successfully retrieved!", Data = userList });
1718
}
18-
19+
1920
[HttpPatch]
21+
[ServiceFilter(typeof(AuthenticationFilter))]
2022
public async Task<IActionResult> PatchUser([FromBody] Users? user)
2123
{
2224
if (user == null)
2325
return Ok(new { Message = "User Body is empty!" });
24-
26+
2527
bool isUpdated = await DatabaseService.UpdateUser(user);
2628

2729
if (isUpdated)
@@ -30,11 +32,12 @@ public async Task<IActionResult> PatchUser([FromBody] Users? user)
3032
GotifySocketService.KillAllWsThread();
3133
gss.Start();
3234
}
33-
35+
3436
return Ok(new { Message = isUpdated ? "User successfully updated!" : "User didn't updated!" });
3537
}
36-
38+
3739
[HttpDelete("{userId}")]
40+
[ServiceFilter(typeof(AuthenticationFilter))]
3841
public async Task<IActionResult> DeleteUser(int userId)
3942
{
4043
bool isDeleted = false;
@@ -49,7 +52,7 @@ public async Task<IActionResult> DeleteUser(int userId)
4952
GotifySocketService.KillAllWsThread();
5053
gss.Start();
5154
}
52-
55+
5356
return Ok(new { Message = isDeleted ? "User successfully deleted!" : "User didn't deleted!" });
5457
}
5558
}

Program.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
options.SerializerOptions.PropertyNamingPolicy = null; // Preserve exact casing
2121
});
2222

23+
24+
if (Environments.enableUserUi)
25+
{
26+
builder.Services.AddSingleton<PasswordGenerator>();
27+
builder.Services.AddScoped<AuthenticationFilter>();
28+
}
29+
2330
builder.Services.AddSingleton(builder.Configuration);
2431
builder.Services.AddOpenApi();
2532
builder.Services.AddTransient<IStartupFilter, StartUpBuilder>();

Services/AuthenticationFilter.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Mvc.Filters;
3+
4+
namespace iGotify_Notification_Assist.Services;
5+
6+
public class AuthenticationFilter : IAsyncActionFilter, IAsyncAuthorizationFilter
7+
{
8+
private string? token = "";
9+
10+
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
11+
{
12+
//Console.WriteLine(token);
13+
await next();
14+
}
15+
16+
public void OnActionExecuted(ActionExecutedContext context)
17+
{
18+
// our code after action executes
19+
}
20+
21+
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
22+
{
23+
var auth = context.HttpContext.Request.Headers.Authorization;
24+
25+
if (auth.ToString().Length > 0 && auth.ToString().Contains("Bearer"))
26+
{
27+
var cleared = auth.ToString().Replace("Bearer ", "");
28+
token = cleared;
29+
var result = PasswordGenerator.IsValid(token);
30+
if (!result)
31+
context.Result = new UnauthorizedResult();
32+
}
33+
else
34+
{
35+
context.Result = new UnauthorizedResult();
36+
}
37+
}
38+
}

Services/PasswordGenerator.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Security.Cryptography;
2+
using Microsoft.AspNetCore.Identity;
3+
4+
namespace iGotify_Notification_Assist.Services;
5+
6+
public class PasswordGenerator
7+
{
8+
public static void EnsurePasswordExists()
9+
{
10+
var path = $"{GetLocationsOf.App}/data/secure";
11+
//Create Database File
12+
var passwordFile = Path.Combine(path, "api-password.hash");
13+
Directory.CreateDirectory(Path.GetDirectoryName(passwordFile)!);
14+
if (File.Exists(passwordFile))
15+
return;
16+
17+
var password = GenerateSecurePassword();
18+
var hasher = new PasswordHasher<string>();
19+
var hash = hasher.HashPassword("api", password);
20+
File.WriteAllText(passwordFile, hash);
21+
AppLog.Info("PG", "====================================================");
22+
AppLog.Info("PG", "Initial API password generated:");
23+
AppLog.Info("PG", $"{password}");
24+
AppLog.Info("PG", "Please save this password. It will not be shown again.");
25+
AppLog.Info("PG", "====================================================");
26+
}
27+
28+
public static bool IsValid(string password)
29+
{
30+
var path = $"{GetLocationsOf.App}/data/secure";
31+
//Create Database File
32+
var passwordFile = Path.Combine(path, "api-password.hash");
33+
if (!File.Exists(passwordFile))
34+
return false;
35+
36+
var hash = File.ReadAllText(passwordFile);
37+
var hasher = new PasswordHasher<string>();
38+
var result = hasher.VerifyHashedPassword("api", hash, password);
39+
return result == PasswordVerificationResult.Success || result == PasswordVerificationResult.SuccessRehashNeeded;
40+
}
41+
42+
private static string GenerateSecurePassword(int length = 32)
43+
{
44+
const string chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@$%_-";
45+
var bytes = RandomNumberGenerator.GetBytes(length);
46+
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
47+
}
48+
}

Services/StartUpBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
66
{
77
return builder =>
88
{
9+
PasswordGenerator.EnsurePasswordExists();
910
// Create GotifyInstance after starting of the API
1011
var gss = GotifySocketService.getInstance();
1112
gss.Init();

iGotify Notification Assist.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
<ImplicitUsings>enable</ImplicitUsings>
77
<InvariantGlobalization>true</InvariantGlobalization>
88
<RootNamespace>iGotify_Notification_Assist</RootNamespace>
9-
<AssemblyVersion>1.5.1.3</AssemblyVersion>
10-
<FileVersion>1.5.1.3</FileVersion>
11-
<Version>1.5.1.3</Version>
9+
<AssemblyVersion>1.6.0.0</AssemblyVersion>
10+
<FileVersion>1.6.0.0</FileVersion>
11+
<Version>1.6.0.0</Version>
1212
<LangVersion>default</LangVersion>
1313
</PropertyGroup>
1414

wwwroot/chunk-7WZFGM7C.js

Lines changed: 0 additions & 144 deletions
This file was deleted.

wwwroot/chunk-DPSKJKXC.js

Lines changed: 0 additions & 1562 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)