add jwt token and refresh token for auth

This commit is contained in:
dibakor 2026-06-16 15:18:22 +06:00
parent 495f2df15d
commit 70fe243e37
11 changed files with 126 additions and 108 deletions

View File

@ -30,6 +30,7 @@ public class InsertRefreshTokenRequest : RefreshToken
public class RevokedRefreshTokenRequest
{
public string RefreshToken { get; set; }
public int UserId { get; set; }
}
public class GenerateRefreshTokenRequest

View File

@ -5,15 +5,17 @@ using System.Text;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class IntegrationLoginResponse
public class IntegrationLoginResponse : ResponseBase
{
public string LoginId { get; set; }
public string AccessToken { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
public EnumLoginStatus LoginStatus { get; set; }
public DateTime AccessTokenExpiry { get; set; }
}
public class RefreshTokenResponse
public class RefreshTokenResponse : ResponseBase
{
public string UserId { get; set; }
public string TokenHash { get; set; }
@ -24,7 +26,7 @@ public class RefreshTokenResponse
public bool IsActive { get; set; }
}
public class GenerateRefreshTokenResponse
public class GenerateRefreshTokenResponse : ResponseBase
{
public string RefreshToken { get; set; }
public DateTime ExpireTime { get; set; }

View File

@ -34,7 +34,7 @@ public class RefreshTokenService : IRefreshTokenService
try
{
using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
await AddAsync(tc, refreshToken);
AddAsync(tc, refreshToken);
tc.End();
}
@ -46,7 +46,7 @@ public class RefreshTokenService : IRefreshTokenService
return returnValue;
}
private async Task<bool> AddAsync( TransactionContext tc, InsertRefreshTokenRequest refreshToken)
private bool AddAsync( TransactionContext tc, InsertRefreshTokenRequest refreshToken)
{
bool returnValue = false;
@ -60,7 +60,7 @@ public class RefreshTokenService : IRefreshTokenService
SqlHelperExtension.CreateInParam(pName: "@CreatedAt", pType: SqlDbType.DateTime, pValue: DateTime.Now),
SqlHelperExtension.CreateInParam(pName: "@ExpiredAt", pType: SqlDbType.DateTime, pValue: DateTime.Now.AddMinutes(_settings.RefreshTokenDuration)),
];
_ = await tc.ExecuteNonQuerySpAsync(spName: "dbo.InsertRefreshToken", parameterValues: p);
_ = tc.ExecuteNonQuerySp(spName: "dbo.InsertRefreshToken", parameterValues: p);
returnValue = true;
}
@ -170,7 +170,7 @@ public class RefreshTokenService : IRefreshTokenService
using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
try
{
returnValue = await RevokeAsync(tc, token);
returnValue = RevokeAsync(tc, token);
tc.End();
}
@ -189,7 +189,7 @@ public class RefreshTokenService : IRefreshTokenService
return returnValue;
}
private async Task<bool> RevokeAsync(TransactionContext tc, RevokedRefreshTokenRequest token)
private bool RevokeAsync(TransactionContext tc, RevokedRefreshTokenRequest token)
{
bool returnValue = false;
try
@ -198,7 +198,7 @@ public class RefreshTokenService : IRefreshTokenService
[
SqlHelperExtension.CreateInParam(pName: "@RefreshToken", pType: SqlDbType.NVarChar, pValue: token.RefreshToken)
];
_ = await tc.ExecuteNonQuerySpAsync(spName: "dbo.RevokedAllRefreshToken", parameterValues: p);
_ = tc.ExecuteNonQuerySp(spName: "dbo.RevokedAllRefreshToken", parameterValues: p);
returnValue = true;
@ -222,11 +222,11 @@ public class RefreshTokenService : IRefreshTokenService
var storedToken = await GetByTokenHashAsync(tc, tokenHash);
if (!storedToken.IsActive)
if (storedToken.UserId is not null && !storedToken.IsActive)
throw new UnauthorizedAccessException("Refresh token has expired or been revoked.");
// Rotate: revoke old token, issue new one
await RevokeAsync(tc,new RevokedRefreshTokenRequest() { RefreshToken = storedToken.TokenHash });
RevokeAsync(tc,new RevokedRefreshTokenRequest() { RefreshToken = storedToken.TokenHash });
refreshTokenResponse= await IssueTokensAsync(tc,request.User, request.IpAddress);
@ -262,7 +262,7 @@ public class RefreshTokenService : IRefreshTokenService
ExpiresAt = DateTime.UtcNow.AddDays(_settings.RefreshTokenDuration)
};
await AddAsync(tc,refreshToken);
AddAsync(tc,refreshToken);
return new GenerateRefreshTokenResponse
{

View File

@ -366,8 +366,8 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems
#region Read User data using authentication data
string commandText = SQLParser.MakeSQL("SELECT UserId,Password,LoginId, UserName, Status, MobileNo, EmailAddress,"
+ " AuthKey, AuthValue, IsLocked FROM Users WHERE LoginID=%s", request.LoginId);
string commandText = SQLParser.MakeSQL("SELECT UserId,Password,LoginId, UserName, Status, ISNULL(MobileNo,'')MobileNo ,ISNULL( EmailAddress,'')EmailAddress,"
+ " ISNULL(AuthKey,'') AuthKey, ISNULL(AuthValue,'')AuthValue, IsLocked FROM Users WHERE LoginID=%s", request.LoginId);
using (IDataReader dr = tc.ExecuteReader(commandText: commandText))
{
@ -397,7 +397,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems
throw new InvalidOperationException($"User not found with login: {request.LoginId}");
}
if (!request.Password.Equals(user.Password))
if (!password.Equals(user.Password))
{
tc.End();
throw new InvalidOperationException($"Password mismatch for login: {request.LoginId}");
@ -481,13 +481,12 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems
SqlParameter[] p =
[
SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId),
SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30),
SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime),
SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: DateTime.Now)
];
_ = await tc.ExecuteNonQuerySpAsync(spName: "dbo.SaveAccessLog", parameterValues: p);
_ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p);
}
#endregion

View File

@ -1,8 +1,10 @@
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Setups;
using Microsoft.Extensions.DependencyInjection;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Setups;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Setups;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems;
using Microsoft.Extensions.DependencyInjection;
namespace OnlineSalesAutoCrop.CoreAPI.Configuration.DI
{
@ -25,6 +27,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Configuration.DI
services.AddTransient<IParamTypeService, ParamTypeService>();
services.AddTransient<IThisSystemService, ThisSystemService>();
services.AddTransient<IAuthModulesService, AuthModulesService>();
services.AddTransient<IRefreshTokenService, RefreshTokenService>();
}
}

View File

@ -16,6 +16,7 @@ using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Setups;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
@ -31,7 +32,7 @@ using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
/// <summary>
///
@ -43,14 +44,13 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
/// <param name="appSettings"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
/// <param name="hub"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/users")]
[Route("api/v{version:apiVersion}/IntegrationAuth")]
public class AuthController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<AuthController> logger, IRefreshTokenService refreshTokenService) : ControllerBase
public class IntegrationAuthController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<IntegrationAuthController> logger, IRefreshTokenService refreshTokenService) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;
@ -59,6 +59,16 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
private readonly AppSettings _appSettings = appSettings?.Value;
private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();
[HttpGet]
[Route("GetServerDateTime")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> GetServerDateTime()
{
return Ok(DateTime.Now.ToString());
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
@ -70,24 +80,26 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
[HttpPost("login")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(LoginResponse))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))]
public async Task<IActionResult> Login([FromBody] IntegrationLoginRequest request)
{
ArgumentNullException.ThrowIfNull(request);
IntegrationLoginResponse loginReponse = new();
LoginResponse response = new();
if (string.IsNullOrEmpty(request.LoginId))
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("Login ID is required.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed;
loginReponse.ReturnMessage.Add("Login ID is required.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (string.IsNullOrEmpty(request.Password))
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("Password is required.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed;
loginReponse.ReturnMessage.Add("Password is required.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
@ -97,14 +109,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
#region Decrypt LoginID
string cipherSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.CipherSecretKey);
request.LoginId = Helper.DecryptData(secret: cipherSecretKey, data: request.LoginId);
request.Password = Helper.DecryptData(secret: cipherSecretKey, data: request.Password);
if (request.LoginId.Equals("*Key/Data Error*") || request.Password.Equals("*Key/Data Error*") )
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("Key or Data Error...!");
return BadRequest(response);
}
#endregion
@ -139,10 +144,10 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
if (user == null || user.UserId == 0)
{
response.LoginStatus = EnumLoginStatus.Error;
response.ReturnStatus = StatusCodes.Status403Forbidden;
response.ReturnMessage.Add(checkPwd ? "Login ID/Password is invalid." : "You are not Authorized to login into the System");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.LoginStatus = EnumLoginStatus.Error;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add(checkPwd ? "Login ID/Password is invalid." : "You are not Authorized to login into the System");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (user.LoginStatus == EnumLoginStatus.Unsuccessful)
@ -151,39 +156,39 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
if (user != null && !string.IsNullOrEmpty(user.UnsuccessfulMsg))
usm = $" ({user.UnsuccessfulMsg})";
response.LoginStatus = user.LoginStatus;
response.ReturnStatus = StatusCodes.Status403Forbidden;
response.ReturnMessage.Add(checkPwd ? $"Login ID/Password is invalid{usm}." : "You are not Authorized to login into the System");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add(checkPwd ? $"Login ID/Password is invalid{usm}." : "You are not Authorized to login into the System");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (user.IsLocked)
{
response.LoginStatus = user.LoginStatus;
response.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
if (!user.NextLoginTime.HasValue)
response.ReturnMessage.Add("You are locked, please contact Head office.");
loginReponse.ReturnMessage.Add("You are locked, please contact Head office.");
else
response.ReturnMessage.Add($"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.ReturnMessage.Add($"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (user.Status != EnumStatus.Authorized)
{
response.LoginStatus = user.LoginStatus;
response.ReturnStatus = StatusCodes.Status403Forbidden;
response.ReturnMessage.Add("You are not Authorized to Login into the System, Please contact with System Administrator.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add("You are not Authorized to Login into the System, Please contact with System Administrator.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
response.ValidUser = user.LoginStatus == EnumLoginStatus.Success;
if (!response.ValidUser)
{
response.LoginStatus = user.LoginStatus;
response.ReturnStatus = StatusCodes.Status403Forbidden;
response.ReturnMessage.Add("Unknown error.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add("Unknown error.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
response.Map(user);
@ -218,30 +223,33 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
var refreshToken =await _refreshTokenService.GenerateRefreshToken(refreshTokenRequest);
//If token length is greater than or equal to 4096 (4KB) then return error
//because cookie can not store more than 4KB data and we are storing this token in cookie for authentication
if (userToken.Length >= 4096) //4Kb
{
response.LoginStatus = user.LoginStatus;
response.ReturnStatus = StatusCodes.Status431RequestHeaderFieldsTooLarge;
response.ReturnMessage.Add("Authentication Token is too large for cookie.");
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, response);
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status431RequestHeaderFieldsTooLarge;
loginReponse.ReturnMessage.Add("Authentication Token is too large for cookie.");
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse);
}
response.Expires = tokenDescriptor.Expires;
response.AuthenticationToken = userToken;
response.LoginTime = $"{DateTime.Now:dd-MM-yy H:mm:ss}";
return Ok(response);
loginReponse.ReturnStatus = response.ReturnStatus;
loginReponse.ReturnMessage = response.ReturnMessage;
loginReponse.LoginId = response.LoginId;
loginReponse.AccessToken = userToken;
loginReponse.RefreshToken = refreshToken.RefreshToken;
loginReponse.AccessTokenExpiry = refreshToken.ExpireTime;
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse);
}
catch (Exception ex)
{
string msg = $"{request?.LoginId}~{ipAddress}";
_logger.LogError(exception: ex, message: msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
loginReponse.ReturnStatus = StatusCodes.Status500InternalServerError;
loginReponse.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, loginReponse);
}
}

View File

@ -0,0 +1,36 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.IntegretionApi
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="service"></param>
/// <param name="appSettings"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/Integration")]
public class IntegrationController : ControllerBase
{
[HttpGet("Customers")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))]
public async Task<IActionResult> Customers()
{
return Ok("Data Insert Successfully");
}
}
}

View File

@ -49,7 +49,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/users")]
public class UserController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<AuthController> logger, IHubContext<NotificationHub, INotificationHub> hub) : ControllerBase
public class UserController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<IntegrationAuthController> logger, IHubContext<NotificationHub, INotificationHub> hub) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;

View File

@ -1,33 +0,0 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed.
# For more information, please see https://aka.ms/containercompat
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0-nanoserver-1809 AS base
WORKDIR /app
EXPOSE 8080
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0-nanoserver-1809 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Api/OnlineSalesAutoCrop.CoreAPI/OnlineSalesAutoCrop.CoreAPI.csproj", "Api/OnlineSalesAutoCrop.CoreAPI/"]
COPY ["Api/OnlineSalesAutoCrop.CoreAPI.Models/OnlineSalesAutoCrop.CoreAPI.Models.csproj", "Api/OnlineSalesAutoCrop.CoreAPI.Models/"]
COPY ["Api/OnlineSalesAutoCrop.CoreAPI.Services/OnlineSalesAutoCrop.CoreAPI.Services.csproj", "Api/OnlineSalesAutoCrop.CoreAPI.Services/"]
RUN dotnet restore "./Api/OnlineSalesAutoCrop.CoreAPI/OnlineSalesAutoCrop.CoreAPI.csproj"
COPY . .
WORKDIR "/src/Api/OnlineSalesAutoCrop.CoreAPI"
RUN dotnet build "./OnlineSalesAutoCrop.CoreAPI.csproj" -c %BUILD_CONFIGURATION% -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./OnlineSalesAutoCrop.CoreAPI.csproj" -c %BUILD_CONFIGURATION% -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OnlineSalesAutoCrop.CoreAPI.dll"]

View File

@ -3,6 +3,8 @@
<PropertyGroup>
<NameOfLastUsedPublishProfile>D:\Local\EaseBilling\Api\EaseO2C.CoreAPI\Properties\PublishProfiles\Windows.Publish.pubxml</NameOfLastUsedPublishProfile>
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>

View File

@ -29,8 +29,8 @@
"DbConfig": [
{
"ConnectionNode": {
"ConnectionString": "Data Source=210.4.65.222,3341;Initial Catalog=OnlineSalesAutoCrop;User ID=OnlineSalesAutoCropSysUser;Password=OnlineSalesAutoCrop;Encrypt=false;",
"Key": "spadb7",
"ConnectionString": "Data Source=210.4.65.222,3341;Initial Catalog=AutoCropDB;User ID=dmsuser;Password=dMsu@er;Encrypt=false;",
"Key": "spadb1",
"Provider": "sql",
"SqlSyntax": "SQL",
"EncryptKey": ""