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.Identity.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
|
||||
namespace Surge365.MassEmailReact.Server.Controllers
|
||||
{
|
||||
//[Route("api/[controller]")]
|
||||
//[ApiController]
|
||||
//public class AuthenticationController : ControllerBase
|
||||
//{
|
||||
// private readonly IAuthService _authService;
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class AuthenticationController : ControllerBase
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
// public AuthenticationController(IAuthService authService)
|
||||
// {
|
||||
// _authService = authService;
|
||||
// }
|
||||
|
||||
// [HttpPost("login")]
|
||||
// public IActionResult Authenticate([FromBody] LoginRequest request)
|
||||
// {
|
||||
// var token = _authService.Authenticate(request.Username, request.Password);
|
||||
// if (token == null)
|
||||
// {
|
||||
// return Unauthorized(new { message = "Invalid credentials" });
|
||||
// }
|
||||
|
||||
// return Ok(new { token });
|
||||
// }
|
||||
//}
|
||||
public AuthenticationController(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
[HttpPost("authenticate")]
|
||||
public async Task<IActionResult> Authenticate([FromBody] LoginRequest request)
|
||||
{
|
||||
var authResponse = await _authService.Authenticate(request.Username, request.Password);
|
||||
if (!authResponse.authenticated)
|
||||
return Unauthorized(new { message = authResponse.errorMessage });
|
||||
else if(authResponse.token == null)
|
||||
return Unauthorized(new { message = "Invalid credentials" });
|
||||
|
||||
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);
|
||||
|
||||
// Add services to the container.
|
||||
@ -5,7 +10,8 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
builder.Services.AddScoped<IUserRepository, UserRepository>();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseDefaultFiles();
|
||||
|
||||
@ -5,5 +5,13 @@
|
||||
"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" />
|
||||
</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>
|
||||
|
||||
@ -51,8 +51,8 @@ function checkLoggedInStatus() {
|
||||
|
||||
// $("#spanMenuName").html(user.FirstName + ' ' + user.LastName);
|
||||
|
||||
// var userid = $.localStorage("session_currentUser").UserID;
|
||||
// var imgid = userid + "&t=" + new Date().getTime();
|
||||
// var userId = $.localStorage("session_currentUser").userId;
|
||||
// var imgid = userId + "&t=" + new Date().getTime();
|
||||
/*
|
||||
if (user.HasProfileImage) {
|
||||
$("#imgRightProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid);
|
||||
@ -391,7 +391,7 @@ function formatSelect2DataForUser(id, data) {
|
||||
for (var x = 0; x < data.length; x++) {
|
||||
var obj = {};
|
||||
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);
|
||||
|
||||
}
|
||||
@ -426,7 +426,7 @@ function fillLocationsDropDown(deferred) {
|
||||
|
||||
var search = {};
|
||||
if (!$.userHasRole("Administrators")) {
|
||||
search.UserID = $.localStorage("session_currentUser").UserID;
|
||||
search.userId = $.localStorage("session_currentUser").userId;
|
||||
}
|
||||
else {
|
||||
search = null;
|
||||
@ -531,7 +531,7 @@ function fillEmployeesDropDown(deferred, dropdown) {
|
||||
$.sessionStorage("session_users", json.data);
|
||||
|
||||
if ($.getBoolean(json.success)) {
|
||||
var data = formatSelect2DataForUser("UserID", $.sessionStorage("session_users"));
|
||||
var data = formatSelect2DataForUser("userId", $.sessionStorage("session_users"));
|
||||
dropdown.select2({
|
||||
placeholder: "Select an employee...",
|
||||
allowClear: true,
|
||||
@ -601,7 +601,7 @@ function growlSuccess(msg) {
|
||||
|
||||
function getUser(users, id) {
|
||||
for (var x = 0; x < users.length; x++) {
|
||||
if (users[x].UserID == id) {
|
||||
if (users[x].userId == id) {
|
||||
return users[x];
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,118 +1,84 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, FormEvent } from 'react';
|
||||
import { Modal, Button, Form } from 'react-bootstrap';
|
||||
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 }) => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const [emailNotFound, setEmailNotFound] = useState(false);
|
||||
type ForgotPasswordModalProps = {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
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 validate = () => {
|
||||
const validate = (): boolean => {
|
||||
setFormErrors({});
|
||||
|
||||
const errors = {};
|
||||
if (!email.trim()) {
|
||||
errors.email = 'Email is required';
|
||||
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
||||
errors.email = 'Invalid email address';
|
||||
const errors: FormErrors = {};
|
||||
if (!username.trim()) {
|
||||
errors.username = 'Username is required';
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setFormErrors(errors);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleStartPasswordRecovery = async (e) => {
|
||||
const handleStartPasswordRecovery = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setEmailNotFound(false);
|
||||
setUsernameNotFound(false);
|
||||
|
||||
if (validate()) {
|
||||
console.log('Processing forgot password for', email);
|
||||
console.log('Processing forgot password for', username);
|
||||
await utils.webMethod({
|
||||
'methodPage': 'UserMethods',
|
||||
'methodName': 'GeneratePasswordRecovery',
|
||||
'parameters': { "emailAddress": email },
|
||||
success: function (json) {
|
||||
methodPage: 'authenticate',
|
||||
methodName: 'generatepasswordrecovery',
|
||||
parameters: { username },
|
||||
success: (json: any) => {
|
||||
if (utils.getBoolean(json.success)) {
|
||||
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 (
|
||||
<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.Title>Forgot your password?</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{emailNotFound && (
|
||||
<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>
|
||||
{usernameNotFound && (
|
||||
<span>An email has been sent to the address you provided. Please follow the instructions to reset your password.</span>
|
||||
)}
|
||||
{!recoveryStarted && (
|
||||
<Form onSubmit={handleStartPasswordRecovery}>
|
||||
<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="visually-hidden">Email Addresss</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</Form.Label>
|
||||
<Form.Control
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
isInvalid={!!formErrors.email} // Add Bootstrap's invalid styling
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
type="username"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
isInvalid={!!formErrors.username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
autoFocus
|
||||
size="lg"
|
||||
/>
|
||||
{/* Validation Icon */}
|
||||
{formErrors.email && (
|
||||
<FaExclamationCircle
|
||||
className="validation-icon text-danger"
|
||||
title={formErrors.email}
|
||||
/>
|
||||
{formErrors.username && (
|
||||
<FaExclamationCircle className="validation-icon text-danger" title={formErrors.username} />
|
||||
)}
|
||||
{/* Validation Message */}
|
||||
<Form.Control.Feedback type="invalid">{formErrors.email}</Form.Control.Feedback>
|
||||
<Form.Control.Feedback type="invalid">{formErrors.username}</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Button variant="primary" className="bg-orange btn-flat w-100" type="submit">
|
||||
Submit
|
||||
@ -121,7 +87,7 @@ const ForgotPasswordModal = ({ show, onClose }) => {
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={onClose} type="button">
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
@ -129,9 +95,5 @@ const ForgotPasswordModal = ({ show, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
ForgotPasswordModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ForgotPasswordModal;
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ function Login() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||
const [email, setEmail] = useState('');
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false);
|
||||
const [user, setUser] = useState<any>(null);
|
||||
@ -40,17 +40,14 @@ function Login() {
|
||||
setShowForgotPasswordModal(false);
|
||||
};
|
||||
|
||||
const handleEmailBlur = () => {
|
||||
};
|
||||
|
||||
const validateLoginForm = () => {
|
||||
setFormErrors({});
|
||||
|
||||
const errors: FormErrors = {};
|
||||
if (!email.trim()) {
|
||||
errors.email = 'Email is required';
|
||||
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
||||
errors.email = 'Invalid email address';
|
||||
if (!username.trim()) {
|
||||
errors.username = 'Username is required';
|
||||
//} else if (!/\S+@\S+\.\S+/.test(email)) {
|
||||
// errors.email = 'Invalid email address';
|
||||
}
|
||||
if (!password.trim()) {
|
||||
errors.password = 'Password is required';
|
||||
@ -75,9 +72,9 @@ function Login() {
|
||||
let loggedInUser: any = null;
|
||||
|
||||
await utils.webMethod({
|
||||
methodPage: 'UserMethods',
|
||||
methodName: 'AuthenticateUser',
|
||||
parameters: { emailAddress: email, password: password },
|
||||
methodPage: 'authentication',
|
||||
methodName: 'authenticate',
|
||||
parameters: { username: username, password: password },
|
||||
success: (json: any) => {
|
||||
if (utils.getBoolean(json.success)) {
|
||||
loggedInUser = json.data;
|
||||
@ -132,14 +129,13 @@ function Login() {
|
||||
{loginError && (
|
||||
<Form.Label style={{ color: 'red' }}>Login error</Form.Label>
|
||||
)}
|
||||
<Form.Group className="mb-3" controlId="txtEmail">
|
||||
<Form.Label className="visually-hidden">Email address</Form.Label>
|
||||
<Form.Group className="mb-3" controlId="txtUsernamel">
|
||||
<Form.Label className="visually-hidden">Username</Form.Label>
|
||||
<Form.Control
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onBlur={handleEmailBlur}
|
||||
type="username"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
autoFocus
|
||||
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
|
||||
declare global {
|
||||
interface Window {
|
||||
API_BASE_URL: object
|
||||
API_BASE_URL: string
|
||||
}
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
|
||||
@ -47,7 +47,7 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'^/weatherforecast': {
|
||||
'^/api': {
|
||||
target,
|
||||
secure: false
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user