final1
This commit is contained in:
@@ -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