From e9e5bc096d3e4eba85395da72d2de608ac87af76 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Tue, 4 Feb 2025 18:45:24 -0600 Subject: [PATCH] Move registration to AuthenticationService, add scripts for UserLogin and UserRegistration --- LANCommander.SDK/Services/ScriptService.cs | 78 ++++++++++++- .../AuthenticationService.cs | 108 +++++++++++++++++- .../Exceptions/UserRegistrationException.cs | 4 + .../Controllers/Api/AuthController.cs | 66 +---------- .../UI/Components/ScriptEditor.razor | 6 +- .../UI/Pages/Settings/Scripts.razor | 9 ++ 6 files changed, 197 insertions(+), 74 deletions(-) create mode 100644 LANCommander.Server/UI/Pages/Settings/Scripts.razor diff --git a/LANCommander.SDK/Services/ScriptService.cs b/LANCommander.SDK/Services/ScriptService.cs index be71bb66..780c85d4 100644 --- a/LANCommander.SDK/Services/ScriptService.cs +++ b/LANCommander.SDK/Services/ScriptService.cs @@ -3,16 +3,10 @@ using LANCommander.SDK.Helpers; using LANCommander.SDK.Models; using LANCommander.SDK.PowerShell; using Microsoft.Extensions.Logging; -using Steamworks.Data; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Management.Automation; -using System.Text; using System.Threading.Tasks; -using YamlDotNet.Serialization; namespace LANCommander.SDK.Services { @@ -41,6 +35,78 @@ namespace LANCommander.SDK.Services Client = client; Logger = logger; } + + #region Authentication Scripts + public async Task RunUserLoginScript(Script loginScript, User user) + { + try + { + using (var op = Logger.BeginOperation("Executing user login script")) + { + var script = new PowerShellScript(Enums.ScriptType.UserLogin); + + script.AddVariable("User", user); + script.AddVariable("ServerAddress", Client.BaseUrl.ToString()); + + script.UseInline(loginScript.Contents); + + try + { + op + .Enrich("UserId", user.Id) + .Enrich("Username", user.UserName) + .Enrich("ScriptId", loginScript.Id) + .Enrich("ScriptName", loginScript.Name); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not enrich logs"); + } + + await script.ExecuteAsync(); + } + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not execute user login script"); + } + } + + public async Task RunUserRegistrationScript(Script registrationScript, User user) + { + try + { + using (var op = Logger.BeginOperation("Executing user registration script")) + { + var script = new PowerShellScript(Enums.ScriptType.UserRegistration); + + script.AddVariable("User", user); + script.AddVariable("ServerAddress", Client.BaseUrl.ToString()); + + script.UseInline(registrationScript.Contents); + + try + { + op + .Enrich("UserId", user.Id) + .Enrich("Username", user.UserName) + .Enrich("ScriptId", registrationScript.Id) + .Enrich("ScriptName", registrationScript.Name); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not enrich logs"); + } + + await script.ExecuteAsync(); + } + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not execute user registration script"); + } + } + #endregion #region Redistributables public async Task RunDetectInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId) diff --git a/LANCommander.Server.Services/AuthenticationService.cs b/LANCommander.Server.Services/AuthenticationService.cs index 1bab4ec0..98e071f4 100644 --- a/LANCommander.Server.Services/AuthenticationService.cs +++ b/LANCommander.Server.Services/AuthenticationService.cs @@ -3,20 +3,52 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; +using AutoMapper; +using LANCommander.SDK.Enums; using LANCommander.SDK.Models; +using LANCommander.Server.Services.Exceptions; using Microsoft.IdentityModel.Tokens; -using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization; +using PascalCaseNamingConvention = YamlDotNet.Serialization.NamingConventions.PascalCaseNamingConvention; namespace LANCommander.Server.Services { - public class AuthenticationService(ILogger logger, UserService userService) : BaseService(logger) + public class AuthenticationService( + ILogger logger, + IMapper mapper, + UserService userService, + RoleService roleService, + ScriptService scriptService) : BaseService(logger) { public async Task LoginAsync(string userName, string password) { if (!String.IsNullOrWhiteSpace(userName) && await userService.CheckPassword(userName, password)) { - return await LoginAsync(userName); + var token = await LoginAsync(userName); + + try + { + var user = await userService.GetAsync(userName); + var scripts = await scriptService.GetAsync(s => s.Type == ScriptType.UserLogin); + + if (scripts.Any()) + { + var client = new SDK.Client(_settings.Beacon.Address, "", logger); + + client.UseToken(token); + + foreach (var script in scripts) + { + await client.Scripts.RunUserLoginScript(script, user); + } + } + } + catch (Exception ex) + { + logger?.LogError(ex, "Could not execute user login script"); + } + + return token; } else throw new Exception("Invalid username or password"); @@ -108,6 +140,76 @@ namespace LANCommander.Server.Services Expiration = newAccessToken.ValidTo }; } + + public async Task RegisterAsync(string userName, string password, string passwordConfirmation) + { + if (password != passwordConfirmation) + throw new UserRegistrationException("Passwords don't match"); + + var user = await userService.GetAsync(userName); + + if (user != null) + { + _logger?.LogDebug("Cannot register user with username {UserName}, already exists", userName); + + throw new UserRegistrationException("Username is unavailable"); + } + + user = new Data.Models.User(); + user.UserName = userName; + + user = await userService.AddAsync(user); + + if (user != null) + { + await userService.ChangePassword(user.UserName, password); + + try + { + if (_settings.Roles.DefaultRoleId == Guid.Empty) + { + var defaultRole = await roleService.GetAsync(_settings.Roles.DefaultRoleId); + + if (defaultRole != null) + await userService.AddToRoleAsync(user.UserName, defaultRole.Name); + } + + var token = await LoginAsync(user.UserName, password); + + logger?.LogDebug("Successfully registered user {UserName}", user.UserName); + + try + { + var scripts = await scriptService.GetAsync(s => s.Type == ScriptType.UserLogin); + + if (scripts.Any()) + { + var client = new SDK.Client(_settings.Beacon.Address, "", logger); + + client.UseToken(token); + + foreach (var script in scripts) + { + await client.Scripts.RunUserRegistrationScript(script, mapper.Map(user)); + } + } + } + catch (Exception ex) + { + logger?.LogError(ex, "Could not execute user login script"); + } + + return token; + } + catch (Exception ex) + { + logger?.LogError(ex, "Could not register user {UserName}", user.UserName); + throw new UserRegistrationException("An unknown error occurred while registering"); + } + } + else + throw new UserRegistrationException("Unknown Error"); + } private ClaimsPrincipal? GetPrincipalFromExpiredToken(string? token) { diff --git a/LANCommander.Server.Services/Exceptions/UserRegistrationException.cs b/LANCommander.Server.Services/Exceptions/UserRegistrationException.cs index 872b332b..540f8160 100644 --- a/LANCommander.Server.Services/Exceptions/UserRegistrationException.cs +++ b/LANCommander.Server.Services/Exceptions/UserRegistrationException.cs @@ -6,6 +6,10 @@ public class UserRegistrationException : Exception { public IdentityResult IdentityResult { get; set; } + public UserRegistrationException(string message) : base(message) + { + } + public UserRegistrationException(IdentityResult identityResult, string message) : base(message) { IdentityResult = identityResult; diff --git a/LANCommander.Server/Controllers/Api/AuthController.cs b/LANCommander.Server/Controllers/Api/AuthController.cs index 80426f4f..866675eb 100644 --- a/LANCommander.Server/Controllers/Api/AuthController.cs +++ b/LANCommander.Server/Controllers/Api/AuthController.cs @@ -17,13 +17,6 @@ using User = LANCommander.Server.Data.Models.User; namespace LANCommander.Server.Controllers.Api { - public class TokenModel - { - public string AccessToken { get; set; } - public string RefreshToken { get; set; } - public DateTime Expiration { get; set; } - } - public class LoginModel { public string UserName { get; set; } @@ -147,64 +140,17 @@ namespace LANCommander.Server.Controllers.Api [HttpPost("Register")] public async Task RegisterAsync([FromBody] RegisterModel model) { - if (model.Password != model.PasswordConfirmation) - return Unauthorized(new - { - Message = "Passwords don't match." - }); - - var user = await UserService.GetAsync(model.UserName); - - if (user != null) + try { - Logger?.LogDebug("Cannot register user with username {UserName}, already exists", model.UserName); + var token = await AuthenticationService.RegisterAsync(model.UserName, model.Password, + model.PasswordConfirmation); - return Unauthorized(new - { - Message = "Username is unavailable" - }); + return Ok(token); } - - user = new User(); - - user.UserName = model.UserName; - - user = await UserService.AddAsync(user); - - if (user != null) + catch (Exception ex) { - await UserService.ChangePassword(user.UserName, model.Password); - - try - { - if (Settings.Roles.DefaultRoleId != Guid.Empty) - { - var defaultRole = await RoleService.GetAsync(Settings.Roles.DefaultRoleId); - - if (defaultRole != null) - await UserService.AddToRoleAsync(user.UserName, defaultRole.Name); - } - - var token = await AuthenticationService.LoginAsync(user.UserName, model.Password); - - Logger?.LogDebug("Successfully registered user {UserName}", user.UserName); - - return Ok(token); - } - catch (Exception ex) - { - Logger?.LogError(ex, "Could not register user {UserName}", user.UserName); - return BadRequest(new - { - Message = "An unknown error occurred" - }); - } + return Unauthorized(ex.Message); } - - return Unauthorized(new - { - //Message = "Error:\n" + String.Join('\n', result.Errors.Select(e => e.Description)) - }); } [HttpGet("AuthenticationProviders")] diff --git a/LANCommander.Server/UI/Components/ScriptEditor.razor b/LANCommander.Server/UI/Components/ScriptEditor.razor index 37b4ea8b..38fdce1b 100644 --- a/LANCommander.Server/UI/Components/ScriptEditor.razor +++ b/LANCommander.Server/UI/Components/ScriptEditor.razor @@ -9,7 +9,7 @@ TItem="Script" HidePagination Responsive - Query="s => (s.GameId != Guid.Empty && s.GameId == GameId) || (s.RedistributableId != Guid.Empty && s.RedistributableId == RedistributableId) || (s.ServerId != Guid.Empty && s.ServerId == ServerId)"> + Query="s => s.GameId == Guid.Empty && s.RedistributableId == Guid.Empty && s.ServerId == Guid.Empty"> @context.Type.GetDisplayName() @@ -63,10 +63,6 @@ { ScriptId = scriptId ?? default, AllowedTypes = AllowedTypes, - ArchiveId = ArchiveId, - GameId = GameId, - RedistributableId = RedistributableId, - ServerId = ServerId }; var modalRef = await ModalService.CreateModalAsync(modalOptions, options); diff --git a/LANCommander.Server/UI/Pages/Settings/Scripts.razor b/LANCommander.Server/UI/Pages/Settings/Scripts.razor new file mode 100644 index 00000000..a04af5c1 --- /dev/null +++ b/LANCommander.Server/UI/Pages/Settings/Scripts.razor @@ -0,0 +1,9 @@ +@page "/Settings/Scripts" +@using LANCommander.SDK.Enums +@attribute [Authorize(Roles = RoleService.AdministratorRoleName)] + + + +
+ +
\ No newline at end of file