init
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Authentication
|
||||
{
|
||||
public class AuthenticationConfiguration
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
// Token
|
||||
public string TokenKey { get; private set; }
|
||||
public string TokenIssuer { get; private set; }
|
||||
public string TokenAudience { get; private set; }
|
||||
public int TokenLifetime { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationConfiguration(IConfiguration configuration)
|
||||
{
|
||||
TokenKey = configuration.GetSection("Authentication").GetSection("Token")["Key"];
|
||||
TokenIssuer = configuration.GetSection("Authentication").GetSection("Token")["Issuer"];
|
||||
TokenAudience = configuration.GetSection("Authentication").GetSection("Token")["Audience"];
|
||||
TokenLifetime = int.Parse(configuration.GetSection("Authentication").GetSection("Token")["Lifetime"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using SecureBank.Database;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Authentication
|
||||
{
|
||||
public class AuthenticationHelper
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private DatabaseContext _database;
|
||||
private AuthenticationConfiguration _configuration;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationHelper(DatabaseContext database, AuthenticationConfiguration configuration)
|
||||
{
|
||||
_database = database;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
public string GenerateToken(Guid tokenId, int accountId, bool oneTimeToken = false)
|
||||
{
|
||||
DateTime expirationTime = DateTime.UtcNow.AddMinutes(_configuration.TokenLifetime);
|
||||
|
||||
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Jti, tokenId.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Sub, accountId.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.ToString()),
|
||||
new Claim("one_time_token", oneTimeToken.ToString()),
|
||||
new Claim("admin", "false"), //TODO: w zależności od użytkownika
|
||||
}),
|
||||
Expires = expirationTime,
|
||||
Issuer = _configuration.TokenIssuer,
|
||||
Audience = _configuration.TokenAudience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.TokenKey)), SecurityAlgorithms.HmacSha512)
|
||||
};
|
||||
|
||||
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
|
||||
handler.InboundClaimTypeMap.Clear();
|
||||
|
||||
SecurityToken token = handler.CreateToken(tokenDescriptor);
|
||||
|
||||
return handler.WriteToken(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,96 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Identity.Client;
|
||||
using SecureBank.API.Services;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Common.Accounts;
|
||||
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/accounts")]
|
||||
public class AccountsController : ControllerBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private IAccountsService _accountsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AccountsController(IAccountsService accountsService)
|
||||
{
|
||||
_accountsService = accountsService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
[HttpPost]
|
||||
[Route("create-account")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<APIResponse<int>>> CreateAccount([FromBody] CreateAccountRequest data)
|
||||
{
|
||||
APIResponse<int> response = await _accountsService.CreateAccount(data);
|
||||
if (response.Success)
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{account_id}/password-variant")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<APIResponse<GetPasswordVariantResponse>>> GetPasswordVariant([FromRoute(Name = "account_id")] int accountId)
|
||||
{
|
||||
APIResponse<GetPasswordVariantResponse> response = await _accountsService.GetPasswordVariant(accountId);
|
||||
if (response.Success)
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{account_id}/authentication")]
|
||||
[AllowAnonymous]
|
||||
/*
|
||||
* Action codes:
|
||||
* 1 - Go back to client code input
|
||||
* 2 - Failed login count increment
|
||||
*/
|
||||
public async Task<ActionResult<APIResponse<string>>> Authentication([FromRoute(Name = "account_id")] int accountId, [FromBody] AuthenticationRequest data)
|
||||
{
|
||||
APIResponse<string> response = await _accountsService.Authentication(accountId, data);
|
||||
if (response.Success)
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"SecureBank.API": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Common\SecureBank.Common.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Services\SecureBank.API.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
SecureBank.API/SecureBank.API.Helpers/Check.cs
Normal file
14
SecureBank.API/SecureBank.API.Helpers/Check.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Helpers
|
||||
{
|
||||
public class Check<T>
|
||||
{
|
||||
public Predicate<T> CheckAction { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
383
SecureBank.API/SecureBank.API.Services/AccountsService.cs
Normal file
383
SecureBank.API/SecureBank.API.Services/AccountsService.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SecureBank.API.Helpers;
|
||||
using SecureBank.API.Authentication;
|
||||
using SecureBank.Common;
|
||||
using SecureBank.Common.Accounts;
|
||||
using SecureBank.Database;
|
||||
using SecureBank.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureBank.API.Services
|
||||
{
|
||||
public interface IAccountsService
|
||||
{
|
||||
Task<APIResponse<int>> CreateAccount(CreateAccountRequest data);
|
||||
Task<APIResponse<GetPasswordVariantResponse>> GetPasswordVariant(int accountId);
|
||||
Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class AccountsService : IAccountsService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private AuthenticationHelper _authenticationHelper;
|
||||
|
||||
private DatabaseContext _database;
|
||||
|
||||
private ILogger<AccountsService> _logger;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AccountsService(AuthenticationHelper authenticationHelper, DatabaseContext database, ILogger<AccountsService> logger)
|
||||
{
|
||||
_authenticationHelper = authenticationHelper;
|
||||
|
||||
_database = database;
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<APIResponse<int>> CreateAccount(CreateAccountRequest data)
|
||||
{
|
||||
Check<CreateAccountRequest>[] checks = new Check<CreateAccountRequest>[]
|
||||
{
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => x is null),
|
||||
Message = "Body cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.FirstName)),
|
||||
Message = "First name cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.LastName)),
|
||||
Message = "Last name cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.Email)),
|
||||
Message = "Email cannot be empty"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
MailAddress m = new MailAddress(x.Email);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
Message = "Invalid email"
|
||||
},
|
||||
new Check<CreateAccountRequest>
|
||||
{
|
||||
CheckAction = new Predicate<CreateAccountRequest>((x) => string.IsNullOrWhiteSpace(x.PhoneNumber)),
|
||||
Message = "Phone number cannot be empty"
|
||||
},
|
||||
};
|
||||
|
||||
foreach (Check<CreateAccountRequest> check in checks)
|
||||
{
|
||||
if (check.CheckAction.Invoke(data))
|
||||
{
|
||||
return new APIResponse<int>
|
||||
{
|
||||
Message = check.Message,
|
||||
Success = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Account account = new Account
|
||||
{
|
||||
FirstName = data.FirstName,
|
||||
LastName = data.LastName,
|
||||
Email = data.Email,
|
||||
PhoneNumber = data.PhoneNumber.Replace(" ", string.Empty),
|
||||
};
|
||||
await _database.Accounts.AddAsync(account);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
string password = GeneratePassword();
|
||||
|
||||
await GeneratePasswordVariants(password, account.Id);
|
||||
|
||||
//Send client code and temporary password to client by mail
|
||||
_logger.LogInformation($"INFO DIRECTLY TO CLIENT: Your client code is {account.Id:00000000}. Your temporary password is {password}. You will be prompted to change it at first login");
|
||||
|
||||
return new APIResponse<int>
|
||||
{
|
||||
Data = account.Id,
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse<GetPasswordVariantResponse>> GetPasswordVariant(int accountId)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
if (account.LoginFailedCount >= 3)
|
||||
{
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"The number of failed login attempts for this account has exceeded 3. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
if (account.LockReason is not null)
|
||||
{
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerable<AccountPassword> accountPasswords = await _database.AccountPasswords.Where(x => x.AccountId == accountId).ToArrayAsync();
|
||||
int randomIndex = Random.Shared.Next(0, accountPasswords.Count());
|
||||
AccountPassword passwordVariant = accountPasswords.ElementAt(randomIndex);
|
||||
|
||||
AccountPasswordIndex[] indexes = await _database.AccountPasswordIndexes.Where(x => x.AccountPasswordId == passwordVariant.Id).ToArrayAsync();
|
||||
|
||||
DateTime validTo = DateTime.Now.AddMinutes(5);
|
||||
AccountLoginRequest loginRequest = new AccountLoginRequest
|
||||
{
|
||||
AccountPasswordId = passwordVariant.Id,
|
||||
ValidTo = validTo,
|
||||
};
|
||||
await _database.AccountLoginRequests.AddAsync(loginRequest);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse<GetPasswordVariantResponse>
|
||||
{
|
||||
Success = true,
|
||||
Data = new GetPasswordVariantResponse
|
||||
{
|
||||
LoginRequestId = loginRequest.Id,
|
||||
Indexes = indexes.Select(x => (int)x.Index).ToArray(),
|
||||
ValidTo = validTo
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<APIResponse<string>> Authentication(int accountId, AuthenticationRequest data)
|
||||
{
|
||||
Account? account = await _database.Accounts.FirstOrDefaultAsync(x => x.Id == accountId);
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Account does not exists"
|
||||
};
|
||||
}
|
||||
|
||||
AccountLoginRequest? loginRequest = await _database.AccountLoginRequests.FirstOrDefaultAsync(x => x.Id == data.LoginRequestId);
|
||||
|
||||
if (loginRequest is null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Login request does not exist"
|
||||
};
|
||||
}
|
||||
|
||||
AccountPassword password = loginRequest.AccountPassword;
|
||||
|
||||
Account loginRequestAccount = password.Account;
|
||||
|
||||
if (loginRequestAccount.Id != account.Id)
|
||||
{
|
||||
account.LockReason = "Suspicious login attempt. The account provided does not match the account to which the login request is assigned.";
|
||||
loginRequestAccount.LockReason = "Suspicious login attempt. The account provided does not match the account to which the login request is assigned.";
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Suspicious activity was detected during login. The account provided does not match the account to which the login request is assigned. Both accounts have been blocked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
if (account.LockReason is not null)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
|
||||
};
|
||||
}
|
||||
|
||||
if (loginRequest.ValidTo < DateTime.Now)
|
||||
{
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
ActionCode = 1,
|
||||
Message = $"Login request has expired. Go back and try again."
|
||||
};
|
||||
}
|
||||
|
||||
byte[] passwordDb = password.Password;
|
||||
byte[] passwordProvided = HashPassword(data.Password, password.LeftSalt, password.RightSalt);
|
||||
|
||||
if (Enumerable.SequenceEqual(passwordDb, passwordProvided))
|
||||
{
|
||||
account.LoginFailedCount++;
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Success = false,
|
||||
ActionCode = 2,
|
||||
Message = $"Incorrect password"
|
||||
};
|
||||
}
|
||||
|
||||
string token = _authenticationHelper.GenerateToken(Guid.NewGuid(), account.Id, account.TemporaryPassword);
|
||||
|
||||
return new APIResponse<string>
|
||||
{
|
||||
Data = token,
|
||||
Success = true,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected byte[] HashPassword(string password, string leftSalt, string rightSalt)
|
||||
{
|
||||
SHA512 sha = SHA512.Create();
|
||||
string toHash = password;
|
||||
for (int c = 0; c < 100; c++)
|
||||
{
|
||||
string before = $"{leftSalt}{toHash}{rightSalt}";
|
||||
byte[] bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(before));
|
||||
toHash = Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
return Encoding.UTF8.GetBytes(toHash);
|
||||
}
|
||||
|
||||
protected async Task GeneratePasswordVariants(string password, int accountId)
|
||||
{
|
||||
int charCount = password.Length / 2;
|
||||
|
||||
IEnumerable<int> charIndexes = Enumerable.Range(0, password.Length);
|
||||
|
||||
IEnumerable<IEnumerable<int>> indexesVariants = charIndexes.GetCombinations(charCount).OrderBy(x => Random.Shared.Next()).Take(50);
|
||||
|
||||
foreach (IEnumerable<int> indexes in indexesVariants)
|
||||
{
|
||||
List<char> chars = new List<char>();
|
||||
foreach (int i in indexes)
|
||||
{
|
||||
chars.Add(password[i]);
|
||||
}
|
||||
string leftSalt = StringExtensions.CreateRandom(20);
|
||||
string rightSalt = StringExtensions.CreateRandom(20);
|
||||
string toHash = string.Join(string.Empty, chars);
|
||||
byte[] hashed = HashPassword(toHash, leftSalt, rightSalt);
|
||||
|
||||
AccountPassword accountPassword = new AccountPassword
|
||||
{
|
||||
AccountId = accountId,
|
||||
LeftSalt = leftSalt,
|
||||
RightSalt = rightSalt,
|
||||
Password = hashed,
|
||||
};
|
||||
await _database.AccountPasswords.AddAsync(accountPassword);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
IEnumerable<AccountPasswordIndex> indexesDB = indexes.Select(x => new AccountPasswordIndex
|
||||
{
|
||||
AccountPasswordId = accountPassword.Id,
|
||||
Index = (byte)x
|
||||
});
|
||||
await _database.AccountPasswordIndexes.AddRangeAsync(indexesDB);
|
||||
await _database.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected string GeneratePassword()
|
||||
{
|
||||
string passwordDigits = StringExtensions.CreateRandom(2, "1234567890");
|
||||
string passwordSymbols = StringExtensions.CreateRandom(2, "`~!@#$%^&*()-_=+[{]};:'\"\\|,<.>/?");
|
||||
string passwordSmall = StringExtensions.CreateRandom(2, "qwertyuiopasdfghjklzxcvbnm");
|
||||
string passwordBig = StringExtensions.CreateRandom(2, "QWERTYUIOPASDFGHJKLZXCVBNM");
|
||||
|
||||
return string.Concat(passwordDigits, passwordSymbols, passwordSmall, passwordBig).Shuffle();
|
||||
}
|
||||
|
||||
protected IEnumerable<string> CheckPassword(string password)
|
||||
{
|
||||
int minLength = 8;
|
||||
|
||||
if (password.Length < minLength)
|
||||
{
|
||||
yield return $"Password must be at least {minLength} characters long";
|
||||
}
|
||||
if (!password.Any(x => Char.IsUpper(x)))
|
||||
{
|
||||
yield return $"Password must contain at least one uppercase character";
|
||||
}
|
||||
if (!password.Any(x => Char.IsLower(x)))
|
||||
{
|
||||
yield return $"Password must contain at least one lowercase character";
|
||||
}
|
||||
if (!password.Any(x => Char.IsDigit(x)))
|
||||
{
|
||||
yield return $"Password must contain at least one digit";
|
||||
}
|
||||
if (!password.Any(x => Char.IsSymbol(x)))
|
||||
{
|
||||
yield return $"Password must contain at least one special character";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SecureBank.Common\SecureBank.Common.csproj" />
|
||||
<ProjectReference Include="..\..\SecureBank.Database\SecureBank.Database.csproj" />
|
||||
<ProjectReference Include="..\..\SecureBank.Extensions\SecureBank.Extensions.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Authentication\SecureBank.API.Authentication.csproj" />
|
||||
<ProjectReference Include="..\SecureBank.API.Helpers\SecureBank.API.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user