Skip to content

Commit

Permalink
Merge pull request #293 from dymaptic/feature/simplify-registration
Browse files Browse the repository at this point in the history
Simplify registration, don't require callback to server
  • Loading branch information
TimPurdum authored Jan 21, 2024
2 parents 7659fda + 5bf70c8 commit 8da07a1
Showing 1 changed file with 38 additions and 195 deletions.
233 changes: 38 additions & 195 deletions src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Configuration;
using Microsoft.JSInterop;
using System.Diagnostics;
using System.Net.Http.Json;
using System.Security;
using System.Text;
using System.Text.Json;
using System.Web;


#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

namespace dymaptic.GeoBlazor.Core;
Expand All @@ -18,157 +17,59 @@ public interface IAppValidator

internal class RegistrationValidator: IAppValidator
{
public RegistrationValidator(GeoBlazorSettings settings, IJSRuntime jsRuntime, HttpClient httpClient,
NavigationManager navigationManager, IConfiguration configuration)
public RegistrationValidator(GeoBlazorSettings settings, IJSRuntime jsRuntime, NavigationManager navigationManager)
{
_settings = settings;
_jsRuntime = jsRuntime;
_httpClient = httpClient;
_navigationManager = navigationManager;
_machineName = Environment.MachineName;
#if DEBUG
_licenseServerUrl = configuration["LicenseServerUrl"] ?? "https://licensing-dev.dymaptic.com/";
#endif
}

public async Task ValidateLicense()
{
// if we've already shown the message, there's no need to check again
if (_messageShown)
// if we've already shown the message or validated, there's no need to check again
if (_messageShown || _isValidated)
{
return;
}

// if we've already found a valid license while the software is running, there's no need to check
if ( _inMemoryValidationResult is not null && _inMemoryValidationResult.IsValid)
{
if (_inMemoryValidationResult.BaseUri != _navigationManager.BaseUri)
{
_inMemoryValidationResult = null;
}
else
{
return;
}
}
if (_validating) return;

string? storedValidation;
BlazorMode blazorMode;
_validating = true;

if (_jsRuntime.GetType().Name.Contains("Remote")) // Server
{
blazorMode = BlazorMode.Server;
storedValidation = await GetServerFileValidationResult();
}
else
{
blazorMode = OperatingSystem.IsBrowser() ? BlazorMode.WebAssembly : BlazorMode.Maui;
storedValidation = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "validationResult");
}
BlazorMode blazorMode = _jsRuntime.GetType().Name.Contains("Remote") ? BlazorMode.Server :
OperatingSystem.IsBrowser() ? BlazorMode.WebAssembly : BlazorMode.Maui;

ValidationResult? validationResult = null;
ValidationResult? storedValidationResult = null;
string? registration = _settings.RegistrationKey;
bool valid = registration is not null;

if (storedValidation is not null)
if (valid)
{
try
{
storedValidationResult = JsonSerializer.Deserialize<ValidationResult>(storedValidation);
string registrationText = Encoding.UTF8.GetString(Convert.FromBase64String(registration!));

// don't use stored results with a different base uri or machine name
if (storedValidationResult?.BaseUri != _navigationManager.BaseUri ||
!storedValidationResult.MachineName.Equals(_machineName, StringComparison.OrdinalIgnoreCase))
RegistrationObject registrationObject =
JsonSerializer.Deserialize<RegistrationObject>(registrationText)!;
if (valid && registrationObject!.LicenseVersion != 1)
{
storedValidationResult = null;
valid = false;
}

if (storedValidationResult?.AttemptedConnect is not null &&
storedValidationResult.AttemptedConnect.Value.AddMinutes(5) > DateTime.UtcNow)
{
// too soon to check again, the connection was down
return;
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}

// if there is no valid stored license, call the server to get a new license
if ((storedValidationResult is null || !storedValidationResult.IsValid) &&
_settings.RegistrationKey is not null)
{
try
{
var queryString = new Dictionary<string, string>
if (valid && registrationObject.LicenseType != "Free")
{
{ "licenseKey", HttpUtility.UrlEncode(_settings.RegistrationKey) },
{ "licenseTypeName", "Free" },
{ "softwareName", "GeoBlazorCore" }
};

if (!string.IsNullOrWhiteSpace(_settings.MauiAppName))
{
queryString["mauiAppName"] = HttpUtility.UrlEncode(_settings.MauiAppName);
valid = false;
}

// build query string without QueryHelpers, which is not available in WebAssembly
var queryStringString = string.Join("&", queryString.Select(kvp => $"{kvp.Key}={kvp.Value}"));
#if DEBUG
var url = $"{_licenseServerUrl}/api/validate?{queryStringString}";
#else
var url = $"https://licensing.dymaptic.com/api/validate?{queryStringString}";
#endif
_httpClient.DefaultRequestHeaders.Referrer = new Uri(_navigationManager.BaseUri);
HttpResponseMessage response = await _httpClient.GetAsync(url);

validationResult = await response.Content.ReadFromJsonAsync<ValidationResult>();

if (validationResult is not null)
if (valid && registrationObject.Software != "GeoBlazorCore")
{
validationResult.MachineName = _machineName;
validationResult.BaseUri = _navigationManager.BaseUri;
string jsonResult = JsonSerializer.Serialize(validationResult);

await SaveFile(blazorMode, jsonResult);
valid = false;
}
}
catch (HttpRequestException)
catch
{
if (storedValidationResult?.AttemptedConnect is not null)
{
validationResult = storedValidationResult;
validationResult.AttemptedConnect = DateTime.UtcNow;
}
else
{
validationResult =
new ValidationResult(false, DateTime.MaxValue, "Unable to reach license server.")
{
AttemptedConnect = DateTime.UtcNow
};
}

string jsonResult = JsonSerializer.Serialize(validationResult);
await SaveFile(blazorMode, jsonResult);

// the server appears to be down, try again in 5 minutes
return;
}
catch (Exception)
{
// don't throw anything here, we will deal with failure after checking stored result
valid = false;
}
}

// if we failed to reach the server, or the server returned an error, use the stored result
if (validationResult is null && storedValidationResult is not null)
{
validationResult = storedValidationResult;
}

if (validationResult is null || !validationResult.IsValid)

if (!valid)
{
if (!_messageShown)
{
Expand All @@ -180,86 +81,22 @@ public async Task ValidateLicense()
}

_messageShown = true;
return;
}
return;
}

_inMemoryValidationResult = validationResult;
_isValidated = true;
_validating = false;
}

private async Task<string?> GetServerFileValidationResult()
{
string directoryPath = _settings.ValidationServerStoragePath ?? Path.GetTempPath();
string filePath = Path.Combine(directoryPath, ServerFileName);

if (!File.Exists(filePath))
{
return null;
}

return await File.ReadAllTextAsync(filePath);
}

private async Task SaveFile(BlazorMode blazorMode, string encodedResult)
{
if (blazorMode == BlazorMode.Server) // Server
{
await SaveServerFileValidationResult(encodedResult);
}
else
{
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "validationResult", encodedResult);
}
}

private async Task SaveServerFileValidationResult(string result)
{
string directoryPath = _settings.ValidationServerStoragePath ?? Path.GetTempPath();
try
{
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}

string filePath = Path.Combine(directoryPath, ServerFileName);
await File.WriteAllTextAsync(filePath, result);
}
catch (SecurityException)
{
string failMessage =
$"Unable to save registration validation file. Please verify that the application has write access to the directory {directoryPath}.";
await _jsRuntime.InvokeVoidAsync(
$"console.log('{failMessage}'");
Console.WriteLine(failMessage);
}
}

private static ValidationResult? _inMemoryValidationResult;
private static bool _isValidated;
private readonly GeoBlazorSettings _settings;
private readonly IJSRuntime _jsRuntime;
private readonly HttpClient _httpClient;
private readonly NavigationManager _navigationManager;
private readonly string _machineName;
private const string ServerFileName = "geoblazor-registration-validation";
private readonly string _registrationMessage =
"Thank you for using GeoBlazor! Please visit https://licensing.dymaptic.com/geoblazor-core to register.";
private static bool _messageShown;
#if DEBUG
private readonly string _licenseServerUrl;
#endif
}

/// <summary>
/// For internal use only
/// </summary>
public record ValidationResult(bool IsValid, DateTime ExpirationDate, string? Message = null)
{
public string MachineName { get; set; } = string.Empty;
public Version? Version { get; set; }

public DateTime? AttemptedConnect { get; set; }
public string? BaseUri { get; set; }
private bool _validating;
}

internal enum BlazorMode
Expand All @@ -269,4 +106,10 @@ internal enum BlazorMode
WebAssembly,
Maui
#pragma warning restore CS1591
}
}

internal record RegistrationObject(
string Email,
string LicenseType,
string Software,
int LicenseVersion);

0 comments on commit 8da07a1

Please sign in to comment.