final1
This commit is contained in:
@@ -36,7 +36,7 @@ namespace SecureBank.API.Authentication
|
||||
|
||||
#region METHODS
|
||||
|
||||
public string GenerateToken(Guid tokenId, int accountId, bool oneTimeToken = false)
|
||||
public string GenerateToken(Guid tokenId, Account account, bool oneTimeToken = false)
|
||||
{
|
||||
DateTime expirationTime = DateTime.UtcNow.AddMinutes(_configuration.TokenLifetime);
|
||||
|
||||
@@ -44,11 +44,13 @@ namespace SecureBank.API.Authentication
|
||||
{
|
||||
Subject = new ClaimsIdentity(new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Jti, tokenId.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Sub, accountId.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.ToString()),
|
||||
new Claim("jti", tokenId.ToString()),
|
||||
new Claim("uid", account.Id.ToString()),
|
||||
new Claim("first_name", account.FirstName),
|
||||
new Claim("last_name", account.LastName),
|
||||
new Claim("exp", expirationTime.ToString()),
|
||||
new Claim("one_time_token", oneTimeToken.ToString()),
|
||||
new Claim("admin", "false"), //TODO: w zależności od użytkownika
|
||||
new Claim("admin", account.IsAdmin.ToString()),
|
||||
}),
|
||||
Expires = expirationTime,
|
||||
Issuer = _configuration.TokenIssuer,
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Identity.Client;
|
||||
using SecureBank.API.Authentication;
|
||||
using SecureBank.API.Services;
|
||||
using SecureBank.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Common.Accounts;
|
||||
using SecureBank.Helpers.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
@@ -40,18 +46,17 @@ namespace SecureBank.API.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("create-account")]
|
||||
[AllowAnonymous]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[RequiresClaim("admin", "True")]
|
||||
public async Task<ActionResult<APIResponse<int>>> CreateAccount([FromBody] CreateAccountRequest data)
|
||||
{
|
||||
APIResponse<int> response = await _accountsService.CreateAccount(data);
|
||||
if (response.Success)
|
||||
return response.Status switch
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -60,35 +65,102 @@ namespace SecureBank.API.Controllers
|
||||
public async Task<ActionResult<APIResponse<GetPasswordVariantResponse>>> GetPasswordVariant([FromRoute(Name = "account_id")] int accountId)
|
||||
{
|
||||
APIResponse<GetPasswordVariantResponse> response = await _accountsService.GetPasswordVariant(accountId);
|
||||
if (response.Success)
|
||||
return response.Status switch
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{account_id}/authentication")]
|
||||
[Route("authentication")]
|
||||
[AllowAnonymous]
|
||||
/*
|
||||
* Action codes:
|
||||
* 1 - Go back to client code input
|
||||
* 2 - Failed login count increment
|
||||
* 2 - Change password required
|
||||
*/
|
||||
public async Task<ActionResult<APIResponse<string>>> Authentication([FromRoute(Name = "account_id")] int accountId, [FromBody] AuthenticationRequest data)
|
||||
public async Task<ActionResult<APIResponse<string>>> Authentication([FromBody] AuthenticationRequest data)
|
||||
{
|
||||
APIResponse<string> response = await _accountsService.Authentication(accountId, data);
|
||||
if (response.Success)
|
||||
APIResponse<string> response = await _accountsService.Authentication(data);
|
||||
return response.Status switch
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
else
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("authentication-refresh")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public async Task<ActionResult<APIResponse<string>>> AuthenticationRefresh()
|
||||
{
|
||||
APIResponse<string> response = await _accountsService.AuthenticationRefresh(new Claims(User.Claims));
|
||||
return response.Status switch
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Route("change-password")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public async Task<ActionResult<APIResponse>> ChangePassword([FromBody] ChangePasswordRequest data)
|
||||
{
|
||||
APIResponse response = await _accountsService.ChangePassword(new Claims(User.Claims), data);
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public async Task<ActionResult<APIResponse<IEnumerable<AccountResponse>>>> GetAccounts([FromQuery]int? id, [FromQuery] string? iban)
|
||||
{
|
||||
APIResponse<IEnumerable<AccountResponse>> response = await _accountsService.GetAccounts(iban, id, new Claims(User.Claims));
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Route("{account_id}/reset-password")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[RequiresClaim("admin", "True")]
|
||||
public async Task<ActionResult<APIResponse>> ResetPassword([FromRoute(Name = "account_id")] int accountId)
|
||||
{
|
||||
APIResponse response = await _accountsService.ResetPassword(accountId);
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Route("{account_id}/unlock")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[RequiresClaim("admin", "True")]
|
||||
public async Task<ActionResult<APIResponse>> UnlockAccount([FromRoute(Name = "account_id")] int accountId)
|
||||
{
|
||||
APIResponse response = await _accountsService.UnlockAccount(accountId);
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SecureBank.API.Services;
|
||||
using SecureBank.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Helpers.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/balance")]
|
||||
public class BalanceController : ControllerBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private IBalanceService _balanceService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public BalanceController(IBalanceService balanceService)
|
||||
{
|
||||
_balanceService = balanceService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
[HttpGet]
|
||||
[Route("{account_id}")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[RequiresClaim("admin", "True")]
|
||||
public async Task<ActionResult<APIResponse<decimal>>> GetAccountBalance([FromRoute(Name = "account_id")]int accountId)
|
||||
{
|
||||
APIResponse<decimal> response = await _balanceService.GetAccountBalance(accountId);
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public async Task<ActionResult<APIResponse<decimal>>> GetBalance()
|
||||
{
|
||||
APIResponse<decimal> response = await _balanceService.GetBalance(new Claims(User.Claims));
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Authentication\SecureBank.Authentication.csproj" />
|
||||
<ProjectReference Include="..\..\SecureBank.Common\SecureBank.Common.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Services\SecureBank.API.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
103
SecureBank.API/SecureBank.API.Controllers/TransfersController.cs
Normal file
103
SecureBank.API/SecureBank.API.Controllers/TransfersController.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SecureBank.API.Services;
|
||||
using SecureBank.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Common.Transfers;
|
||||
using SecureBank.Helpers.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace SecureBank.API.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/transfers")]
|
||||
public class TransfersController : ControllerBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private ITransfersService _transfersService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TransfersController(ITransfersService transfersService)
|
||||
{
|
||||
_transfersService = transfersService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public async Task<ActionResult<APIResponse<IEnumerable<TransferResponse>>>> GetTransfers()
|
||||
{
|
||||
APIResponse<IEnumerable<TransferResponse>> response = await _transfersService.GetTransfers(new Claims(User.Claims));
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("{account_id}")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[RequiresClaim("admin", "True")]
|
||||
public async Task<ActionResult<APIResponse<IEnumerable<TransferResponse>>>> GetUserTransfers([FromRoute(Name = "account_id")]int accountId)
|
||||
{
|
||||
APIResponse<IEnumerable<TransferResponse>> response = await _transfersService.GetUserTransfers(accountId);
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("admin-transfer")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[RequiresClaim("admin", "True")]
|
||||
public async Task<ActionResult<APIResponse>> CreateAdminTransfer([FromBody]CreateAdminTransferRequest data)
|
||||
{
|
||||
APIResponse response = await _transfersService.CreateAdminTransfer(data);
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("user-transfer")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public async Task<ActionResult<APIResponse>> CreateUserTransfer([FromBody] CreateUserTransferRequest data)
|
||||
{
|
||||
APIResponse response = await _transfersService.CreateUserTransfer(data, new Claims(User.Claims));
|
||||
return response.Status switch
|
||||
{
|
||||
ResponseStatus.Ok => Ok(response),
|
||||
ResponseStatus.BadRequest => BadRequest(response),
|
||||
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Encryption
|
||||
{
|
||||
public class EncryptionConfiguration
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public byte[] Key { get; private set; }
|
||||
public byte[] IV { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public EncryptionConfiguration(IConfiguration configuration)
|
||||
{
|
||||
Key = Encoding.UTF8.GetBytes(configuration.GetSection("Encryption")["Key"]);
|
||||
IV = Encoding.UTF8.GetBytes(configuration.GetSection("Encryption")["IV"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
75
SecureBank.API/SecureBank.API.Encryption/EncryptionHelper.cs
Normal file
75
SecureBank.API/SecureBank.API.Encryption/EncryptionHelper.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace SecureBank.API.Encryption
|
||||
{
|
||||
public class EncryptionHelper
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private EncryptionConfiguration _configuration;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private Aes _aes;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public EncryptionHelper(EncryptionConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
_aes = Aes.Create();
|
||||
_aes.Key = _configuration.Key;
|
||||
_aes.IV = _configuration.IV;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public byte[] Encrypt(string data)
|
||||
{
|
||||
ICryptoTransform encryptor = _aes.CreateEncryptor(_aes.Key, _aes.IV);
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
|
||||
using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
|
||||
{
|
||||
streamWriter.Write(data);
|
||||
}
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public string Decrypt(byte[] data)
|
||||
{
|
||||
ICryptoTransform decryptor = _aes.CreateDecryptor(_configuration.Key, _configuration.IV);
|
||||
using (MemoryStream memoryStream = new MemoryStream(data))
|
||||
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
byte[] outputBytes = new byte[data.Length];
|
||||
int decryptedByteCount = cryptoStream.Read(outputBytes, 0, outputBytes.Length);
|
||||
return Encoding.UTF8.GetString(outputBytes.Take(decryptedByteCount).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.Helpers.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class RequiresClaimAttribute : Attribute, IAuthorizationFilter
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private readonly string _claimName;
|
||||
private readonly string _claimValue;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public RequiresClaimAttribute(string claimName, string claimValue)
|
||||
{
|
||||
_claimName = claimName;
|
||||
_claimValue = claimValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
if (!context.HttpContext.User.HasClaim(_claimName, _claimValue))
|
||||
{
|
||||
context.Result = new ForbidResult();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,17 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Enums\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SecureBank.API.Helpers;
|
||||
using SecureBank.API.Authentication;
|
||||
using SecureBank.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Common.Accounts;
|
||||
using SecureBank.Database;
|
||||
@@ -15,6 +16,10 @@ using System.Runtime.Intrinsics.Arm;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SecureBank.API.Encryption;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Identity.Client;
|
||||
|
||||
namespace SecureBank.API.Services
|
||||
{
|
||||
@@ -22,7 +27,12 @@ namespace SecureBank.API.Services
|
||||
{
|
||||
Task<APIResponse<int>> CreateAccount(CreateAccountRequest data);
|
||||
Task<APIResponse<GetPasswordVariantResponse>> GetPasswordVariant(int accountId);
|
||||
Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data);
|
||||
Task<APIResponse<string>> Authentication(AuthenticationRequest data);
|
||||
Task<APIResponse<string>> AuthenticationRefresh(Claims claims);
|
||||
Task<APIResponse> ChangePassword(Claims claims, ChangePasswordRequest data);
|
||||
Task<APIResponse<IEnumerable<AccountResponse>>> GetAccounts(string? iban, int? id, Claims claims);
|
||||
Task<APIResponse> ResetPassword(int accountId);
|
||||
Task<APIResponse> UnlockAccount(int accountId);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +43,8 @@ namespace SecureBank.API.Services
|
||||
|
||||
private AuthenticationHelper _authenticationHelper;
|
||||
|
||||
private EncryptionHelper _encryptionHelper;
|
||||
|
||||
private DatabaseContext _database;
|
||||
|
||||
private ILogger<AccountsService> _logger;
|
||||
@@ -43,12 +55,11 @@ namespace SecureBank.API.Services
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AccountsService(AuthenticationHelper authenticationHelper, DatabaseContext database, ILogger<AccountsService> logger)
|
||||
public AccountsService(AuthenticationHelper authenticationHelper, EncryptionHelper encryptionHelper, DatabaseContext database, ILogger<AccountsService> logger)
|
||||
{
|
||||
_authenticationHelper = authenticationHelper;
|
||||
|
||||
_encryptionHelper = encryptionHelper;
|
||||
_database = database;
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -103,6 +114,31 @@ namespace SecureBank.API.Services
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.PhoneNumber)),
|
||||
Message = "Phone number cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.Address)),
|
||||
Message = "Address cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.PESEL)),
|
||||
Message = "PESEL cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => x.PESEL.Length != 11),
|
||||
Message = "PESEL must be 11 charaters long"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.IdCardNumber)),
|
||||
Message = "Id card number cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => x.IdCardNumber.Length != 9),
|
||||
Message = "Id card number must be 9 characters long"
|
||||
},
|
||||
};
|
||||
|
||||
foreach (Check<CreateAccountRequest> check in checks)
|
||||
@@ -112,21 +148,44 @@ namespace SecureBank.API.Services
|
||||
return new APIResponse<int>
|
||||
{
|
||||
Message = check.Message,
|
||||
Success = false
|
||||
Status = ResponseStatus.BadRequest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
byte[] pesel = _encryptionHelper.Encrypt(data.PESEL);
|
||||
byte[] idCardNumber = _encryptionHelper.Encrypt(data.IdCardNumber);
|
||||
byte[] cardCVV = _encryptionHelper.Encrypt(StringExtensions.CreateRandom(3, "1234567890"));
|
||||
byte[] cardExpirationDate = _encryptionHelper.Encrypt(DateTime.Now.AddYears(5).ToString("MM/yy"));
|
||||
|
||||
Account account = new Account
|
||||
{
|
||||
FirstName = data.FirstName,
|
||||
LastName = data.LastName,
|
||||
Email = data.Email,
|
||||
PhoneNumber = data.PhoneNumber.Replace(" ", string.Empty),
|
||||
Address = data.Address,
|
||||
PESEL = pesel,
|
||||
IdCardNumber = idCardNumber,
|
||||
IBAN = string.Empty,
|
||||
CardNumber = new byte[0],
|
||||
CardCVV = cardCVV,
|
||||
CardExpirationDate = cardExpirationDate
|
||||
};
|
||||
await _database.Accounts.AddAsync(account);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
|
||||
string ibanGen = $"549745{StringExtensions.CreateRandom(12, "1234567890")}{account.Id:00000000}";
|
||||
|
||||
string cardNumberGen = $"49{StringExtensions.CreateRandom(6, "1234567890")}{account.Id:00000000}";
|
||||
byte[] cardNumber = _encryptionHelper.Encrypt(cardNumberGen);
|
||||
|
||||
account.IBAN = ibanGen;
|
||||
account.CardNumber = cardNumber;
|
||||
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
string password = GeneratePassword();
|
||||
|
||||
await GeneratePasswordVariants(password, account.Id);
|
||||
@@ -134,10 +193,9 @@ namespace SecureBank.API.Services
|
||||
//Send client code and temporary password to client by mail
|
||||
_logger.LogInformation($"INFO DIRECTLY TO CLIENT: Your client code is {account.Id:00000000}. Your temporary password is {password}. You will be prompted to change it at first login");
|
||||
|
||||
return new APIResponse<int>
|
||||
return new APIResponse<int>
|
||||
{
|
||||
Data = account.Id,
|
||||
Success = true
|
||||
Data = account.Id
|
||||
};
|
||||
}
|
||||
|
||||
@@ -148,7 +206,7 @@ namespace SecureBank.API.Services
|
||||
{
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = false,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
@@ -157,7 +215,7 @@ namespace SecureBank.API.Services
|
||||
{
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = false,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"The number of failed login attempts for this account has exceeded 3. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
@@ -166,7 +224,7 @@ namespace SecureBank.API.Services
|
||||
{
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = false,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
@@ -188,7 +246,6 @@ namespace SecureBank.API.Services
|
||||
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = true,
|
||||
Data = new GetPasswordVariantResponse
|
||||
{
|
||||
LoginRequestId = loginRequest.Id,
|
||||
@@ -198,26 +255,15 @@ namespace SecureBank.API.Services
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data)
|
||||
public async Task<APIResponse<string>> Authentication(AuthenticationRequest data)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
AccountLoginRequest? loginRequest = await _database.AccountLoginRequests.FirstOrDefaultAsync(x => x.Id == data.LoginRequestId);
|
||||
|
||||
if (loginRequest is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Login request does not exist"
|
||||
};
|
||||
}
|
||||
@@ -226,33 +272,17 @@ namespace SecureBank.API.Services
|
||||
|
||||
Account loginRequestAccount = password.Account;
|
||||
|
||||
if (loginRequestAccount.Id != account.Id)
|
||||
APIResponse<string>? accountCheck = CheckAccount(loginRequestAccount);
|
||||
if (accountCheck is not null)
|
||||
{
|
||||
account.LockReason = "Suspicious login attempt. The account provided does not match the account to which the login request is assigned.";
|
||||
loginRequestAccount.LockReason = "Suspicious login attempt. The account provided does not match the account to which the login request is assigned.";
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Suspicious activity was detected during login. The account provided does not match the account to which the login request is assigned. Both accounts have been blocked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
if (account.LockReason is not null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
return accountCheck;
|
||||
}
|
||||
|
||||
if (loginRequest.ValidTo < DateTime.Now)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
ActionCode = 1,
|
||||
Message = $"Login request has expired. Go back and try again."
|
||||
};
|
||||
@@ -261,25 +291,199 @@ namespace SecureBank.API.Services
|
||||
byte[] passwordDb = password.Password;
|
||||
byte[] passwordProvided = HashPassword(data.Password, password.LeftSalt, password.RightSalt);
|
||||
|
||||
if (Enumerable.SequenceEqual(passwordDb, passwordProvided))
|
||||
if (!Enumerable.SequenceEqual(passwordDb, passwordProvided))
|
||||
{
|
||||
account.LoginFailedCount++;
|
||||
loginRequestAccount.LoginFailedCount++;
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
ActionCode = 2,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Incorrect password"
|
||||
};
|
||||
}
|
||||
|
||||
string token = _authenticationHelper.GenerateToken(Guid.NewGuid(), account.Id, account.TemporaryPassword);
|
||||
loginRequestAccount.LoginFailedCount = 0;
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
string token = _authenticationHelper.GenerateToken(Guid.NewGuid(), loginRequestAccount, loginRequestAccount.TemporaryPassword);
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
ActionCode = loginRequestAccount.TemporaryPassword ? 2 : 0,
|
||||
Data = token
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse<string>> AuthenticationRefresh(Claims claims)
|
||||
{
|
||||
if (claims.IsOneTimeToken)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"One time token cannot be refreshed."
|
||||
};
|
||||
}
|
||||
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == claims.AccountId);
|
||||
|
||||
APIResponse<string>? accountCheck = CheckAccount(account);
|
||||
if (accountCheck is not null)
|
||||
{
|
||||
return accountCheck;
|
||||
}
|
||||
|
||||
string token = _authenticationHelper.GenerateToken(Guid.NewGuid(), account, false);
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Data = token,
|
||||
Success = true,
|
||||
Data = token
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse> ChangePassword(Claims claims, ChangePasswordRequest data)
|
||||
{
|
||||
string password = data.Password;
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == claims.AccountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerable<string> passwordChecks = CheckPassword(password);
|
||||
if (passwordChecks.Any())
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("Provided password does not meet the security requirements:");
|
||||
foreach (string check in passwordChecks)
|
||||
{
|
||||
sb.AppendLine(check);
|
||||
}
|
||||
return new APIResponse
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = sb.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerable<AccountPasswordIndex> indexes = await _database.AccountPasswordIndexes.Where(x => x.AccountPassword.AccountId == claims.AccountId).ToListAsync();
|
||||
_database.AccountPasswordIndexes.AttachRange(indexes);
|
||||
_database.AccountPasswordIndexes.RemoveRange(indexes);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
IEnumerable<AccountPassword> variants = await _database.AccountPasswords.Where(x => x.AccountId == claims.AccountId).ToListAsync();
|
||||
_database.AccountPasswords.AttachRange(variants);
|
||||
_database.AccountPasswords.RemoveRange(variants);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
await GeneratePasswordVariants(password, claims.AccountId);
|
||||
|
||||
account.TemporaryPassword = false;
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse();
|
||||
}
|
||||
|
||||
public async Task<APIResponse<IEnumerable<AccountResponse>>> GetAccounts(string? iban, int? id, Claims claims)
|
||||
{
|
||||
IEnumerable<Account> accounts = await _database.Accounts.ToListAsync();
|
||||
if (id is not null)
|
||||
{
|
||||
accounts = accounts.Where(x => x.Id == id);
|
||||
}
|
||||
if (iban is not null)
|
||||
{
|
||||
accounts = accounts.Where(x => x.IBAN == iban);
|
||||
}
|
||||
|
||||
if (accounts.Any(x => x.Id != claims.AccountId) && !claims.IsAdmin)
|
||||
{
|
||||
return new APIResponse<IEnumerable<AccountResponse>>
|
||||
{
|
||||
Status = ResponseStatus.Unauthorized,
|
||||
Message = $"You don't have permission to get information about accounts that aren't yours"
|
||||
};
|
||||
}
|
||||
|
||||
List<AccountResponse> data = new List<AccountResponse>();
|
||||
foreach (Account account in accounts)
|
||||
{
|
||||
data.Add(new AccountResponse
|
||||
{
|
||||
Id = account.Id,
|
||||
FirstName = account.FirstName,
|
||||
LastName = account.LastName,
|
||||
Email = account.Email,
|
||||
PhoneNumber = account.PhoneNumber,
|
||||
Address = account.Address,
|
||||
PESEL = _encryptionHelper.Decrypt(account.PESEL),
|
||||
IdCardNumber = _encryptionHelper.Decrypt(account.IdCardNumber),
|
||||
IBAN = account.IBAN,
|
||||
CardNumber = _encryptionHelper.Decrypt(account.CardNumber),
|
||||
CardExpirationDate = _encryptionHelper.Decrypt(account.CardExpirationDate),
|
||||
CardCVV = _encryptionHelper.Decrypt(account.CardCVV),
|
||||
IsAdmin = account.IsAdmin,
|
||||
LoginFailedCount = account.LoginFailedCount,
|
||||
TemporaryPassword = account.TemporaryPassword,
|
||||
LockReason = account.LockReason,
|
||||
});
|
||||
}
|
||||
|
||||
return new APIResponse<IEnumerable<AccountResponse>>
|
||||
{
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse> ResetPassword(int accountId)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
await PasswordReset(account);
|
||||
|
||||
return new APIResponse<int>
|
||||
{
|
||||
Data = account.Id
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse> UnlockAccount(int accountId)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
await PasswordReset(account);
|
||||
|
||||
account.LockReason = null;
|
||||
account.LoginFailedCount = 0;
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse<int>
|
||||
{
|
||||
Data = account.Id
|
||||
};
|
||||
}
|
||||
|
||||
@@ -289,6 +493,60 @@ namespace SecureBank.API.Services
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected async Task PasswordReset(Account account)
|
||||
{
|
||||
IEnumerable<AccountPasswordIndex> indexes = await _database.AccountPasswordIndexes.Where(x => x.AccountPassword.AccountId == account.Id).ToListAsync();
|
||||
_database.AccountPasswordIndexes.AttachRange(indexes);
|
||||
_database.AccountPasswordIndexes.RemoveRange(indexes);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
IEnumerable<AccountPassword> variants = await _database.AccountPasswords.Where(x => x.AccountId == account.Id).ToListAsync();
|
||||
_database.AccountPasswords.AttachRange(variants);
|
||||
_database.AccountPasswords.RemoveRange(variants);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
string password = GeneratePassword();
|
||||
|
||||
await GeneratePasswordVariants(password, account.Id);
|
||||
|
||||
account.TemporaryPassword = true;
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation($"INFO DIRECTLY TO CLIENT: Your new temporary password is {password}. You will be prompted to change it at first login");
|
||||
}
|
||||
|
||||
protected APIResponse<string>? CheckAccount(Account? account)
|
||||
{
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account does not exists."
|
||||
};
|
||||
}
|
||||
|
||||
if (account.LockReason is not null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
if (account.LoginFailedCount >= 3)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = $"The number of failed login attempts for this account has exceeded 3. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected byte[] HashPassword(string password, string leftSalt, string rightSalt)
|
||||
{
|
||||
SHA512 sha = SHA512.Create();
|
||||
@@ -355,11 +613,16 @@ namespace SecureBank.API.Services
|
||||
protected IEnumerable<string> CheckPassword(string password)
|
||||
{
|
||||
int minLength = 8;
|
||||
uint maxLength = 20;
|
||||
|
||||
if (password.Length < minLength)
|
||||
{
|
||||
yield return $"Password must be at least {minLength} characters long";
|
||||
}
|
||||
if (password.Length > maxLength)
|
||||
{
|
||||
yield return $"Password cannot be longer than {maxLength} characters";
|
||||
}
|
||||
if (!password.Any(x => Char.IsUpper(x)))
|
||||
{
|
||||
yield return $"Password must contain at least one uppercase character";
|
||||
@@ -372,7 +635,7 @@ namespace SecureBank.API.Services
|
||||
{
|
||||
yield return $"Password must contain at least one digit";
|
||||
}
|
||||
if (!password.Any(x => Char.IsSymbol(x)))
|
||||
if (!password.Any(x => !Char.IsDigit(x) && !Char.IsUpper(x) && !Char.IsLower(x)))
|
||||
{
|
||||
yield return $"Password must contain at least one special character";
|
||||
}
|
||||
|
||||
81
SecureBank.API/SecureBank.API.Services/BalanceService.cs
Normal file
81
SecureBank.API/SecureBank.API.Services/BalanceService.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SecureBank.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Database;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Services
|
||||
{
|
||||
public interface IBalanceService
|
||||
{
|
||||
Task<APIResponse<decimal>> GetAccountBalance(int accountId);
|
||||
Task<APIResponse<decimal>> GetBalance(Claims claims);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class BalanceService : IBalanceService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private DatabaseContext _database;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public BalanceService(DatabaseContext database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<APIResponse<decimal>> GetBalance(Claims claims) => await GetAccountBalance(claims.AccountId);
|
||||
|
||||
public async Task<APIResponse<decimal>> GetAccountBalance(int accountId)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<decimal>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = "Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
string iban = account.IBAN;
|
||||
|
||||
Transfer[] transfersIncoming = await _database.Transfers.Where(x => x.ReceiverAccountNumber == iban).ToArrayAsync();
|
||||
Transfer[] transfersOutcoming = await _database.Transfers.Where(x => x.SenderAccountNumber == iban).ToArrayAsync();
|
||||
|
||||
return new APIResponse<decimal>
|
||||
{
|
||||
Data = 0 + transfersIncoming.Sum(x => x.Amount) - transfersOutcoming.Sum(x => x.Amount),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Authentication\SecureBank.Authentication.csproj" />
|
||||
<ProjectReference Include="..\..\SecureBank.Common\SecureBank.Common.csproj" />
|
||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||
<ProjectReference Include="..\..\SecureBank.Extensions\SecureBank.Extensions.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Authentication\SecureBank.API.Authentication.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Encryption\SecureBank.API.Encryption.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Helpers\SecureBank.API.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
250
SecureBank.API/SecureBank.API.Services/TransfersService.cs
Normal file
250
SecureBank.API/SecureBank.API.Services/TransfersService.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Identity.Client;
|
||||
using SecureBank.API.Helpers;
|
||||
using SecureBank.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Common.Accounts;
|
||||
using SecureBank.Common.Transfers;
|
||||
using SecureBank.Database;
|
||||
using SecureBank.Helpers.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Services
|
||||
{
|
||||
public interface ITransfersService
|
||||
{
|
||||
Task<APIResponse<IEnumerable<TransferResponse>>> GetTransfers(Claims claims);
|
||||
Task<APIResponse<IEnumerable<TransferResponse>>> GetUserTransfers(int accountId);
|
||||
Task<APIResponse> CreateAdminTransfer(CreateAdminTransferRequest data);
|
||||
Task<APIResponse> CreateUserTransfer(CreateUserTransferRequest data, Claims claims);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public class TransfersService : ITransfersService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private DatabaseContext _database;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TransfersService(DatabaseContext database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<APIResponse<IEnumerable<TransferResponse>>> GetTransfers(Claims claims) => await GetUserTransfers(claims.AccountId);
|
||||
public async Task<APIResponse<IEnumerable<TransferResponse>>> GetUserTransfers(int accountId)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<IEnumerable<TransferResponse>>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = "Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
string iban = account.IBAN;
|
||||
|
||||
List<TransferResponse> list = new List<TransferResponse>();
|
||||
await foreach (Transfer transfer in _database.Transfers.Where(x => x.SenderAccountNumber == iban || x.ReceiverAccountNumber == iban).AsAsyncEnumerable())
|
||||
{
|
||||
list.Add(new TransferResponse
|
||||
{
|
||||
Id = transfer.Id,
|
||||
SenderAccountNumber = transfer.SenderAccountNumber,
|
||||
SenderAddress = transfer.SenderAddress,
|
||||
SenderName = transfer.SenderName,
|
||||
ReceiverAccountNumber = transfer.ReceiverAccountNumber,
|
||||
ReceiverAddress = transfer.ReceiverAddress,
|
||||
ReceiverName = transfer.ReceiverName,
|
||||
Amount = transfer.Amount,
|
||||
Title = transfer.Title,
|
||||
Date = transfer.Date,
|
||||
});
|
||||
}
|
||||
|
||||
return new APIResponse<IEnumerable<TransferResponse>>
|
||||
{
|
||||
Data = list
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse> CreateAdminTransfer(CreateAdminTransferRequest data)
|
||||
{
|
||||
Check<CreateAdminTransferRequest>[] checks = new Check<CreateAdminTransferRequest>[]
|
||||
{
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => x is null),
|
||||
Message = "Body cannot be empty"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => x.SenderAccountNumber is null),
|
||||
Message = "Sender account number cannot be empty"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => !x.SenderAccountNumber.All(y => char.IsDigit(y))),
|
||||
Message = "Wrong sender account number format. Account number consists only of digits"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => x.SenderAccountNumber.Length != 26),
|
||||
Message = "Sender account number cannot be empty"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => x.ReceiverAccountNumber is null),
|
||||
Message = "Receiver account number cannot be empty"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => !x.ReceiverAccountNumber.All(y => char.IsDigit(y))),
|
||||
Message = "Wrong receiver account number format. Account number consists only of digits"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => x.ReceiverAccountNumber.Length != 26),
|
||||
Message = "Receiver account number cannot be empty"
|
||||
},
|
||||
new Check<CreateAdminTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAdminTransferRequest>((x) => x.Amount <= 0),
|
||||
Message = "Receiver account number cannot be empty"
|
||||
},
|
||||
};
|
||||
|
||||
foreach (Check<CreateAdminTransferRequest> check in checks)
|
||||
{
|
||||
if (check.CheckAction.Invoke(data))
|
||||
{
|
||||
return new APIResponse
|
||||
{
|
||||
Message = check.Message,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
data.Amount = Math.Round(data.Amount, 2, MidpointRounding.ToEven);
|
||||
|
||||
Transfer transfer = new Transfer
|
||||
{
|
||||
SenderAccountNumber = data.SenderAccountNumber,
|
||||
SenderAddress = data.SenderAddress,
|
||||
SenderName = data.SenderName,
|
||||
ReceiverAccountNumber = data.ReceiverAccountNumber,
|
||||
ReceiverAddress = data.ReceiverAddress,
|
||||
ReceiverName = data.ReceiverName,
|
||||
Amount = data.Amount,
|
||||
Title = data.Title,
|
||||
Date = DateTime.Now,
|
||||
};
|
||||
await _database.Transfers.AddAsync(transfer);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse();
|
||||
}
|
||||
|
||||
public async Task<APIResponse> CreateUserTransfer(CreateUserTransferRequest data, Claims claims)
|
||||
{
|
||||
Check<CreateUserTransferRequest>[] checks = new Check<CreateUserTransferRequest>[]
|
||||
{
|
||||
new Check<CreateUserTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateUserTransferRequest>((x) => x is null),
|
||||
Message = "Body cannot be empty"
|
||||
},
|
||||
new Check<CreateUserTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateUserTransferRequest>((x) => x.ReceiverAccountNumber is null),
|
||||
Message = "Receiver account number cannot be empty"
|
||||
},
|
||||
new Check<CreateUserTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateUserTransferRequest>((x) => !x.ReceiverAccountNumber.All(y => char.IsDigit(y))),
|
||||
Message = "Wrong receiver account number format. Account number consists only of digits"
|
||||
},
|
||||
new Check<CreateUserTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateUserTransferRequest>((x) => x.ReceiverAccountNumber.Length != 26),
|
||||
Message = "Receiver account number cannot be empty"
|
||||
},
|
||||
new Check<CreateUserTransferRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateUserTransferRequest>((x) => x.Amount <= 0),
|
||||
Message = "Receiver account number cannot be empty"
|
||||
},
|
||||
};
|
||||
|
||||
foreach (Check<CreateUserTransferRequest> check in checks)
|
||||
{
|
||||
if (check.CheckAction.Invoke(data))
|
||||
{
|
||||
return new APIResponse
|
||||
{
|
||||
Message = check.Message,
|
||||
Status = ResponseStatus.BadRequest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == claims.AccountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<IEnumerable<TransferResponse>>
|
||||
{
|
||||
Status = ResponseStatus.BadRequest,
|
||||
Message = "Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
data.Amount = Math.Round(data.Amount, 2, MidpointRounding.ToEven);
|
||||
|
||||
Transfer transfer = new Transfer
|
||||
{
|
||||
SenderAccountNumber = account.IBAN,
|
||||
SenderAddress = account.Address,
|
||||
SenderName = $"{account.FirstName} {account.LastName}",
|
||||
ReceiverAccountNumber = data.ReceiverAccountNumber,
|
||||
ReceiverAddress = data.ReceiverAddress,
|
||||
ReceiverName = data.ReceiverName,
|
||||
Amount = data.Amount,
|
||||
Title = data.Title,
|
||||
Date = DateTime.Now,
|
||||
};
|
||||
await _database.Transfers.AddAsync(transfer);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user