From 9c610b1a3585bb2417e154f90461815a9300865a Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Sat, 1 Nov 2025 16:27:35 +0500 Subject: [PATCH 1/9] feat: change reset password task for test accounts in local env --- Api/Services/UsersService.cs | 17 ++++++++++++++--- Api/ci/helmfile.yaml | 3 ++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Api/Services/UsersService.cs b/Api/Services/UsersService.cs index 139a09a..8e265e7 100644 --- a/Api/Services/UsersService.cs +++ b/Api/Services/UsersService.cs @@ -17,6 +17,7 @@ public class UsersService private readonly IPasswordValidator _passwordValidator; private readonly UserBlockCommand _userBlockCommand; private readonly UserUnblockCommand _userUnblockCommand; + private readonly bool _isLocalMode; public UsersService( UserManager userManager, @@ -25,7 +26,8 @@ public UsersService( ILogger logger, IPasswordValidator passwordValidator, UserBlockCommand userBlockCommand, - UserUnblockCommand userUnblockCommand) + UserUnblockCommand userUnblockCommand, + IConfiguration configuration) { _userManager = userManager; _findUserQuery = findUserQuery; @@ -34,6 +36,7 @@ public UsersService( _passwordValidator = passwordValidator; _userBlockCommand = userBlockCommand; _userUnblockCommand = userUnblockCommand; + _isLocalMode = configuration.GetValue("IsLocalMode"); } public async Task RegisterAsync(RegistrationModel registrationModel) @@ -87,16 +90,23 @@ public async Task UnblockAsync(long accountId) await _userUnblockCommand.ExecuteAsync(accountId); } - public async Task ResetPasswordAsync(string corporateEmail) + public async Task ResetPasswordAsync(string corporateEmail) { var user = await _findUserQuery.FindUserByCorporateEmailAsync(corporateEmail); if (user == null) { - throw new NullReferenceException("User doesn't exists"); + throw new NullReferenceException("User doesn't exist"); } var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user); + + if (_isLocalMode) + { + return resetToken; + } + await _innerCircleHttpClient.SendPasswordResetLink(corporateEmail, resetToken); + return null!; } public async Task ChangePasswordAsync(PasswordChangeModel passwordChangeModel) @@ -125,6 +135,7 @@ public async Task ChangePasswordAsync(PasswordChangeModel passwordChangeModel) await _userManager.ResetPasswordAsync(user, passwordChangeModel.PasswordResetToken, passwordChangeModel.NewPassword); + } } } \ No newline at end of file diff --git a/Api/ci/helmfile.yaml b/Api/ci/helmfile.yaml index a9fbff2..8134ce8 100644 --- a/Api/ci/helmfile.yaml +++ b/Api/ci/helmfile.yaml @@ -22,4 +22,5 @@ releases: InnerCircleServiceUrls__MailServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__MailServiceUrl }}" InnerCircleServiceUrls__AuthUIServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__AuthUIServiceUrl }}" InnerCircleServiceUrls__AccountsServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__AccountsServiceUrl }}" - InnerCircleServiceUrls__EmployeesServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__EmployeesServiceUrl }}" \ No newline at end of file + InnerCircleServiceUrls__EmployeesServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__EmployeesServiceUrl }}" + IsLocalMode: "{{ .StateValues.extraSecretEnvVars.IsLocalMode }}" \ No newline at end of file From 16ab76a66eff650d6a1fa7f112ee85dd84dc7002 Mon Sep 17 00:00:00 2001 From: akovylyaeva Date: Wed, 5 Nov 2025 15:15:36 +0500 Subject: [PATCH 2/9] fix: change reset password response in controller --- Api/Controllers/UsersController.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Api/Controllers/UsersController.cs b/Api/Controllers/UsersController.cs index 413fda5..d337d33 100644 --- a/Api/Controllers/UsersController.cs +++ b/Api/Controllers/UsersController.cs @@ -53,9 +53,30 @@ public async Task DeleteUserAsync([FromBody] DeletionModel deletio } [HttpPost("reset")] - public Task RegisterUser([FromQuery] string corporateEmail) + public async Task RegisterUser([FromQuery] string corporateEmail) { - return _usersService.ResetPasswordAsync(corporateEmail); + try + { + var token = await _usersService.ResetPasswordAsync(corporateEmail); + if (token != null) + { + return Ok(new + { + passwordResetToken = token + }); + } + else + { + return Ok(new + { + message = "Password reset link sent successfully" + }); + } + } + catch (Exception ex) + { + return Problem(ex.Message, null, (int)HttpStatusCode.BadRequest); + } } [HttpPut("change-password")] From 5dbce29abc3bbba91c264d72e1b34cb5ac0f6753 Mon Sep 17 00:00:00 2001 From: akovylyaeva Date: Tue, 11 Nov 2025 16:56:43 +0500 Subject: [PATCH 3/9] fix: fix karateDockerfile --- e2e/KarateDockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/KarateDockerfile b/e2e/KarateDockerfile index e3f5cfa..da646e6 100644 --- a/e2e/KarateDockerfile +++ b/e2e/KarateDockerfile @@ -1,11 +1,11 @@ -FROM openjdk:11-jre-slim +FROM eclipse-temurin:17-jre-noble RUN apt-get update && apt-get install -y curl RUN apt-get install -y unzip -RUN curl -o /karate.jar -L 'https://github.com/intuit/karate/releases/download/v1.4.1/karate-1.4.1.jar' +RUN curl -o /karate.jar -L 'https://github.com/intuit/karate/releases/download/v1.5.1/karate-1.5.1.jar' COPY ./e2e/js-utils.js . -ENTRYPOINT ["java", "-jar", "karate.jar"] +ENTRYPOINT ["java", "-jar", "karate.jar"] \ No newline at end of file From 67c67e52c1251746240bf8877a8331055bdacbd5 Mon Sep 17 00:00:00 2001 From: akovylyaeva Date: Fri, 14 Nov 2025 15:54:52 +0500 Subject: [PATCH 4/9] fix: add new endpoint instead of previous decision --- Api/Controllers/UsersController.cs | 35 +++++++++---------------- Api/Services/Models/PasswordSetModel.cs | 8 ++++++ Api/Services/UsersService.cs | 15 ++++++++++- 3 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 Api/Services/Models/PasswordSetModel.cs diff --git a/Api/Controllers/UsersController.cs b/Api/Controllers/UsersController.cs index d337d33..188d5d8 100644 --- a/Api/Controllers/UsersController.cs +++ b/Api/Controllers/UsersController.cs @@ -52,31 +52,20 @@ public async Task DeleteUserAsync([FromBody] DeletionModel deletio } } + // Add new endpoint to create a user for local env protected with auth and new permission + // CanCreateUserWithPasswordBypassingEmailConfirmation (can change) + [Authorize] + [RequiresPermission(UserClaimsProvider.CanSetUserPasswordBypassingEmailConfirmation)] + [HttpPut("set-password")] + public Task SetPassword([FromBody] PasswordSetModel passwordSetModel) + { + return _usersService.SetPasswordBypassingEmailConfirmationAsync(passwordSetModel); + } + [HttpPost("reset")] - public async Task RegisterUser([FromQuery] string corporateEmail) + public Task RegisterUser([FromQuery] string corporateEmail) { - try - { - var token = await _usersService.ResetPasswordAsync(corporateEmail); - if (token != null) - { - return Ok(new - { - passwordResetToken = token - }); - } - else - { - return Ok(new - { - message = "Password reset link sent successfully" - }); - } - } - catch (Exception ex) - { - return Problem(ex.Message, null, (int)HttpStatusCode.BadRequest); - } + return _usersService.ResetPasswordAsync(corporateEmail); } [HttpPut("change-password")] diff --git a/Api/Services/Models/PasswordSetModel.cs b/Api/Services/Models/PasswordSetModel.cs new file mode 100644 index 0000000..9949acd --- /dev/null +++ b/Api/Services/Models/PasswordSetModel.cs @@ -0,0 +1,8 @@ +namespace Api.Services.Models; + +public readonly struct PasswordSetModel +{ + public string CorporateEmail { get; init; } + + public string NewPassword { get; init; } +} \ No newline at end of file diff --git a/Api/Services/UsersService.cs b/Api/Services/UsersService.cs index 8e265e7..f7f4355 100644 --- a/Api/Services/UsersService.cs +++ b/Api/Services/UsersService.cs @@ -135,7 +135,20 @@ public async Task ChangePasswordAsync(PasswordChangeModel passwordChangeModel) await _userManager.ResetPasswordAsync(user, passwordChangeModel.PasswordResetToken, passwordChangeModel.NewPassword); - + } + + public async Task SetPasswordBypassingEmailConfirmationAsync(PasswordSetModel passwordSetModel) + { + var user = await _findUserQuery.FindUserByCorporateEmailAsync(passwordSetModel.CorporateEmail); + + if (user == null) + { + throw new NullReferenceException($"User with the corporate email [{passwordSetModel.CorporateEmail}] doesn't exists"); + } + + var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user); + + await _userManager.ResetPasswordAsync(user, resetToken, passwordSetModel.NewPassword); } } } \ No newline at end of file From 8b47679590e9dcafb2dbff1826f7f3866e2f9239 Mon Sep 17 00:00:00 2001 From: akovylyaeva Date: Fri, 14 Nov 2025 15:58:08 +0500 Subject: [PATCH 5/9] chore: remove IsLocalMode flag --- Api/Services/UsersService.cs | 14 ++------------ Api/ci/helmfile.yaml | 1 - 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Api/Services/UsersService.cs b/Api/Services/UsersService.cs index f7f4355..e56b390 100644 --- a/Api/Services/UsersService.cs +++ b/Api/Services/UsersService.cs @@ -17,7 +17,6 @@ public class UsersService private readonly IPasswordValidator _passwordValidator; private readonly UserBlockCommand _userBlockCommand; private readonly UserUnblockCommand _userUnblockCommand; - private readonly bool _isLocalMode; public UsersService( UserManager userManager, @@ -26,8 +25,7 @@ public UsersService( ILogger logger, IPasswordValidator passwordValidator, UserBlockCommand userBlockCommand, - UserUnblockCommand userUnblockCommand, - IConfiguration configuration) + UserUnblockCommand userUnblockCommand) { _userManager = userManager; _findUserQuery = findUserQuery; @@ -36,7 +34,6 @@ public UsersService( _passwordValidator = passwordValidator; _userBlockCommand = userBlockCommand; _userUnblockCommand = userUnblockCommand; - _isLocalMode = configuration.GetValue("IsLocalMode"); } public async Task RegisterAsync(RegistrationModel registrationModel) @@ -90,7 +87,7 @@ public async Task UnblockAsync(long accountId) await _userUnblockCommand.ExecuteAsync(accountId); } - public async Task ResetPasswordAsync(string corporateEmail) + public async Task ResetPasswordAsync(string corporateEmail) { var user = await _findUserQuery.FindUserByCorporateEmailAsync(corporateEmail); if (user == null) @@ -99,14 +96,7 @@ public async Task ResetPasswordAsync(string corporateEmail) } var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user); - - if (_isLocalMode) - { - return resetToken; - } - await _innerCircleHttpClient.SendPasswordResetLink(corporateEmail, resetToken); - return null!; } public async Task ChangePasswordAsync(PasswordChangeModel passwordChangeModel) diff --git a/Api/ci/helmfile.yaml b/Api/ci/helmfile.yaml index 8134ce8..a265268 100644 --- a/Api/ci/helmfile.yaml +++ b/Api/ci/helmfile.yaml @@ -23,4 +23,3 @@ releases: InnerCircleServiceUrls__AuthUIServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__AuthUIServiceUrl }}" InnerCircleServiceUrls__AccountsServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__AccountsServiceUrl }}" InnerCircleServiceUrls__EmployeesServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__EmployeesServiceUrl }}" - IsLocalMode: "{{ .StateValues.extraSecretEnvVars.IsLocalMode }}" \ No newline at end of file From 9707e2431e836789b5ea34918035f6d445a16ce7 Mon Sep 17 00:00:00 2001 From: akovylyaeva Date: Fri, 14 Nov 2025 15:59:14 +0500 Subject: [PATCH 6/9] chore: remove empty line --- Api/ci/helmfile.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/ci/helmfile.yaml b/Api/ci/helmfile.yaml index a265268..a9fbff2 100644 --- a/Api/ci/helmfile.yaml +++ b/Api/ci/helmfile.yaml @@ -22,4 +22,4 @@ releases: InnerCircleServiceUrls__MailServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__MailServiceUrl }}" InnerCircleServiceUrls__AuthUIServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__AuthUIServiceUrl }}" InnerCircleServiceUrls__AccountsServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__AccountsServiceUrl }}" - InnerCircleServiceUrls__EmployeesServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__EmployeesServiceUrl }}" + InnerCircleServiceUrls__EmployeesServiceUrl: "{{ .StateValues.extraSecretEnvVars.InnerCircleServiceUrls__EmployeesServiceUrl }}" \ No newline at end of file From ebc013dbe9e78ef08fde1a297f7b84866d405065 Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Tue, 18 Nov 2025 20:32:54 +0500 Subject: [PATCH 7/9] feat: add permission for can set user password by passing email confirmation --- Api/Controllers/UsersController.cs | 4 +--- Api/UserClaimsProvider.cs | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Api/Controllers/UsersController.cs b/Api/Controllers/UsersController.cs index 188d5d8..9915dcf 100644 --- a/Api/Controllers/UsersController.cs +++ b/Api/Controllers/UsersController.cs @@ -52,10 +52,8 @@ public async Task DeleteUserAsync([FromBody] DeletionModel deletio } } - // Add new endpoint to create a user for local env protected with auth and new permission - // CanCreateUserWithPasswordBypassingEmailConfirmation (can change) [Authorize] - [RequiresPermission(UserClaimsProvider.CanSetUserPasswordBypassingEmailConfirmation)] + [RequiresPermission(UserClaimsProvider.AUTO_TESTS_ONLY_IsSetUserPasswordBypassingEmailConfirmationAllowed)] [HttpPut("set-password")] public Task SetPassword([FromBody] PasswordSetModel passwordSetModel) { diff --git a/Api/UserClaimsProvider.cs b/Api/UserClaimsProvider.cs index f8c89d9..48a2f10 100644 --- a/Api/UserClaimsProvider.cs +++ b/Api/UserClaimsProvider.cs @@ -11,6 +11,8 @@ public class UserClaimsProvider : IUserClaimsProvider public const string IsAccountsHardDeleteAllowed = "IsAccountsHardDeleteAllowed"; + public const string AUTO_TESTS_ONLY_IsSetUserPasswordBypassingEmailConfirmationAllowed = "AUTO_TESTS_ONLY_IsSetUserPasswordBypassingEmailConfirmationAllowed"; + public Task> GetUserClaimsAsync(string login) { throw new NotImplementedException(); From 3aa5e4ac432b38d4f6e36fdc7627e3b0a61bca71 Mon Sep 17 00:00:00 2001 From: akovylyaeva Date: Wed, 19 Nov 2025 14:06:51 +0500 Subject: [PATCH 8/9] chore: change put to post request --- Api/Controllers/UsersController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Controllers/UsersController.cs b/Api/Controllers/UsersController.cs index 9915dcf..3732505 100644 --- a/Api/Controllers/UsersController.cs +++ b/Api/Controllers/UsersController.cs @@ -54,7 +54,7 @@ public async Task DeleteUserAsync([FromBody] DeletionModel deletio [Authorize] [RequiresPermission(UserClaimsProvider.AUTO_TESTS_ONLY_IsSetUserPasswordBypassingEmailConfirmationAllowed)] - [HttpPut("set-password")] + [HttpPost("set-password")] public Task SetPassword([FromBody] PasswordSetModel passwordSetModel) { return _usersService.SetPasswordBypassingEmailConfirmationAsync(passwordSetModel); From 6f8d06ba69f8c565a1f9f367524ebb8ecdbfe570 Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Wed, 19 Nov 2025 14:48:20 +0500 Subject: [PATCH 9/9] chore: change exception text --- Api/Services/UsersService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Services/UsersService.cs b/Api/Services/UsersService.cs index e56b390..708f244 100644 --- a/Api/Services/UsersService.cs +++ b/Api/Services/UsersService.cs @@ -133,7 +133,7 @@ public async Task SetPasswordBypassingEmailConfirmationAsync(PasswordSetModel pa if (user == null) { - throw new NullReferenceException($"User with the corporate email [{passwordSetModel.CorporateEmail}] doesn't exists"); + throw new NullReferenceException($"User with the corporate email [{passwordSetModel.CorporateEmail}] doesn't exist"); } var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);