Skip to content

Commit

Permalink
fixes several issues related to the v2.4.0 release.
Browse files Browse the repository at this point in the history
  • Loading branch information
replaysMike committed Apr 24, 2023
1 parent af0794f commit 891c0f0
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Binner/Binner.Web/Binner.Web.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<LibraryVersion Condition="'$(LibraryVersion)'==''">1.0.6</LibraryVersion>
<LibraryVersion Condition="'$(LibraryVersion)'==''">1.0.8</LibraryVersion>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<RuntimeIdentifiers>win10-x64;linux-arm;linux-arm64;linux-x64;osx.10.12-x64;ubuntu.14.04-x64</RuntimeIdentifiers>
Expand Down
1 change: 1 addition & 0 deletions Binner/Binner.Web/ClientApp/src/common/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const formatCurrency = (number, currency = 'USD', maxDecimals = 5) => {
*/
export const getCurrencySymbol = (currency = 'USD') => {
const locale = getLocaleLanguage();
if (!locale) return '';
return (0).toLocaleString(locale, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 0 }).replace(/\d/g, '').trim();
};

Expand Down
4 changes: 1 addition & 3 deletions Binner/Binner.Web/ClientApp/src/common/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export const getImagesToken = () => {
*/
export const setUserAccount = (user) => {
localStorage.setItem("user", JSON.stringify(user));
console.log('user authenticated', user.name);
};

/**
Expand All @@ -67,7 +66,6 @@ export const setUserAccount = (user) => {
*/
export const deAuthenticateUserAccount = () => {
localStorage.removeItem("user");
console.log('deAuthenticated user');
};

/**
Expand Down Expand Up @@ -100,7 +98,7 @@ export const deAuthenticateUserAccount = () => {
console.log('reissuing original request', requestContext);
// todo: this part is a little wonky and could be refactored. because fetchApi returns json() body and not the response,
// we need to get it from our predefined response structure.
const finalResult = await fetchApi(requestContext.url, requestContext.data);
const finalResult = await fetchApi(requestContext.url, requestContext.data, true);
return finalResult.responseObject;
} else {
console.log('failed to refresh token', newResponseData.message);
Expand Down
30 changes: 22 additions & 8 deletions Binner/Binner.Web/ClientApp/src/common/fetchApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ const noData = { message: `No message was specified.` };
* Automatically includes jwt bearer authentication credentials and handles token refresh requests.
* @param {string} url the url to fetch
* @param {any} data the fetch request object
* @param {bool} isReissuedRequest true if this is a reissued request (via refresh-token fetch)
* @returns json response from api with an embedded response object
*/
export const fetchApi = async (url, data = { method: "GET", headers: {}, body: null, catchErrors: false }) => {
export const fetchApi = async (url, data = { method: "GET", headers: {}, body: null, catchErrors: false }, isReissuedRequest = false) => {
const userAccount = getUserAccount();

let headers = data.headers || {};
Expand Down Expand Up @@ -42,7 +43,7 @@ export const fetchApi = async (url, data = { method: "GET", headers: {}, body: n

const responseBody = await fetch(requestContext.url, requestContext.data)
.then((response) => {
return handleJsonResponse(response, requestContext);
return handleJsonResponse(response, requestContext, isReissuedRequest);
})
.catch((response) => {
// opportunity to handle errors internally
Expand All @@ -57,12 +58,14 @@ export const fetchApi = async (url, data = { method: "GET", headers: {}, body: n
/**
* Get the json response from a fetch operation
* @param {any} response the response from fetch
* @param {bool} requestContext the request context
* @param {bool} isReissuedRequest true if this is a reissued request (via refresh-token fetch)
* @returns {any} the response json object
*/
export const handleJsonResponse = async (response, requestContext) => {
export const handleJsonResponse = async (response, requestContext, isReissuedRequest) => {
// store the last version header we have seen
if (response.headers.has("x-version")) window.version = response.headers.get("x-version");
const wrappedResponse = await handle401UnauthorizedAsync(response, requestContext);
const wrappedResponse = await handle401UnauthorizedAsync(response, requestContext, isReissuedRequest);
if (!wrappedResponse.ok) return wrappedResponse.response;

// valid response
Expand All @@ -79,10 +82,12 @@ export const handleJsonResponse = async (response, requestContext) => {
/**
* Get the response from a fetch operation
* @param {any} response the response from fetch
* @param {bool} requestContext the request context
* @param {bool} isReissuedRequest true if this is a reissued request (via refresh-token fetch)
* @returns {any} the response binary object
*/
export const handleBinaryResponse = async (response, requestContext) => {
const wrappedResponse = await handle401UnauthorizedAsync(response, requestContext);
export const handleBinaryResponse = async (response, requestContext, isReissuedRequest) => {
const wrappedResponse = await handle401UnauthorizedAsync(response, requestContext, isReissuedRequest);
if (!wrappedResponse.ok) return wrappedResponse.response;

// valid response
Expand Down Expand Up @@ -138,10 +143,20 @@ export const getErrorsString = (response) => {
* This handles the process required to refresh jwt tokens when necessary and reissue the original request
* @param {any} response the response object from fetch
* @param {any} requestContext the original request being sent
* @param {bool} isReissuedRequest true if this is a reissued request (via refresh-token fetch)
* @returns new reissued response, or original response
*/
const handle401UnauthorizedAsync = async (response, requestContext) => {
const handle401UnauthorizedAsync = async (response, requestContext, isReissuedRequest) => {
if (response.status === 401 || response.status === 403) {
// unauthorized
if (isReissuedRequest){
deAuthenticateUserAccount();
window.location.replace("/login");
return {
ok: false,
response: Promise.reject(response)
};
}
// unauthorized, must ask for new jwt token
const reissuedResponse = await refreshTokenAuthorizationAsync(response, requestContext);
const reissuedResponseData = await reissuedResponse.clone().json();
Expand All @@ -161,7 +176,6 @@ const handle401UnauthorizedAsync = async (response, requestContext) => {
if (!response.ok) {
if (response.status === 426) {
// response has an licensing error, we want to display a licensing error modal box
console.log("received 426 license error");
return {
ok: false,
response: await invokeLicensingErrorHandler(response)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LibraryVersion Condition="'$(LibraryVersion)'==''">1.0.6</LibraryVersion>
<LibraryVersion Condition="'$(LibraryVersion)'==''">1.0.8</LibraryVersion>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<RuntimeIdentifiers>win10-x64;linux-arm;linux-arm64;linux-x64;osx.10.12-x64;ubuntu.14.04-x64</RuntimeIdentifiers>
Expand Down
70 changes: 70 additions & 0 deletions Binner/Library/Binner.Common/Auth/SecurityKeyProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Binner.Global.Common;
using System;
using System.IO;
using System.Reflection;

namespace Binner.Common.Auth
{
public class SecurityKeyProvider
{
public enum KeyTypes
{
Jwt
}

private string GetTokenFilename(KeyTypes keyType)
{
var keyTypeName = "";
switch (keyType)
{
case KeyTypes.Jwt:
keyTypeName = "jwt.key";
break;
default:
throw new NotImplementedException($"Invalid key type: '{keyType}'");
}
var assembly = Assembly.GetEntryAssembly();
if (assembly != null)
{
var path = Path.GetDirectoryName(assembly.Location);
if (string.IsNullOrEmpty(path))
throw new BinnerConfigurationException("Failed to get assembly path!");
var filename = Path.Combine(path, keyTypeName);
return filename;
}
throw new BinnerConfigurationException("Error: Could not get entry assembly information to store the JWT key!");
}

/// <summary>
/// Load the key from disk
/// </summary>
/// <param name="keyType"></param>
/// <param name="length"></param>
/// <returns>The key or null if it does not exist</returns>
public string LoadOrGenerateKey(KeyTypes keyType, int length)
{
var filename = GetTokenFilename(keyType);
if (File.Exists(filename))
{
var key = File.ReadAllText(filename);
return key;
}

return CreateKey(filename);
}

/// <summary>
/// Create a new key and save to disk
/// </summary>
/// <param name="filename"></param>
/// <returns>The new key</returns>
public string CreateKey(string filename)
{
var key = GenerateSecurityKey();
File.WriteAllText(filename, key);
return key;
}

private string GenerateSecurityKey() => ConfirmationTokenGenerator.NewSecurityToken(40);
}
}
2 changes: 1 addition & 1 deletion Binner/Library/Binner.Common/Binner.Common.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LibraryVersion Condition="'$(LibraryVersion)'==''">1.0.6</LibraryVersion>
<LibraryVersion Condition="'$(LibraryVersion)'==''">1.0.8</LibraryVersion>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<RuntimeIdentifiers>win10-x64;linux-arm;linux-arm64;linux-x64;osx.10.12-x64;ubuntu.14.04-x64</RuntimeIdentifiers>
Expand Down
34 changes: 30 additions & 4 deletions Binner/Library/Binner.Common/Services/Authentication/JwtService.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Binner.Common.IO;
using Binner.Common.Auth;
using Binner.Common.IO;
using Binner.Data;
using Binner.Data.Model;
using Binner.Global.Common;
using Binner.Model.Authentication;
using Binner.Model.Configuration;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -55,6 +57,7 @@ public string GenerateJwtToken(UserContext user)
// generate token that is valid for 15 minutes
var tokenHandler = new JwtSecurityTokenHandler();
var claims = GetClaims(user);
var signingKey = GetJwtSigningKey();
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _configuration.JwtIssuer,
Expand All @@ -63,7 +66,7 @@ public string GenerateJwtToken(UserContext user)
IssuedAt = DateTime.UtcNow,
Expires = DateTime.UtcNow.Add(_configuration.JwtAccessTokenExpiryTime),
NotBefore = DateTime.UtcNow,
SigningCredentials = new SigningCredentials(GetSecurityKey(HardwareId.Get()), GetSecurityAlgorithm(_configuration.EncryptionBits))
SigningCredentials = new SigningCredentials(signingKey, GetSecurityAlgorithm(_configuration.EncryptionBits))
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
Expand Down Expand Up @@ -147,9 +150,11 @@ public string GetSecurityAlgorithm(EncryptionBits encryptionBits)
/// <param name="authConfig"></param>
/// <returns></returns>
public TokenValidationParameters GetTokenValidationParameters()
=> new TokenValidationParameters
{
var signingKey = GetJwtSigningKey();
return new TokenValidationParameters
{
IssuerSigningKey = GetSecurityKey(HardwareId.Get()),
IssuerSigningKey = signingKey,
ValidateIssuerSigningKey = _configuration.ValidateIssuerSigningKey,
ValidateIssuer = _configuration.ValidateIssuer,
ValidIssuer = _configuration.JwtIssuer,
Expand All @@ -160,6 +165,27 @@ public TokenValidationParameters GetTokenValidationParameters()
ClockSkew = _configuration.ClockSkew,
// LifetimeValidator = TokenLifetimeValidator.Validate
};
}

private static SecurityKey GetJwtSigningKey()
{
var keyProvider = new SecurityKeyProvider();
var key = string.Empty;
try
{
// use the machine's hardware id to sign jwt
key = HardwareId.Get();
}
catch (Exception)
{
// use fallback
}

if (string.IsNullOrWhiteSpace(key))
key = keyProvider.LoadOrGenerateKey(SecurityKeyProvider.KeyTypes.Jwt, 40);
return GetSecurityKey(key);
}


public static class TokenLifetimeValidator
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Binner.Global.Common
using System.Security.Cryptography;

namespace Binner.Global.Common
{
/// <summary>
/// Generates confirmation tokens
Expand All @@ -12,5 +14,16 @@ public static class ConfirmationTokenGenerator
public static string NewToken()
// note: don't allow / or +, which are valid base64 chars, as these are used in URLs without encoding
=> Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("/", "M").Replace("+", "b");

/// <summary>
/// Generate a new token
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
public static string NewSecurityToken(int length)
{
var bytes = RandomNumberGenerator.GetBytes(length);
return Convert.ToBase64String(bytes).Replace("/", "M").Replace("+", "b").Replace("==", "").Substring(0, length);
}
}
}

0 comments on commit 891c0f0

Please sign in to comment.