Compare commits
8 Commits
Hargata/be
...
v1.4.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72a5960d40 | ||
|
|
3429f1e4f9 | ||
|
|
f8bea8bf81 | ||
|
|
43794dd223 | ||
|
|
5cc84a7b46 | ||
|
|
48248a4386 | ||
|
|
95305402e6 | ||
|
|
29f24c527f |
@@ -4,6 +4,7 @@ using CarCareTracker.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Controllers
|
||||
@@ -133,10 +134,12 @@ namespace CarCareTracker.Controllers
|
||||
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||
{
|
||||
//validate JWT token
|
||||
var jwtResult = _loginLogic.ValidateOAuthToken(userJwt);
|
||||
if (jwtResult.Success && !string.IsNullOrWhiteSpace(jwtResult.EmailAddress))
|
||||
var tokenParser = new JwtSecurityTokenHandler();
|
||||
var parsedToken = tokenParser.ReadJwtToken(userJwt);
|
||||
var userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||
if (!string.IsNullOrWhiteSpace(userEmailAddress))
|
||||
{
|
||||
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel { EmailAddress = jwtResult.EmailAddress });
|
||||
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
|
||||
if (userData.Id != default)
|
||||
{
|
||||
AuthCookie authCookie = new AuthCookie
|
||||
@@ -150,15 +153,12 @@ namespace CarCareTracker.Controllers
|
||||
return new RedirectResult("/Home");
|
||||
} else
|
||||
{
|
||||
_logger.LogInformation($"User {jwtResult.EmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
||||
return View("OpenIDRegistration", model: jwtResult.EmailAddress);
|
||||
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
||||
return View("OpenIDRegistration", model: userEmailAddress);
|
||||
}
|
||||
} else if (jwtResult.Success)
|
||||
{
|
||||
_logger.LogInformation("OpenID Provider did not provide a valid email address for the user");
|
||||
} else
|
||||
{
|
||||
_logger.LogError("OpenID Token Failed Validation");
|
||||
_logger.LogInformation("OpenID Provider did not provide a valid email address for the user");
|
||||
}
|
||||
} else
|
||||
{
|
||||
|
||||
@@ -303,10 +303,6 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
|
||||
}
|
||||
public static long GetEpochFromDateTimeSeconds(DateTime date)
|
||||
{
|
||||
return new DateTimeOffset(date).ToUnixTimeSeconds();
|
||||
}
|
||||
public static void InitMessage(IConfiguration config)
|
||||
{
|
||||
Console.WriteLine($"LubeLogger {VersionNumber}");
|
||||
|
||||
@@ -3,7 +3,6 @@ using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -24,7 +23,6 @@ namespace CarCareTracker.Logic
|
||||
OperationResponse ResetUserPassword(LoginModel credentials);
|
||||
OperationResponse SendRegistrationToken(LoginModel credentials);
|
||||
UserData ValidateUserCredentials(LoginModel credentials);
|
||||
JWTValidateResult ValidateOAuthToken(string jwtToken);
|
||||
UserData ValidateOpenIDUser(LoginModel credentials);
|
||||
bool CheckIfUserIsValid(int userId);
|
||||
bool CreateRootUserCredentials(LoginModel credentials);
|
||||
@@ -40,20 +38,18 @@ namespace CarCareTracker.Logic
|
||||
private readonly ITokenRecordDataAccess _tokenData;
|
||||
private readonly IMailHelper _mailHelper;
|
||||
private readonly IConfigHelper _configHelper;
|
||||
private readonly ILogger<LoginLogic> _logger;
|
||||
private IMemoryCache _cache;
|
||||
public LoginLogic(IUserRecordDataAccess userData,
|
||||
ITokenRecordDataAccess tokenData,
|
||||
IMailHelper mailHelper,
|
||||
IConfigHelper configHelper,
|
||||
IMemoryCache memoryCache, ILogger<LoginLogic> logger)
|
||||
IMemoryCache memoryCache)
|
||||
{
|
||||
_userData = userData;
|
||||
_tokenData = tokenData;
|
||||
_mailHelper = mailHelper;
|
||||
_configHelper = configHelper;
|
||||
_cache = memoryCache;
|
||||
_logger = logger;
|
||||
}
|
||||
public bool CheckIfUserIsValid(int userId)
|
||||
{
|
||||
@@ -277,39 +273,6 @@ namespace CarCareTracker.Logic
|
||||
}
|
||||
}
|
||||
}
|
||||
public JWTValidateResult ValidateOAuthToken(string jwtToken)
|
||||
{
|
||||
var jwtResult = new JWTValidateResult();
|
||||
var tokenParser = new JwtSecurityTokenHandler();
|
||||
var openIdConfig = _configHelper.GetOpenIDConfig();
|
||||
try
|
||||
{
|
||||
var parsedToken = tokenParser.ReadJwtToken(jwtToken);
|
||||
//Validate Token
|
||||
var expiration = long.Parse(parsedToken.Claims.First(x => x.Type == "exp").Value);
|
||||
var audience = parsedToken.Claims.First(x => x.Type == "aud").Value;
|
||||
if (audience != openIdConfig.ClientId)
|
||||
{
|
||||
_logger.LogError($"Error Validating JWT Token: mismatch audience, expecting {openIdConfig.ClientId} but received {audience}");
|
||||
jwtResult.Success = false;
|
||||
return jwtResult;
|
||||
}
|
||||
if (expiration < StaticHelper.GetEpochFromDateTimeSeconds(DateTime.Now))
|
||||
{
|
||||
_logger.LogError($"Error Validating JWT Token: expired token");
|
||||
jwtResult.Success = false;
|
||||
return jwtResult;
|
||||
}
|
||||
var userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||
jwtResult.EmailAddress = userEmailAddress;
|
||||
jwtResult.Success = true;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error Validating JWT Token: {ex.Message}");
|
||||
jwtResult.Success = false;
|
||||
}
|
||||
return jwtResult;
|
||||
}
|
||||
public UserData ValidateOpenIDUser(LoginModel credentials)
|
||||
{
|
||||
//validate for root user
|
||||
|
||||
@@ -58,51 +58,39 @@ namespace CarCareTracker.Middleware
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(request_header))
|
||||
{
|
||||
bool useBearerAuth = request_header.ToString().Contains("Bearer");
|
||||
var cleanedHeader = useBearerAuth ? request_header.ToString().Replace("Bearer ", "").Trim() : request_header.ToString().Replace("Basic ", "").Trim();
|
||||
var userData = new UserData();
|
||||
if (useBearerAuth)
|
||||
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var splitString = decodedString.Split(":");
|
||||
if (splitString.Count() != 2)
|
||||
{
|
||||
//validate OpenID User from Bearer token
|
||||
var jwtResult = _loginLogic.ValidateOAuthToken(cleanedHeader);
|
||||
if (jwtResult.Success)
|
||||
{
|
||||
userData = _loginLogic.ValidateOpenIDUser(new LoginModel { EmailAddress = jwtResult.EmailAddress });
|
||||
}
|
||||
}
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
else
|
||||
{
|
||||
//perform basic auth.
|
||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var splitString = decodedString.Split(":");
|
||||
if (splitString.Count() != 2)
|
||||
var userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
||||
if (userData.Id != default)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
var appIdentity = new ClaimsIdentity("Custom");
|
||||
var userIdentity = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, splitString[0]),
|
||||
new(ClaimTypes.NameIdentifier, userData.Id.ToString()),
|
||||
new(ClaimTypes.Email, userData.EmailAddress),
|
||||
new(ClaimTypes.Role, "APIAuth")
|
||||
};
|
||||
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);
|
||||
}
|
||||
userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
||||
}
|
||||
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")
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(access_token))
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class JWTValidateResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string EmailAddress { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
@model KioskViewModel
|
||||
@section Scripts {
|
||||
<script src="~/lib/masonry/masonry.min.js"></script>
|
||||
<script src="~/lib/drawdown/drawdown.js"></script>
|
||||
}
|
||||
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
|
||||
<div class="progress-bar" style="width: 0%"></div>
|
||||
@@ -123,9 +124,15 @@
|
||||
}
|
||||
function toggleReminderNote(sender){
|
||||
var reminderNote = $(sender).find('.reminder-note');
|
||||
if (reminderNote.text().trim() != ''){
|
||||
var reminderNoteText = reminderNote.text().trim();
|
||||
if (reminderNoteText != ''){
|
||||
if (reminderNote.hasClass('d-none')) {
|
||||
reminderNote.removeClass('d-none');
|
||||
if (!reminderNote.hasClass('reminder-note-markdown')){
|
||||
let markedDownReminderNote = markdown(reminderNoteText);
|
||||
reminderNote.html(markedDownReminderNote);
|
||||
reminderNote.addClass('reminder-note-markdown');
|
||||
}
|
||||
} else {
|
||||
reminderNote.addClass('d-none');
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
<input type="text" onkeyup="callBackOnEnter(event, requestPasswordReset)" id="inputUserName" class="form-control">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Request")</button>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="inputUserPassword" onkeyup="handlePasswordKeyPress(event)" class="form-control">
|
||||
<input type="password" id="inputUserPassword" onkeyup="callBackOnEnter(event, performLogin)" class="form-control">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control" value="@Model">
|
||||
<input type="text" id="inputUserName" class="form-control" value="@Model" onkeyup="callBackOnEnter(event, performOpenIdRegistration)">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performOpenIdRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="inputUserPassword" class="form-control">
|
||||
<input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performRegistration)">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="inputUserPassword" class="form-control">
|
||||
<input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performPasswordReset)">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<title>LubeLogger Configurator</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
@@ -227,7 +227,9 @@
|
||||
<textarea id="outputModalText" readonly style="width:100%; height:450px;"></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary btn-strip me-auto" onclick="removeDoubleQuotes()">Remove Double Quotes</button>
|
||||
<button type="button" class="btn btn-secondary btn-strip me-auto" onclick="removeDoubleQuotes()">Remove Double Quotes</button>
|
||||
<input id="appSettingsUpload" onChange="readUploadedFile()" class="d-none" type="file" accept="application/json">
|
||||
<button type="button" class="btn btn-secondary btn-upload me-auto" onclick="uploadAndMerge()">Upload appsettings.json</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary btn-copy" onclick="copyToClipboard()">Copy</button>
|
||||
</div>
|
||||
@@ -237,6 +239,44 @@
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
function uploadAndMerge(){
|
||||
$("#appSettingsUpload").click();
|
||||
}
|
||||
function readUploadedFile(){
|
||||
let fl_files = $("#appSettingsUpload")[0].files; // JS FileList object
|
||||
|
||||
if (fl_files.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// use the 1st file from the list
|
||||
let fl_file = fl_files[0];
|
||||
|
||||
let reader = new FileReader(); // built in API
|
||||
|
||||
let display_file = ( e ) => { // set the contents of the <textarea>
|
||||
mergeIntoUploadedFile(e.target.result);
|
||||
};
|
||||
|
||||
let on_reader_load = ( fl ) => {
|
||||
return display_file; // a function
|
||||
};
|
||||
|
||||
// Closure to capture the file information.
|
||||
reader.onload = on_reader_load( fl_file );
|
||||
|
||||
// Read the file as text.
|
||||
reader.readAsText( fl_file );
|
||||
}
|
||||
function mergeIntoUploadedFile(fileContents){
|
||||
var newJsonObject = JSON.parse("{" + $("#outputModalText").text() + "}");
|
||||
var currentJsonObject = JSON.parse(fileContents);
|
||||
var mergedJsonObject = {...currentJsonObject, ...newJsonObject};
|
||||
$("#outputModalLabel").text("Content for appsettings.json");
|
||||
$("#outputModalText").text(JSON.stringify(mergedJsonObject, null, 2));
|
||||
//clear out uploaded file content
|
||||
$("#appSettingsUpload").val("");
|
||||
}
|
||||
function removeDoubleQuotes(){
|
||||
var currentText = $("#outputModalText").text();
|
||||
$("#outputModalText").text(currentText.replaceAll('"', ''));
|
||||
@@ -319,6 +359,11 @@ function generateConfig(){
|
||||
$("#outputModalLabel").text("Append into appsettings.json");
|
||||
$("#outputModalText").text(JSON.stringify(windowConfig, null, 2).slice(1,-1));
|
||||
$(".btn-strip").hide();
|
||||
if (jQuery.isEmptyObject(windowConfig)){
|
||||
$(".btn-upload").hide();
|
||||
} else {
|
||||
$(".btn-upload").show();
|
||||
}
|
||||
$("#outputModal").modal("show");
|
||||
} else {
|
||||
var dockerConfig = [];
|
||||
@@ -375,6 +420,7 @@ function generateConfig(){
|
||||
$("#outputModalLabel").text("Content for .env");
|
||||
$("#outputModalText").text(dockerConfig.join("\r\n"));
|
||||
$(".btn-strip").show();
|
||||
$(".btn-upload").hide();
|
||||
$("#outputModal").modal("show");
|
||||
}
|
||||
}
|
||||
@@ -55,12 +55,6 @@ function performPasswordReset() {
|
||||
});
|
||||
}
|
||||
|
||||
function handlePasswordKeyPress(event) {
|
||||
if (event.keyCode == 13) {
|
||||
performLogin();
|
||||
}
|
||||
}
|
||||
|
||||
function remoteLogin() {
|
||||
$.get('/Login/GetRemoteLoginLink', function (data) {
|
||||
if (data) {
|
||||
|
||||
@@ -1535,4 +1535,10 @@ function handleTableColumnDragEnd(tabName) {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
function callBackOnEnter(event, callBack) {
|
||||
if (event.keyCode == 13) {
|
||||
callBack();
|
||||
}
|
||||
}
|
||||
@@ -98,8 +98,8 @@
|
||||
replace(rx_link, function(all, p1, p2, p3, p4, p5, p6) {
|
||||
stash[--si] = p4
|
||||
? p2
|
||||
? '<img src="' + p4 + '" alt="' + p3 + '"/>'
|
||||
: '<a href="' + p4 + '">' + unesc(highlight(p3)) + '</a>'
|
||||
? '<img style="max-width:100%;max-height:100%;object-fit:scale-down;" src="' + p4 + '" alt="' + p3 + '"/>'
|
||||
: '<a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" target="_blank" href="' + p4 + '">' + unesc(highlight(p3)) + '</a>'
|
||||
: p6;
|
||||
return si + '\uf8ff';
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user