add migration and api key based auth method.

This commit is contained in:
DESKTOP-T0O5CDB\DESK-555BD 2026-01-24 18:18:34 -07:00
parent 9403ea437e
commit 05aa49171f
3 changed files with 104 additions and 9 deletions

View File

@ -56,7 +56,8 @@ namespace CarCareTracker.Controllers
"CREATE TABLE IF NOT EXISTS app.inspectionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.equipmentrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.userhouseholdrecords (parentUserId INT, childUserId INT, data jsonb not null, PRIMARY KEY(parentUserId, childUserId))"
"CREATE TABLE IF NOT EXISTS app.userhouseholdrecords (parentUserId INT, childUserId INT, data jsonb not null, PRIMARY KEY(parentUserId, childUserId))",
"CREATE TABLE IF NOT EXISTS app.apikeyrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, userId INT not null, apiKey TEXT not null, data jsonb not null)"
};
foreach(string cmd in cmds)
{
@ -108,6 +109,7 @@ namespace CarCareTracker.Controllers
var equipmentrecords = new List<EquipmentRecord>();
var userhouseholdrecords = new List<UserHousehold>();
var apikeyrecords = new List<APIKey>();
#region "Part1"
string cmd = $"SELECT data FROM app.vehicles";
using (var ctext = pgDataSource.CreateCommand(cmd))
@ -499,6 +501,25 @@ namespace CarCareTracker.Controllers
}
;
}
cmd = $"SELECT data FROM app.apikeyrecords";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
APIKey result = JsonSerializer.Deserialize<APIKey>(reader["data"] as string);
apikeyrecords.Add(result);
}
}
foreach (var record in apikeyrecords)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<APIKey>("apikeyrecords");
table.Upsert(record);
}
;
}
#endregion
var destFilePath = $"{fullFolderPath}.zip";
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
@ -552,6 +573,7 @@ namespace CarCareTracker.Controllers
var equipmentrecords = new List<EquipmentRecord>();
var userhouseholdrecords = new List<UserHousehold>();
var apikeyrecords = new List<APIKey>();
#region "Part1"
using (var db = new LiteDatabase(fullFileName))
{
@ -899,6 +921,24 @@ namespace CarCareTracker.Controllers
ctext.ExecuteNonQuery();
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<APIKey>("apikeyrecords");
apikeyrecords = table.FindAll().ToList();
}
;
foreach (var record in apikeyrecords)
{
string cmd = $"INSERT INTO app.apikeyrecords (id, userId, apiKey, data) VALUES(@id, @userId, @apiKey, CAST(@data AS jsonb)); SELECT setval('app.apikeyrecords_id_seq', (SELECT MAX(id) from app.apikeyrecords));";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("userId", record.UserId);
ctext.Parameters.AddWithValue("apiKey", record.Key);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
#endregion
return Json(OperationResponse.Succeed("Data Imported Successfully"));
}

View File

@ -24,6 +24,7 @@ namespace CarCareTracker.Logic
OperationResponse SendRegistrationToken(LoginModel credentials);
UserData ValidateUserCredentials(LoginModel credentials);
UserData ValidateOpenIDUser(LoginModel credentials);
UserData ValidateAPIKey(string apiKey);
bool CheckIfUserIsValid(int userId);
bool CreateRootUserCredentials(LoginModel credentials);
bool DeleteRootUserCredentials();
@ -36,17 +37,20 @@ namespace CarCareTracker.Logic
{
private readonly IUserRecordDataAccess _userData;
private readonly ITokenRecordDataAccess _tokenData;
private readonly IApiKeyRecordDataAccess _apiKeyData;
private readonly IMailHelper _mailHelper;
private readonly IConfigHelper _configHelper;
private IMemoryCache _cache;
public LoginLogic(IUserRecordDataAccess userData,
ITokenRecordDataAccess tokenData,
IApiKeyRecordDataAccess apiKeyData,
IMailHelper mailHelper,
IConfigHelper configHelper,
IMemoryCache memoryCache)
{
_userData = userData;
_tokenData = tokenData;
_apiKeyData = apiKeyData;
_mailHelper = mailHelper;
_configHelper = configHelper;
_cache = memoryCache;
@ -293,6 +297,26 @@ namespace CarCareTracker.Logic
return new UserData();
}
}
public UserData ValidateAPIKey(string apiKey)
{
var hashedAPIKey = StaticHelper.GetHash(apiKey);
var apiKeyUser = _apiKeyData.GetAPIKeyByKey(hashedAPIKey);
if (apiKeyUser.UserId != default)
{
if (apiKeyUser.UserId == -1)
{
var rootUserData = GetRootUserData(apiKeyUser.Name);
return rootUserData;
}
var result = _userData.GetUserRecordById(apiKeyUser.UserId);
if (result.Id != default)
{
result.Password = string.Empty;
return result;
}
}
return new UserData();
}
#region "Admin Functions"
public bool MakeUserAdmin(int userId, bool isAdmin)
{

View File

@ -53,7 +53,13 @@ namespace CarCareTracker.Middleware
var access_token = _httpContext.HttpContext.Request.Cookies[StaticHelper.LoginCookieName];
//auth using Basic Auth for API.
var request_header = _httpContext.HttpContext.Request.Headers["Authorization"];
if (string.IsNullOrWhiteSpace(access_token) && string.IsNullOrWhiteSpace(request_header))
//auth using API Key for API.
var apikey_header = _httpContext.HttpContext.Request.Headers["x-api-key"];
if (string.IsNullOrWhiteSpace(apikey_header))
{
apikey_header = _httpContext.HttpContext.Request.Query["apiKey"];
}
if (string.IsNullOrWhiteSpace(access_token) && string.IsNullOrWhiteSpace(request_header) && string.IsNullOrWhiteSpace(apikey_header))
{
return AuthenticateResult.Fail("Cookie is invalid or does not exist.");
}
@ -146,20 +152,45 @@ namespace CarCareTracker.Middleware
{
return AuthenticateResult.Fail("Corrupted credentials");
}
}
else if (!string.IsNullOrWhiteSpace(apikey_header) && _httpContext.HttpContext.Request.Path.StartsWithSegments("/api"))
{
//only do API Key Auth for API methods
var userData = _loginLogic.ValidateAPIKey(apikey_header);
if (userData.Id != default)
{
var appIdentity = new ClaimsIdentity("Custom");
var userIdentity = new List<Claim>
{
new(ClaimTypes.Name, userData.UserName),
new(ClaimTypes.NameIdentifier, userData.Id.ToString()),
new(ClaimTypes.Email, userData.EmailAddress),
new(ClaimTypes.Role, "APIAuth"),
new(ClaimTypes.Role, "APIKeyAuth")
};
if (userData.IsAdmin)
{
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin)));
}
if (userData.IsRootUser)
{
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
}
appIdentity.AddClaims(userIdentity);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
return AuthenticateResult.Fail("Invalid credentials");
}
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
if (Request.RouteValues.TryGetValue("controller", out object value))
if (Request.RouteValues.TryGetValue("controller", out object value) && value?.ToString()?.ToLower() == "api")
{
if (value.ToString().ToLower() == "api")
{
Response.StatusCode = 401;
Response.Headers.Append("WWW-Authenticate", "Basic");
return Task.CompletedTask;
}
Response.StatusCode = 401;
Response.Headers.Append("WWW-Authenticate", "Basic");
return Task.CompletedTask;
}
if (Request.Path.Value == "/Vehicle/Index" && Request.QueryString.HasValue)
{