Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 57 additions & 36 deletions SecurityService.BusinessLogic/RequestHandlers/UserRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using Shared.Logger;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;

namespace SecurityService.BusinessLogic.RequestHandlers;
Expand Down Expand Up @@ -63,23 +64,12 @@ private async Task<TokenResponse> GetToken()
{
// Get a token to talk to the estate service
String clientId = this.Options.Value.ClientId;

//Logger.LogInformation($"Client Id is {clientId}");
//Logger.LogInformation($"Client Secret is {clientSecret}");

if (this.TokenResponse == null) {
String clientToken = this.ClientJwtService.CreateClientAssertion(clientId, this.Options.Value.IssuerUrl, 60);
this.TokenResponse = TokenResponse.Create(clientToken, null, 3600, DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(3600));
// Logger.LogInformation($"Token is {this.TokenResponse.AccessToken}");
return this.TokenResponse;
}

if (this.TokenResponse.Expires.UtcDateTime.Subtract(DateTime.UtcNow) < TimeSpan.FromMinutes(2))
{
// Logger.LogInformation($"Token is about to expire at {this.TokenResponse.Expires.DateTime:O}");
// Logger.LogInformation($"Token is {this.TokenResponse.AccessToken}");
return this.TokenResponse;
}
return this.TokenResponse;
}

Expand All @@ -94,15 +84,18 @@ private async Task<Result> SendConfirmationEmail(ApplicationUser newIdentityUser
SendEmailRequest emailRequest = this.BuildEmailConfirmationRequest(newIdentityUser, uri);
Result sendEmailResult = await this.MessagingServiceClient.SendEmail(token.AccessToken, emailRequest, cancellationToken);
// TODO: Error handling
//if (sendEmailResult.IsFailed)
//Logger.LogWarning($"Error sending email to {newIdentityUser.Email} as part of user creation {sendEmailResult}");
if (sendEmailResult.IsFailed) {
Logger.LogWarning($"Error sending email to {newIdentityUser.Email} as part of user creation {sendEmailResult}");
return ResultHelpers.CreateFailure(sendEmailResult);
}

return Result.Success();
}

private SendEmailRequest BuildEmailConfirmationRequest(ApplicationUser user,
String emailConfirmationToken)
{
StringBuilder mesasgeBuilder = new StringBuilder();
StringBuilder mesasgeBuilder = new();

mesasgeBuilder.Append("<html>");
mesasgeBuilder.Append("<body>");
Expand Down Expand Up @@ -414,7 +407,46 @@ public async Task<Result> Handle(SecurityServiceCommands.SendWelcomeEmailCommand
return Result.Success();
}


public async Task<Result> Handle(SecurityServiceCommands.ResendWelcomeEmailCommand command,
CancellationToken cancellationToken)
{
ApplicationUser? user = await this.UserManager.FindByNameAsync(command.Username);
if (user == null)
{
return Result.NotFound($"No user found with username {command.Username}");
}

IdentityResult removePasswordResult = await this.UserManager.RemovePasswordAsync(user);
if (removePasswordResult.Succeeded == false)
{
return Result.Failure($"Errors removing password for user [{command.Username}]");
}

Result<string> generatedPasswordResult = PasswordGenerator.GenerateRandomPassword(this.Options.Value.PasswordOptions);
if (generatedPasswordResult.IsFailed)
{
return ResultHelpers.CreateFailure(generatedPasswordResult);
}

IdentityResult addPasswordResult = await this.UserManager.AddPasswordAsync(user, generatedPasswordResult.Data);
if (addPasswordResult.Succeeded == false)
{
return Result.Failure($"Errors adding password for user [{command.Username}]");
}

TokenResponse token = await this.GetToken();
string emailAddress = user.Email ?? user.UserName ?? string.Empty;
SendEmailRequest emailRequest = this.BuildWelcomeEmail(emailAddress, generatedPasswordResult.Data);
Result sendEmailResult = await this.MessagingServiceClient.SendEmail(token.AccessToken, emailRequest, cancellationToken);
if (sendEmailResult.IsFailed)
{
return ResultHelpers.CreateFailure(sendEmailResult);
}

return Result.Success();
}




public static class PasswordGenerator {
Expand Down Expand Up @@ -489,45 +521,37 @@ private static void SecureShuffle(List<char> chars) {

public async Task<Result<ChangeUserPasswordResult>> Handle(SecurityServiceCommands.ChangeUserPasswordCommand command,
CancellationToken cancellationToken) {
//Logger.LogWarning("In Handle ChangeUserPasswordCommand");
// Find the user based on the user name passed in
ApplicationUser user = await this.UserManager.FindByNameAsync(command.UserName);

if (user == null)
{
//Logger.LogWarning("In Handle ChangeUserPasswordCommand - user is null");
if (user == null) {
// this prevents giving away info to a potential hacker...
return Result.NotFound();
}
//Logger.LogWarning("In Handle ChangeUserPasswordCommand - user is not null");

IdentityResult result = await this.UserManager.ChangePasswordAsync(user,
command.CurrentPassword,
command.NewPassword);

if (result.Succeeded == false)
{
// Log any errors
///Logger.LogInformation($"Errors during password change for user [{command.UserName} and Client [{command.ClientId}]");
StringBuilder errors = new StringBuilder();
foreach (IdentityError identityError in result.Errors)
{
//Logger.LogInformation($"Code {identityError.Code} Description {identityError.Description}");
errors.AppendLine($"Code {identityError.Code} Description {identityError.Description}");
}

return Result.Failure($"Errors during password change for user [{command.UserName} and Client [{command.ClientId}]");
return Result.Failure($"Errors during password change for user [{command.UserName}] and Client [{command.ClientId}]: {errors}");
}

//Logger.LogWarning("In Handle ChangeUserPasswordCommand - password changed");
// build the redirect uri
var client = await this.DbContext.ClientDefinitions.Where(c => c.ClientId == command.ClientId).SingleOrDefaultAsync(cancellationToken);

if (client == null)
{
//Logger.LogWarning("In Handle ChangeUserPasswordCommand - client not found");
//Logger.LogInformation($"Client not found for clientId {command.ClientId}");
if (client == null) {
return Result.Invalid($"Client not found for clientId {command.ClientId}");
}

//Logger.LogWarning($"Client uri: {client.ClientUri}");
return Result.Success(new ChangeUserPasswordResult { IsSuccessful = true, RedirectUri = client.ClientUri });
}

Expand All @@ -536,8 +560,7 @@ public async Task<Result> Handle(SecurityServiceCommands.ProcessPasswordResetReq
// Find the user based on the user name passed in
ApplicationUser user = await this.UserManager.FindByNameAsync(command.Username);

if (user == null)
{
if (user == null) {
return Result.Success();
}

Expand Down Expand Up @@ -594,7 +617,6 @@ public async Task<Result<String>> Handle(SecurityServiceCommands.ProcessPassword
if (user == null)
{
// this prevents giving away info to a potential hacker...
//Logger.LogInformation($"user not found for username {command.Username}");
return Result.NotFound($"user not found for username {command.Username}");
}

Expand All @@ -604,21 +626,20 @@ public async Task<Result<String>> Handle(SecurityServiceCommands.ProcessPassword
if (result.Succeeded == false)
{
// Log any errors
//Logger.LogInformation($"Errors during password reset for user [{command.Username} and Client [{command.ClientId}]");
StringBuilder errors = new StringBuilder();
foreach (IdentityError identityError in result.Errors)
{
//Logger.LogInformation($"Code {identityError.Code} Description {identityError.Description}");
errors.AppendLine($"Code {identityError.Code} Description {identityError.Description}");
}

return Result.Failure($"Errors during password reset for user [{command.Username} and Client [{command.ClientId}]");
return Result.Failure($"Errors during password reset for user [{command.Username}] and Client [{command.ClientId}]: {errors}");
}

// build the redirect uri
var client = await this.DbContext.ClientDefinitions.Where(c => c.ClientId == command.ClientId).SingleOrDefaultAsync(cancellationToken);

if (client == null)
{
//Logger.LogInformation($"Client not found for clientId {command.ClientId}");
return Result.Invalid($"Client not found for clientId {command.ClientId}");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public record ChangeUserPasswordCommand(String UserName,
public record ConfirmUserEmailAddressCommand(String UserName, String ConfirmEmailToken) : IRequest<Result>;

public record SendWelcomeEmailCommand(String Username) : IRequest<Result>;
public record ResendWelcomeEmailCommand(String Username) : IRequest<Result>;
public record ProcessPasswordResetRequestCommand(String Username,
String EmailAddress,
String ClientId) : IRequest<Result>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="OpenIddict.Server" Version="7.3.0" />
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="7.3.0" />
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="7.3.0" />
<PackageReference Include="Shared.Logger" Version="2026.5.5" />
<PackageReference Include="Shared.Results" Version="2026.5.5" />
<PackageReference Include="SimpleResults" Version="4.0.0" />
</ItemGroup>
Expand Down
1 change: 0 additions & 1 deletion SecurityService.BusinessLogic/SecurityServiceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ public async Task<Result> SendEmail(String accessToken,
SendEmailRequest request,
CancellationToken cancellationToken)
{
//Logger.LogWarning($"Sending Email {request.Subject}");
this.LastEmailRequest = new SendEmailRequest() {
Body = request.Body,
ConnectionIdentifier = request.ConnectionIdentifier,
Expand Down
46 changes: 0 additions & 46 deletions SecurityService/Factories/OidcResponseFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,50 +56,4 @@ internal static IResult TranslateResultStatus(ResultBase result)
_ => Microsoft.AspNetCore.Http.Results.StatusCode((int)HttpStatusCode.NotImplemented)
};
}

//public static IResult FromResult(Result<TokenCommandResult> result)
//{
// if (result.IsSuccess == false)
// {
// return TranslateResultStatus(result);
// }

// return result.Data switch
// {
// TokenSignInResult r => Results.SignIn(r.Principal, properties: null, authenticationScheme: r.AuthenticationScheme),
// TokenForbidResult r => Results.Forbid(r.Properties, r.AuthenticationSchemes),
// TokenBadRequestResult r => Results.BadRequest(r.Error),
// _ => Results.StatusCode(500)
// };
//}

//public static IResult FromResult(Result<LogoutCommandResult> result)
//{
// if (result.IsSuccess == false)
// {
// return TranslateResultStatus(result);
// }

// return result.Data switch
// {
// LogoutSignOutResult r => Results.SignOut(r.Properties, r.AuthenticationSchemes),
// LogoutRedirectResult r => Results.Redirect(r.Url),
// _ => Results.StatusCode(500)
// };
//}

//public static IResult FromResult(Result<UserInfoCommandResult> result)
//{
// if (result.IsSuccess == false)
// {
// return TranslateResultStatus(result);
// }

// return result.Data switch
// {
// UserInfoJsonResult r => Results.Json(r.Data),
// UserInfoChallengeResult r => Results.Challenge(r.Properties, r.AuthenticationSchemes),
// _ => Results.StatusCode(500)
// };
//}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
using System.Text;
using Microsoft.AspNetCore.Identity;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using SecurityService.Database;
using SecurityService.BusinessLogic.Requests;

namespace SecurityService.Pages.Account.ResendConfirmationEmail;

public sealed class IndexModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;

public IndexModel(UserManager<ApplicationUser> userManager)
{
this._userManager = userManager;
private readonly IMediator Mediator;

public IndexModel(IMediator mediator) {
this.Mediator = mediator;
}

[BindProperty]
Expand All @@ -29,26 +26,15 @@ public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken
return this.Page();
}

var user = await this.ResolveUserAsync(this.Input.EmailOrUserName);
if (user is not null && string.IsNullOrWhiteSpace(user.Email) == false && user.EmailConfirmed == false)
{
var code = await this._userManager.GenerateEmailConfirmationTokenAsync(user);
var encoded = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = this.Url.Page("/Account/ConfirmEmail/Index", null, new { userId = user.Id, code = encoded }, this.Request.Scheme) ?? $"/Account/ConfirmEmail?userId={Uri.EscapeDataString(user.Id)}&code={Uri.EscapeDataString(encoded)}";
// TODO: Send email with callback URL
//await this._emailService.SendAsync(new AccountMessage(user.Email!, "Confirm your email", $"Confirm your account by visiting {callbackUrl}"), cancellationToken);
}
SecurityServiceCommands.ResendWelcomeEmailCommand command = new(this.Input.EmailOrUserName);
await this.Mediator.Send(command, cancellationToken);



this.EmailQueued = true;
return this.Page();
}

private async Task<ApplicationUser?> ResolveUserAsync(string value)
{
var user = await this._userManager.FindByEmailAsync(value);
return user ?? await this._userManager.FindByNameAsync(value);
}

public sealed class InputModel
{
public string EmailOrUserName { get; set; } = string.Empty;
Expand Down
Loading
Loading