From ba61383fa552178854bc1263d00270f4832b156d Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 14 Jan 2024 21:50:51 -0600 Subject: [PATCH 1/2] Simplify registration, don't require callback to server --- .../RegistrationValidator.cs | 227 +++--------------- 1 file changed, 36 insertions(+), 191 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs index 1dce542e..feb60072 100644 --- a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs +++ b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs @@ -4,6 +4,7 @@ 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 @@ -18,157 +19,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("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(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(registrationText)!; + if (valid && registrationObject!.LicenseVersion != 1) { - storedValidationResult = null; + valid = false; } - - if (storedValidationResult?.AttemptedConnect is not null && - storedValidationResult.AttemptedConnect.Value.AddMinutes(5) > DateTime.UtcNow) + if (valid && registrationObject.LicenseType != "Free") { - // too soon to check again, the connection was down - return; + valid = false; } - } - 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 - { - { "licenseKey", HttpUtility.UrlEncode(_settings.RegistrationKey) }, - { "licenseTypeName", "Free" }, - { "softwareName", "GeoBlazorCore" } - }; - - if (!string.IsNullOrWhiteSpace(_settings.MauiAppName)) - { - queryString["mauiAppName"] = HttpUtility.UrlEncode(_settings.MauiAppName); - } - - // 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(); - - if (validationResult is not null) - { - validationResult.MachineName = _machineName; - validationResult.BaseUri = _navigationManager.BaseUri; - string jsonResult = JsonSerializer.Serialize(validationResult); - - await SaveFile(blazorMode, jsonResult); - } - } - catch (HttpRequestException) - { - if (storedValidationResult?.AttemptedConnect is not null) + if (valid && registrationObject.Software != "GeoBlazorCore") { - validationResult = storedValidationResult; - validationResult.AttemptedConnect = DateTime.UtcNow; + valid = false; } - 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) + catch { - // 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) { @@ -180,86 +83,22 @@ public async Task ValidateLicense() } _messageShown = true; + return; } - return; - } - - _inMemoryValidationResult = validationResult; - } - - private async Task 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); - } + _isValidated = true; + _validating = false; } - 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 -} - -/// -/// For internal use only -/// -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 @@ -269,4 +108,10 @@ internal enum BlazorMode WebAssembly, Maui #pragma warning restore CS1591 -} \ No newline at end of file +} + +internal record RegistrationObject( + string Email, + string LicenseType, + string Software, + int LicenseVersion); \ No newline at end of file From 5bf70c8797e82e00f7a48d43b5366b2b00c5bfac Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Sun, 14 Jan 2024 22:03:13 -0600 Subject: [PATCH 2/2] clean up usings --- src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs index feb60072..01f0cc1b 100644 --- a/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs +++ b/src/dymaptic.GeoBlazor.Core/RegistrationValidator.cs @@ -1,12 +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;