Enhance authentication features and refactor codebase
Updated `AuthenticationController` with new methods for authentication, token refresh, and password recovery. Improved error handling and response structure. Refactored dependency injection in `Program.cs` and added JWT settings in `appsettings.json`. Removed unused `Class1.cs` files. Introduced new DTOs for authentication requests and updated `IAuthService` and `IUserRepository` interfaces. Enhanced `User` class and added `AuthResult` enum for standardized responses. Expanded `DataAccess` for better database operations and updated `UserRepository` and `AuthService` to implement new authentication logic. Front-end changes include renaming variables for consistency and updating the `ForgotPasswordModal` and `Login.tsx` components to use usernames instead of email addresses. Updated API proxy path in `vite.config.ts` and ensured proper typing for `API_BASE_URL` in global TypeScript definitions.
This commit is contained in:
parent
88bcac382c
commit
b3f266f9a8
@ -1,30 +1,51 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity.Data;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Server.Controllers
|
namespace Surge365.MassEmailReact.Server.Controllers
|
||||||
{
|
{
|
||||||
//[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
//[ApiController]
|
[ApiController]
|
||||||
//public class AuthenticationController : ControllerBase
|
public class AuthenticationController : ControllerBase
|
||||||
//{
|
{
|
||||||
// private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
|
|
||||||
// public AuthenticationController(IAuthService authService)
|
public AuthenticationController(IAuthService authService)
|
||||||
// {
|
{
|
||||||
// _authService = authService;
|
_authService = authService;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// [HttpPost("login")]
|
[HttpPost("authenticate")]
|
||||||
// public IActionResult Authenticate([FromBody] LoginRequest request)
|
public async Task<IActionResult> Authenticate([FromBody] LoginRequest request)
|
||||||
// {
|
{
|
||||||
// var token = _authService.Authenticate(request.Username, request.Password);
|
var authResponse = await _authService.Authenticate(request.Username, request.Password);
|
||||||
// if (token == null)
|
if (!authResponse.authenticated)
|
||||||
// {
|
return Unauthorized(new { message = authResponse.errorMessage });
|
||||||
// return Unauthorized(new { message = "Invalid credentials" });
|
else if(authResponse.token == null)
|
||||||
// }
|
return Unauthorized(new { message = "Invalid credentials" });
|
||||||
|
|
||||||
// return Ok(new { token });
|
return Ok(new { success = true, authResponse.token.Value.accessToken });
|
||||||
// }
|
}
|
||||||
//}
|
[HttpPost("refreshtoken")]
|
||||||
|
public IActionResult RefreshToken([FromBody] RefreshTokenRequest request)
|
||||||
|
{
|
||||||
|
Guid? userId = Guid.NewGuid();//TODO: Lookup user in session
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
return Unauthorized("Invalid refresh token");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens = _authService.GenerateTokens(userId.Value, request.RefreshToken);
|
||||||
|
if(tokens == null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return Ok(new { accessToken = tokens.Value.accessToken, refreshToken = tokens.Value.refreshToken });
|
||||||
|
}
|
||||||
|
[HttpPost("generatepasswordrecovery")]
|
||||||
|
public IActionResult GeneratePasswordRecovery([FromBody] GeneratePasswordRecoveryRequest request)
|
||||||
|
{
|
||||||
|
return Ok(new { });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
|
using Surge365.MassEmailReact.Infrastructure.Repositories;
|
||||||
|
using Surge365.MassEmailReact.Infrastructure.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
@ -5,7 +10,8 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
builder.Services.AddScoped<IUserRepository, UserRepository>();
|
||||||
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
|
|||||||
@ -5,5 +5,13 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"Jwt": {
|
||||||
|
"Secret": "Z9R5aFml+eRMeb7tyf8N9wCq3tZpS/EM6nGqOxlXPtOw4cJ3zS1AByczrIlD5F9d"
|
||||||
|
},
|
||||||
|
"AppCode": "MassEmailReactApi",
|
||||||
|
"EnvironmentCode": "UAT",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Marketing.ConnectionString": "data source=uat.surge365.com;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Application Name=##application_name##"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
namespace Surge365.MassEmailReact.Application
|
|
||||||
{
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
|
{
|
||||||
|
public class GeneratePasswordRecoveryRequest
|
||||||
|
{
|
||||||
|
public required string Username { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Surge365.MassEmailReact.Application/DTOs/LoginRequest.cs
Normal file
14
Surge365.MassEmailReact.Application/DTOs/LoginRequest.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
|
{
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
public required string Username { get; set; }
|
||||||
|
public required string Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Application.DTOs
|
||||||
|
{
|
||||||
|
public class RefreshTokenRequest
|
||||||
|
{
|
||||||
|
public required string RefreshToken { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
namespace Surge365.MassEmailReact.Application.Interfaces
|
||||||
|
{
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<(bool authenticated, (string accessToken, string refreshToken)? token, string errorMessage)> Authenticate(string username, string password);
|
||||||
|
(string accessToken, string refreshToken)? GenerateTokens(Guid userId, string refreshToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Application.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
Task<(User? user, string message)> Authenticate(string username, string password);
|
||||||
|
bool Authenticate(Guid userId, string refreshToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +0,0 @@
|
|||||||
namespace Surge365.MassEmailReact.Domain
|
|
||||||
{
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
Surge365.MassEmailReact.Domain/Entities/User.cs
Normal file
34
Surge365.MassEmailReact.Domain/Entities/User.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Domain.Entities
|
||||||
|
{
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int? UserKey { get; private set; }
|
||||||
|
public Guid UserId { get; private set; }
|
||||||
|
public string Username { get; private set; }
|
||||||
|
public string FirstName { get; private set; }
|
||||||
|
public string MiddleInitial { get; private set; }
|
||||||
|
public string LastName { get; private set; }
|
||||||
|
public bool IsActive { get; private set; }
|
||||||
|
|
||||||
|
private User(int userKey, Guid userId, string username, string? firstName, string? middleInitial, string? lastName, bool isActive)
|
||||||
|
{
|
||||||
|
UserKey = userKey;
|
||||||
|
UserId = userId;
|
||||||
|
Username = username;
|
||||||
|
Username = firstName ?? "";
|
||||||
|
MiddleInitial = middleInitial ?? "";
|
||||||
|
LastName = lastName ?? "";
|
||||||
|
IsActive = isActive;
|
||||||
|
}
|
||||||
|
public static User Create(int userKey, Guid userId, string username, string? firstName, string? middleInitial, string? lastName, bool isActive)
|
||||||
|
{
|
||||||
|
return new User(userKey, userId, username, firstName, middleInitial, lastName, isActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Surge365.MassEmailReact.Domain/Enums/AuthResult.cs
Normal file
18
Surge365.MassEmailReact.Domain/Enums/AuthResult.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Domain.Enums
|
||||||
|
{
|
||||||
|
public enum AuthResult
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
LoginNotFound = 1,
|
||||||
|
LoginLocked = 2,
|
||||||
|
InvalidPassword = 3,
|
||||||
|
UnexpectedError = 4,
|
||||||
|
NotAllowed = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Domain.Enums.Extensions
|
||||||
|
{
|
||||||
|
public static class AuthResultExtensions
|
||||||
|
{
|
||||||
|
public static string GetMessage(this AuthResult response)
|
||||||
|
{
|
||||||
|
return response switch
|
||||||
|
{
|
||||||
|
AuthResult.Success => "Login successful.",
|
||||||
|
AuthResult.InvalidPassword or AuthResult.LoginNotFound => "Invalid username and/or password.",
|
||||||
|
AuthResult.LoginLocked => "User has been locked out.",
|
||||||
|
AuthResult.NotAllowed => "User has not been granted access to this application.",
|
||||||
|
AuthResult.UnexpectedError or _ => "Unexpected error."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +0,0 @@
|
|||||||
namespace Surge365.MassEmailReact.Infrastructure
|
|
||||||
{
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
414
Surge365.MassEmailReact.Infrastructure/DataAccess.cs
Normal file
414
Surge365.MassEmailReact.Infrastructure/DataAccess.cs
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection.PortableExecutable;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Infrastructure
|
||||||
|
{
|
||||||
|
public class DataAccess
|
||||||
|
{
|
||||||
|
private string GetConnectionString(string connectionStringName)
|
||||||
|
{
|
||||||
|
if (_configuration == null)
|
||||||
|
return "";
|
||||||
|
return _configuration[$"ConnectionStrings:{connectionStringName}"] ?? "";
|
||||||
|
}
|
||||||
|
private string GetAppCode()
|
||||||
|
{
|
||||||
|
if (_configuration == null)
|
||||||
|
return "";
|
||||||
|
return _configuration[$"AppCode"] ?? "";
|
||||||
|
}
|
||||||
|
public DataAccess(IConfiguration configuration, string connectionStringName)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_connectionString = GetConnectionString(connectionStringName).Replace("##application_code##", GetAppCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IConfiguration? _configuration;
|
||||||
|
internal Guid _sessionID = Guid.NewGuid();
|
||||||
|
internal string _connectionString;
|
||||||
|
internal int _timeout = 30;
|
||||||
|
internal SqlTransaction? _transaction;
|
||||||
|
internal SqlConnection? _connection;
|
||||||
|
internal bool _transactionStarted;
|
||||||
|
internal Object? _transactionStarter;
|
||||||
|
|
||||||
|
public string ConnectionString
|
||||||
|
{
|
||||||
|
get { return _connectionString; }
|
||||||
|
set { _connectionString = value; }
|
||||||
|
}
|
||||||
|
public int Timeout
|
||||||
|
{
|
||||||
|
get { return _timeout; }
|
||||||
|
set { _timeout = value; }
|
||||||
|
}
|
||||||
|
public bool TransactionStarted
|
||||||
|
{
|
||||||
|
get { return _transactionStarted; }
|
||||||
|
internal set { _transactionStarted = value; }
|
||||||
|
}
|
||||||
|
public Object? TransactionStarter
|
||||||
|
{
|
||||||
|
get { return _transactionStarter; }
|
||||||
|
internal set { _transactionStarter = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Non-Async
|
||||||
|
internal void OpenConnection()
|
||||||
|
{
|
||||||
|
_connection = new SqlConnection(_connectionString);
|
||||||
|
_connection.Open();
|
||||||
|
}
|
||||||
|
internal void CloseConnection()
|
||||||
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_connection.Close();
|
||||||
|
_connection.Dispose();
|
||||||
|
_connection = null;
|
||||||
|
}
|
||||||
|
public SqlTransaction BeginTransaction(Object sender)
|
||||||
|
{
|
||||||
|
if (_transaction != null)
|
||||||
|
return _transaction;
|
||||||
|
|
||||||
|
OpenConnection();
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(_connection);
|
||||||
|
|
||||||
|
_transaction = _connection.BeginTransaction();
|
||||||
|
_transactionStarted = true;
|
||||||
|
_transactionStarter = sender;
|
||||||
|
return _transaction;
|
||||||
|
}
|
||||||
|
public void RollbackTransaction(Object sender)
|
||||||
|
{
|
||||||
|
if (_transaction != null && sender == _transactionStarter)
|
||||||
|
{
|
||||||
|
_transaction.Rollback();
|
||||||
|
_transaction = null;
|
||||||
|
CloseConnection();
|
||||||
|
_transactionStarter = null;
|
||||||
|
_transactionStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void CommitTransaction(Object sender)
|
||||||
|
{
|
||||||
|
if (_transaction != null && sender == _transactionStarter)
|
||||||
|
{
|
||||||
|
_transaction.Commit();
|
||||||
|
_transaction = null;
|
||||||
|
CloseConnection();
|
||||||
|
_transactionStarter = null;
|
||||||
|
_transactionStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSet CallRetrievalProcedure(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
||||||
|
{
|
||||||
|
DataSet ds = new DataSet();
|
||||||
|
bool createdConnection = false;
|
||||||
|
if (timeoutSeconds == 0)
|
||||||
|
timeoutSeconds = _timeout;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
{
|
||||||
|
createdConnection = true;
|
||||||
|
OpenConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
||||||
|
cmd.Transaction = _transaction;
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
foreach (SqlParameter p in parameters)
|
||||||
|
{
|
||||||
|
if (p != null)
|
||||||
|
cmd.Parameters.Add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.CommandType = CommandType.StoredProcedure;
|
||||||
|
cmd.CommandTimeout = timeoutSeconds;
|
||||||
|
using SqlDataAdapter da = new SqlDataAdapter(cmd);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
da.Fill(ds);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string message = String.Format("Unable to retrieve data from proc: {0}", proc);
|
||||||
|
LogError(ex, message, "CallRetrievalProcedure");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
CloseConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
CloseConnection();
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
public int CallActionProcedure(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
||||||
|
{
|
||||||
|
int iReturnVal = -1;
|
||||||
|
if (timeoutSeconds == 0)
|
||||||
|
timeoutSeconds = _timeout;
|
||||||
|
bool createdConnection = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
{
|
||||||
|
createdConnection = true;
|
||||||
|
OpenConnection();
|
||||||
|
}
|
||||||
|
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
||||||
|
cmd.Transaction = _transaction;
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
foreach (SqlParameter p in parameters)
|
||||||
|
{
|
||||||
|
if (p != null)
|
||||||
|
cmd.Parameters.Add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.CommandType = CommandType.StoredProcedure;
|
||||||
|
cmd.CommandTimeout = timeoutSeconds;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
iReturnVal = cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string message = String.Format("Unable to execute proc: {0}", proc);
|
||||||
|
LogError(ex, message, "CallActionProcedure");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
CloseConnection();
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
CloseConnection();
|
||||||
|
}
|
||||||
|
return iReturnVal;
|
||||||
|
}
|
||||||
|
private static void LogError(Exception ex, string message, string task)
|
||||||
|
{
|
||||||
|
Exception ex1 = ex;
|
||||||
|
if (ex == null)
|
||||||
|
ex1 = new Exception(message);
|
||||||
|
else if (!string.IsNullOrEmpty(message))
|
||||||
|
ex1 = new Exception(message, ex);
|
||||||
|
|
||||||
|
//Log
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Async
|
||||||
|
internal async Task OpenConnectionAsync()
|
||||||
|
{
|
||||||
|
_connection = new SqlConnection(_connectionString);
|
||||||
|
await _connection.OpenAsync();
|
||||||
|
}
|
||||||
|
internal async Task CloseConnectionAsync()
|
||||||
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await _connection.CloseAsync();
|
||||||
|
_connection.Dispose();
|
||||||
|
_connection = null;
|
||||||
|
}
|
||||||
|
public async Task<SqlTransaction> BeginTransactionAsync(Object sender)
|
||||||
|
{
|
||||||
|
if (_transaction != null)
|
||||||
|
return _transaction;
|
||||||
|
|
||||||
|
await OpenConnectionAsync();
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(_connection);
|
||||||
|
|
||||||
|
_transaction = _connection.BeginTransaction();
|
||||||
|
_transactionStarted = true;
|
||||||
|
_transactionStarter = sender;
|
||||||
|
return _transaction;
|
||||||
|
}
|
||||||
|
public async Task RollbackTransactionAsync(Object sender)
|
||||||
|
{
|
||||||
|
if (_transaction != null && sender == _transactionStarter)
|
||||||
|
{
|
||||||
|
await _transaction.RollbackAsync();
|
||||||
|
_transaction = null;
|
||||||
|
await CloseConnectionAsync();
|
||||||
|
_transactionStarter = null;
|
||||||
|
_transactionStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task CommitTransactionAsync(Object sender)
|
||||||
|
{
|
||||||
|
if (_transaction != null && sender == _transactionStarter)
|
||||||
|
{
|
||||||
|
await _transaction.CommitAsync();
|
||||||
|
_transaction = null;
|
||||||
|
await CloseConnectionAsync();
|
||||||
|
_transactionStarter = null;
|
||||||
|
_transactionStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task<DataSet> CallRetrievalProcedureAsync(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
||||||
|
{
|
||||||
|
DataSet ds = new DataSet();
|
||||||
|
bool createdConnection = false;
|
||||||
|
if (timeoutSeconds == 0)
|
||||||
|
timeoutSeconds = _timeout;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
{
|
||||||
|
createdConnection = true;
|
||||||
|
await OpenConnectionAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
||||||
|
|
||||||
|
cmd.Transaction = _transaction;
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
foreach (SqlParameter p in parameters)
|
||||||
|
{
|
||||||
|
if (p != null)
|
||||||
|
cmd.Parameters.Add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.CommandType = CommandType.StoredProcedure;
|
||||||
|
cmd.CommandTimeout = timeoutSeconds;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//using var reader = await cmd.ExecuteReaderAsync();
|
||||||
|
using var adapter = new SqlDataAdapter(cmd);
|
||||||
|
await Task.Run(() => adapter.Fill(ds));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string message = String.Format("Unable to retrieve data from proc: {0}", proc);
|
||||||
|
await LogErrorAsync(ex, message, "CallRetrievalProcedure");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
await CloseConnectionAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
await CloseConnectionAsync();
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
public async Task<int> CallActionProcedureAsync(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
||||||
|
{
|
||||||
|
int iReturnVal = -1;
|
||||||
|
if (timeoutSeconds == 0)
|
||||||
|
timeoutSeconds = _timeout;
|
||||||
|
bool createdConnection = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
{
|
||||||
|
createdConnection = true;
|
||||||
|
await OpenConnectionAsync();
|
||||||
|
}
|
||||||
|
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
||||||
|
|
||||||
|
cmd.Transaction = _transaction;
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
foreach (SqlParameter p in parameters)
|
||||||
|
{
|
||||||
|
if (p != null)
|
||||||
|
cmd.Parameters.Add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.CommandType = CommandType.StoredProcedure;
|
||||||
|
cmd.CommandTimeout = timeoutSeconds;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
iReturnVal = await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string message = String.Format("Unable to execute proc: {0}", proc);
|
||||||
|
await LogErrorAsync(ex, message, "CallActionProcedure");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
await CloseConnectionAsync();
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdConnection)
|
||||||
|
{
|
||||||
|
await CloseConnectionAsync();
|
||||||
|
}
|
||||||
|
return iReturnVal;
|
||||||
|
}
|
||||||
|
private static async Task LogErrorAsync(Exception ex, string message, string task)
|
||||||
|
{
|
||||||
|
Exception ex1 = ex;
|
||||||
|
if (ex == null)
|
||||||
|
ex1 = new Exception(message);
|
||||||
|
else if (!string.IsNullOrEmpty(message))
|
||||||
|
ex1 = new Exception(message, ex);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using SqlConnection c = new SqlConnection();
|
||||||
|
await c.OpenAsync();
|
||||||
|
using SqlCommand cmd = new SqlCommand("lg_log_error", c);
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
catch (Exception iex) //Trap all errors, don't do any
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Unhandled Exception occurred logging exception: exception={iex}; trying to log exception={ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
using Surge365.MassEmailReact.Domain.Enums;
|
||||||
|
using Surge365.MassEmailReact.Domain.Enums.Extensions;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||||
|
{
|
||||||
|
public class UserRepository (IConfiguration config) : IUserRepository
|
||||||
|
{
|
||||||
|
private IConfiguration _config = config;
|
||||||
|
private const string _connectionStringName = "Marketing.ConnectionString";
|
||||||
|
|
||||||
|
//private static readonly List<User> Users = new();
|
||||||
|
|
||||||
|
public async Task<(User? user, string message)> Authenticate(string username, string password)
|
||||||
|
{
|
||||||
|
List<SqlParameter> pms = new List<SqlParameter>();
|
||||||
|
pms.Add(new SqlParameter("username", username));
|
||||||
|
pms.Add(new SqlParameter("password", password));
|
||||||
|
pms.Add(new SqlParameter("application_code", "MassEmailWeb")); //TODO: Pull from config
|
||||||
|
|
||||||
|
SqlParameter pmResponseNumber = new SqlParameter("response_number", SqlDbType.SmallInt);
|
||||||
|
pmResponseNumber.Direction = ParameterDirection.Output;
|
||||||
|
pms.Add(pmResponseNumber);
|
||||||
|
|
||||||
|
SqlParameter pUserKey = new SqlParameter("login_key", SqlDbType.Int);
|
||||||
|
pUserKey.Direction = ParameterDirection.Output;
|
||||||
|
pms.Add(pUserKey);
|
||||||
|
|
||||||
|
DataAccess da = new DataAccess(_config, _connectionStringName);
|
||||||
|
DataSet ds = await da.CallRetrievalProcedureAsync(pms, "adm_authenticate_login");
|
||||||
|
|
||||||
|
var result = (AuthResult)Convert.ToInt16(pmResponseNumber.Value);
|
||||||
|
|
||||||
|
string responseMessage = AuthResultExtensions.GetMessage(result);
|
||||||
|
if (result == AuthResult.Success)
|
||||||
|
{
|
||||||
|
if (ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
|
||||||
|
return (null, "No user row returned");
|
||||||
|
return (LoadFromDataRow(ds.Tables[0].Rows[0]), responseMessage);
|
||||||
|
}
|
||||||
|
return (null, responseMessage);
|
||||||
|
}
|
||||||
|
public bool Authenticate(Guid userId, string refreshToken)
|
||||||
|
{
|
||||||
|
//TODO: Validate refresh token
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public async Task<User?> GetByUsername(string username)
|
||||||
|
{
|
||||||
|
List<SqlParameter> pms = new List<SqlParameter>();
|
||||||
|
pms.Add(new SqlParameter("username", username));
|
||||||
|
|
||||||
|
DataAccess da = new DataAccess(_config, _connectionStringName);
|
||||||
|
DataSet ds = await da.CallRetrievalProcedureAsync(pms, "adm_get_login_by_username");
|
||||||
|
if (ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<User> users = LoadFromDataRow(ds.Tables[0]);
|
||||||
|
return users.FirstOrDefault();
|
||||||
|
}
|
||||||
|
public async Task<User?> GetByKey(int userKey)
|
||||||
|
{
|
||||||
|
List<SqlParameter> pms = new List<SqlParameter>();
|
||||||
|
pms.Add(new SqlParameter("login_key", userKey));
|
||||||
|
|
||||||
|
DataAccess da = new DataAccess(_config, _connectionStringName);
|
||||||
|
DataSet ds = await da.CallRetrievalProcedureAsync(pms, "adm_get_adm_login_by_key");
|
||||||
|
if (ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<User> users = LoadFromDataRow(ds.Tables[0]);
|
||||||
|
return users.FirstOrDefault();
|
||||||
|
}
|
||||||
|
public async Task<User?> GetById(Guid userId)
|
||||||
|
{
|
||||||
|
List<SqlParameter> pms = new List<SqlParameter>();
|
||||||
|
pms.Add(new SqlParameter("login_id", userId));
|
||||||
|
|
||||||
|
DataAccess da = new DataAccess(_config, _connectionStringName);
|
||||||
|
DataSet ds = await da.CallRetrievalProcedureAsync(pms, "adm_get_adm_login_by_id");
|
||||||
|
if (ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<User> users = LoadFromDataRow(ds.Tables[0]);
|
||||||
|
return users.FirstOrDefault();
|
||||||
|
}
|
||||||
|
public async Task<List<User>> GetAll(bool activeOnly = true)
|
||||||
|
{
|
||||||
|
List<SqlParameter> pms = new List<SqlParameter>();
|
||||||
|
pms.Add(new SqlParameter("active_only", activeOnly));
|
||||||
|
|
||||||
|
DataAccess da = new DataAccess(_config, _connectionStringName);
|
||||||
|
DataSet ds = await da.CallRetrievalProcedureAsync(pms, "adm_get_adm_login_all");
|
||||||
|
if (ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return LoadFromDataRow(ds.Tables[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public void Add(User user)
|
||||||
|
//{
|
||||||
|
// Users.Add(user);
|
||||||
|
//}
|
||||||
|
|
||||||
|
private List<User> LoadFromDataRow(DataTable dt)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(dt);
|
||||||
|
ArgumentNullException.ThrowIfNull(dt.Rows);
|
||||||
|
|
||||||
|
List<User> users = new List<User>();
|
||||||
|
foreach (DataRow dr in dt.Rows)
|
||||||
|
{
|
||||||
|
users.Add(LoadFromDataRow(dr));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
private User LoadFromDataRow(DataRow dr)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(dr);
|
||||||
|
return User.Create(dr.Field<int>("login_key"),
|
||||||
|
dr.Field<Guid>("login_id"),
|
||||||
|
dr.Field<string>("username")!,
|
||||||
|
dr.Field<string?>("first_name"),
|
||||||
|
dr.Field<string?>("middle_initial"),
|
||||||
|
dr.Field<string?>("last_name"),
|
||||||
|
dr.Field<bool>("is_active"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||||
|
{
|
||||||
|
public class AuthService : IAuthService
|
||||||
|
{
|
||||||
|
private const int TOKEN_MINUTES = 60;
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
|
||||||
|
public AuthService(IUserRepository userRepository, IConfiguration config)
|
||||||
|
{
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool authenticated, (string accessToken, string refreshToken)? token, string errorMessage)> Authenticate(string username, string password)
|
||||||
|
{
|
||||||
|
var authResponse = await _userRepository.Authenticate(username, password);
|
||||||
|
if (authResponse.user == null)
|
||||||
|
{
|
||||||
|
return (false, null, authResponse.message);
|
||||||
|
}
|
||||||
|
// Generate JWT token
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var key = Encoding.UTF8.GetBytes(_config["Jwt:Secret"]!);
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(new[]
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, authResponse.user.UserId.ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.UniqueName, username),
|
||||||
|
new Claim(ClaimTypes.Role, "User")
|
||||||
|
}),
|
||||||
|
Expires = DateTime.UtcNow.AddMinutes(TOKEN_MINUTES),
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(key),
|
||||||
|
SecurityAlgorithms.HmacSha256Signature
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||||
|
var accessToken = tokenHandler.WriteToken(token);
|
||||||
|
var refreshToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
|
||||||
|
//_userRepository.SaveRefreshToken(userId.Value, refreshToken); // TODO: Store refresh token in DB
|
||||||
|
|
||||||
|
return (true, (accessToken, refreshToken), "");
|
||||||
|
}
|
||||||
|
public (string accessToken, string refreshToken)? GenerateTokens(Guid userId, string refreshToken)
|
||||||
|
{
|
||||||
|
if (!_userRepository.Authenticate(userId, refreshToken))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var key = Encoding.UTF8.GetBytes(_config["Jwt:Secret"]!);
|
||||||
|
var username = "";
|
||||||
|
//TODO: Look update User
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(new[]
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()!),
|
||||||
|
new Claim(JwtRegisteredClaimNames.UniqueName, username),
|
||||||
|
new Claim(ClaimTypes.Role, "User")
|
||||||
|
}),
|
||||||
|
Expires = DateTime.UtcNow.AddMinutes(TOKEN_MINUTES),
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(key),
|
||||||
|
SecurityAlgorithms.HmacSha256Signature
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||||
|
var accessToken = tokenHandler.WriteToken(token);
|
||||||
|
var newRefreshToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
|
||||||
|
//_userRepository.SaveRefreshToken(userId.Value, newRefreshToken); // TODO: Store refresh token in DB
|
||||||
|
|
||||||
|
return (accessToken, newRefreshToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,4 +11,13 @@
|
|||||||
<ProjectReference Include="..\Surge365.MassEmailReact.Domain\Surge365.MassEmailReact.Domain.csproj" />
|
<ProjectReference Include="..\Surge365.MassEmailReact.Domain\Surge365.MassEmailReact.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.2" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -51,8 +51,8 @@ function checkLoggedInStatus() {
|
|||||||
|
|
||||||
// $("#spanMenuName").html(user.FirstName + ' ' + user.LastName);
|
// $("#spanMenuName").html(user.FirstName + ' ' + user.LastName);
|
||||||
|
|
||||||
// var userid = $.localStorage("session_currentUser").UserID;
|
// var userId = $.localStorage("session_currentUser").userId;
|
||||||
// var imgid = userid + "&t=" + new Date().getTime();
|
// var imgid = userId + "&t=" + new Date().getTime();
|
||||||
/*
|
/*
|
||||||
if (user.HasProfileImage) {
|
if (user.HasProfileImage) {
|
||||||
$("#imgRightProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid);
|
$("#imgRightProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid);
|
||||||
@ -391,7 +391,7 @@ function formatSelect2DataForUser(id, data) {
|
|||||||
for (var x = 0; x < data.length; x++) {
|
for (var x = 0; x < data.length; x++) {
|
||||||
var obj = {};
|
var obj = {};
|
||||||
obj.id = data[x][id];
|
obj.id = data[x][id];
|
||||||
obj.text = data[x]["FirstName"] + ' ' + data[x]["LastName"] + ' - ' + data[x]["UserID"];
|
obj.text = data[x]["FirstName"] + ' ' + data[x]["LastName"] + ' - ' + data[x]["userId"];
|
||||||
newdata.push(obj);
|
newdata.push(obj);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -426,7 +426,7 @@ function fillLocationsDropDown(deferred) {
|
|||||||
|
|
||||||
var search = {};
|
var search = {};
|
||||||
if (!$.userHasRole("Administrators")) {
|
if (!$.userHasRole("Administrators")) {
|
||||||
search.UserID = $.localStorage("session_currentUser").UserID;
|
search.userId = $.localStorage("session_currentUser").userId;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
search = null;
|
search = null;
|
||||||
@ -531,7 +531,7 @@ function fillEmployeesDropDown(deferred, dropdown) {
|
|||||||
$.sessionStorage("session_users", json.data);
|
$.sessionStorage("session_users", json.data);
|
||||||
|
|
||||||
if ($.getBoolean(json.success)) {
|
if ($.getBoolean(json.success)) {
|
||||||
var data = formatSelect2DataForUser("UserID", $.sessionStorage("session_users"));
|
var data = formatSelect2DataForUser("userId", $.sessionStorage("session_users"));
|
||||||
dropdown.select2({
|
dropdown.select2({
|
||||||
placeholder: "Select an employee...",
|
placeholder: "Select an employee...",
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
@ -601,7 +601,7 @@ function growlSuccess(msg) {
|
|||||||
|
|
||||||
function getUser(users, id) {
|
function getUser(users, id) {
|
||||||
for (var x = 0; x < users.length; x++) {
|
for (var x = 0; x < users.length; x++) {
|
||||||
if (users[x].UserID == id) {
|
if (users[x].userId == id) {
|
||||||
return users[x];
|
return users[x];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,118 +1,84 @@
|
|||||||
import { useState } from 'react';
|
import { useState, FormEvent } from 'react';
|
||||||
import { Modal, Button, Form } from 'react-bootstrap';
|
import { Modal, Button, Form } from 'react-bootstrap';
|
||||||
import { FaExclamationCircle } from 'react-icons/fa'; // For optional font icon
|
import { FaExclamationCircle } from 'react-icons/fa'; // For optional font icon
|
||||||
import PropTypes from 'prop-types';
|
import utils from '@/ts/utils';
|
||||||
|
|
||||||
import utils from '@/ts/utils.ts'
|
type FormErrors = Record<string, string>;
|
||||||
|
|
||||||
const ForgotPasswordModal = ({ show, onClose }) => {
|
type ForgotPasswordModalProps = {
|
||||||
const [email, setEmail] = useState('');
|
show: boolean;
|
||||||
const [formErrors, setFormErrors] = useState({});
|
onClose: () => void;
|
||||||
const [emailNotFound, setEmailNotFound] = useState(false);
|
};
|
||||||
|
|
||||||
|
const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose }) => {
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||||
|
const [usernameNotFound, setUsernameNotFound] = useState(false);
|
||||||
const [recoveryStarted, setRecoveryStarted] = useState(false);
|
const [recoveryStarted, setRecoveryStarted] = useState(false);
|
||||||
|
|
||||||
const validate = () => {
|
const validate = (): boolean => {
|
||||||
setFormErrors({});
|
setFormErrors({});
|
||||||
|
const errors: FormErrors = {};
|
||||||
const errors = {};
|
if (!username.trim()) {
|
||||||
if (!email.trim()) {
|
errors.username = 'Username is required';
|
||||||
errors.email = 'Email is required';
|
|
||||||
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
|
||||||
errors.email = 'Invalid email address';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(errors).length > 0) {
|
if (Object.keys(errors).length > 0) {
|
||||||
setFormErrors(errors);
|
setFormErrors(errors);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleStartPasswordRecovery = async (e) => {
|
const handleStartPasswordRecovery = async (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setEmailNotFound(false);
|
setUsernameNotFound(false);
|
||||||
|
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
console.log('Processing forgot password for', email);
|
console.log('Processing forgot password for', username);
|
||||||
await utils.webMethod({
|
await utils.webMethod({
|
||||||
'methodPage': 'UserMethods',
|
methodPage: 'authenticate',
|
||||||
'methodName': 'GeneratePasswordRecovery',
|
methodName: 'generatepasswordrecovery',
|
||||||
'parameters': { "emailAddress": email },
|
parameters: { username },
|
||||||
success: function (json) {
|
success: (json: any) => {
|
||||||
if (utils.getBoolean(json.success)) {
|
if (utils.getBoolean(json.success)) {
|
||||||
setRecoveryStarted(true);
|
setRecoveryStarted(true);
|
||||||
|
} else {
|
||||||
|
setUsernameNotFound(true);
|
||||||
}
|
}
|
||||||
else {
|
},
|
||||||
setEmailNotFound(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
$("#btnResetPassword").click(function (e) {
|
|
||||||
$("#frmResetPassword").validator('validate');
|
|
||||||
e.preventDefault();
|
|
||||||
var isValid = !$('#frmResetPassword .has-error').length
|
|
||||||
|
|
||||||
if (isValid) {
|
|
||||||
$.webMethod({
|
|
||||||
'methodPage': 'UserMethods',
|
|
||||||
'methodName': 'ResetPasswordFromToken',
|
|
||||||
'parameters': { "token": getParameterByName("guid"), "password": $("#txtNewPassword").val() },
|
|
||||||
success: function (json) {
|
|
||||||
|
|
||||||
if ($.getBoolean(json.success)) {
|
|
||||||
|
|
||||||
//redirect user to Dashboard
|
|
||||||
location.href = "/vehicles";
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//error
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
<Modal show={show} onHide={onClose} backdrop="static" keyboard={false} centered={true} animation={false} >
|
<Modal show={show} onHide={onClose} backdrop="static" keyboard={false} centered animation={false}>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>Forgot your password?</Modal.Title>
|
<Modal.Title>Forgot your password?</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{emailNotFound && (
|
{usernameNotFound && (
|
||||||
<span>An email has been sent to the address you provided. Please follow the instructions in the email in order to reset your password.</span>
|
<span>An email has been sent to the address you provided. Please follow the instructions to reset your password.</span>
|
||||||
)}
|
)}
|
||||||
{!recoveryStarted && (
|
{!recoveryStarted && (
|
||||||
<Form onSubmit={handleStartPasswordRecovery}>
|
<Form onSubmit={handleStartPasswordRecovery}>
|
||||||
<Form.Group controlId="formForgotEmail" className="position-relative mb-3">
|
<Form.Group controlId="formForgotEmail" className="position-relative mb-3">
|
||||||
<Form.Label className="mb-4 text-center">Enter your email address below and we'll send you instructions on how to reset your password...</Form.Label>
|
<Form.Label className="mb-4 text-center">Enter your email address below and we'll send you instructions on how to reset your password...</Form.Label>
|
||||||
<Form.Label className="visually-hidden">Email Addresss</Form.Label>
|
<Form.Label className="visually-hidden">Email</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="email"
|
type="username"
|
||||||
placeholder="Email address"
|
placeholder="Username"
|
||||||
value={email}
|
value={username}
|
||||||
isInvalid={!!formErrors.email} // Add Bootstrap's invalid styling
|
isInvalid={!!formErrors.username}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
{/* Validation Icon */}
|
{formErrors.username && (
|
||||||
{formErrors.email && (
|
<FaExclamationCircle className="validation-icon text-danger" title={formErrors.username} />
|
||||||
<FaExclamationCircle
|
|
||||||
className="validation-icon text-danger"
|
|
||||||
title={formErrors.email}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{/* Validation Message */}
|
<Form.Control.Feedback type="invalid">{formErrors.username}</Form.Control.Feedback>
|
||||||
<Form.Control.Feedback type="invalid">{formErrors.email}</Form.Control.Feedback>
|
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Button variant="primary" className="bg-orange btn-flat w-100" type="submit">
|
<Button variant="primary" className="bg-orange btn-flat w-100" type="submit">
|
||||||
Submit
|
Submit
|
||||||
@ -121,7 +87,7 @@ const ForgotPasswordModal = ({ show, onClose }) => {
|
|||||||
)}
|
)}
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button variant="secondary" onClick={onClose} type="button">
|
<Button variant="secondary" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
@ -129,9 +95,5 @@ const ForgotPasswordModal = ({ show, onClose }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ForgotPasswordModal.propTypes = {
|
|
||||||
show: PropTypes.bool.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ForgotPasswordModal;
|
export default ForgotPasswordModal;
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ function Login() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
||||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||||
const [email, setEmail] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false);
|
const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false);
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
@ -40,17 +40,14 @@ function Login() {
|
|||||||
setShowForgotPasswordModal(false);
|
setShowForgotPasswordModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEmailBlur = () => {
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateLoginForm = () => {
|
const validateLoginForm = () => {
|
||||||
setFormErrors({});
|
setFormErrors({});
|
||||||
|
|
||||||
const errors: FormErrors = {};
|
const errors: FormErrors = {};
|
||||||
if (!email.trim()) {
|
if (!username.trim()) {
|
||||||
errors.email = 'Email is required';
|
errors.username = 'Username is required';
|
||||||
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
//} else if (!/\S+@\S+\.\S+/.test(email)) {
|
||||||
errors.email = 'Invalid email address';
|
// errors.email = 'Invalid email address';
|
||||||
}
|
}
|
||||||
if (!password.trim()) {
|
if (!password.trim()) {
|
||||||
errors.password = 'Password is required';
|
errors.password = 'Password is required';
|
||||||
@ -75,9 +72,9 @@ function Login() {
|
|||||||
let loggedInUser: any = null;
|
let loggedInUser: any = null;
|
||||||
|
|
||||||
await utils.webMethod({
|
await utils.webMethod({
|
||||||
methodPage: 'UserMethods',
|
methodPage: 'authentication',
|
||||||
methodName: 'AuthenticateUser',
|
methodName: 'authenticate',
|
||||||
parameters: { emailAddress: email, password: password },
|
parameters: { username: username, password: password },
|
||||||
success: (json: any) => {
|
success: (json: any) => {
|
||||||
if (utils.getBoolean(json.success)) {
|
if (utils.getBoolean(json.success)) {
|
||||||
loggedInUser = json.data;
|
loggedInUser = json.data;
|
||||||
@ -132,14 +129,13 @@ function Login() {
|
|||||||
{loginError && (
|
{loginError && (
|
||||||
<Form.Label style={{ color: 'red' }}>Login error</Form.Label>
|
<Form.Label style={{ color: 'red' }}>Login error</Form.Label>
|
||||||
)}
|
)}
|
||||||
<Form.Group className="mb-3" controlId="txtEmail">
|
<Form.Group className="mb-3" controlId="txtUsernamel">
|
||||||
<Form.Label className="visually-hidden">Email address</Form.Label>
|
<Form.Label className="visually-hidden">Username</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="email"
|
type="username"
|
||||||
placeholder="Email address"
|
placeholder="Username"
|
||||||
value={email}
|
value={username}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
onBlur={handleEmailBlur}
|
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
|||||||
// Add global values to window object for use in static JS
|
// Add global values to window object for use in static JS
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
API_BASE_URL: object
|
API_BASE_URL: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'^/weatherforecast': {
|
'^/api': {
|
||||||
target,
|
target,
|
||||||
secure: false
|
secure: false
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user