Skip to content

Commit

Permalink
Merge pull request #151 from soat-fiap/cognito_gateway
Browse files Browse the repository at this point in the history
Cognito gateway
  • Loading branch information
italopessoa authored Sep 22, 2024
2 parents 419f63e + 92d75a4 commit a656d94
Show file tree
Hide file tree
Showing 27 changed files with 531 additions and 24 deletions.
24 changes: 21 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ jobs:
environment: dev
permissions:
contents: read
outputs:
CONFIG_VERSION: ${{ steps.plan-upload.outputs.configuration_version_id }}

steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -215,8 +218,9 @@ jobs:
db_user = "${{ secrets.BMB_MYSQL_USER }}"
db_pwd = "${{ secrets.BMB_MYSQL_PASSWORD }}"
rds_cluster_identifier = "${{ vars.BMB_MYSQL_CLUSTER }}"
access_key_id = "${{ secrets.AWS_ACCESS_KEY_ID }}"
secret_access_key = "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
api_access_key_id = "${{ secrets.AWS_API_ACCESS_KEY_ID }}"
api_secret_access_key = "${{ secrets.AWS_API_SECRET_ACCESS_KEY }}"
user_pool_name = "${{ vars.BMB_USER_POOL_NAME }}"
EOF
- name: Upload Configuration
Expand All @@ -239,4 +243,18 @@ jobs:
id: apply
with:
run: ${{ steps.apply-run.outputs.run_id }}
comment: "Confirmed from GitHub Actions CI ${{ github.sha }}"
comment: "Confirmed from GitHub Actions CI ${{ github.sha }}"

destroy-plan:
name: "Create terraform destroy plan"
needs: [create-app]
runs-on: ubuntu-latest

steps:
- name: Create plan
uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.1
id: destroy-plan
with:
workspace: ${{ env.TF_WORKSPACE }}
configuration_version: ${{ NEEDS.create-app.outputs.CONFIG_VERSION }}
is_destroy: true
5 changes: 3 additions & 2 deletions .github/workflows/terraform-plan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ jobs:
db_user = "${{ secrets.BMB_MYSQL_USER }}"
db_pwd = "${{ secrets.BMB_MYSQL_PASSWORD }}"
rds_cluster_identifier = "${{ vars.BMB_MYSQL_CLUSTER }}"
access_key_id = "${{ secrets.AWS_ACCESS_KEY_ID }}"
secret_access_key = "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
api_access_key_id = "${{ secrets.AWS_API_ACCESS_KEY_ID }}"
api_secret_access_key = "${{ secrets.AWS_API_SECRET_ACCESS_KEY }}"
user_pool_name = "${{ vars.BMB_USER_POOL_NAME }}"
EOF
- name: Upload Configuration
Expand Down
Binary file added FIAP.TechChallenge-P3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions FIAP.TechChallenge.ByteMeBurger.sln
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "terraform", "terraform", "{
tf\variables.tf = tf\variables.tf
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway", "src\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.csproj", "{36FD200F-16AB-44A6-A103-E5EABD6EA263}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test", "tests\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test.csproj", "{46B22CD7-BB91-4E80-870F-28DA476ED6A3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -118,6 +122,8 @@ Global
{9E8287AF-C7A6-46A6-96D8-8460642ADE4B} = {5EC029C6-230A-4782-9B62-CB475C7D05F8}
{93545102-0476-48CB-8074-5C71AC81E040} = {C244E1D6-E1EB-4314-9F39-BB59FA1F7C71}
{B2898AA3-86A6-4AF1-B27F-A3706B2B0557} = {2F192BA8-59FC-4B0C-B59D-511EFA89F428}
{36FD200F-16AB-44A6-A103-E5EABD6EA263} = {5EC029C6-230A-4782-9B62-CB475C7D05F8}
{46B22CD7-BB91-4E80-870F-28DA476ED6A3} = {C244E1D6-E1EB-4314-9F39-BB59FA1F7C71}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B8F244B5-F703-441C-8A8A-C720F605709C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -188,5 +194,13 @@ Global
{93545102-0476-48CB-8074-5C71AC81E040}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93545102-0476-48CB-8074-5C71AC81E040}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93545102-0476-48CB-8074-5C71AC81E040}.Release|Any CPU.Build.0 = Release|Any CPU
{36FD200F-16AB-44A6-A103-E5EABD6EA263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36FD200F-16AB-44A6-A103-E5EABD6EA263}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36FD200F-16AB-44A6-A103-E5EABD6EA263}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36FD200F-16AB-44A6-A103-E5EABD6EA263}.Release|Any CPU.Build.0 = Release|Any CPU
{46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
8 changes: 8 additions & 0 deletions src/FIAP.TechChallenge.ByteMeBurger.Api/Auth/BmbRoles.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FIAP.TechChallenge.ByteMeBurger.Api.Auth;

public static class BmbRoles
{
public const string Admin = "admin";
public const string Kitchen = "kitchen";
public const string Customer = "customer";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using FIAP.TechChallenge.ByteMeBurger.Api.Auth;
using FIAP.TechChallenge.ByteMeBurger.Api.Model.Customers;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Contracts;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Dto;
Expand All @@ -12,7 +13,6 @@ namespace FIAP.TechChallenge.ByteMeBurger.Api.Controllers;
/// </summary>
/// <param name="customerService">Customer service (port implementation).</param>
/// <param name="logger">Logger</param>
[Obsolete("To be migrated to different project")]
[Route("api/[controller]")]
[Produces("application/json")]
[Consumes("application/json")]
Expand All @@ -28,7 +28,7 @@ public class CustomersController(ICustomerService customerService, ILogger<Custo
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Customer</returns>
[HttpGet]
[Authorize(Roles = "admin")]
[Authorize(Roles = BmbRoles.Admin)]
public async Task<ActionResult<CustomerDto>> Get([FromQuery] [MaxLength(14)] string cpf,
CancellationToken cancellationToken)
{
Expand All @@ -51,6 +51,7 @@ public async Task<ActionResult<CustomerDto>> Get([FromQuery] [MaxLength(14)] str
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Customer</returns>
[HttpPost]
[Authorize(Roles = BmbRoles.Customer)]
public async Task<ActionResult<CustomerDto>> Post([FromBody] CreateCustomerRequest createCustomerRequest,
CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public async Task<ActionResult<NewOrderDto>> Post(
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Orders list</returns>
[HttpGet]
[Authorize(Roles = $"{BmbRoles.Admin},{BmbRoles.Kitchen}")]
public async Task<ActionResult<ReadOnlyCollection<OrderListItemDto>>> Get(bool listAll,
CancellationToken cancellationToken)
{
Expand All @@ -71,6 +72,7 @@ public async Task<ActionResult<ReadOnlyCollection<OrderListItemDto>>> Get(bool l
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Order details</returns>
[HttpGet("{id:guid}")]
[Authorize(Roles = $"{BmbRoles.Admin},{BmbRoles.Kitchen}")]
public async Task<ActionResult<OrderDetailDto>> Get(Guid id, CancellationToken cancellationToken)
{
logger.LogInformation("Getting order with ID: {OrderId}", id);
Expand Down Expand Up @@ -98,6 +100,7 @@ public async Task<ActionResult<OrderDetailDto>> Get(Guid id, CancellationToken c
/// <param name="cancellationToken">Cancellation token</param>
[Route("{id:guid}/status")]
[HttpPatch]
[Authorize(Roles = $"{BmbRoles.Admin}")]
public async Task<ActionResult<OrderDetailDto>> Patch(Guid id,
[FromBody] UpdateOrderStatusRequest command,
CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using FIAP.TechChallenge.ByteMeBurger.Api.Auth;
using FIAP.TechChallenge.ByteMeBurger.Api.Model.Payment;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Contracts;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Dto;
Expand All @@ -14,7 +15,7 @@ namespace FIAP.TechChallenge.ByteMeBurger.Api.Controllers;
[ApiConventionType(typeof(DefaultApiConventions))]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize]
[Authorize(Roles = BmbRoles.Admin)]
public class PaymentsController : ControllerBase
{
private readonly IPaymentService _paymentService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using FIAP.TechChallenge.ByteMeBurger.Api.Auth;
using FIAP.TechChallenge.ByteMeBurger.Api.Model.Products;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Contracts;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Dto;
Expand All @@ -17,7 +18,7 @@ namespace FIAP.TechChallenge.ByteMeBurger.Api.Controllers;
[ApiConventionType(typeof(DefaultApiConventions))]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize]
[Authorize(Roles = BmbRoles.Admin)]
public class ProductsController(IProductService productService, ILogger<ProductsController> logger)
: ControllerBase
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public DomainEventsHandler(ILogger<DomainEventsHandler> logger, HybridCache cach
private void OnCustomerRegistered(object? sender, CustomerRegistered e)
{
_logger.LogInformation("New Customer registered: {@Customer}", e.Payload);
_logger.LogInformation("Sending email to customer: {CustomerName}", e.Payload.Name);
_publisher.PublishAsync(e).ConfigureAwait(false);
}

private void OnOrderStatusChanged(object? sender, OrderStatusChanged e)
Expand Down Expand Up @@ -78,6 +78,7 @@ private void OnProductDeleted(object? sender, ProductDeleted e)
private void OnProductCreated(object? sender, ProductCreated e)
{
_logger.LogInformation("Product created: {@Product}", e.Payload);
_publisher.PublishAsync(e).ConfigureAwait(false);
}

private void OnPaymentCreated(object? sender, PaymentCreated e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
"ClientSecret": "",
"ClientId": ""
},
"CognitoSettings": {
"UserPoolId": "",
"UserPoolClientId": "",
"Enabled": true,
"Region": "us-east-1",
"ClientSecret": "",
"ClientId": ""
},
"JwtOptions": {
"Issuer": "https://localhost:7000",
"Audience": "https://localhost:7000",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Amazon;
using Amazon.CognitoIdentityProvider;
using Amazon.Runtime;
using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
using Microsoft.Extensions.Options;

namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;

public class CognitoClientFactory(IOptions<CognitoSettings> settings) : ICognitoClientFactory
{
public IAmazonCognitoIdentityProvider CreateClient()
{
return new AmazonCognitoIdentityProviderClient(
new BasicAWSCredentials(settings.Value.ClientId, settings.Value.ClientSecret),
RegionEndpoint.GetBySystemName(settings.Value.Region));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;

/// <summary>
/// Cognito User Pool settings
/// </summary>
public class CognitoSettings
{
/// <summary>
/// User Pool Id
/// </summary>
public string UserPoolId { get; set; } = string.Empty;


/// <summary>
/// Client Id
/// </summary>
public string UserPoolClientId { get; set; } = string.Empty;

/// <summary>
/// Enabled
/// </summary>
public bool Enabled { get; set; } = false;

/// <summary>
/// AWS Region
/// </summary>
public string Region { get; set; } = string.Empty;

/// <summary>
/// AWS Secret Id
/// </summary>
public string ClientSecret { get; set; } = string.Empty;

/// <summary>
/// AWS Client Id
/// </summary>
public string ClientId { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System.Security.Cryptography;
using FIAP.TechChallenge.ByteMeBurger.Domain.Entities;
using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
using FIAP.TechChallenge.ByteMeBurger.Domain.Base;
using Microsoft.Extensions.Options;

namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;

public class CognitoUserManager : ICustomerRepository
{
private readonly IAmazonCognitoIdentityProvider _cognitoClient;
private readonly string _userPoolId;
private readonly string _clientId;

public CognitoUserManager(ICognitoClientFactory cognitoClientFactory, IOptions<CognitoSettings> settings)
{
_cognitoClient = cognitoClientFactory.CreateClient();
_userPoolId = settings.Value.UserPoolId;
_clientId = settings.Value.UserPoolClientId;
}

public async Task<Customer?> FindByCpfAsync(string cpf)
{
try
{
var response = await _cognitoClient.AdminGetUserAsync(new AdminGetUserRequest
{
UserPoolId = _userPoolId,
Username = cpf
});

var email = response.UserAttributes.First(attr => attr.Name == "email").Value;
var name = response.UserAttributes.First(attr => attr.Name == "name").Value;
var sub = response.UserAttributes.First(attr => attr.Name == "sub").Value;
var customer = new Customer(Guid.Parse(sub), cpf, name, email);

return customer;
}
catch (UserNotFoundException)
{
return null;
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching user: {ex.Message}");
throw;
}
}

public async Task<Customer> CreateAsync(Customer customer)
{
try
{
var signUpResponse = await _cognitoClient.AdminCreateUserAsync(new AdminCreateUserRequest()
{
Username = customer.Cpf,
UserPoolId = _userPoolId,
UserAttributes =
{
new AttributeType { Name = "email", Value = customer.Email },
new AttributeType { Name = "name", Value = customer.Name }
}
});

customer.Id = Guid.Parse(signUpResponse.User.Attributes.First(a=>a.Name is "sub").Value);
return customer;
}
catch (UsernameExistsException ex)
{
Console.WriteLine($"Error registering user: {ex.Message}");
throw new DomainException("There's already a customer using the provided CPF value.");
}
catch (Exception ex)
{
Console.WriteLine($"Error registering user: {ex.Message}");
throw;
}
}

private static string GenerateRandomPassword(int length)
{
using var rng = RandomNumberGenerator.Create();
var characterSets = new[]
{
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"1234567890",
"!@#$%^&*()"
};
var allChars = string.Concat(characterSets);
var passwordChars = new char[length];
// Ensure the password contains at least one character from each character set
for (int i = 0; i < characterSets.Length && i < length; i++)
{
passwordChars[i] = GetRandomChar(characterSets[i], rng);
}
// Fill the rest of the password with random characters
for (int i = characterSets.Length; i < length; i++)
{
passwordChars[i] = GetRandomChar(allChars, rng);
}
// Shuffle the password to ensure randomness
passwordChars = passwordChars.OrderBy(_ => GetRandomInt(rng, int.MaxValue)).ToArray();
return new string(passwordChars);
}
private static char GetRandomChar(string chars, RandomNumberGenerator rng)
{
var bytes = new byte[4];
rng.GetBytes(bytes);
var index = BitConverter.ToUInt32(bytes, 0) % chars.Length;
return chars[(int)index];
}
private static int GetRandomInt(RandomNumberGenerator rng, int max)
{
var bytes = new byte[4];
rng.GetBytes(bytes);
return (int)(BitConverter.ToUInt32(bytes, 0) % max);
}
}
Loading

0 comments on commit a656d94

Please sign in to comment.