final1
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -398,4 +398,4 @@ FodyWeavers.xsd
|
|||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
|
||||||
# SQLite database
|
# SQLite database
|
||||||
*.db
|
database.db
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
USER app
|
USER app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 443
|
EXPOSE 8080
|
||||||
EXPOSE 80
|
|
||||||
ENV ASPNETCORE_URLS=https://+:443;http://+:80
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
@@ -15,9 +13,10 @@ COPY ["SecureBank.API/SecureBank.API.Controllers/SecureBank.API.Controllers.cspr
|
|||||||
COPY ["SecureBank.Common/SecureBank.Common.csproj", "SecureBank.Common/"]
|
COPY ["SecureBank.Common/SecureBank.Common.csproj", "SecureBank.Common/"]
|
||||||
COPY ["SecureBank.API/SecureBank.API.Services/SecureBank.API.Services.csproj", "SecureBank.API/SecureBank.API.Services/"]
|
COPY ["SecureBank.API/SecureBank.API.Services/SecureBank.API.Services.csproj", "SecureBank.API/SecureBank.API.Services/"]
|
||||||
COPY ["SecureBank.Extensions/SecureBank.Extensions.csproj", "SecureBank.Extensions/"]
|
COPY ["SecureBank.Extensions/SecureBank.Extensions.csproj", "SecureBank.Extensions/"]
|
||||||
|
COPY ["SecureBank.Authentication/SecureBank.Authentication.csproj", "SecureBank.Authentication/"]
|
||||||
COPY ["SecureBank.API/SecureBank.API.Helpers/SecureBank.API.Helpers.csproj", "SecureBank.API/SecureBank.API.Helpers/"]
|
COPY ["SecureBank.API/SecureBank.API.Helpers/SecureBank.API.Helpers.csproj", "SecureBank.API/SecureBank.API.Helpers/"]
|
||||||
|
COPY ["SecureBank.API/SecureBank.API.Encryption/SecureBank.API.Encryption.csproj", "SecureBank.API/SecureBank.API.Encryption/"]
|
||||||
COPY ["SecureBank.Website/SecureBank.Website.Authentication/SecureBank.Website.Authentication.csproj", "SecureBank.Website/SecureBank.Website.Authentication/"]
|
COPY ["SecureBank.Website/SecureBank.Website.Authentication/SecureBank.Website.Authentication.csproj", "SecureBank.Website/SecureBank.Website.Authentication/"]
|
||||||
COPY ["SecureBank.Helpers/SecureBank.Helpers.csproj", "SecureBank.Helpers/"]
|
|
||||||
COPY ["SecureBank.Website/SecureBank.Website.Services/SecureBank.Website.Services.csproj", "SecureBank.Website/SecureBank.Website.Services/"]
|
COPY ["SecureBank.Website/SecureBank.Website.Services/SecureBank.Website.Services.csproj", "SecureBank.Website/SecureBank.Website.Services/"]
|
||||||
COPY ["SecureBank.Website/SecureBank.Website.API/SecureBank.Website.API.csproj", "SecureBank.Website/SecureBank.Website.API/"]
|
COPY ["SecureBank.Website/SecureBank.Website.API/SecureBank.Website.API.csproj", "SecureBank.Website/SecureBank.Website.API/"]
|
||||||
RUN dotnet restore "./SecureBank/./SecureBank.csproj"
|
RUN dotnet restore "./SecureBank/./SecureBank.csproj"
|
||||||
@@ -32,4 +31,6 @@ RUN dotnet publish "./SecureBank.csproj" -c $BUILD_CONFIGURATION -o /app/publish
|
|||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
|
COPY database-default.db database.db
|
||||||
|
USER root
|
||||||
ENTRYPOINT ["dotnet", "SecureBank.dll"]
|
ENTRYPOINT ["dotnet", "SecureBank.dll"]
|
||||||
6
Nginx.Dockerfile
Normal file
6
Nginx.Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
ARG KEY_PASSWORD=default
|
||||||
|
|
||||||
|
COPY ./Nginx/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY ./Nginx/localhost.crt /etc/ssl/certs/localhost.crt
|
||||||
|
COPY ./Nginx/localhost.key /etc/ssl/private/localhost.key
|
||||||
31
Nginx/localhost.conf
Normal file
31
Nginx/localhost.conf
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
default_keyfile = localhost.key
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
req_extensions = req_ext
|
||||||
|
x509_extensions = v3_ca
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
countryName = Country Name (2 letter code)
|
||||||
|
countryName_default = PL
|
||||||
|
stateOrProvinceName = State or Province Name (full name)
|
||||||
|
stateOrProvinceName_default = Mazowieckie
|
||||||
|
localityName = Locality Name (eg, city)
|
||||||
|
localityName_default = Warszawa
|
||||||
|
organizationName = Organization Name (eg, company)
|
||||||
|
organizationName_default = localhost
|
||||||
|
organizationalUnitName = organizationalunit
|
||||||
|
organizationalUnitName_default = Production
|
||||||
|
commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||||
|
commonName_default = localhost
|
||||||
|
commonName_max = 64
|
||||||
|
|
||||||
|
[req_ext]
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[v3_ca]
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = localhost
|
||||||
|
DNS.2 = 127.0.0.1
|
||||||
22
Nginx/localhost.crt
Normal file
22
Nginx/localhost.crt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDtjCCAp6gAwIBAgIUcyGfC/bnYYhO8GSeVSiRc7Kkr2IwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwczELMAkGA1UEBhMCUEwxFDASBgNVBAgMC01hem93aWVja2llMREwDwYDVQQH
|
||||||
|
DAhXYXJzemF3YTESMBAGA1UECgwJbG9jYWxob3N0MRMwEQYDVQQLDApQcm9kdWN0
|
||||||
|
aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjQwMTE5MTgyNzIwWhcNMjUwMTE4
|
||||||
|
MTgyNzIwWjBzMQswCQYDVQQGEwJQTDEUMBIGA1UECAwLTWF6b3dpZWNraWUxETAP
|
||||||
|
BgNVBAcMCFdhcnN6YXdhMRIwEAYDVQQKDAlsb2NhbGhvc3QxEzARBgNVBAsMClBy
|
||||||
|
b2R1Y3Rpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||||
|
ggEPADCCAQoCggEBAO7Pf3zgx31E2BDEAvCAzxBHnHaKSQqXaoOGFlltJ0jkDxFb
|
||||||
|
Vyowjmt3VEX2k91QX+W1hguzaRJlxmNpL6IAhvidWywu9NG8TjwkW8CifvT71rL5
|
||||||
|
c0jioSnbbxPUgQAeS6bcNW3RlL9umBQOVOet6ELnecq2iAkriPjT6Hpf6HXKXxUM
|
||||||
|
kDnk4dYNKhp78l8tqeZp4kW90PWg2zOaw129ZwSZ+EIUgVlqBKzq0/f9JDxQoTNE
|
||||||
|
ynZ4HRYveIfJtWLLG2s2i7pUBUrPCk1YzquN+mXfhCkjcBT7awwu+R4/1umVCrIy
|
||||||
|
DaVEq0aJzfXJ3D1wxa5zdp3z5/V0Pa2JicTXxB0CAwEAAaNCMEAwHwYDVR0RBBgw
|
||||||
|
FoIJbG9jYWxob3N0ggkxMjcuMC4wLjEwHQYDVR0OBBYEFEbrV6mArhT6VoJjvyvZ
|
||||||
|
sDhNY+OjMA0GCSqGSIb3DQEBCwUAA4IBAQAw0pYip/FamfiioGpg+QQ96Ef4zSOB
|
||||||
|
/tRC6nVob4YAO+mpim3LsT0JKS82Xe8ylZ2OgQ28/KQDdNffpnojgVR5Q15e/HqB
|
||||||
|
TK3aWUJ4vgFzvZ6mDhXTMOLXG9gLncxQM9YuyfOMm6ru3BNW7l2vPPPP+GQYHhkg
|
||||||
|
lIVv64Z0kHR5iNJRg2Ji6kR8tELd3EckxvVAMqwvsreMVTzej9bvR6OW65mGNmFE
|
||||||
|
qaG8CqZqysk7uzfE3kDfgN4Z6p7eAnILkPxFBGt4iCQ/N5vgF9TDe0smcoRvm/ie
|
||||||
|
Ekg0Y45ozFz4Tu/yHv4sKs5U69knz6fUu15GOCWk+EMUkFu+h0wPJtEA
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
Nginx/localhost.key
Normal file
28
Nginx/localhost.key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDuz3984Md9RNgQ
|
||||||
|
xALwgM8QR5x2ikkKl2qDhhZZbSdI5A8RW1cqMI5rd1RF9pPdUF/ltYYLs2kSZcZj
|
||||||
|
aS+iAIb4nVssLvTRvE48JFvAon70+9ay+XNI4qEp228T1IEAHkum3DVt0ZS/bpgU
|
||||||
|
DlTnrehC53nKtogJK4j40+h6X+h1yl8VDJA55OHWDSoae/JfLanmaeJFvdD1oNsz
|
||||||
|
msNdvWcEmfhCFIFZagSs6tP3/SQ8UKEzRMp2eB0WL3iHybViyxtrNou6VAVKzwpN
|
||||||
|
WM6rjfpl34QpI3AU+2sMLvkeP9bplQqyMg2lRKtGic31ydw9cMWuc3ad8+f1dD2t
|
||||||
|
iYnE18QdAgMBAAECggEABQkvIQ0EZ+nZvJDFlxjARRGdhQppe+Wxg1CCjCQ3Hdxh
|
||||||
|
TPZmIief+Tgs+MS4XYRiYU+wofKIlrC6vEHtlTb5oah/0bCzzPBxnecOYEOM9dGR
|
||||||
|
t3h3K+RLjbxo3Twv9DavVbdzEPvIbTkBz/p1Y+VQ7og/Ez1CJR9b5hNh5aK8Hi+5
|
||||||
|
37v1m2jtCWu9aMcz6SFQbi0o4qN3ZCKK9qlTbr6AIoFJosIOsMS6szoNcYNIYyxl
|
||||||
|
V4MogBZ0IGU+nC9GTb8XOsfUd2c8lStOMEhrc2F1RzwgnQiUF4W2n04L7yZ32X92
|
||||||
|
v8cJQq44wLdU64KFqzKQGfQvISqFLpb08HjNv+WWqQKBgQD6np8dzRvC4E+8Xapz
|
||||||
|
ablPGuvOdsFQNIUF99xXVVpRhZg6XSsZvPOClgolNUmkG0B2kbsHWmsczc8MCBjD
|
||||||
|
/0PC2vMeD/AImiaoayFu4dDysgWId9iVT7OUM72+spKH3zox8TTvSBx+qd9xUHec
|
||||||
|
PIv5KzIY58m9ZwPFHjUR4m7xRwKBgQDz7/mcCQdHRxnk73XcQeqeUPC6j5HMxq2Y
|
||||||
|
/jDh1OcugJiABBa0hH1Gc1uOnAl4Q74yDryD5cjLJasS8u5aC3ea5yEdCyPlpTGT
|
||||||
|
+XGWp9tJhOkza2v9NNSYC9CU90XCor+SIhf4oK4FicFmH0/PD0ypl5QnAFM5ilaf
|
||||||
|
77Oj12rxewKBgAk2VXD5/iA+sI+i/cX6R+aBfdN8CAUTuSQMBVxsdcJzX7IdhJ0Q
|
||||||
|
lf7h1wOhHtDac5coKjDOAQvxAMBXd9mUg4AhHjinq1IVoIAmV/dEc7LIGm32wc5T
|
||||||
|
PK2g7UOaOsqlyPTXAfQduXZqdh0rMQpcK2UAUnoZ4w+EPt47CwZaRWu3AoGBAJNv
|
||||||
|
BRQzuLxx0sq1mCyJgn4xOW3ofByiMCX57B1yCk/m1UT6M6bjNLwx2LJ2yJpxhzDG
|
||||||
|
C1ZEuXleyOjo/vpZV+69pxvgsc+IalCfQPHnffgPZsr3MAuXtK445dZDJVyf9N0j
|
||||||
|
0g0pQESEn+lTH29sNc+Cl76CycpLPFPqEk8CUdalAoGAFGkmys1mo83b3Rcbqkmh
|
||||||
|
Gl0LSKYwbbO+rJWS39NyXP/iI5Aykrx0v8N7/wTa/XWPtAD3MzQ04GAcG/VcqhDp
|
||||||
|
fMQURYBnRJlvctWxrTTBnBqlbDTM39i/wGqDt5CPLICT17rPFjWfUGrRX0sdsND1
|
||||||
|
ZMXdlsU48M++9GCs6VFs4ks=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
43
Nginx/nginx.conf
Normal file
43
Nginx/nginx.conf
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
events { worker_connections 1024; }
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
upstream securebank {
|
||||||
|
server securebank-website:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
ssl_certificate /etc/ssl/certs/localhost.crt;
|
||||||
|
ssl_certificate_key /etc/ssl/private/localhost.key;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://securebank;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection keep-alive;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $server_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ namespace SecureBank.API.Authentication
|
|||||||
|
|
||||||
#region METHODS
|
#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);
|
DateTime expirationTime = DateTime.UtcNow.AddMinutes(_configuration.TokenLifetime);
|
||||||
|
|
||||||
@@ -44,11 +44,13 @@ namespace SecureBank.API.Authentication
|
|||||||
{
|
{
|
||||||
Subject = new ClaimsIdentity(new List<Claim>
|
Subject = new ClaimsIdentity(new List<Claim>
|
||||||
{
|
{
|
||||||
new Claim(JwtRegisteredClaimNames.Jti, tokenId.ToString()),
|
new Claim("jti", tokenId.ToString()),
|
||||||
new Claim(JwtRegisteredClaimNames.Sub, accountId.ToString()),
|
new Claim("uid", account.Id.ToString()),
|
||||||
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.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("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,
|
Expires = expirationTime,
|
||||||
Issuer = _configuration.TokenIssuer,
|
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;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.Identity.Client;
|
using Microsoft.Identity.Client;
|
||||||
|
using SecureBank.API.Authentication;
|
||||||
using SecureBank.API.Services;
|
using SecureBank.API.Services;
|
||||||
|
using SecureBank.Authentication;
|
||||||
using SecureBank.Common;
|
using SecureBank.Common;
|
||||||
using SecureBank.Common.Accounts;
|
using SecureBank.Common.Accounts;
|
||||||
|
using SecureBank.Helpers.Attributes;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
@@ -40,18 +46,17 @@ namespace SecureBank.API.Controllers
|
|||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("create-account")]
|
[Route("create-account")]
|
||||||
[AllowAnonymous]
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[RequiresClaim("admin", "True")]
|
||||||
public async Task<ActionResult<APIResponse<int>>> CreateAccount([FromBody] CreateAccountRequest data)
|
public async Task<ActionResult<APIResponse<int>>> CreateAccount([FromBody] CreateAccountRequest data)
|
||||||
{
|
{
|
||||||
APIResponse<int> response = await _accountsService.CreateAccount(data);
|
APIResponse<int> response = await _accountsService.CreateAccount(data);
|
||||||
if (response.Success)
|
return response.Status switch
|
||||||
{
|
{
|
||||||
return Ok(response);
|
ResponseStatus.Ok => Ok(response),
|
||||||
}
|
ResponseStatus.BadRequest => BadRequest(response),
|
||||||
else
|
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||||
{
|
};
|
||||||
return BadRequest(response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -60,35 +65,102 @@ namespace SecureBank.API.Controllers
|
|||||||
public async Task<ActionResult<APIResponse<GetPasswordVariantResponse>>> GetPasswordVariant([FromRoute(Name = "account_id")] int accountId)
|
public async Task<ActionResult<APIResponse<GetPasswordVariantResponse>>> GetPasswordVariant([FromRoute(Name = "account_id")] int accountId)
|
||||||
{
|
{
|
||||||
APIResponse<GetPasswordVariantResponse> response = await _accountsService.GetPasswordVariant(accountId);
|
APIResponse<GetPasswordVariantResponse> response = await _accountsService.GetPasswordVariant(accountId);
|
||||||
if (response.Success)
|
return response.Status switch
|
||||||
{
|
{
|
||||||
return Ok(response);
|
ResponseStatus.Ok => Ok(response),
|
||||||
}
|
ResponseStatus.BadRequest => BadRequest(response),
|
||||||
else
|
ResponseStatus.Unauthorized => Unauthorized(response),
|
||||||
{
|
};
|
||||||
return BadRequest(response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{account_id}/authentication")]
|
[Route("authentication")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
/*
|
/*
|
||||||
* Action codes:
|
* Action codes:
|
||||||
* 1 - Go back to client code input
|
* 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);
|
APIResponse<string> response = await _accountsService.Authentication(data);
|
||||||
if (response.Success)
|
return response.Status switch
|
||||||
{
|
{
|
||||||
return Ok(response);
|
ResponseStatus.Ok => Ok(response),
|
||||||
}
|
ResponseStatus.BadRequest => BadRequest(response),
|
||||||
else
|
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
|
#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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\SecureBank.Authentication\SecureBank.Authentication.csproj" />
|
||||||
<ProjectReference Include="..\..\SecureBank.Common\SecureBank.Common.csproj" />
|
<ProjectReference Include="..\..\SecureBank.Common\SecureBank.Common.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.API.Services\SecureBank.API.Services.csproj" />
|
<ProjectReference Include="..\SecureBank.API.Services\SecureBank.API.Services.csproj" />
|
||||||
</ItemGroup>
|
</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>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Enums\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SecureBank.API.Helpers;
|
using SecureBank.API.Helpers;
|
||||||
using SecureBank.API.Authentication;
|
using SecureBank.API.Authentication;
|
||||||
|
using SecureBank.Authentication;
|
||||||
using SecureBank.Common;
|
using SecureBank.Common;
|
||||||
using SecureBank.Common.Accounts;
|
using SecureBank.Common.Accounts;
|
||||||
using SecureBank.Database;
|
using SecureBank.Database;
|
||||||
@@ -15,6 +16,10 @@ using System.Runtime.Intrinsics.Arm;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SecureBank.API.Encryption;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.Identity.Client;
|
||||||
|
|
||||||
namespace SecureBank.API.Services
|
namespace SecureBank.API.Services
|
||||||
{
|
{
|
||||||
@@ -22,7 +27,12 @@ namespace SecureBank.API.Services
|
|||||||
{
|
{
|
||||||
Task<APIResponse<int>> CreateAccount(CreateAccountRequest data);
|
Task<APIResponse<int>> CreateAccount(CreateAccountRequest data);
|
||||||
Task<APIResponse<GetPasswordVariantResponse>> GetPasswordVariant(int accountId);
|
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 AuthenticationHelper _authenticationHelper;
|
||||||
|
|
||||||
|
private EncryptionHelper _encryptionHelper;
|
||||||
|
|
||||||
private DatabaseContext _database;
|
private DatabaseContext _database;
|
||||||
|
|
||||||
private ILogger<AccountsService> _logger;
|
private ILogger<AccountsService> _logger;
|
||||||
@@ -43,12 +55,11 @@ namespace SecureBank.API.Services
|
|||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
public AccountsService(AuthenticationHelper authenticationHelper, DatabaseContext database, ILogger<AccountsService> logger)
|
public AccountsService(AuthenticationHelper authenticationHelper, EncryptionHelper encryptionHelper, DatabaseContext database, ILogger<AccountsService> logger)
|
||||||
{
|
{
|
||||||
_authenticationHelper = authenticationHelper;
|
_authenticationHelper = authenticationHelper;
|
||||||
|
_encryptionHelper = encryptionHelper;
|
||||||
_database = database;
|
_database = database;
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +114,31 @@ namespace SecureBank.API.Services
|
|||||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.PhoneNumber)),
|
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.PhoneNumber)),
|
||||||
Message = "Phone number cannot be empty"
|
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)
|
foreach (Check<CreateAccountRequest> check in checks)
|
||||||
@@ -112,21 +148,44 @@ namespace SecureBank.API.Services
|
|||||||
return new APIResponse<int>
|
return new APIResponse<int>
|
||||||
{
|
{
|
||||||
Message = check.Message,
|
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
|
Account account = new Account
|
||||||
{
|
{
|
||||||
FirstName = data.FirstName,
|
FirstName = data.FirstName,
|
||||||
LastName = data.LastName,
|
LastName = data.LastName,
|
||||||
Email = data.Email,
|
Email = data.Email,
|
||||||
PhoneNumber = data.PhoneNumber.Replace(" ", string.Empty),
|
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.Accounts.AddAsync(account);
|
||||||
await _database.SaveChangesAsync();
|
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();
|
string password = GeneratePassword();
|
||||||
|
|
||||||
await GeneratePasswordVariants(password, account.Id);
|
await GeneratePasswordVariants(password, account.Id);
|
||||||
@@ -136,8 +195,7 @@ namespace SecureBank.API.Services
|
|||||||
|
|
||||||
return new APIResponse<int>
|
return new APIResponse<int>
|
||||||
{
|
{
|
||||||
Data = account.Id,
|
Data = account.Id
|
||||||
Success = true
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +206,7 @@ namespace SecureBank.API.Services
|
|||||||
{
|
{
|
||||||
return new APIResponse<GetPasswordVariantResponse>
|
return new APIResponse<GetPasswordVariantResponse>
|
||||||
{
|
{
|
||||||
Success = false,
|
Status = ResponseStatus.BadRequest,
|
||||||
Message = $"Account does not exists"
|
Message = $"Account does not exists"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -157,7 +215,7 @@ namespace SecureBank.API.Services
|
|||||||
{
|
{
|
||||||
return new APIResponse<GetPasswordVariantResponse>
|
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."
|
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>
|
return new APIResponse<GetPasswordVariantResponse>
|
||||||
{
|
{
|
||||||
Success = false,
|
Status = ResponseStatus.BadRequest,
|
||||||
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
|
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>
|
return new APIResponse<GetPasswordVariantResponse>
|
||||||
{
|
{
|
||||||
Success = true,
|
|
||||||
Data = new GetPasswordVariantResponse
|
Data = new GetPasswordVariantResponse
|
||||||
{
|
{
|
||||||
LoginRequestId = loginRequest.Id,
|
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);
|
AccountLoginRequest? loginRequest = await _database.AccountLoginRequests.FirstOrDefaultAsync(x => x.Id == data.LoginRequestId);
|
||||||
|
|
||||||
if (loginRequest is null)
|
if (loginRequest is null)
|
||||||
{
|
{
|
||||||
return new APIResponse<string>
|
return new APIResponse<string>
|
||||||
{
|
{
|
||||||
Success = false,
|
Status = ResponseStatus.BadRequest,
|
||||||
Message = $"Login request does not exist"
|
Message = $"Login request does not exist"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -226,33 +272,17 @@ namespace SecureBank.API.Services
|
|||||||
|
|
||||||
Account loginRequestAccount = password.Account;
|
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.";
|
return accountCheck;
|
||||||
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."
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginRequest.ValidTo < DateTime.Now)
|
if (loginRequest.ValidTo < DateTime.Now)
|
||||||
{
|
{
|
||||||
return new APIResponse<string>
|
return new APIResponse<string>
|
||||||
{
|
{
|
||||||
Success = false,
|
Status = ResponseStatus.BadRequest,
|
||||||
ActionCode = 1,
|
ActionCode = 1,
|
||||||
Message = $"Login request has expired. Go back and try again."
|
Message = $"Login request has expired. Go back and try again."
|
||||||
};
|
};
|
||||||
@@ -261,25 +291,199 @@ namespace SecureBank.API.Services
|
|||||||
byte[] passwordDb = password.Password;
|
byte[] passwordDb = password.Password;
|
||||||
byte[] passwordProvided = HashPassword(data.Password, password.LeftSalt, password.RightSalt);
|
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();
|
await _database.SaveChangesAsync();
|
||||||
|
|
||||||
return new APIResponse<string>
|
return new APIResponse<string>
|
||||||
{
|
{
|
||||||
Success = false,
|
Status = ResponseStatus.BadRequest,
|
||||||
ActionCode = 2,
|
|
||||||
Message = $"Incorrect password"
|
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>
|
return new APIResponse<string>
|
||||||
{
|
{
|
||||||
Data = token,
|
ActionCode = loginRequestAccount.TemporaryPassword ? 2 : 0,
|
||||||
Success = true,
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
#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)
|
protected byte[] HashPassword(string password, string leftSalt, string rightSalt)
|
||||||
{
|
{
|
||||||
SHA512 sha = SHA512.Create();
|
SHA512 sha = SHA512.Create();
|
||||||
@@ -355,11 +613,16 @@ namespace SecureBank.API.Services
|
|||||||
protected IEnumerable<string> CheckPassword(string password)
|
protected IEnumerable<string> CheckPassword(string password)
|
||||||
{
|
{
|
||||||
int minLength = 8;
|
int minLength = 8;
|
||||||
|
uint maxLength = 20;
|
||||||
|
|
||||||
if (password.Length < minLength)
|
if (password.Length < minLength)
|
||||||
{
|
{
|
||||||
yield return $"Password must be at least {minLength} characters long";
|
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)))
|
if (!password.Any(x => Char.IsUpper(x)))
|
||||||
{
|
{
|
||||||
yield return $"Password must contain at least one uppercase character";
|
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";
|
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";
|
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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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.Common\SecureBank.Common.csproj" />
|
||||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||||
<ProjectReference Include="..\..\SecureBank.Extensions\SecureBank.Extensions.csproj" />
|
<ProjectReference Include="..\..\SecureBank.Extensions\SecureBank.Extensions.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.API.Authentication\SecureBank.API.Authentication.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" />
|
<ProjectReference Include="..\SecureBank.API.Helpers\SecureBank.API.Helpers.csproj" />
|
||||||
</ItemGroup>
|
</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
|
||||||
|
}
|
||||||
|
}
|
||||||
41
SecureBank.Authentication/Claims.cs
Normal file
41
SecureBank.Authentication/Claims.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Authentication
|
||||||
|
{
|
||||||
|
public class Claims
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public Guid Id { get; protected set; }
|
||||||
|
public int AccountId { get; protected set; }
|
||||||
|
public string FirstName { get; protected set; }
|
||||||
|
public string LastName { get; protected set; }
|
||||||
|
public DateTime ExpirationTime { get; protected set; }
|
||||||
|
public bool IsOneTimeToken { get; protected set; }
|
||||||
|
public bool IsAdmin { get; protected set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public Claims(IEnumerable<Claim> claims)
|
||||||
|
{
|
||||||
|
Id = Guid.Parse(claims.Where(x => x.Type == "jti").First().Value);
|
||||||
|
AccountId = int.Parse(claims.Where(x => x.Type == "uid").First().Value);
|
||||||
|
FirstName = claims.Where(x => x.Type == "first_name").First().Value;
|
||||||
|
LastName = claims.Where(x => x.Type == "last_name").First().Value;
|
||||||
|
ExpirationTime = new DateTime(long.Parse(claims.Where(x => x.Type == "exp").First().Value));
|
||||||
|
IsOneTimeToken = bool.Parse(claims.Where(x => x.Type == "one_time_token").First().Value);
|
||||||
|
IsAdmin = bool.Parse(claims.Where(x => x.Type == "admin").First().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,21 +12,21 @@ namespace SecureBank.Common
|
|||||||
{
|
{
|
||||||
[JsonProperty("message")]
|
[JsonProperty("message")]
|
||||||
[JsonPropertyName("message")]
|
[JsonPropertyName("message")]
|
||||||
public string Message { get; set; }
|
public string? Message { get; set; }
|
||||||
|
|
||||||
[JsonProperty("success")]
|
[JsonProperty("status")]
|
||||||
[JsonPropertyName("success")]
|
[JsonPropertyName("status")]
|
||||||
public bool Success { get; set; }
|
public ResponseStatus Status { get; set; } = ResponseStatus.Ok;
|
||||||
|
|
||||||
[JsonProperty("action_code")]
|
[JsonProperty("action_code")]
|
||||||
[JsonPropertyName("action_code")]
|
[JsonPropertyName("action_code")]
|
||||||
public int ActionCode { get; set; }
|
public int? ActionCode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class APIResponse<T> : APIResponse
|
public class APIResponse<T> : APIResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("data")]
|
[JsonProperty("data")]
|
||||||
[JsonPropertyName("data")]
|
[JsonPropertyName("data")]
|
||||||
public T Data { get; set; }
|
public T? Data { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
SecureBank.Common/Accounts/AccountResponse.cs
Normal file
78
SecureBank.Common/Accounts/AccountResponse.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Common.Accounts
|
||||||
|
{
|
||||||
|
public class AccountResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("first_name")]
|
||||||
|
[JsonPropertyName("first_name")]
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("last_name")]
|
||||||
|
[JsonPropertyName("last_name")]
|
||||||
|
public string LastName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("email")]
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("phone_number")]
|
||||||
|
[JsonPropertyName("phone_number")]
|
||||||
|
public string PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("address")]
|
||||||
|
[JsonPropertyName("address")]
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("pesel")]
|
||||||
|
[JsonPropertyName("pesel")]
|
||||||
|
public string PESEL { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("id_card_number")]
|
||||||
|
[JsonPropertyName("id_card_number")]
|
||||||
|
public string IdCardNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("iban")]
|
||||||
|
[JsonPropertyName("iban")]
|
||||||
|
public string IBAN { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("card_number")]
|
||||||
|
[JsonPropertyName("card_number")]
|
||||||
|
public string CardNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("card_expiration_date")]
|
||||||
|
[JsonPropertyName("card_expiration_date")]
|
||||||
|
public string CardExpirationDate { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("card_cvv")]
|
||||||
|
[JsonPropertyName("card_cvv")]
|
||||||
|
public string CardCVV { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("is_admin")]
|
||||||
|
[JsonPropertyName("is_admin")]
|
||||||
|
public bool IsAdmin { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("login_failed_count")]
|
||||||
|
[JsonPropertyName("login_failed_count")]
|
||||||
|
public int LoginFailedCount { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("temporary_password")]
|
||||||
|
[JsonPropertyName("temporary_password")]
|
||||||
|
public bool TemporaryPassword { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("lock_reason")]
|
||||||
|
[JsonPropertyName("lock_reason")]
|
||||||
|
public string? LockReason { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
SecureBank.Common/Accounts/ChangePasswordRequest.cs
Normal file
17
SecureBank.Common/Accounts/ChangePasswordRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Common.Accounts
|
||||||
|
{
|
||||||
|
public class ChangePasswordRequest
|
||||||
|
{
|
||||||
|
[JsonProperty("password")]
|
||||||
|
[JsonPropertyName("password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,5 +25,17 @@ namespace SecureBank.Common.Accounts
|
|||||||
[JsonProperty("phone_number")]
|
[JsonProperty("phone_number")]
|
||||||
[JsonPropertyName("phone_number")]
|
[JsonPropertyName("phone_number")]
|
||||||
public string PhoneNumber { get; set; }
|
public string PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("address")]
|
||||||
|
[JsonPropertyName("address")]
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("pesel")]
|
||||||
|
[JsonPropertyName("pesel")]
|
||||||
|
public string PESEL { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("id_card_number")]
|
||||||
|
[JsonPropertyName("id_card_number")]
|
||||||
|
public string IdCardNumber { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
SecureBank.Common/ResponseStatus.cs
Normal file
15
SecureBank.Common/ResponseStatus.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Common
|
||||||
|
{
|
||||||
|
public enum ResponseStatus
|
||||||
|
{
|
||||||
|
Ok,
|
||||||
|
Unauthorized,
|
||||||
|
BadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
45
SecureBank.Common/Transfers/CreateAdminTransferRequest.cs
Normal file
45
SecureBank.Common/Transfers/CreateAdminTransferRequest.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Common.Transfers
|
||||||
|
{
|
||||||
|
public class CreateAdminTransferRequest
|
||||||
|
{
|
||||||
|
[JsonProperty("sender_account_number")]
|
||||||
|
[JsonPropertyName("sender_account_number")]
|
||||||
|
public string SenderAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sender_name")]
|
||||||
|
[JsonPropertyName("sender_name")]
|
||||||
|
public string? SenderName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sender_address")]
|
||||||
|
[JsonPropertyName("sender_address")]
|
||||||
|
public string? SenderAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_account_number")]
|
||||||
|
[JsonPropertyName("receiver_account_number")]
|
||||||
|
public string ReceiverAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_name")]
|
||||||
|
[JsonPropertyName("receiver_name")]
|
||||||
|
public string? ReceiverName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_address")]
|
||||||
|
[JsonPropertyName("receiver_address")]
|
||||||
|
public string? ReceiverAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("title")]
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("amount")]
|
||||||
|
[JsonPropertyName("amount")]
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
34
SecureBank.Common/Transfers/CreateUserTransferRequest.cs
Normal file
34
SecureBank.Common/Transfers/CreateUserTransferRequest.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Common.Transfers
|
||||||
|
{
|
||||||
|
public class CreateUserTransferRequest
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonProperty("receiver_account_number")]
|
||||||
|
[JsonPropertyName("receiver_account_number")]
|
||||||
|
public string ReceiverAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_name")]
|
||||||
|
[JsonPropertyName("receiver_name")]
|
||||||
|
public string? ReceiverName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_address")]
|
||||||
|
[JsonPropertyName("receiver_address")]
|
||||||
|
public string? ReceiverAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("title")]
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("amount")]
|
||||||
|
[JsonPropertyName("amount")]
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
54
SecureBank.Common/Transfers/TransferResponse.cs
Normal file
54
SecureBank.Common/Transfers/TransferResponse.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Common.Transfers
|
||||||
|
{
|
||||||
|
public class TransferResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sender_account_number")]
|
||||||
|
[JsonPropertyName("sender_account_number")]
|
||||||
|
public string SenderAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sender_name")]
|
||||||
|
[JsonPropertyName("sender_name")]
|
||||||
|
public string? SenderName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sender_address")]
|
||||||
|
[JsonPropertyName("sender_address")]
|
||||||
|
public string? SenderAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_account_number")]
|
||||||
|
[JsonPropertyName("receiver_account_number")]
|
||||||
|
public string ReceiverAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_name")]
|
||||||
|
[JsonPropertyName("receiver_name")]
|
||||||
|
public string? ReceiverName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("receiver_address")]
|
||||||
|
[JsonPropertyName("receiver_address")]
|
||||||
|
public string? ReceiverAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("amount")]
|
||||||
|
[JsonPropertyName("amount")]
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("title")]
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("date")]
|
||||||
|
[JsonPropertyName("date")]
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,37 @@ namespace SecureBank.Database
|
|||||||
[MaxLength(20)]
|
[MaxLength(20)]
|
||||||
public string PhoneNumber { get; set; }
|
public string PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public byte[] PESEL { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public byte[] IdCardNumber { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(26)]
|
||||||
|
public string IBAN { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(32)]
|
||||||
|
public byte[] CardNumber { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public byte[] CardExpirationDate { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public byte[] CardCVV { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool IsAdmin { get; set; } = false;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public byte LoginFailedCount { get; set; } = 0;
|
public byte LoginFailedCount { get; set; } = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace SecureBank.Database
|
|||||||
public virtual DbSet<AccountPassword> AccountPasswords { get; set; }
|
public virtual DbSet<AccountPassword> AccountPasswords { get; set; }
|
||||||
public virtual DbSet<AccountPasswordIndex> AccountPasswordIndexes { get; set; }
|
public virtual DbSet<AccountPasswordIndex> AccountPasswordIndexes { get; set; }
|
||||||
public virtual DbSet<AccountLoginRequest> AccountLoginRequests { get; set; }
|
public virtual DbSet<AccountLoginRequest> AccountLoginRequests { get; set; }
|
||||||
|
public virtual DbSet<Transfer> Transfers { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using SecureBank.Database;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace SecureBank.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DatabaseContext))]
|
|
||||||
[Migration("20240114165546_Migration1")]
|
|
||||||
partial class Migration1
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.Account", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte>("LoginFailedCount")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("TemporaryPassword")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Accounts");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPassword", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("AccountId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("LeftSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Password")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(1000)
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("RightSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AccountId");
|
|
||||||
|
|
||||||
b.ToTable("AccountPasswords");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPasswordIndex", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("AccountPasswordId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<byte>("Index")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasIndex("AccountPasswordId");
|
|
||||||
|
|
||||||
b.ToTable("AccountPasswordIndexes");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPassword", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.Account", "Account")
|
|
||||||
.WithMany("AccountPasswords")
|
|
||||||
.HasForeignKey("AccountId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Account");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPasswordIndex", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AccountPasswordId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("AccountPassword");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.Account", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("AccountPasswords");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using SecureBank.Database;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace SecureBank.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DatabaseContext))]
|
|
||||||
[Migration("20240114170227_Migration2")]
|
|
||||||
partial class Migration2
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.Account", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte>("LoginFailedCount")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("TemporaryPassword")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Accounts");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPassword", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("AccountId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("LeftSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Password")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(1000)
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("RightSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AccountId");
|
|
||||||
|
|
||||||
b.ToTable("AccountPasswords");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPasswordIndex", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("AccountPasswordId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<byte>("Index")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AccountPasswordId");
|
|
||||||
|
|
||||||
b.ToTable("AccountPasswordIndexes");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPassword", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.Account", "Account")
|
|
||||||
.WithMany("AccountPasswords")
|
|
||||||
.HasForeignKey("AccountId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Account");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPasswordIndex", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AccountPasswordId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("AccountPassword");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.Account", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("AccountPasswords");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace SecureBank.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class Migration2 : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<long>(
|
|
||||||
name: "Id",
|
|
||||||
table: "AccountPasswordIndexes",
|
|
||||||
type: "INTEGER",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: 0L)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddPrimaryKey(
|
|
||||||
name: "PK_AccountPasswordIndexes",
|
|
||||||
table: "AccountPasswordIndexes",
|
|
||||||
column: "Id");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropPrimaryKey(
|
|
||||||
name: "PK_AccountPasswordIndexes",
|
|
||||||
table: "AccountPasswordIndexes");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Id",
|
|
||||||
table: "AccountPasswordIndexes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using SecureBank.Database;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace SecureBank.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DatabaseContext))]
|
|
||||||
[Migration("20240115084527_Migration3")]
|
|
||||||
partial class Migration3
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.Account", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte>("LoginFailedCount")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("TemporaryPassword")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Accounts");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountLoginRequest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("AccountPasswordId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("ValidTo")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AccountPasswordId");
|
|
||||||
|
|
||||||
b.ToTable("AccountLoginRequests");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPassword", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("AccountId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("LeftSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Password")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(1000)
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("RightSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AccountId");
|
|
||||||
|
|
||||||
b.ToTable("AccountPasswords");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPasswordIndex", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("AccountPasswordId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<byte>("Index")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AccountPasswordId");
|
|
||||||
|
|
||||||
b.ToTable("AccountPasswordIndexes");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountLoginRequest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AccountPasswordId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("AccountPassword");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPassword", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.Account", "Account")
|
|
||||||
.WithMany("AccountPasswords")
|
|
||||||
.HasForeignKey("AccountId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Account");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountPasswordIndex", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AccountPasswordId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("AccountPassword");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.Account", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("AccountPasswords");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace SecureBank.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class Migration3 : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "AccountLoginRequests",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
|
||||||
AccountPasswordId = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
ValidTo = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_AccountLoginRequests", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_AccountLoginRequests_AccountPasswords_AccountPasswordId",
|
|
||||||
column: x => x.AccountPasswordId,
|
|
||||||
principalTable: "AccountPasswords",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_AccountLoginRequests_AccountPasswordId",
|
|
||||||
table: "AccountLoginRequests",
|
|
||||||
column: "AccountPasswordId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "AccountLoginRequests");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace SecureBank.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class Migration4 : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "LockReason",
|
|
||||||
table: "Accounts",
|
|
||||||
type: "TEXT",
|
|
||||||
maxLength: 1000,
|
|
||||||
nullable: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "LockReason",
|
|
||||||
table: "Accounts");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,8 @@ using SecureBank.Database;
|
|||||||
namespace SecureBank.Database.Migrations
|
namespace SecureBank.Database.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DatabaseContext))]
|
[DbContext(typeof(DatabaseContext))]
|
||||||
[Migration("20240115132220_Migration4")]
|
[Migration("20240122225115_Migration1")]
|
||||||
partial class Migration4
|
partial class Migration1
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@@ -26,6 +26,26 @@ namespace SecureBank.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CardCVV")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CardExpirationDate")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CardNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(300)
|
.HasMaxLength(300)
|
||||||
@@ -36,6 +56,19 @@ namespace SecureBank.Database.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("IBAN")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<byte[]>("IdCardNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAdmin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
b.Property<string>("LastName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
@@ -48,6 +81,11 @@ namespace SecureBank.Database.Migrations
|
|||||||
b.Property<byte>("LoginFailedCount")
|
b.Property<byte>("LoginFailedCount")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("PESEL")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
b.Property<string>("PhoneNumber")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(20)
|
.HasMaxLength(20)
|
||||||
@@ -130,6 +168,55 @@ namespace SecureBank.Database.Migrations
|
|||||||
b.ToTable("AccountPasswordIndexes");
|
b.ToTable("AccountPasswordIndexes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SecureBank.Database.Transfer", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(14, 2)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReceiverAccountNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReceiverAddress")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReceiverName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderAccountNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderAddress")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Transfers");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountLoginRequest", b =>
|
modelBuilder.Entity("SecureBank.Database.AccountLoginRequest", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
@@ -20,14 +21,43 @@ namespace SecureBank.Database.Migrations
|
|||||||
LastName = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
LastName = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||||
Email = table.Column<string>(type: "TEXT", maxLength: 300, nullable: false),
|
Email = table.Column<string>(type: "TEXT", maxLength: 300, nullable: false),
|
||||||
PhoneNumber = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
|
PhoneNumber = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
|
||||||
|
Address = table.Column<string>(type: "TEXT", maxLength: 500, nullable: false),
|
||||||
|
PESEL = table.Column<byte[]>(type: "BLOB", maxLength: 16, nullable: false),
|
||||||
|
IdCardNumber = table.Column<byte[]>(type: "BLOB", maxLength: 16, nullable: false),
|
||||||
|
IBAN = table.Column<string>(type: "TEXT", maxLength: 26, nullable: false),
|
||||||
|
CardNumber = table.Column<byte[]>(type: "BLOB", maxLength: 32, nullable: false),
|
||||||
|
CardExpirationDate = table.Column<byte[]>(type: "BLOB", maxLength: 16, nullable: false),
|
||||||
|
CardCVV = table.Column<byte[]>(type: "BLOB", maxLength: 16, nullable: false),
|
||||||
|
IsAdmin = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
LoginFailedCount = table.Column<byte>(type: "INTEGER", nullable: false),
|
LoginFailedCount = table.Column<byte>(type: "INTEGER", nullable: false),
|
||||||
TemporaryPassword = table.Column<bool>(type: "INTEGER", nullable: false)
|
TemporaryPassword = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
LockReason = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Accounts", x => x.Id);
|
table.PrimaryKey("PK_Accounts", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Transfers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
SenderAccountNumber = table.Column<string>(type: "TEXT", maxLength: 26, nullable: false),
|
||||||
|
SenderName = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||||
|
SenderAddress = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||||
|
ReceiverAccountNumber = table.Column<string>(type: "TEXT", maxLength: 26, nullable: false),
|
||||||
|
ReceiverName = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||||
|
ReceiverAddress = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||||
|
Amount = table.Column<decimal>(type: "TEXT", precision: 14, scale: 2, nullable: false),
|
||||||
|
Title = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||||
|
Date = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Transfers", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "AccountPasswords",
|
name: "AccountPasswords",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -50,15 +80,37 @@ namespace SecureBank.Database.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AccountLoginRequests",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
AccountPasswordId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
|
ValidTo = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AccountLoginRequests", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AccountLoginRequests_AccountPasswords_AccountPasswordId",
|
||||||
|
column: x => x.AccountPasswordId,
|
||||||
|
principalTable: "AccountPasswords",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "AccountPasswordIndexes",
|
name: "AccountPasswordIndexes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
|
Id = table.Column<long>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
AccountPasswordId = table.Column<long>(type: "INTEGER", nullable: false),
|
AccountPasswordId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
Index = table.Column<byte>(type: "INTEGER", nullable: false)
|
Index = table.Column<byte>(type: "INTEGER", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
|
table.PrimaryKey("PK_AccountPasswordIndexes", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_AccountPasswordIndexes_AccountPasswords_AccountPasswordId",
|
name: "FK_AccountPasswordIndexes_AccountPasswords_AccountPasswordId",
|
||||||
column: x => x.AccountPasswordId,
|
column: x => x.AccountPasswordId,
|
||||||
@@ -67,6 +119,11 @@ namespace SecureBank.Database.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AccountLoginRequests_AccountPasswordId",
|
||||||
|
table: "AccountLoginRequests",
|
||||||
|
column: "AccountPasswordId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_AccountPasswordIndexes_AccountPasswordId",
|
name: "IX_AccountPasswordIndexes_AccountPasswordId",
|
||||||
table: "AccountPasswordIndexes",
|
table: "AccountPasswordIndexes",
|
||||||
@@ -81,9 +138,15 @@ namespace SecureBank.Database.Migrations
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AccountLoginRequests");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AccountPasswordIndexes");
|
name: "AccountPasswordIndexes");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Transfers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AccountPasswords");
|
name: "AccountPasswords");
|
||||||
|
|
||||||
@@ -23,6 +23,26 @@ namespace SecureBank.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CardCVV")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CardExpirationDate")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CardNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(300)
|
.HasMaxLength(300)
|
||||||
@@ -33,6 +53,19 @@ namespace SecureBank.Database.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("IBAN")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<byte[]>("IdCardNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAdmin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
b.Property<string>("LastName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
@@ -45,6 +78,11 @@ namespace SecureBank.Database.Migrations
|
|||||||
b.Property<byte>("LoginFailedCount")
|
b.Property<byte>("LoginFailedCount")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("PESEL")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
b.Property<string>("PhoneNumber")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(20)
|
.HasMaxLength(20)
|
||||||
@@ -127,6 +165,55 @@ namespace SecureBank.Database.Migrations
|
|||||||
b.ToTable("AccountPasswordIndexes");
|
b.ToTable("AccountPasswordIndexes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SecureBank.Database.Transfer", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(14, 2)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReceiverAccountNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReceiverAddress")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReceiverName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderAccountNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderAddress")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Transfers");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("SecureBank.Database.AccountLoginRequest", b =>
|
modelBuilder.Entity("SecureBank.Database.AccountLoginRequest", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
b.HasOne("SecureBank.Database.AccountPassword", "AccountPassword")
|
||||||
|
|||||||
52
SecureBank.Database/Transfer.cs
Normal file
52
SecureBank.Database/Transfer.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Database
|
||||||
|
{
|
||||||
|
public partial class Transfer
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(26)]
|
||||||
|
public string SenderAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string? SenderName { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string? SenderAddress { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(26)]
|
||||||
|
public string ReceiverAccountNumber { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string? ReceiverName { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string? ReceiverAddress { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[Precision(14, 2)]
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SecureBank.Helpers
|
|
||||||
{
|
|
||||||
public class AuthenticationHelper
|
|
||||||
{
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
public static IEnumerable<Claim> ParseToken(string token)
|
|
||||||
{
|
|
||||||
string payload = token.Split('.')[1];
|
|
||||||
|
|
||||||
switch (payload.Length % 4)
|
|
||||||
{
|
|
||||||
case 2: payload += "=="; break;
|
|
||||||
case 3: payload += "="; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] jsonBytes = Convert.FromBase64String(payload);
|
|
||||||
Dictionary<string, object>? keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
|
|
||||||
|
|
||||||
if (keyValuePairs is null)
|
|
||||||
{
|
|
||||||
throw new Exception("Incorrect token");
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using SecureBank.Common;
|
using SecureBank.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -31,28 +32,32 @@ namespace SecureBank.Website.API
|
|||||||
|
|
||||||
#region PUBLIC METHODS
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
public async Task<APIResponse<TResponse>> SendAsync<TResponse>(APIMethodType type, string url)
|
public async Task<APIResponse<TResponse>> SendAsync<TResponse>(APIMethodType type, string url, Dictionary<string, string>? query = null)
|
||||||
{
|
{
|
||||||
return await SendRequestAsync<APIResponse<TResponse>>(type, url, null);
|
url = AddQuery(url, query);
|
||||||
|
return await SendRequestAndParseBodyAsync<TResponse>(type, url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<APIResponse> SendAsync(APIMethodType type, string url)
|
public async Task<APIResponse> SendAsync(APIMethodType type, string url, Dictionary<string, string>? query = null)
|
||||||
{
|
{
|
||||||
return await SendRequestAsync<APIResponse>(type, url, null);
|
url = AddQuery(url, query);
|
||||||
|
return await SendRequestAndParseBodyAsync(type, url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<APIResponse<TResponse>> SendAsync<TResponse, TBody>(APIMethodType type, string url, TBody body)
|
public async Task<APIResponse<TResponse>> SendAsync<TResponse, TBody>(APIMethodType type, string url, TBody body, Dictionary<string, string>? query = null)
|
||||||
{
|
{
|
||||||
|
url = AddQuery(url, query);
|
||||||
HttpContent content = PrepareBody(body);
|
HttpContent content = PrepareBody(body);
|
||||||
|
|
||||||
return await SendRequestAsync<APIResponse<TResponse>>(type, url, content);
|
return await SendRequestAndParseBodyAsync<TResponse>(type, url, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<APIResponse> SendAsync<TBody>(APIMethodType type, string url, TBody body)
|
public async Task<APIResponse> SendAsync<TBody>(APIMethodType type, string url, TBody body, Dictionary<string, string>? query = null)
|
||||||
{
|
{
|
||||||
|
url = AddQuery(url, query);
|
||||||
HttpContent content = PrepareBody(body);
|
HttpContent content = PrepareBody(body);
|
||||||
|
|
||||||
return await SendRequestAsync<APIResponse>(type, url, content);
|
return await SendRequestAndParseBodyAsync(type, url, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -61,6 +66,25 @@ namespace SecureBank.Website.API
|
|||||||
|
|
||||||
#region PRIVATE METHODS
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private string AddQuery(string url, Dictionary<string, string>? query)
|
||||||
|
{
|
||||||
|
if (query is not null && query.Count > 0)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> queryNew = query.ToDictionary();
|
||||||
|
StringBuilder sb = new StringBuilder(url);
|
||||||
|
KeyValuePair<string, string> item = queryNew.ElementAt(0);
|
||||||
|
queryNew.Remove(item.Key);
|
||||||
|
sb.Append($"?{item.Key}={item.Value}");
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, string> item2 in queryNew)
|
||||||
|
{
|
||||||
|
sb.Append($"&{item2.Key}={item2.Value}");
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
private HttpContent PrepareBody<T>(T body)
|
private HttpContent PrepareBody<T>(T body)
|
||||||
{
|
{
|
||||||
string json = JsonConvert.SerializeObject(body);
|
string json = JsonConvert.SerializeObject(body);
|
||||||
@@ -71,37 +95,103 @@ namespace SecureBank.Website.API
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<T> SendRequestAsync<T>(APIMethodType type, string url, HttpContent? content)
|
private async Task<APIResponse> SendRequestAndParseBodyAsync(APIMethodType type, string url, HttpContent? content)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = type switch
|
HttpResponseMessage response = await SendRequestAsync(type, url, content);
|
||||||
{
|
|
||||||
APIMethodType.GET => await _httpClient.GetAsync(url),
|
|
||||||
APIMethodType.POST => await _httpClient.PostAsync(url, content),
|
|
||||||
APIMethodType.PUT => await _httpClient.PutAsync(url, content),
|
|
||||||
APIMethodType.DELETE => await _httpClient.DeleteAsync(url),
|
|
||||||
_ => throw new NotImplementedException()
|
|
||||||
};
|
|
||||||
|
|
||||||
string responseBodyString = await response.Content.ReadAsStringAsync();
|
string stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
T? responseBodyObject = JsonConvert.DeserializeObject<T>(responseBodyString);
|
APIResponse? responseBodyObject = JsonConvert.DeserializeObject<APIResponse>(stringResponse);
|
||||||
|
|
||||||
if (responseBodyObject is null)
|
if (responseBodyObject is null)
|
||||||
{
|
{
|
||||||
throw new Exception($"Wrong response type. Response: {responseBodyString}; {response.StatusCode}");
|
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
return new APIResponse
|
||||||
|
{
|
||||||
|
Status = ResponseStatus.Unauthorized,
|
||||||
|
Message = $"You do not have permission"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new APIResponse
|
||||||
|
{
|
||||||
|
Status = ResponseStatus.BadRequest,
|
||||||
|
Message = $"Wrong response type. Response: {stringResponse}; {response.StatusCode}"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return responseBodyObject;
|
return responseBodyObject;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex);
|
return new APIResponse
|
||||||
throw;
|
{
|
||||||
|
Status = ResponseStatus.BadRequest,
|
||||||
|
Message = ex.Message
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<APIResponse<T>> SendRequestAndParseBodyAsync<T>(APIMethodType type, string url, HttpContent? content)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await SendRequestAsync(type, url, content);
|
||||||
|
|
||||||
|
string stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
APIResponse<T>? responseBodyObject = JsonConvert.DeserializeObject<APIResponse<T>>(stringResponse);
|
||||||
|
|
||||||
|
if (responseBodyObject is null)
|
||||||
|
{
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
return new APIResponse<T>
|
||||||
|
{
|
||||||
|
Status = ResponseStatus.Unauthorized,
|
||||||
|
Message = $"You do not have permission"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new APIResponse<T>
|
||||||
|
{
|
||||||
|
Status = ResponseStatus.BadRequest,
|
||||||
|
Message = $"Wrong response type. Response: {stringResponse}; {response.StatusCode}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBodyObject;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new APIResponse<T>
|
||||||
|
{
|
||||||
|
Status = ResponseStatus.BadRequest,
|
||||||
|
Message = ex.Message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseMessage> SendRequestAsync(APIMethodType type, string url, HttpContent? content)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
APIMethodType.GET => await _httpClient.GetAsync(url),
|
||||||
|
APIMethodType.POST => await _httpClient.PostAsync(url, content),
|
||||||
|
APIMethodType.PUT => await _httpClient.PutAsync(url, content),
|
||||||
|
APIMethodType.PATCH => await _httpClient.PatchAsync(url, content),
|
||||||
|
APIMethodType.DELETE => await _httpClient.DeleteAsync(url),
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,25 @@ namespace SecureBank.Website.API
|
|||||||
// Accounts
|
// Accounts
|
||||||
public string AccountsBase { get; private set; }
|
public string AccountsBase { get; private set; }
|
||||||
public string AccountsCreateAccount { get; private set; }
|
public string AccountsCreateAccount { get; private set; }
|
||||||
|
public string AccountsChangePassword { get; private set; }
|
||||||
public string AccountsGetPasswordVariant { get; private set; }
|
public string AccountsGetPasswordVariant { get; private set; }
|
||||||
public string AccountsAuthentication { get; private set; }
|
public string AccountsAuthentication { get; private set; }
|
||||||
public string AccountsAuthenticationRefresh { get; private set; }
|
public string AccountsAuthenticationRefresh { get; private set; }
|
||||||
|
public string AccountsGetAccounts { get; private set; }
|
||||||
|
public string AccountsResetPassword { get; private set; }
|
||||||
|
public string AccountsUnlockAccount { get; private set; }
|
||||||
|
|
||||||
|
// Balance
|
||||||
|
public string BalanceBase { get; private set; }
|
||||||
|
public string BalanceGetAccountBalance { get; private set; }
|
||||||
|
public string BalanceGetBalance { get; private set; }
|
||||||
|
|
||||||
|
// Transfers
|
||||||
|
public string TransfersBase { get; private set; }
|
||||||
|
public string TransfersGetUserTransfers { get; private set; }
|
||||||
|
public string TransfersGetTransfers { get; private set; }
|
||||||
|
public string TransfersCreateAdminTransfer { get; private set; }
|
||||||
|
public string TransfersCreateUserTransfer { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -32,9 +48,23 @@ namespace SecureBank.Website.API
|
|||||||
|
|
||||||
AccountsBase = $"{Base}{configuration.GetSection("Endpoints").GetSection("Accounts")["Base"]}";
|
AccountsBase = $"{Base}{configuration.GetSection("Endpoints").GetSection("Accounts")["Base"]}";
|
||||||
AccountsCreateAccount = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["CreateAccount"]}";
|
AccountsCreateAccount = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["CreateAccount"]}";
|
||||||
|
AccountsChangePassword = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["ChangePassword"]}";
|
||||||
AccountsGetPasswordVariant = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["GetPasswordVariant"]}";
|
AccountsGetPasswordVariant = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["GetPasswordVariant"]}";
|
||||||
AccountsAuthentication = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["Authentication"]}";
|
AccountsAuthentication = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["Authentication"]}";
|
||||||
AccountsAuthenticationRefresh = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["AuthenticationRefresh"]}";
|
AccountsAuthenticationRefresh = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["AuthenticationRefresh"]}";
|
||||||
|
AccountsGetAccounts = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["GetAccounts"]}";
|
||||||
|
AccountsResetPassword = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["ResetPassword"]}";
|
||||||
|
AccountsUnlockAccount = $"{AccountsBase}{configuration.GetSection("Endpoints").GetSection("Accounts")["UnlockAccount"]}";
|
||||||
|
|
||||||
|
BalanceBase = $"{Base}{configuration.GetSection("Endpoints").GetSection("Balance")["Base"]}";
|
||||||
|
BalanceGetAccountBalance = $"{BalanceBase}{configuration.GetSection("Endpoints").GetSection("Balance")["GetAccountBalance"]}";
|
||||||
|
BalanceGetBalance = $"{BalanceBase}{configuration.GetSection("Endpoints").GetSection("Balance")["GetBalance"]}";
|
||||||
|
|
||||||
|
TransfersBase = $"{Base}{configuration.GetSection("Endpoints").GetSection("Transfers")["Base"]}";
|
||||||
|
TransfersGetTransfers = $"{TransfersBase}{configuration.GetSection("Endpoints").GetSection("Transfers")["GetTransfers"]}";
|
||||||
|
TransfersGetUserTransfers = $"{TransfersBase}{configuration.GetSection("Endpoints").GetSection("Transfers")["GetUserTransfers"]}";
|
||||||
|
TransfersCreateAdminTransfer = $"{TransfersBase}{configuration.GetSection("Endpoints").GetSection("Transfers")["CreateAdminTransfer"]}";
|
||||||
|
TransfersCreateUserTransfer = $"{TransfersBase}{configuration.GetSection("Endpoints").GetSection("Transfers")["CreateUserTransfer"]}";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace SecureBank.Website.API
|
|||||||
GET,
|
GET,
|
||||||
POST,
|
POST,
|
||||||
PUT,
|
PUT,
|
||||||
|
PATCH,
|
||||||
DELETE
|
DELETE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0" />
|
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.1" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\SecureBank.Helpers\SecureBank.Helpers.csproj" />
|
|
||||||
<ProjectReference Include="..\SecureBank.Website.Services\SecureBank.Website.Services.csproj" />
|
<ProjectReference Include="..\SecureBank.Website.Services\SecureBank.Website.Services.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -56,16 +57,22 @@ namespace SecureBank.Website.Authentication
|
|||||||
|
|
||||||
APIResponse<string> refreshResponse = await _accountsService.AuthenticationRefresh();
|
APIResponse<string> refreshResponse = await _accountsService.AuthenticationRefresh();
|
||||||
|
|
||||||
if (!refreshResponse.Success)
|
if (refreshResponse.Status != ResponseStatus.Ok)
|
||||||
{
|
{
|
||||||
|
await _authenticationHelper.RemoveToken();
|
||||||
_httpClient.DefaultRequestHeaders.Authorization = null;
|
_httpClient.DefaultRequestHeaders.Authorization = null;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
token = refreshResponse.Data;
|
token = refreshResponse.Data;
|
||||||
|
|
||||||
|
await _authenticationHelper.SaveToken(token);
|
||||||
|
|
||||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
state = new AuthenticationState(new ClaimsPrincipal()); //TODO: Add claims
|
|
||||||
|
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
JwtSecurityToken tokenParsed = tokenHandler.ReadJwtToken(token);
|
||||||
|
state = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(tokenParsed.Claims)));
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ namespace SecureBank.Website.Services
|
|||||||
Task<APIResponse<GetPasswordVariantResponse>> GetPasswordVariant(int accountId);
|
Task<APIResponse<GetPasswordVariantResponse>> GetPasswordVariant(int accountId);
|
||||||
Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data);
|
Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data);
|
||||||
Task<APIResponse<string>> AuthenticationRefresh();
|
Task<APIResponse<string>> AuthenticationRefresh();
|
||||||
|
Task<APIResponse> ChangePassword(ChangePasswordRequest data);
|
||||||
|
Task<APIResponse<IEnumerable<AccountResponse>>> GetAccounts(int? id = null, string? iban = null);
|
||||||
|
Task<APIResponse> ResetPassword(int accountId);
|
||||||
|
Task<APIResponse> UnlockAccount(int accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -57,8 +61,7 @@ namespace SecureBank.Website.Services
|
|||||||
|
|
||||||
public async Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data)
|
public async Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data)
|
||||||
{
|
{
|
||||||
string url = string.Format(_configuration.AccountsAuthentication, accountId);
|
return await _apiClient.SendAsync<string, AuthenticationRequest>(APIMethodType.POST, _configuration.AccountsAuthentication, data);
|
||||||
return await _apiClient.SendAsync<string, AuthenticationRequest>(APIMethodType.POST, url, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<APIResponse<string>> AuthenticationRefresh()
|
public async Task<APIResponse<string>> AuthenticationRefresh()
|
||||||
@@ -66,6 +69,37 @@ namespace SecureBank.Website.Services
|
|||||||
return await _apiClient.SendAsync<string>(APIMethodType.POST, _configuration.AccountsAuthenticationRefresh);
|
return await _apiClient.SendAsync<string>(APIMethodType.POST, _configuration.AccountsAuthenticationRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse> ChangePassword(ChangePasswordRequest data)
|
||||||
|
{
|
||||||
|
return await _apiClient.SendAsync(APIMethodType.PATCH, _configuration.AccountsChangePassword, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse<IEnumerable<AccountResponse>>> GetAccounts(int? id = null, string? iban = null)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> query = new Dictionary<string, string>();
|
||||||
|
if (id.HasValue)
|
||||||
|
{
|
||||||
|
query.Add("id", id.Value.ToString());
|
||||||
|
}
|
||||||
|
if (iban is not null)
|
||||||
|
{
|
||||||
|
query.Add("iban", iban);
|
||||||
|
}
|
||||||
|
return await _apiClient.SendAsync<IEnumerable<AccountResponse>>(APIMethodType.GET, _configuration.AccountsGetAccounts, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse> ResetPassword(int accountId)
|
||||||
|
{
|
||||||
|
string url = string.Format(_configuration.AccountsResetPassword, accountId);
|
||||||
|
return await _apiClient.SendAsync(APIMethodType.PATCH, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse> UnlockAccount(int accountId)
|
||||||
|
{
|
||||||
|
string url = string.Format(_configuration.AccountsUnlockAccount, accountId);
|
||||||
|
return await _apiClient.SendAsync(APIMethodType.PATCH, url);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using SecureBank.Common.Accounts;
|
||||||
|
using SecureBank.Common;
|
||||||
|
using SecureBank.Website.API;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SecureBank.Website.Services
|
||||||
|
{
|
||||||
|
public interface IBalanceService
|
||||||
|
{
|
||||||
|
Task<APIResponse<decimal>> GetAccountBalance(int accountId);
|
||||||
|
Task<APIResponse<decimal>> GetBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class BalanceService : IBalanceService
|
||||||
|
{
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private readonly APIClient _apiClient;
|
||||||
|
private readonly APIEndpointsConfiguration _configuration;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public BalanceService(APIClient apiClient, APIEndpointsConfiguration configuration)
|
||||||
|
{
|
||||||
|
_apiClient = apiClient;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
public async Task<APIResponse<decimal>> GetAccountBalance(int accountId)
|
||||||
|
{
|
||||||
|
string url = string.Format(_configuration.BalanceGetAccountBalance, accountId);
|
||||||
|
return await _apiClient.SendAsync<decimal>(APIMethodType.GET, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse<decimal>> GetBalance()
|
||||||
|
{
|
||||||
|
return await _apiClient.SendAsync<decimal>(APIMethodType.GET, _configuration.BalanceGetBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using SecureBank.Common.Accounts;
|
||||||
|
using SecureBank.Common;
|
||||||
|
using SecureBank.Website.API;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SecureBank.Common.Transfers;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
|
namespace SecureBank.Website.Services
|
||||||
|
{
|
||||||
|
public interface ITransfersService
|
||||||
|
{
|
||||||
|
Task<APIResponse> CreateAdminTransfer(CreateAdminTransferRequest data);
|
||||||
|
Task<APIResponse> CreateUserTransfer(CreateUserTransferRequest data);
|
||||||
|
Task<APIResponse<IEnumerable<TransferResponse>>> GetTransfers();
|
||||||
|
Task<APIResponse<IEnumerable<TransferResponse>>> GetUserTransfers(int accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class TransfersService : ITransfersService
|
||||||
|
{
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private readonly APIClient _apiClient;
|
||||||
|
private readonly APIEndpointsConfiguration _configuration;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public TransfersService(APIClient apiClient, APIEndpointsConfiguration configuration)
|
||||||
|
{
|
||||||
|
_apiClient = apiClient;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
public async Task<APIResponse<IEnumerable<TransferResponse>>> GetTransfers()
|
||||||
|
{
|
||||||
|
return await _apiClient.SendAsync<IEnumerable<TransferResponse>>(APIMethodType.GET, _configuration.TransfersGetTransfers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse<IEnumerable<TransferResponse>>> GetUserTransfers(int accountId)
|
||||||
|
{
|
||||||
|
string url = string.Format(_configuration.TransfersGetUserTransfers, accountId);
|
||||||
|
return await _apiClient.SendAsync<IEnumerable<TransferResponse>>(APIMethodType.GET, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse> CreateAdminTransfer(CreateAdminTransferRequest data)
|
||||||
|
{
|
||||||
|
return await _apiClient.SendAsync(APIMethodType.POST, _configuration.TransfersCreateAdminTransfer, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponse> CreateUserTransfer(CreateUserTransferRequest data)
|
||||||
|
{
|
||||||
|
return await _apiClient.SendAsync(APIMethodType.POST, _configuration.TransfersCreateUserTransfer, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,15 +29,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecureBank.API.Authenticati
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecureBank.Website.Authentication", "SecureBank.Website\SecureBank.Website.Authentication\SecureBank.Website.Authentication.csproj", "{4BC964A3-91C9-47FD-9A78-1E43301E9779}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecureBank.Website.Authentication", "SecureBank.Website\SecureBank.Website.Authentication\SecureBank.Website.Authentication.csproj", "{4BC964A3-91C9-47FD-9A78-1E43301E9779}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecureBank.Helpers", "SecureBank.Helpers\SecureBank.Helpers.csproj", "{F98EC0ED-E78E-4908-A00E-5D9F45D88E33}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6A111947-FD57-4EF7-8B03-C46E73B68DE1}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6A111947-FD57-4EF7-8B03-C46E73B68DE1}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.dockerignore = .dockerignore
|
.dockerignore = .dockerignore
|
||||||
database.db = database.db
|
database.db = database.db
|
||||||
Dockerfile = Dockerfile
|
docker-compose.yml = docker-compose.yml
|
||||||
|
Main.Dockerfile = Main.Dockerfile
|
||||||
|
Nginx.Dockerfile = Nginx.Dockerfile
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nginx", "Nginx", "{E82E5EB5-85CD-4E15-A5F7-AC1D0F99841E}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
localhost.conf = localhost.conf
|
||||||
|
Nginx\nginx.conf = Nginx\nginx.conf
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecureBank.Authentication", "SecureBank.Authentication\SecureBank.Authentication.csproj", "{3A36A494-9B6C-4E18-AE67-064ACD57BAB6}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecureBank.API.Encryption", "SecureBank.API\SecureBank.API.Encryption\SecureBank.API.Encryption.csproj", "{D5988236-1C22-4A5B-B1E0-A4258A9B1A1F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -88,10 +98,14 @@ Global
|
|||||||
{4BC964A3-91C9-47FD-9A78-1E43301E9779}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4BC964A3-91C9-47FD-9A78-1E43301E9779}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4BC964A3-91C9-47FD-9A78-1E43301E9779}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{4BC964A3-91C9-47FD-9A78-1E43301E9779}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4BC964A3-91C9-47FD-9A78-1E43301E9779}.Release|Any CPU.Build.0 = Release|Any CPU
|
{4BC964A3-91C9-47FD-9A78-1E43301E9779}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{F98EC0ED-E78E-4908-A00E-5D9F45D88E33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3A36A494-9B6C-4E18-AE67-064ACD57BAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{F98EC0ED-E78E-4908-A00E-5D9F45D88E33}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3A36A494-9B6C-4E18-AE67-064ACD57BAB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F98EC0ED-E78E-4908-A00E-5D9F45D88E33}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3A36A494-9B6C-4E18-AE67-064ACD57BAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F98EC0ED-E78E-4908-A00E-5D9F45D88E33}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3A36A494-9B6C-4E18-AE67-064ACD57BAB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D5988236-1C22-4A5B-B1E0-A4258A9B1A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D5988236-1C22-4A5B-B1E0-A4258A9B1A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D5988236-1C22-4A5B-B1E0-A4258A9B1A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D5988236-1C22-4A5B-B1E0-A4258A9B1A1F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -104,6 +118,8 @@ Global
|
|||||||
{41FA4292-92B0-4810-8127-6E70E2073D38} = {E23C57D5-1527-4482-963B-374CF1A098D5}
|
{41FA4292-92B0-4810-8127-6E70E2073D38} = {E23C57D5-1527-4482-963B-374CF1A098D5}
|
||||||
{54220F73-24DE-47BB-A027-5326E90A59B6} = {E23C57D5-1527-4482-963B-374CF1A098D5}
|
{54220F73-24DE-47BB-A027-5326E90A59B6} = {E23C57D5-1527-4482-963B-374CF1A098D5}
|
||||||
{4BC964A3-91C9-47FD-9A78-1E43301E9779} = {EFBD13EE-AF89-4792-A3DE-FF38BDA38DD0}
|
{4BC964A3-91C9-47FD-9A78-1E43301E9779} = {EFBD13EE-AF89-4792-A3DE-FF38BDA38DD0}
|
||||||
|
{E82E5EB5-85CD-4E15-A5F7-AC1D0F99841E} = {6A111947-FD57-4EF7-8B03-C46E73B68DE1}
|
||||||
|
{D5988236-1C22-4A5B-B1E0-A4258A9B1A1F} = {E23C57D5-1527-4482-963B-374CF1A098D5}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {626ACE64-EFF9-4C9A-84DC-DFF35D76AF59}
|
SolutionGuid = {626ACE64-EFF9-4C9A-84DC-DFF35D76AF59}
|
||||||
|
|||||||
@@ -2,12 +2,20 @@
|
|||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<NavMenu />
|
<NavMenu Claims="@_claims"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="top-row px-4">
|
<div class="top-row px-4">
|
||||||
<a href="/auth">Login</a>
|
@if (_claims is null)
|
||||||
|
{
|
||||||
|
<button class="btn btn-primary" @onclick="@Login">Login</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="vertical-center" style="margin: 0px 80px 0px 0px;">Logged as: @($"{_claims.FirstName} {_claims.LastName} ({_claims.AccountId:00000000})")</p>
|
||||||
|
<button class="btn btn-primary" @onclick="@Logout">Logout</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<article class="content px-4">
|
<article class="content px-4">
|
||||||
@@ -21,3 +29,58 @@
|
|||||||
<a href="" class="reload">Reload</a>
|
<a href="" class="reload">Reload</a>
|
||||||
<a class="dismiss">🗙</a>
|
<a class="dismiss">🗙</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected AuthenticationHelper _authenticationHelper { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
_claims = null;
|
||||||
|
if (authState.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(authState.User.Claims);
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Login()
|
||||||
|
{
|
||||||
|
_navigationManager.NavigateTo("/auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task Logout()
|
||||||
|
{
|
||||||
|
await _authenticationHelper.RemoveToken();
|
||||||
|
_navigationManager.NavigateTo("/", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -12,7 +12,34 @@
|
|||||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@if (Claims is not null)
|
||||||
|
{
|
||||||
|
<NavLink class="nav-link" href="/dashboard" Match="NavLinkMatch.All">
|
||||||
|
<span class="bi bi-dashboard-nav-menu" aria-hidden="true"></span> Dashboard
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="nav-link" href="/create-transfer" Match="NavLinkMatch.All">
|
||||||
|
<span class="bi bi-transfer-nav-menu" aria-hidden="true"></span> Create transfer
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="nav-link" href="/account-details" Match="NavLinkMatch.All">
|
||||||
|
<span class="bi bi-account-details-nav-menu" aria-hidden="true"></span> Account details
|
||||||
|
</NavLink>
|
||||||
|
@if (Claims.IsAdmin)
|
||||||
|
{
|
||||||
|
<NavLink class="nav-link" href="/admin" Match="NavLinkMatch.All">
|
||||||
|
<span class="bi bi-admin-nav-menu" aria-hidden="true"></span> Admin panel
|
||||||
|
</NavLink>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Claims? Claims { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -46,6 +46,22 @@
|
|||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bi-admin-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-gear-wide-connected' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M7.068.727c.243-.97 1.62-.97 1.864 0l.071.286a.96.96 0 0 0 1.622.434l.205-.211c.695-.719 1.888-.03 1.613.931l-.08.284a.96.96 0 0 0 1.187 1.187l.283-.081c.96-.275 1.65.918.931 1.613l-.211.205a.96.96 0 0 0 .434 1.622l.286.071c.97.243.97 1.62 0 1.864l-.286.071a.96.96 0 0 0-.434 1.622l.211.205c.719.695.03 1.888-.931 1.613l-.284-.08a.96.96 0 0 0-1.187 1.187l.081.283c.275.96-.918 1.65-1.613.931l-.205-.211a.96.96 0 0 0-1.622.434l-.071.286c-.243.97-1.62.97-1.864 0l-.071-.286a.96.96 0 0 0-1.622-.434l-.205.211c-.695.719-1.888.03-1.613-.931l.08-.284a.96.96 0 0 0-1.186-1.187l-.284.081c-.96.275-1.65-.918-.931-1.613l.211-.205a.96.96 0 0 0-.434-1.622l-.286-.071c-.97-.243-.97-1.62 0-1.864l.286-.071a.96.96 0 0 0 .434-1.622l-.211-.205c-.719-.695-.03-1.888.931-1.613l.284.08a.96.96 0 0 0 1.187-1.186l-.081-.284c-.275-.96.918-1.65 1.613-.931l.205.211a.96.96 0 0 0 1.622-.434zM12.973 8.5H8.25l-2.834 3.779A4.998 4.998 0 0 0 12.973 8.5m0-1a4.998 4.998 0 0 0-7.557-3.779l2.834 3.78zM5.048 3.967l-.087.065zm-.431.355A4.98 4.98 0 0 0 3.002 8c0 1.455.622 2.765 1.615 3.678L7.375 8zm.344 7.646.087.065z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-transfer-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-left-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5m14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-dashboard-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-bank2' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.277.084a.5.5 0 0 0-.554 0l-7.5 5A.5.5 0 0 0 .5 6h1.875v7H1.5a.5.5 0 0 0 0 1h13a.5.5 0 1 0 0-1h-.875V6H15.5a.5.5 0 0 0 .277-.916zM12.375 6v7h-1.25V6zm-2.5 0v7h-1.25V6zm-2.5 0v7h-1.25V6zm-2.5 0v7h-1.25V6zM8 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2M.5 15a.5.5 0 0 0 0 1h15a.5.5 0 1 0 0-1z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-account-details-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
|
|||||||
185
SecureBank/Components/Pages/Admin/Accounts.razor
Normal file
185
SecureBank/Components/Pages/Admin/Accounts.razor
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
@page "/admin/accounts-management"
|
||||||
|
|
||||||
|
<h3>Accounts</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null && _claims.IsAdmin)
|
||||||
|
{
|
||||||
|
@switch (_dataLoadedState)
|
||||||
|
{
|
||||||
|
case DataLoadState.Loading:
|
||||||
|
<p>Waiting for data...</p>
|
||||||
|
break;
|
||||||
|
case DataLoadState.NotLoaded:
|
||||||
|
<p>Data cannot be loaded. Try again later.</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
break;
|
||||||
|
case DataLoadState.Loaded:
|
||||||
|
@if (_errorMessage is not null)
|
||||||
|
{
|
||||||
|
<p>@_errorMessage</p>
|
||||||
|
}
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th class="table-brd">Id</th>
|
||||||
|
<th class="table-brd">Name</th>
|
||||||
|
<th class="table-brd">Email</th>
|
||||||
|
<th class="table-brd">Phone number</th>
|
||||||
|
<th class="table-brd">IBAN</th>
|
||||||
|
<th class="table-brd">Is password temporary</th>
|
||||||
|
<th class="table-brd">Is locked</th>
|
||||||
|
<th class="table-brd">Operations</th>
|
||||||
|
</tr>
|
||||||
|
@foreach (var account in _accounts)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="table-brd">@account.Id</td>
|
||||||
|
<td class="table-brd">@account.FirstName @account.LastName</td>
|
||||||
|
<td class="table-brd">@account.Email</td>
|
||||||
|
<td class="table-brd">@account.PhoneNumber</td>
|
||||||
|
<td class="table-brd">@account.IBAN</td>
|
||||||
|
<td class="table-brd">@(account.TemporaryPassword ? "YES" : "NO")</td>
|
||||||
|
<td class="table-brd">@(account.LockReason is not null || account.LoginFailedCount >= 3 ? "YES" : "NO")</td>
|
||||||
|
<td class="table-brd">
|
||||||
|
<button class="btn btn-secondary" @onclick="@(async () => await ResetPassword(account.Id))">Reset password</button>
|
||||||
|
<button class="btn btn-secondary" @onclick="@(async () => await Unlock(account.Id))" disabled="@(account.LockReason is null && account.LoginFailedCount < 3)">Unlock</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
protected enum DataLoadState
|
||||||
|
{
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
NotLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IAccountsService _accountsService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IBalanceService _balanceService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ITransfersService _transfersService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
protected DataLoadState _dataLoadedState;
|
||||||
|
|
||||||
|
protected IEnumerable<AccountResponse> _accounts;
|
||||||
|
|
||||||
|
protected string? _errorMessage;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
_dataLoadedState = DataLoadState.Loading;
|
||||||
|
_errorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (_authLoaded)
|
||||||
|
{
|
||||||
|
APIResponse<IEnumerable<AccountResponse>> accountResponse = await _accountsService.GetAccounts();
|
||||||
|
if (accountResponse.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_accounts = accountResponse.Data;
|
||||||
|
}
|
||||||
|
else if (accountResponse.Status == ResponseStatus.Unauthorized)
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dataLoadedState = accountResponse.Status == ResponseStatus.Ok ? DataLoadState.Loaded : DataLoadState.NotLoaded;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task ResetPassword(int id)
|
||||||
|
{
|
||||||
|
APIResponse response = await _accountsService.ResetPassword(id);
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_navigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorMessage = $"An error occured while reseting password for account with id {id}. {response.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task Unlock(int id)
|
||||||
|
{
|
||||||
|
APIResponse response = await _accountsService.UnlockAccount(id);
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_navigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorMessage = $"An error occured while unlocking account with id {id}. {response.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
337
SecureBank/Components/Pages/Admin/CreateTransfer.razor
Normal file
337
SecureBank/Components/Pages/Admin/CreateTransfer.razor
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
@page "/admin/create-transfer"
|
||||||
|
|
||||||
|
<h3>Create new transfer</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null && _claims.IsAdmin)
|
||||||
|
{
|
||||||
|
@switch (_stage)
|
||||||
|
{
|
||||||
|
case (Stage.Form):
|
||||||
|
<EditForm Model="@_data" OnSubmit="SubmitToValidation">
|
||||||
|
<br />
|
||||||
|
<p><strong>Sender data:</strong></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sender-account-number-input">
|
||||||
|
Sender account number:
|
||||||
|
</label>
|
||||||
|
<InputText id="sender-account-number-input" class="form-control" minlength="26" maxlength="26" @bind-Value="@_data.SenderAccountNumber"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sender-name-input">
|
||||||
|
Sender name:
|
||||||
|
</label>
|
||||||
|
<InputText id="sender-name-input" class="form-control" @bind-Value="@_data.SenderName"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sender-address-input">
|
||||||
|
Sender address:
|
||||||
|
</label>
|
||||||
|
<InputText id="sender-address-input" class="form-control" @bind-Value="@_data.SenderAddress"></InputText>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p><strong>Receiver data:</strong></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="receiver-account-number-input">
|
||||||
|
Receiver account number:
|
||||||
|
</label>
|
||||||
|
<InputText id="receiver-account-number-input" class="form-control" minlength="26" maxlength="26" @bind-Value="@_data.ReceiverAccountNumber"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="receiver-name-input">
|
||||||
|
Receiver name:
|
||||||
|
</label>
|
||||||
|
<InputText id="receiver-name-input" class="form-control" @bind-Value="@_data.ReceiverName"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="receiver-address-input">
|
||||||
|
Receiver address:
|
||||||
|
</label>
|
||||||
|
<InputText id="receiver-address-input" class="form-control" @bind-Value="@_data.ReceiverAddress"></InputText>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p><strong>Transfer data:</strong></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="title-input">
|
||||||
|
Title:
|
||||||
|
</label>
|
||||||
|
<InputText id="title-input" class="form-control" @bind-Value="@_data.Title"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="amount-input">
|
||||||
|
Amount (PLN):
|
||||||
|
</label>
|
||||||
|
<InputNumber id="amount-input" class="form-control" @bind-Value="@_data.Amount"></InputNumber>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<button type="submit" class="btn btn-primary">Submit to validation</button>
|
||||||
|
</EditForm>
|
||||||
|
break;
|
||||||
|
case (Stage.Validated):
|
||||||
|
<p><strong>Sender data:</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Account number:</td>
|
||||||
|
<td>@_data.SenderAccountNumber</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Name:</td>
|
||||||
|
<td>@(_data.SenderName ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Address:</td>
|
||||||
|
<td>@(_data.SenderAddress ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Account balance after transfer:</td>
|
||||||
|
<td>@_senderAmount</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p><strong>Receiver data:</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Account number:</td>
|
||||||
|
<td>@_data.ReceiverAccountNumber</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Name:</td>
|
||||||
|
<td>@(_data.ReceiverName ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Address:</td>
|
||||||
|
<td>@(_data.ReceiverAddress ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Account balance after transfer:</td>
|
||||||
|
<td>@_receiverAmount</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p><strong>Transfer data:</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Title:</td>
|
||||||
|
<td>@(_data.Title ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Amount:</td>
|
||||||
|
<td>@_data.Amount PLN</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="submit" @onclick="@SubmitTransfer">Submit</button>
|
||||||
|
break;
|
||||||
|
case (Stage.Accepted):
|
||||||
|
<p>Transfer for amount @_data.Amount PLN was successfully sent from account with number @_data.SenderAccountNumber to account with number @_data.ReceiverAccountNumber</p>
|
||||||
|
<button class="btn btn-secondary" @onclick="@NavigateToAdminPanel">Go back to admin panel</button>
|
||||||
|
<button class="btn btn-primary" @onclick="@NavigateToNewForm">Create next transfer</button>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_errorMessage))
|
||||||
|
{
|
||||||
|
<p class="text-red">Error: @_errorMessage</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
protected enum Stage
|
||||||
|
{
|
||||||
|
Form,
|
||||||
|
Validated,
|
||||||
|
Accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IAccountsService _accountsService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IBalanceService _balanceService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ITransfersService _transfersService { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
protected Stage _stage;
|
||||||
|
|
||||||
|
protected string? _errorMessage;
|
||||||
|
|
||||||
|
protected CreateAdminTransferRequest _data;
|
||||||
|
|
||||||
|
protected string _senderAmount;
|
||||||
|
protected string _receiverAmount;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
_stage = Stage.Form;
|
||||||
|
_errorMessage = null;
|
||||||
|
_data = new CreateAdminTransferRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async void SubmitToValidation()
|
||||||
|
{
|
||||||
|
if (_data.SenderAccountNumber is null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Sender account number cannot be empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_data.SenderAccountNumber.All(x => char.IsDigit(x)))
|
||||||
|
{
|
||||||
|
_errorMessage = "Wrong sender account number format. Account number consists only of digits";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.SenderAccountNumber.Length != 26)
|
||||||
|
{
|
||||||
|
_errorMessage = "Wrong sender account number format. Account number must have length of 26 digits";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.ReceiverAccountNumber is null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Receiver account number cannot be empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_data.ReceiverAccountNumber.All(x => char.IsDigit(x)))
|
||||||
|
{
|
||||||
|
_errorMessage = "Wrong receiver account number format. Account number consists only of digits";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.ReceiverAccountNumber.Length != 26)
|
||||||
|
{
|
||||||
|
_errorMessage = "Wrong receiver account number format. Account number must have length of 26 digits";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.Amount <= 0)
|
||||||
|
{
|
||||||
|
_errorMessage = "Transfer amount has to be greater than 0 PLN";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.Amount = Math.Round(_data.Amount, 2, MidpointRounding.ToEven);
|
||||||
|
|
||||||
|
_senderAmount = await GetBalance(_data.SenderAccountNumber, -_data.Amount);
|
||||||
|
_receiverAmount = await GetBalance(_data.ReceiverAccountNumber, _data.Amount);
|
||||||
|
|
||||||
|
_stage = Stage.Validated;
|
||||||
|
_errorMessage = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<string> GetBalance(string accountNumber, decimal amount)
|
||||||
|
{
|
||||||
|
APIResponse<IEnumerable<AccountResponse>> senderAccountResponse = await _accountsService.GetAccounts(iban: accountNumber);
|
||||||
|
if (senderAccountResponse.Status == ResponseStatus.Ok && senderAccountResponse.Data.Count() == 1)
|
||||||
|
{
|
||||||
|
APIResponse<decimal> senderAmountResponse = await _balanceService.GetAccountBalance(senderAccountResponse.Data.ElementAt(0).Id);
|
||||||
|
if (senderAmountResponse.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
return $"{(senderAmountResponse.Data + amount):F2} PLN";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "<ERROR: Cannot get sender account balance>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "<NON SECUREBANK ACCOUNT>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SubmitTransfer()
|
||||||
|
{
|
||||||
|
APIResponse response = await _transfersService.CreateAdminTransfer(_data);
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_errorMessage = null;
|
||||||
|
_stage = Stage.Accepted;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorMessage = response.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void NavigateToAdminPanel()
|
||||||
|
{
|
||||||
|
_navigationManager.NavigateTo("/admin", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void NavigateToNewForm()
|
||||||
|
{
|
||||||
|
_navigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
70
SecureBank/Components/Pages/Admin/Panel.razor
Normal file
70
SecureBank/Components/Pages/Admin/Panel.razor
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
@page "/admin"
|
||||||
|
|
||||||
|
<h3>Admin panel</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null && _claims.IsAdmin)
|
||||||
|
{
|
||||||
|
<ul>
|
||||||
|
<li><a href="/admin/create-account">Create new account</a></li>
|
||||||
|
<li><a href="/admin/create-transfer">Create new transfer</a></li>
|
||||||
|
<li><a href="/admin/accounts-management">Accounts management</a></li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -2,76 +2,122 @@
|
|||||||
|
|
||||||
<h3>Create new client account</h3>
|
<h3>Create new client account</h3>
|
||||||
|
|
||||||
@if (_id is null)
|
@if (_authLoaded || true)
|
||||||
{
|
{
|
||||||
<EditForm Model="@_data" OnSubmit="Submit">
|
@if (true || (_claims is not null && _claims.IsAdmin))
|
||||||
<div class="form-group">
|
{
|
||||||
<label for="first-name-input">
|
@if (_id is null)
|
||||||
First name:
|
|
||||||
</label>
|
|
||||||
<InputText id="first-name-input" class="form-control" @bind-Value="@_data.FirstName"></InputText>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="last-name-input">
|
|
||||||
Last name:
|
|
||||||
</label>
|
|
||||||
<InputText id="last-name-input" class="form-control" @bind-Value="@_data.LastName"></InputText>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email-input">
|
|
||||||
Email address:
|
|
||||||
</label>
|
|
||||||
<InputText id="email-input" class="form-control" @bind-Value="@_data.Email"></InputText>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="phone-number-input">
|
|
||||||
Phone number:
|
|
||||||
</label>
|
|
||||||
<InputText id="phone-number-input" class="form-control" @bind-Value="@_data.PhoneNumber"></InputText>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<button class="btn btn-primary" type="submit">Submit</button>
|
|
||||||
@if (!string.IsNullOrWhiteSpace(_message))
|
|
||||||
{
|
{
|
||||||
<p class="text-red">Error: @_message</p>
|
<EditForm Model="@_data" OnSubmit="Submit">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="first-name-input">
|
||||||
|
First name:
|
||||||
|
</label>
|
||||||
|
<InputText id="first-name-input" class="form-control" @bind-Value="@_data.FirstName"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="last-name-input">
|
||||||
|
Last name:
|
||||||
|
</label>
|
||||||
|
<InputText id="last-name-input" class="form-control" @bind-Value="@_data.LastName"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email-input">
|
||||||
|
Email address:
|
||||||
|
</label>
|
||||||
|
<InputText id="email-input" class="form-control" @bind-Value="@_data.Email"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="phone-number-input">
|
||||||
|
Phone number:
|
||||||
|
</label>
|
||||||
|
<InputText id="phone-number-input" class="form-control" @bind-Value="@_data.PhoneNumber"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="address-input">
|
||||||
|
Address:
|
||||||
|
</label>
|
||||||
|
<InputText id="address-input" class="form-control" @bind-Value="@_data.Address"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pesel-input">
|
||||||
|
PESEL:
|
||||||
|
</label>
|
||||||
|
<InputText id="pesel-input" maxlength="11" minlength="11" class="form-control" @bind-Value="@_data.PESEL"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="id-card-number-input">
|
||||||
|
Id card number:
|
||||||
|
</label>
|
||||||
|
<InputText id="id-card-number-input" class="form-control" maxlength="9" minlength="9" @bind-Value="@_data.IdCardNumber"></InputText>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button class="btn btn-primary" type="submit">Submit</button>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_message))
|
||||||
|
{
|
||||||
|
<p class="text-red">Error: @_message</p>
|
||||||
|
}
|
||||||
|
</EditForm>
|
||||||
}
|
}
|
||||||
</EditForm>
|
else
|
||||||
|
{
|
||||||
|
<p><strong>New client account was created</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Client code:</strong></td>
|
||||||
|
<td>@($"{_id:00000000}")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>First name:</strong></td>
|
||||||
|
<td>@_data.FirstName</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Last name:</strong></td>
|
||||||
|
<td>@_data.LastName</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Email:</strong></td>
|
||||||
|
<td>@_data.Email</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Phone number:</strong></td>
|
||||||
|
<td>@_data.PhoneNumber</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Address:</strong></td>
|
||||||
|
<td>@_data.Address</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>PESEL:</strong></td>
|
||||||
|
<td>@_data.PESEL</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Id card number:</strong></td>
|
||||||
|
<td>@_data.IdCardNumber</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Password:</strong></td>
|
||||||
|
<td>******** (Information passed on to the client - LOG)</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<button class="btn btn-secondary" @onclick="NavigateToAdminPanel">Go back to admin panel</button>
|
||||||
|
<button class="btn btn-primary" @onclick="NavigateToNewForm">Create next account</button>
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<p><strong>New client account was created</strong></p>
|
<p>Waiting for authorization...</p>
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Client code:</strong></td>
|
|
||||||
<td>@($"{_id:00000000}")</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>First name:</strong></td>
|
|
||||||
<td>@_data.FirstName</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Last name:</strong></td>
|
|
||||||
<td>@_data.LastName</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Email:</strong></td>
|
|
||||||
<td>@_data.Email</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Phone number:</strong></td>
|
|
||||||
<td>@_data.PhoneNumber</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Password:</strong></td>
|
|
||||||
<td>******** (Information passed on to the client - LOG)</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<br/>
|
|
||||||
<button class="btn btn-secondary" @onclick="NavigateToAdminPanel">Go back to admin panel</button>
|
|
||||||
<button class="btn btn-primary" @onclick="NavigateToNewForm">Create next account</button>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
#region SERVICES
|
#region SERVICES
|
||||||
|
|
||||||
@@ -81,6 +127,9 @@ else
|
|||||||
[Inject]
|
[Inject]
|
||||||
protected NavigationManager _navigationManager { get; set; }
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -93,6 +142,10 @@ else
|
|||||||
|
|
||||||
protected string? _message;
|
protected string? _message;
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -104,12 +157,28 @@ else
|
|||||||
_data = new CreateAccountRequest();
|
_data = new CreateAccountRequest();
|
||||||
_id = null;
|
_id = null;
|
||||||
_message = null;
|
_message = null;
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task Submit()
|
protected async Task Submit()
|
||||||
{
|
{
|
||||||
APIResponse<int> response = await _accountsService.CreateAccount(_data);
|
APIResponse<int> response = await _accountsService.CreateAccount(_data);
|
||||||
if (response.Success)
|
if (response.Status == ResponseStatus.Ok)
|
||||||
{
|
{
|
||||||
_id = response.Data;
|
_id = response.Data;
|
||||||
}
|
}
|
||||||
@@ -121,12 +190,12 @@ else
|
|||||||
|
|
||||||
protected void NavigateToNewForm()
|
protected void NavigateToNewForm()
|
||||||
{
|
{
|
||||||
OnInitialized();
|
_navigationManager.Refresh(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void NavigateToAdminPanel()
|
protected void NavigateToAdminPanel()
|
||||||
{
|
{
|
||||||
_navigationManager.NavigateTo("/"); //TODO: Zmienić na /admin
|
_navigationManager.NavigateTo("/admin", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -2,94 +2,142 @@
|
|||||||
|
|
||||||
<h3>Login</h3>
|
<h3>Login</h3>
|
||||||
|
|
||||||
@if (!_clientCodeAccepted)
|
|
||||||
{
|
|
||||||
<p>Enter your client code:</p>
|
|
||||||
|
|
||||||
<form>
|
@switch (_state)
|
||||||
<InputText @bind-Value="_clientCodeArr[0]" class="single-input" data-index="1" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[1]" class="single-input" data-index="2" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[2]" class="single-input" data-index="3" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[3]" class="single-input" data-index="4" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[4]" class="single-input" data-index="5" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[5]" class="single-input" data-index="6" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[6]" class="single-input" data-index="7" maxlength="1" required></InputText>
|
|
||||||
<InputText @bind-Value="_clientCodeArr[7]" class="single-input" data-index="8" maxlength="1" required></InputText>
|
|
||||||
</form>
|
|
||||||
<br />
|
|
||||||
<button type="submit" class="btn btn-primary" @onclick="SubmitClientCode">Next</button>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
@if (!string.IsNullOrWhiteSpace(_clientCodeMessage))
|
|
||||||
{
|
|
||||||
<p class="text-red">Error: @_clientCodeMessage</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
<p>Enter your password:</p>
|
case AuthState.CodeInput:
|
||||||
|
<p>Enter your client code:</p>
|
||||||
|
|
||||||
<form>
|
<form>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[0]" class="single-input" data-index="1" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(0))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[0]" class="single-input" data-index="1" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[1]" class="single-input" data-index="2" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(1))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[1]" class="single-input" data-index="2" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[2]" class="single-input" data-index="3" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(2))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[2]" class="single-input" data-index="3" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[3]" class="single-input" data-index="4" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(3))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[3]" class="single-input" data-index="4" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[4]" class="single-input" data-index="5" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(4))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[4]" class="single-input" data-index="5" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[5]" class="single-input" data-index="6" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(5))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[5]" class="single-input" data-index="6" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[6]" class="single-input" data-index="7" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(6))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[6]" class="single-input" data-index="7" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[7]" class="single-input" data-index="8" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(7))"></InputText>
|
<InputText @bind-Value="_clientCodeArr[7]" class="single-input" data-index="8" maxlength="1" required></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[8]" class="single-input" data-index="9" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(8))"></InputText>
|
</form>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[9]" class="single-input" data-index="10" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(9))"></InputText>
|
<br />
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[10]" class="single-input" data-index="11" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(10))"></InputText>
|
<button type="submit" class="btn btn-primary" @onclick="SubmitClientCode">Next</button>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[11]" class="single-input" data-index="12" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(11))"></InputText>
|
<br />
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[12]" class="single-input" data-index="13" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(12))"></InputText>
|
<br />
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[13]" class="single-input" data-index="14" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(13))"></InputText>
|
break;
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[14]" class="single-input" data-index="15" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(14))"></InputText>
|
case AuthState.PasswordInput:
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[15]" class="single-input" data-index="16" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(15))"></InputText>
|
<p>Enter your password:</p>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[16]" class="single-input" data-index="17" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(16))"></InputText>
|
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[17]" class="single-input" data-index="18" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(17))"></InputText>
|
<form>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[18]" class="single-input" data-index="19" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(18))"></InputText>
|
<InputText type="password" @bind-Value="_clientPasswordArr[0]" class="single-input" data-index="1" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(0))"></InputText>
|
||||||
<InputText type="password" @bind-Value="_clientPasswordArr[19]" class="single-input" data-index="20" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(19))"></InputText>
|
<InputText type="password" @bind-Value="_clientPasswordArr[1]" class="single-input" data-index="2" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(1))"></InputText>
|
||||||
</form>
|
<InputText type="password" @bind-Value="_clientPasswordArr[2]" class="single-input" data-index="3" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(2))"></InputText>
|
||||||
<br />
|
<InputText type="password" @bind-Value="_clientPasswordArr[3]" class="single-input" data-index="4" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(3))"></InputText>
|
||||||
<button class="btn" @onclick="OnInitialized">Back</button>
|
<InputText type="password" @bind-Value="_clientPasswordArr[4]" class="single-input" data-index="5" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(4))"></InputText>
|
||||||
<button type="submit" class="btn btn-primary" @onclick="SubmitClientPassword">Submit</button>
|
<InputText type="password" @bind-Value="_clientPasswordArr[5]" class="single-input" data-index="6" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(5))"></InputText>
|
||||||
@if (!string.IsNullOrWhiteSpace(_clientPasswordMessage))
|
<InputText type="password" @bind-Value="_clientPasswordArr[6]" class="single-input" data-index="7" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(6))"></InputText>
|
||||||
{
|
<InputText type="password" @bind-Value="_clientPasswordArr[7]" class="single-input" data-index="8" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(7))"></InputText>
|
||||||
<p class="text-red">Error: @_clientPasswordMessage</p>
|
<InputText type="password" @bind-Value="_clientPasswordArr[8]" class="single-input" data-index="9" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(8))"></InputText>
|
||||||
}
|
<InputText type="password" @bind-Value="_clientPasswordArr[9]" class="single-input" data-index="10" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(9))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[10]" class="single-input" data-index="11" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(10))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[11]" class="single-input" data-index="12" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(11))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[12]" class="single-input" data-index="13" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(12))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[13]" class="single-input" data-index="14" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(13))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[14]" class="single-input" data-index="15" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(14))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[15]" class="single-input" data-index="16" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(15))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[16]" class="single-input" data-index="17" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(16))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[17]" class="single-input" data-index="18" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(17))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[18]" class="single-input" data-index="19" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(18))"></InputText>
|
||||||
|
<InputText type="password" @bind-Value="_clientPasswordArr[19]" class="single-input" data-index="20" maxlength="1" disabled="@(!_loginRequest?.Indexes.Contains(19))"></InputText>
|
||||||
|
</form>
|
||||||
|
<br />
|
||||||
|
<button class="btn" @onclick="OnInitialized">Back</button>
|
||||||
|
<button type="submit" class="btn btn-primary" @onclick="SubmitClientPassword">Submit</button>
|
||||||
|
break;
|
||||||
|
case AuthState.PasswordChange:
|
||||||
|
<p>Change temporary password:</p>
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password1-input">
|
||||||
|
Password:
|
||||||
|
</label>
|
||||||
|
<InputText id="password1-input" class="form-control" type="password" @bind-Value="_password1" data-index="1" minlength="8" maxlength="20"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password2-input">
|
||||||
|
Confirm password:
|
||||||
|
</label>
|
||||||
|
<InputText id="password2-input" class="form-control" type="password" @bind-Value="_password2" data-index="1" minlength="8" maxlength="20"></InputText>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
<button class="btn" @onclick="OnInitialized">Back</button>
|
||||||
|
<button type="submit" class="btn btn-primary" @onclick="SubmitChangePassword">Submit</button>
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_messageError))
|
||||||
|
{
|
||||||
|
<p class="text-red">Error: @_messageError</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_messageSuccess))
|
||||||
|
{
|
||||||
|
<p class="text-green">@_messageSuccess</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
protected enum AuthState
|
||||||
|
{
|
||||||
|
CodeInput,
|
||||||
|
PasswordInput,
|
||||||
|
PasswordChange
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region SERVICES
|
#region SERVICES
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected IAccountsService _accountService { get; set; }
|
protected IAccountsService _accountService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected AuthenticationHelper _authenticationHelper { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected HttpClient _httpClient { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region FIELDS
|
#region FIELDS
|
||||||
|
|
||||||
protected bool _clientCodeAccepted;
|
protected AuthState _state;
|
||||||
|
|
||||||
protected string? _clientCodeMessage;
|
protected string? _messageError;
|
||||||
|
protected string? _messageSuccess;
|
||||||
|
|
||||||
protected string[] _clientCodeArr;
|
protected string[] _clientCodeArr;
|
||||||
protected string _clientCode => string.Join(string.Empty, _clientCodeArr);
|
protected string _clientCode => string.Join(string.Empty, _clientCodeArr);
|
||||||
|
|
||||||
protected string[] _clientPasswordArr;
|
protected string[] _clientPasswordArr;
|
||||||
protected string _clientPassword => string.Join(string.Empty, _clientPasswordArr);
|
protected string _clientPassword => string.Join(string.Empty, _clientPasswordArr);
|
||||||
protected byte _clientPasswordFailCount;
|
|
||||||
|
|
||||||
protected string? _clientPasswordMessage;
|
|
||||||
|
|
||||||
protected GetPasswordVariantResponse? _loginRequest;
|
protected GetPasswordVariantResponse? _loginRequest;
|
||||||
|
|
||||||
|
protected int _accountId;
|
||||||
|
|
||||||
|
protected string _password1;
|
||||||
|
protected string _password2;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -98,53 +146,116 @@ else
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_clientCodeAccepted = false;
|
_state = AuthState.CodeInput;
|
||||||
_clientCodeMessage = null;
|
_messageError = null;
|
||||||
|
_messageSuccess = null;
|
||||||
_clientCodeArr = new string[8];
|
_clientCodeArr = new string[8];
|
||||||
_clientPasswordArr = new string[20];
|
_clientPasswordArr = new string[20];
|
||||||
_clientPasswordMessage = null;
|
|
||||||
_loginRequest = null;
|
_loginRequest = null;
|
||||||
|
_accountId = 0;
|
||||||
|
_password1 = string.Empty;
|
||||||
|
_password2 = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async void SubmitClientCode()
|
protected async void SubmitClientCode()
|
||||||
{
|
{
|
||||||
if (_clientCode.Length == 8 && int.TryParse(_clientCode, out int accountId))
|
if (_clientCode.Length == 8 && int.TryParse(_clientCode, out _accountId))
|
||||||
{
|
{
|
||||||
APIResponse<GetPasswordVariantResponse> loginRequest = await _accountService.GetPasswordVariant(accountId);
|
APIResponse<GetPasswordVariantResponse> loginRequest = await _accountService.GetPasswordVariant(_accountId);
|
||||||
|
|
||||||
if (loginRequest.Success)
|
if (loginRequest.Status == ResponseStatus.Ok)
|
||||||
{
|
{
|
||||||
if (loginRequest.Data.ValidTo < DateTime.Now)
|
if (loginRequest.Data.ValidTo < DateTime.Now)
|
||||||
{
|
{
|
||||||
_clientCodeMessage = "Your login request has already expired. Check your internet connection";
|
_messageError = "Your login request has already expired. Check your internet connection";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_clientCodeAccepted = true;
|
_state = AuthState.PasswordInput;
|
||||||
_loginRequest = loginRequest.Data;
|
_loginRequest = loginRequest.Data;
|
||||||
|
_messageError = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_clientCodeMessage = loginRequest.Message;
|
_messageError = loginRequest.Message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_clientCodeMessage = "Wrong client code format";
|
_messageError = "Wrong client code format";
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SubmitClientPassword()
|
protected async Task SubmitClientPassword()
|
||||||
{
|
{
|
||||||
if (_clientPassword.Length == _loginRequest.Indexes.Length)
|
if (_clientPassword.Length == _loginRequest.Indexes.Length)
|
||||||
{
|
{
|
||||||
|
AuthenticationRequest requestData = new AuthenticationRequest
|
||||||
|
{
|
||||||
|
LoginRequestId = _loginRequest.LoginRequestId,
|
||||||
|
Password = _clientPassword,
|
||||||
|
};
|
||||||
|
|
||||||
|
APIResponse<string> response = await _accountService.Authentication(_accountId, requestData);
|
||||||
|
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
if (response.ActionCode == 2)
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", response.Data);
|
||||||
|
_state = AuthState.PasswordChange;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _authenticationHelper.SaveToken(response.Data);
|
||||||
|
_messageSuccess = "Login succedeed. You will be redirected in 5 seconds";
|
||||||
|
StateHasChanged();
|
||||||
|
await Task.Delay(5000);
|
||||||
|
_navigationManager.NavigateTo("/", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (response.ActionCode)
|
||||||
|
{
|
||||||
|
case 1: OnInitialized(); StateHasChanged(); break;
|
||||||
|
}
|
||||||
|
_messageError = response.Message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_clientPasswordMessage = "Not all fields were filled";
|
_messageError = "Not all fields were filled";
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SubmitChangePassword()
|
||||||
|
{
|
||||||
|
if (string.Equals(_password1, _password2))
|
||||||
|
{
|
||||||
|
ChangePasswordRequest data = new ChangePasswordRequest { Password = _password1 };
|
||||||
|
APIResponse response = await _accountService.ChangePassword(data);
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_messageError = null;
|
||||||
|
_messageSuccess = "Password has been changed. Please, login again. You will be redirected in 5 seconds";
|
||||||
|
StateHasChanged();
|
||||||
|
Task redirectionTimer = Task.Delay(5000);
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = null;
|
||||||
|
await redirectionTimer;
|
||||||
|
OnInitialized();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_messageError = response.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_messageError = "Password fields does not match";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
<PageTitle>Home</PageTitle>
|
||||||
|
|
||||||
<h1>Hello, world!</h1>
|
<h1>Hello</h1>
|
||||||
|
|
||||||
Welcome to your new app.
|
Welcome in SecureBank.
|
||||||
|
|||||||
196
SecureBank/Components/Pages/User/AccountDetails.razor
Normal file
196
SecureBank/Components/Pages/User/AccountDetails.razor
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
@page "/account-details"
|
||||||
|
|
||||||
|
<h3>Account details</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null)
|
||||||
|
{
|
||||||
|
@switch (_dataLoadedState)
|
||||||
|
{
|
||||||
|
case DataLoadState.Loading:
|
||||||
|
<p>Waiting for data...</p>
|
||||||
|
break;
|
||||||
|
case DataLoadState.NotLoaded:
|
||||||
|
<p>Data cannot be loaded. Try again later.</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
break;
|
||||||
|
case DataLoadState.Loaded:
|
||||||
|
<h4>Personal data:</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong>First name:</strong></td>
|
||||||
|
<td>@_data.FirstName</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Last name:</strong></td>
|
||||||
|
<td>@_data.LastName</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Email:</strong></td>
|
||||||
|
<td>@_data.Email</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Phone number:</strong></td>
|
||||||
|
<td>@_data.PhoneNumber</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Address:</strong></td>
|
||||||
|
<td>@_data.Address</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>IBAN:</strong></td>
|
||||||
|
<td>@_data.IBAN</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<h4>Id card data:</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong>PESEL:</strong></td>
|
||||||
|
<td>@(_hideIdCardData ? "***********" : _data.PESEL)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Id card number:</strong></td>
|
||||||
|
<td>@(_hideIdCardData ? "*********" : _data.IdCardNumber)</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button class="btn btn-secondary" @onclick="@(() => {_hideIdCardData = !_hideIdCardData; StateHasChanged();})">@(_hideIdCardData ? "Show" : "Hide")</button>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<h4>Debit card data:</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Card number:</strong></td>
|
||||||
|
<td>@(_hideDebitCardData ? "****************" : _data.CardNumber)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Expiration date:</strong></td>
|
||||||
|
<td>@(_hideDebitCardData ? "**/**" : _data.CardExpirationDate)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>CVV:</strong></td>
|
||||||
|
<td>@(_hideDebitCardData ? "***" : _data.CardCVV)</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button class="btn btn-secondary" @onclick="@(() => {_hideDebitCardData = !_hideDebitCardData; StateHasChanged();})">@(_hideDebitCardData ? "Show" : "Hide")</button>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<h4>Options:</h4>
|
||||||
|
<button class="btn btn-primary" @onclick="@ChangePassword">Change password</button>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
protected enum DataLoadState
|
||||||
|
{
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
NotLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IAccountsService _accountsService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IBalanceService _balanceService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ITransfersService _transfersService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
protected DataLoadState _dataLoadedState;
|
||||||
|
|
||||||
|
protected AccountResponse _data;
|
||||||
|
|
||||||
|
protected bool _hideIdCardData;
|
||||||
|
protected bool _hideDebitCardData;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
_dataLoadedState = DataLoadState.Loading;
|
||||||
|
_data = null;
|
||||||
|
_hideIdCardData = true;
|
||||||
|
_hideDebitCardData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (_authLoaded)
|
||||||
|
{
|
||||||
|
APIResponse<IEnumerable<AccountResponse>> dataResponse = await _accountsService.GetAccounts(id: _claims.AccountId);
|
||||||
|
if (dataResponse.Status == ResponseStatus.Ok && dataResponse.Data.Count() == 1)
|
||||||
|
{
|
||||||
|
_data = dataResponse.Data.ElementAt(0);
|
||||||
|
_dataLoadedState = DataLoadState.Loaded;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_dataLoadedState = DataLoadState.NotLoaded;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ChangePassword()
|
||||||
|
{
|
||||||
|
_navigationManager.NavigateTo("/account-details/change-password", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
140
SecureBank/Components/Pages/User/ChangePassword.razor
Normal file
140
SecureBank/Components/Pages/User/ChangePassword.razor
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
@page "/account-details/change-password"
|
||||||
|
|
||||||
|
<h3>Change password</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null)
|
||||||
|
{
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password1-input">
|
||||||
|
Password:
|
||||||
|
</label>
|
||||||
|
<InputText id="password1-input" class="form-control" type="password" @bind-Value="_password1" data-index="1" minlength="8" maxlength="20"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password2-input">
|
||||||
|
Confirm password:
|
||||||
|
</label>
|
||||||
|
<InputText id="password2-input" class="form-control" type="password" @bind-Value="_password2" data-index="1" minlength="8" maxlength="20"></InputText>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<br />
|
||||||
|
<button type="submit" class="btn btn-primary" @onclick="SubmitChangePassword">Submit</button>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_messageError))
|
||||||
|
{
|
||||||
|
<p class="text-red">Error: @_messageError</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_messageSuccess))
|
||||||
|
{
|
||||||
|
<p class="text-green">@_messageSuccess</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IAccountsService _accountsService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected AuthenticationHelper _authenticationHelper { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected HttpClient _httpClient { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
protected string? _messageError;
|
||||||
|
protected string? _messageSuccess;
|
||||||
|
|
||||||
|
protected string _password1;
|
||||||
|
protected string _password2;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
_messageError = null;
|
||||||
|
_messageSuccess = null;
|
||||||
|
_password1 = string.Empty;
|
||||||
|
_password2 = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SubmitChangePassword()
|
||||||
|
{
|
||||||
|
if (string.Equals(_password1, _password2))
|
||||||
|
{
|
||||||
|
ChangePasswordRequest data = new ChangePasswordRequest { Password = _password1 };
|
||||||
|
APIResponse response = await _accountsService.ChangePassword(data);
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_messageError = null;
|
||||||
|
_messageSuccess = "Password has been changed. You will be logged out in 5 seconds.";
|
||||||
|
StateHasChanged();
|
||||||
|
Task redirectionTimer = Task.Delay(5000);
|
||||||
|
await _authenticationHelper.RemoveToken();
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = null;
|
||||||
|
await redirectionTimer;
|
||||||
|
_navigationManager.NavigateTo("/", true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_messageError = response.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_messageError = "Password fields does not match";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
255
SecureBank/Components/Pages/User/CreateTransfer.razor
Normal file
255
SecureBank/Components/Pages/User/CreateTransfer.razor
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
@page "/create-transfer"
|
||||||
|
|
||||||
|
<h3>Create new transfer</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null)
|
||||||
|
{
|
||||||
|
@switch (_stage)
|
||||||
|
{
|
||||||
|
case (Stage.Form):
|
||||||
|
<EditForm Model="@_data" OnSubmit="SubmitToValidation">
|
||||||
|
<br />
|
||||||
|
<p><strong>Receiver data:</strong></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="receiver-account-number-input">
|
||||||
|
Receiver account number:
|
||||||
|
</label>
|
||||||
|
<InputText id="receiver-account-number-input" class="form-control" minlength="26" maxlength="26" @bind-Value="@_data.ReceiverAccountNumber"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="receiver-name-input">
|
||||||
|
Receiver name:
|
||||||
|
</label>
|
||||||
|
<InputText id="receiver-name-input" class="form-control" @bind-Value="@_data.ReceiverName"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="receiver-address-input">
|
||||||
|
Receiver address:
|
||||||
|
</label>
|
||||||
|
<InputText id="receiver-address-input" class="form-control" @bind-Value="@_data.ReceiverAddress"></InputText>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p><strong>Transfer data:</strong></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="title-input">
|
||||||
|
Title:
|
||||||
|
</label>
|
||||||
|
<InputText id="title-input" class="form-control" @bind-Value="@_data.Title"></InputText>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="amount-input">
|
||||||
|
Amount (PLN):
|
||||||
|
</label>
|
||||||
|
<InputNumber id="amount-input" class="form-control" @bind-Value="@_data.Amount"></InputNumber>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<button type="submit" class="btn btn-primary">Submit to validation</button>
|
||||||
|
</EditForm>
|
||||||
|
break;
|
||||||
|
case (Stage.Validated):
|
||||||
|
<p><strong>Receiver data:</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Account number:</td>
|
||||||
|
<td>@_data.ReceiverAccountNumber</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Name:</td>
|
||||||
|
<td>@(_data.ReceiverName ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Address:</td>
|
||||||
|
<td>@(_data.ReceiverAddress ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p><strong>Transfer data:</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Title:</td>
|
||||||
|
<td>@(_data.Title ?? "<EMPTY>")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Amount:</td>
|
||||||
|
<td>@_data.Amount PLN</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Account balance after transfer:</td>
|
||||||
|
<td>@_senderAmount</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="submit" @onclick="@SubmitTransfer">Submit</button>
|
||||||
|
break;
|
||||||
|
case (Stage.Accepted):
|
||||||
|
<p>Transfer for amount @_data.Amount PLN was successfully sent from account to account with number @_data.ReceiverAccountNumber</p>
|
||||||
|
<button class="btn btn-primary" @onclick="@NavigateToNewForm">Create next transfer</button>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_errorMessage))
|
||||||
|
{
|
||||||
|
<p class="text-red">Error: @_errorMessage</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
protected enum Stage
|
||||||
|
{
|
||||||
|
Form,
|
||||||
|
Validated,
|
||||||
|
Accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IAccountsService _accountsService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IBalanceService _balanceService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ITransfersService _transfersService { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
protected Stage _stage;
|
||||||
|
|
||||||
|
protected string? _errorMessage;
|
||||||
|
|
||||||
|
protected CreateUserTransferRequest _data;
|
||||||
|
|
||||||
|
protected string _senderAmount;
|
||||||
|
protected string _receiverAmount;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
_stage = Stage.Form;
|
||||||
|
_errorMessage = null;
|
||||||
|
_data = new CreateUserTransferRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async void SubmitToValidation()
|
||||||
|
{
|
||||||
|
if (_data.ReceiverAccountNumber is null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Receiver account number cannot be empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_data.ReceiverAccountNumber.All(x => char.IsDigit(x)))
|
||||||
|
{
|
||||||
|
_errorMessage = "Wrong receiver account number format. Account number consists only of digits";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.ReceiverAccountNumber.Length != 26)
|
||||||
|
{
|
||||||
|
_errorMessage = "Wrong receiver account number format. Account number must have length of 26 digits";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.Amount <= 0)
|
||||||
|
{
|
||||||
|
_errorMessage = "Transfer amount has to be greater than 0 PLN";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.Amount = Math.Round(_data.Amount, 2, MidpointRounding.ToEven);
|
||||||
|
|
||||||
|
APIResponse<decimal> senderAmountResponse = await _balanceService.GetAccountBalance(_claims.AccountId);
|
||||||
|
if (senderAmountResponse.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_senderAmount = $"{(senderAmountResponse.Data - _data.Amount):F2} PLN";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_senderAmount = "<ERROR: Cannot get sender account balance>";
|
||||||
|
}
|
||||||
|
|
||||||
|
_stage = Stage.Validated;
|
||||||
|
_errorMessage = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SubmitTransfer()
|
||||||
|
{
|
||||||
|
APIResponse response = await _transfersService.CreateUserTransfer(_data);
|
||||||
|
if (response.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_errorMessage = null;
|
||||||
|
_stage = Stage.Accepted;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorMessage = response.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void NavigateToNewForm()
|
||||||
|
{
|
||||||
|
_navigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
152
SecureBank/Components/Pages/User/Dashboard.razor
Normal file
152
SecureBank/Components/Pages/User/Dashboard.razor
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
@page "/dashboard"
|
||||||
|
|
||||||
|
<h3>Dashboard</h3>
|
||||||
|
|
||||||
|
@if (_authLoaded)
|
||||||
|
{
|
||||||
|
@if (_claims is not null)
|
||||||
|
{
|
||||||
|
@switch (_dataLoadedState)
|
||||||
|
{
|
||||||
|
case DataLoadState.Loading:
|
||||||
|
<p>Waiting for data...</p>
|
||||||
|
break;
|
||||||
|
case DataLoadState.NotLoaded:
|
||||||
|
<p>Data cannot be loaded. Try again later.</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
break;
|
||||||
|
case DataLoadState.Loaded:
|
||||||
|
<h5>Account balance:</h5>
|
||||||
|
<p class="text-big">@($"{_balance:F2} PLN")</p>
|
||||||
|
<h5>Operation history:</h5>
|
||||||
|
<table class="table-brd">
|
||||||
|
<tr>
|
||||||
|
<th class="table-brd">Date</th>
|
||||||
|
<th class="table-brd">Title</th>
|
||||||
|
<th class="table-brd">Amount</th>
|
||||||
|
</tr>
|
||||||
|
@foreach (TransferResponse operation in _operations.OrderByDescending(x => x.Date))
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="table-brd">@operation.Date.ToString("dd.MM.yyyy HH:mm")</td>
|
||||||
|
<td class="table-brd">@(string.IsNullOrWhiteSpace(operation.Title) ? "<EMPTY>" : operation.Title)</td>
|
||||||
|
<td class="table-brd @(_account.IBAN == operation.ReceiverAccountNumber ? "text-green": "text-red")">@($"{(_account.IBAN == operation.SenderAccountNumber ? "- " : string.Empty)}{operation.Amount:F2} PLN")</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have permission to view this page</p>
|
||||||
|
<a href="/">Click here to redirect to main page</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Waiting for authorization...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
protected enum DataLoadState
|
||||||
|
{
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
NotLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IAccountsService _accountsService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected IBalanceService _balanceService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ITransfersService _transfersService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected NavigationManager _navigationManager { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected TokenAuthenticationStateProvider _authenticationStateProvider { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Claims? _claims;
|
||||||
|
|
||||||
|
protected bool _authLoaded;
|
||||||
|
|
||||||
|
protected DataLoadState _dataLoadedState;
|
||||||
|
|
||||||
|
protected decimal _balance;
|
||||||
|
protected IEnumerable<TransferResponse> _operations;
|
||||||
|
protected AccountResponse _account;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_claims = null;
|
||||||
|
_authLoaded = false;
|
||||||
|
_dataLoadedState = DataLoadState.Loading;
|
||||||
|
_balance = 0.00M;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
if (state.User.Claims.Any())
|
||||||
|
{
|
||||||
|
_claims = new Claims(state.User.Claims);
|
||||||
|
}
|
||||||
|
_authLoaded = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (_authLoaded)
|
||||||
|
{
|
||||||
|
APIResponse<decimal> balanceResponse = await _balanceService.GetBalance();
|
||||||
|
if (balanceResponse.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_balance = balanceResponse.Data;
|
||||||
|
}
|
||||||
|
APIResponse<IEnumerable<TransferResponse>> operationsResponse = await _transfersService.GetTransfers();
|
||||||
|
if (operationsResponse.Status == ResponseStatus.Ok)
|
||||||
|
{
|
||||||
|
_operations = operationsResponse.Data;
|
||||||
|
}
|
||||||
|
APIResponse<IEnumerable<AccountResponse>> accountResponse = await _accountsService.GetAccounts(id: _claims.AccountId);
|
||||||
|
if (accountResponse.Status == ResponseStatus.Ok && accountResponse.Data.Count() == 1)
|
||||||
|
{
|
||||||
|
_account = accountResponse.Data.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
_dataLoadedState = balanceResponse.Status == ResponseStatus.Ok && operationsResponse.Status == ResponseStatus.Ok && accountResponse.Status == ResponseStatus.Ok ? DataLoadState.Loaded : DataLoadState.NotLoaded;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
|
@using System.Net.Http.Headers;
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using SecureBank
|
@using SecureBank
|
||||||
|
@using SecureBank.Authentication
|
||||||
@using SecureBank.Components
|
@using SecureBank.Components
|
||||||
@using SecureBank.Website.Services;
|
@using SecureBank.Website.Authentication
|
||||||
@using SecureBank.Common;
|
@using SecureBank.Website.Services
|
||||||
@using SecureBank.Common.Accounts;
|
@using SecureBank.Common
|
||||||
|
@using SecureBank.Common.Accounts
|
||||||
|
@using SecureBank.Common.Transfers
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using Blazored.SessionStorage;
|
using Blazored.SessionStorage;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using SecureBank.API.Authentication;
|
using SecureBank.API.Authentication;
|
||||||
|
using SecureBank.API.Encryption;
|
||||||
using SecureBank.Components;
|
using SecureBank.Components;
|
||||||
using SecureBank.Database;
|
using SecureBank.Database;
|
||||||
using SecureBank.Website.API;
|
using SecureBank.Website.API;
|
||||||
@@ -54,8 +56,10 @@ public class Program
|
|||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error");
|
app.UseExceptionHandler("/Error");
|
||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
app.UseHsts();
|
{
|
||||||
|
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -68,13 +72,14 @@ public class Program
|
|||||||
x.SwaggerEndpoint("/api/swagger/v1/swagger.json", "SecureBank API");
|
x.SwaggerEndpoint("/api/swagger/v1/swagger.json", "SecureBank API");
|
||||||
x.RoutePrefix = "api/swagger";
|
x.RoutePrefix = "api/swagger";
|
||||||
});
|
});
|
||||||
|
app.UseHttpsRedirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
|
|
||||||
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
@@ -83,6 +88,17 @@ public class Program
|
|||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var services = scope.ServiceProvider;
|
||||||
|
|
||||||
|
var context = services.GetRequiredService<DatabaseContext>();
|
||||||
|
if (context.Database.GetPendingMigrations().Any())
|
||||||
|
{
|
||||||
|
context.Database.Migrate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,12 +152,16 @@ public class Program
|
|||||||
|
|
||||||
// Configurations
|
// Configurations
|
||||||
_builder.Services.AddSingleton<AuthenticationConfiguration>();
|
_builder.Services.AddSingleton<AuthenticationConfiguration>();
|
||||||
|
_builder.Services.AddSingleton<EncryptionConfiguration>();
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
_builder.Services.AddSingleton<API.Authentication.AuthenticationHelper>();
|
_builder.Services.AddSingleton<API.Authentication.AuthenticationHelper>();
|
||||||
|
_builder.Services.AddSingleton<API.Encryption.EncryptionHelper>();
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
_builder.Services.AddSingleton<API.Services.IAccountsService, API.Services.AccountsService>();
|
_builder.Services.AddSingleton<API.Services.IAccountsService, API.Services.AccountsService>();
|
||||||
|
_builder.Services.AddSingleton<API.Services.IBalanceService, API.Services.BalanceService>();
|
||||||
|
_builder.Services.AddSingleton<API.Services.ITransfersService, API.Services.TransfersService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void BuildWebsite()
|
protected static void BuildWebsite()
|
||||||
@@ -166,6 +186,8 @@ public class Program
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
_builder.Services.AddSingleton<Website.Services.IAccountsService, Website.Services.AccountsService>();
|
_builder.Services.AddSingleton<Website.Services.IAccountsService, Website.Services.AccountsService>();
|
||||||
|
_builder.Services.AddSingleton<Website.Services.IBalanceService, Website.Services.BalanceService>();
|
||||||
|
_builder.Services.AddSingleton<Website.Services.ITransfersService, Website.Services.TransfersService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -30,8 +30,7 @@
|
|||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_HTTPS_PORTS": "443",
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
"ASPNETCORE_HTTP_PORTS": "80"
|
|
||||||
},
|
},
|
||||||
"publishAllPorts": true,
|
"publishAllPorts": true,
|
||||||
"useSSL": true
|
"useSSL": true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<UserSecretsId>68c8748d-7175-410d-8bd6-a8ee07e58478</UserSecretsId>
|
<UserSecretsId>68c8748d-7175-410d-8bd6-a8ee07e58478</UserSecretsId>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
<DockerfileFile>..\Dockerfile</DockerfileFile>
|
<DockerfileFile>..\Main.Dockerfile</DockerfileFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -31,14 +31,22 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SecureBank.API.Encryption\SecureBank.API.Encryption.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Authentication\SecureBank.API.Authentication.csproj" />
|
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Authentication\SecureBank.API.Authentication.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Controllers\SecureBank.API.Controllers.csproj" />
|
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Controllers\SecureBank.API.Controllers.csproj" />
|
||||||
|
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Encryption\SecureBank.API.Encryption.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Services\SecureBank.API.Services.csproj" />
|
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Services\SecureBank.API.Services.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Token\SecureBank.API.Authentication.csproj" />
|
<ProjectReference Include="..\SecureBank.API\SecureBank.API.Token\SecureBank.API.Authentication.csproj" />
|
||||||
|
<ProjectReference Include="..\SecureBank.Authentication\SecureBank.Authentication.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.Common\SecureBank.Common.csproj" />
|
<ProjectReference Include="..\SecureBank.Common\SecureBank.Common.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.Database\SecureBank.Database.csproj" />
|
<ProjectReference Include="..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||||
|
<ProjectReference Include="..\SecureBank.Website\SecureBank.Website.API\SecureBank.Website.API.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.Website\SecureBank.Website.Authentication\SecureBank.Website.Authentication.csproj" />
|
<ProjectReference Include="..\SecureBank.Website\SecureBank.Website.Authentication\SecureBank.Website.Authentication.csproj" />
|
||||||
<ProjectReference Include="..\SecureBank.Website\SecureBank.Website.Services\SecureBank.Website.Services.csproj" />
|
<ProjectReference Include="..\SecureBank.Website\SecureBank.Website.Services\SecureBank.Website.Services.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="..\.dockerignore" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"ConnectionStrings": {
|
||||||
"LogLevel": {
|
"Default": "Data Source = ../database.db"
|
||||||
"Default": "Information",
|
},
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Endpoints": {
|
||||||
}
|
"Base": "https://localhost:7143/api"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
SecureBank/appsettings.Production.json
Normal file
8
SecureBank/appsettings.Production.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Default": "Data Source = database.db"
|
||||||
|
},
|
||||||
|
"Endpoints": {
|
||||||
|
"Base": "https://localhost/api"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,21 +10,37 @@
|
|||||||
"Token": {
|
"Token": {
|
||||||
"Key": "testkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytest",
|
"Key": "testkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytest",
|
||||||
"Issuer": "SecureBank",
|
"Issuer": "SecureBank",
|
||||||
"Audience": "access",
|
"Audience": "access",
|
||||||
"Lifetime": "5"
|
"Lifetime": "5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"Encryption": {
|
||||||
"Default": "Data Source = ../database.db"
|
"Key": "NG'MAjEJ'!'bZknqdME^9|nY?x|D$=9*",
|
||||||
|
"IV": "****************"
|
||||||
},
|
},
|
||||||
"Endpoints": {
|
"Endpoints": {
|
||||||
"Base": "https://localhost:7143/api",
|
|
||||||
"Accounts": {
|
"Accounts": {
|
||||||
"Base": "/accounts",
|
"Base": "/accounts",
|
||||||
"CreateAccount": "/create-account",
|
"CreateAccount": "/create-account",
|
||||||
|
"ChangePassword": "/change-password",
|
||||||
|
"Authentication": "/authentication",
|
||||||
"AuthenticationRefresh": "/authentication-refresh",
|
"AuthenticationRefresh": "/authentication-refresh",
|
||||||
"GetPasswordVariant": "/{0}/password-variant",
|
"GetPasswordVariant": "/{0}/password-variant",
|
||||||
"Authentication": "/{0}/authentication"
|
"GetAccounts": "",
|
||||||
|
"ResetPassword": "/{0}/reset-password",
|
||||||
|
"UnlockAccount": "/{0}/unlock"
|
||||||
|
},
|
||||||
|
"Balance": {
|
||||||
|
"Base": "/balance",
|
||||||
|
"GetAccountBalance": "/{0}",
|
||||||
|
"GetBalance": ""
|
||||||
|
},
|
||||||
|
"Transfers": {
|
||||||
|
"Base": "/transfers",
|
||||||
|
"GetTransfers": "",
|
||||||
|
"GetUserTransfers": "/{0}",
|
||||||
|
"CreateAdminTransfer": "/admin-transfer",
|
||||||
|
"CreateUserTransfer": "/user-transfer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,3 +57,25 @@ h1:focus {
|
|||||||
.text-red {
|
.text-red {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-green {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-big {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-center {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
-ms-transform: translateY(-50%);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-brd {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-collapse: collapse;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
BIN
database-default.db
Normal file
BIN
database-default.db
Normal file
Binary file not shown.
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
securebank-proxy:
|
||||||
|
container_name: securebank-proxy
|
||||||
|
build:
|
||||||
|
dockerfile: Nginx.Dockerfile
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
securebank-website:
|
||||||
|
container_name: securebank-website
|
||||||
|
depends_on:
|
||||||
|
- securebank-proxy
|
||||||
|
build:
|
||||||
|
dockerfile: Main.Dockerfile
|
||||||
|
expose:
|
||||||
|
- "8080:8080"
|
||||||
|
restart: always
|
||||||
Reference in New Issue
Block a user