diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index a7a92ffd..850072f1 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -11,9 +11,6 @@ ... - -
@Localizer["Info.SignedIn"]
-
@if (!twofactor) { @@ -69,259 +66,265 @@ @code { - private bool _allowsitelogin = true; - private bool _allowexternallogin = false; - private ElementReference login; - private bool validated = false; - private bool twofactor = false; - private string _username = string.Empty; - private ElementReference username; - private string _password = string.Empty; - private string _passwordtype = "password"; - private string _togglepassword = string.Empty; - private bool _remember = false; - private string _code = string.Empty; + private bool _allowsitelogin = true; + private bool _allowexternallogin = false; + private ElementReference login; + private bool validated = false; + private bool twofactor = false; + private string _username = string.Empty; + private ElementReference username; + private string _password = string.Empty; + private string _passwordtype = "password"; + private string _togglepassword = string.Empty; + private bool _remember = false; + private string _code = string.Empty; - private string _returnUrl = string.Empty; + private string _returnUrl = string.Empty; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; - public override List Resources => new List() + public override List Resources => new List() { new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } }; - protected override async Task OnInitializedAsync() - { - try - { - _togglepassword = SharedLocalizer["ShowPassword"]; + protected override async Task OnInitializedAsync() + { + try + { + _togglepassword = SharedLocalizer["ShowPassword"]; - if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"])) - { - _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]); - } + if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"])) + { + _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]); + } - if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"])) - { - _allowexternallogin = true; - } + if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"])) + { + _allowexternallogin = true; + } - if (PageState.QueryString.ContainsKey("returnurl")) - { - _returnUrl = PageState.QueryString["returnurl"]; - } + if (PageState.QueryString.ContainsKey("returnurl")) + { + _returnUrl = PageState.QueryString["returnurl"]; + } - if (PageState.QueryString.ContainsKey("name")) - { - _username = PageState.QueryString["name"]; - } + if (PageState.QueryString.ContainsKey("name")) + { + _username = PageState.QueryString["name"]; + } - if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username)) - { - var user = new User(); - user.SiteId = PageState.Site.SiteId; - user.Username = _username; + if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username)) + { + var user = new User(); + user.SiteId = PageState.Site.SiteId; + user.Username = _username; - if (PageState.QueryString.ContainsKey("key")) - { - user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]); - if (user != null) - { - await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username); - AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info); - } - else - { - await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning); - } - _username = ""; - } - else - { - user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); - if (user != null) - { - await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); - AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); - } - else - { - await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning); - } - } - } - else - { - if (PageState.QueryString.ContainsKey("status")) - { - AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info); - } - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Login {Error}", ex.Message); - AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error); - } - } + if (PageState.QueryString.ContainsKey("key")) + { + user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]); + if (user != null) + { + await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username); + AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info); + } + else + { + await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning); + } + _username = ""; + } + else + { + user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); + if (user != null) + { + await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); + AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); + } + else + { + await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning); + } + } + } + else + { + if (PageState.QueryString.ContainsKey("status")) + { + AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info); + } + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Login {Error}", ex.Message); + AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error); + } + } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender && PageState.User == null) - { - await username.FocusAsync(); - } - } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && PageState.User == null) + { + await username.FocusAsync(); + } - private async Task Login() - { - try - { - validated = true; - var interop = new Interop(JSRuntime); - if (await interop.FormValid(login)) - { - var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid); - var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress}; - - if (!twofactor) - { - user = await UserService.LoginUserAsync(user, hybrid, _remember); - } - else - { - user = await UserService.VerifyTwoFactorAsync(user, _code); - } + // redirect logged in user to specified page + if (PageState.User != null) + { + NavigationManager.NavigateTo(PageState.ReturnUrl); + } + } - if (user.IsAuthenticated) - { - await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username); + private async Task Login() + { + try + { + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(login)) + { + var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid); + var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress}; - if (hybrid) - { - // hybrid apps utilize an interactive login - var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider - .GetService(typeof(IdentityAuthenticationStateProvider)); - authstateprovider.NotifyAuthenticationChanged(); - NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true)); - } - else - { - // post back to the Login page so that the cookies are set correctly - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; - string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); - await interop.SubmitForm(url, fields); - } - } - else - { - if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired) - { - twofactor = true; - validated = false; - AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info); - } - else - { - if (!twofactor) - { - await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error); - } - else - { - await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error); - } - } - } - } - else - { - AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Performing Login {Error}", ex.Message); - AddModuleMessage(Localizer["Error.Login"], MessageType.Error); - } - } + if (!twofactor) + { + user = await UserService.LoginUserAsync(user, hybrid, _remember); + } + else + { + user = await UserService.VerifyTwoFactorAsync(user, _code); + } - private void Cancel() - { - NavigationManager.NavigateTo(_returnUrl); - } + if (user.IsAuthenticated) + { + await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username); - private async Task Forgot() - { - try - { - if (_username != string.Empty) - { - var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId); - if (user != null) - { - await UserService.ForgotPasswordAsync(user); - await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username); - AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info); - } - else - { - AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning); - } - } - else - { - AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info); - } + if (hybrid) + { + // hybrid apps utilize an interactive login + var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider + .GetService(typeof(IdentityAuthenticationStateProvider)); + authstateprovider.NotifyAuthenticationChanged(); + NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true)); + } + else + { + // post back to the Login page so that the cookies are set correctly + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; + string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); + await interop.SubmitForm(url, fields); + } + } + else + { + if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired) + { + twofactor = true; + validated = false; + AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info); + } + else + { + if (!twofactor) + { + await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error); + } + else + { + await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error); + } + } + } + } + else + { + AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Performing Login {Error}", ex.Message); + AddModuleMessage(Localizer["Error.Login"], MessageType.Error); + } + } - StateHasChanged(); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message); - AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error); - } - } + private void Cancel() + { + NavigationManager.NavigateTo(_returnUrl); + } - private void Reset() - { - twofactor = false; - _username = ""; - _password = ""; - ClearModuleMessage(); - StateHasChanged(); - } + private async Task Forgot() + { + try + { + if (_username != string.Empty) + { + var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId); + if (user != null) + { + await UserService.ForgotPasswordAsync(user); + await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username); + AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info); + } + else + { + AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning); + } + } + else + { + AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info); + } - private async Task KeyPressed(KeyboardEventArgs e) - { - if (e.Code == "Enter" || e.Code == "NumpadEnter") - { - await Login(); - } - } + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message); + AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error); + } + } - private void TogglePassword() - { - if (_passwordtype == "password") - { - _passwordtype = "text"; - _togglepassword = SharedLocalizer["HidePassword"]; - } - else - { - _passwordtype = "password"; - _togglepassword = SharedLocalizer["ShowPassword"]; - } - } + private void Reset() + { + twofactor = false; + _username = ""; + _password = ""; + ClearModuleMessage(); + StateHasChanged(); + } - private void ExternalLogin() - { + private async Task KeyPressed(KeyboardEventArgs e) + { + if (e.Code == "Enter" || e.Code == "NumpadEnter") + { + await Login(); + } + } + + private void TogglePassword() + { + if (_passwordtype == "password") + { + _passwordtype = "text"; + _togglepassword = SharedLocalizer["HidePassword"]; + } + else + { + _passwordtype = "password"; + _togglepassword = SharedLocalizer["ShowPassword"]; + } + } + + private void ExternalLogin() + { NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true); - } + } } diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 43d23bde..0c5f80d7 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.Loader; @@ -15,6 +17,7 @@ using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; using Oqtane.Documentation; +using Oqtane.Models; using Oqtane.Modules; using Oqtane.Services; using Oqtane.UI; @@ -66,8 +69,13 @@ namespace Oqtane.Client private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider) { + // get alias var navigationManager = serviceProvider.GetRequiredService(); var urlpath = GetUrlPath(navigationManager.Uri); + var json = await http.GetStringAsync($"api/Installation/installed/?path={WebUtility.UrlEncode(urlpath)}"); + var installation = JsonSerializer.Deserialize(json, new JsonSerializerOptions(JsonSerializerDefaults.Web)); + urlpath = installation.Alias.Path; + urlpath = (!string.IsNullOrEmpty(urlpath)) ? urlpath + "/" : urlpath; var dlls = new Dictionary(); var pdbs = new Dictionary(); @@ -80,7 +88,7 @@ namespace Oqtane.Client if (files.Count() != 0) { // get list of assemblies from server - var json = await http.GetStringAsync($"{urlpath}api/Installation/list"); + json = await http.GetStringAsync($"{urlpath}api/Installation/list"); var assemblies = JsonSerializer.Deserialize>(json); // determine which assemblies need to be downloaded @@ -261,9 +269,7 @@ namespace Oqtane.Client private static string GetUrlPath(string url) { - var path = new Uri(url).AbsolutePath.Substring(1); - path = (!string.IsNullOrEmpty(path) && !path.EndsWith("/")) ? path + "/" : path; - return path; + return new Uri(url).AbsolutePath.Substring(1); } } } diff --git a/Oqtane.Maui/Main.razor b/Oqtane.Maui/Main.razor index d139d631..f8b8069c 100644 --- a/Oqtane.Maui/Main.razor +++ b/Oqtane.Maui/Main.razor @@ -1,18 +1,52 @@ - +@using System.Text.Json; +@using System.Text.Json.Nodes; -@code { - Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client"); - private IDictionary Parameters { get; set; } - - protected override void OnInitialized() - { - Parameters = new Dictionary(); - Parameters.Add(new KeyValuePair("AntiForgeryToken", "")); - Parameters.Add(new KeyValuePair("Runtime", "Hybrid")); - Parameters.Add(new KeyValuePair("RenderMode", "Hybrid")); - Parameters.Add(new KeyValuePair("VisitorId", -1)); - Parameters.Add(new KeyValuePair("RemoteIPAddress", "")); - Parameters.Add(new KeyValuePair("AuthorizationToken", "")); - } +@if (string.IsNullOrEmpty(message)) +{ + +} +else +{ +

@message
+} + +@code { + Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client"); + private IDictionary Parameters { get; set; } + private string message = ""; + + protected override void OnInitialized() + { + Parameters = new Dictionary(); + Parameters.Add(new KeyValuePair("AntiForgeryToken", "")); + Parameters.Add(new KeyValuePair("Runtime", "Hybrid")); + Parameters.Add(new KeyValuePair("RenderMode", "Hybrid")); + Parameters.Add(new KeyValuePair("VisitorId", -1)); + Parameters.Add(new KeyValuePair("RemoteIPAddress", "")); + Parameters.Add(new KeyValuePair("AuthorizationToken", "")); + + if (MauiConstants.UseAppSettings) + { + string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json"); + if (File.Exists(file)) + { + using FileStream stream = File.OpenRead(file); + using StreamReader reader = new StreamReader(stream); + var content = reader.ReadToEnd(); + var obj = JsonSerializer.Deserialize(content)!; + if (string.IsNullOrEmpty((string)obj["Url"]) && string.IsNullOrEmpty(MauiConstants.ApiUrl)) + { + message = "You Must Set The Url In Either MauiConstants.cs Or " + file; + } + } + } + else + { + if (string.IsNullOrEmpty(MauiConstants.ApiUrl)) + { + message = "You Must Set The Url In MauiConstants.cs"; + } + } + } } diff --git a/Oqtane.Maui/MauiConstants.cs b/Oqtane.Maui/MauiConstants.cs new file mode 100644 index 00000000..7ff82bbb --- /dev/null +++ b/Oqtane.Maui/MauiConstants.cs @@ -0,0 +1,13 @@ +namespace Oqtane.Maui; + +public static class MauiConstants +{ + // the API service url (used as fallback if not set in appsettings.json) + public static string ApiUrl = ""; + //public static string ApiUrl = "http://localhost:44357/"; // for local development (Oqtane.Server must be already running for MAUI client to connect) + //public static string apiurl = "http://localhost:44357/sitename/"; // local microsite example + //public static string apiurl = "https://www.dnfprojects.com/"; // for testing remote site + + // specify if you wish to allow users to override the url via appsettings.json in the AppDataDirectory + public static bool UseAppSettings = true; +} diff --git a/Oqtane.Maui/MauiProgram.cs b/Oqtane.Maui/MauiProgram.cs index 3ff3b5bb..95a42117 100644 --- a/Oqtane.Maui/MauiProgram.cs +++ b/Oqtane.Maui/MauiProgram.cs @@ -6,19 +6,12 @@ using Oqtane.Modules; using Oqtane.Services; using System.Globalization; using System.Text.Json; -using Windows.Storage.Provider; using System.Text.Json.Nodes; namespace Oqtane.Maui; public static class MauiProgram { - // the API service url - can be overridden in an appsettings.json in AppDataDirectory - - static string apiurl = "http://localhost:44357/"; // for local development (Oqtane.Server must be already running for MAUI client to connect) - //static string apiurl = "http://localhost:44357/sitename/"; // local microsite example - //static string apiurl = "https://www.dnfprojects.com/"; // for testing remote site - public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); @@ -33,16 +26,19 @@ public static class MauiProgram builder.Services.AddBlazorWebViewDeveloperTools(); #endif - LoadAppSettings(); + var apiurl = LoadAppSettings(); - var httpClient = new HttpClient { BaseAddress = new Uri(GetBaseUrl(apiurl)) }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Shared.Constants.MauiUserAgent); - httpClient.DefaultRequestHeaders.Add(Shared.Constants.MauiAliasPath, GetUrlPath(apiurl).Replace("/", "")); - builder.Services.AddSingleton(httpClient); - builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase + if (!string.IsNullOrEmpty(apiurl)) + { + var httpClient = new HttpClient { BaseAddress = new Uri(GetBaseUrl(apiurl)) }; + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Shared.Constants.MauiUserAgent); + httpClient.DefaultRequestHeaders.Add(Shared.Constants.MauiAliasPath, GetUrlPath(apiurl).Replace("/", "")); + builder.Services.AddSingleton(httpClient); + builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase - // dynamically load client assemblies - LoadClientAssemblies(httpClient); + // dynamically load client assemblies + LoadClientAssemblies(httpClient, apiurl); + } // register localization services builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); @@ -67,28 +63,36 @@ public static class MauiProgram } - private static void LoadAppSettings() + private static string LoadAppSettings() { - // appsettings.json file format - // { - // "Url": "http://localhost:44357/" - // } - - string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json"); - if (File.Exists(file)) + var url = MauiConstants.ApiUrl; + if (MauiConstants.UseAppSettings) { - using FileStream stream = File.OpenRead(file); - using StreamReader reader = new StreamReader(stream); - var content = reader.ReadToEnd(); - var obj = JsonSerializer.Deserialize(content)!; - if (!string.IsNullOrEmpty((string)obj["Url"])) + string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json"); + if (File.Exists(file)) { - apiurl = (string)obj["Url"]; + using FileStream stream = File.OpenRead(file); + using StreamReader reader = new StreamReader(stream); + var content = reader.ReadToEnd(); + var obj = JsonSerializer.Deserialize(content)!; + if (!string.IsNullOrEmpty((string)obj["Url"])) + { + url = (string)obj["Url"]; + } + } + else + { + // create template appsettings.json file + using (StreamWriter writer = File.CreateText(file)) + { + writer.WriteLine("{ \"Url\": \"\" }"); + } } } + return url; } - private static void LoadClientAssemblies(HttpClient http) + private static void LoadClientAssemblies(HttpClient http, string apiurl) { try { @@ -227,7 +231,7 @@ public static class MauiProgram } catch (Exception ex) { - Debug.WriteLine($"Oqtane Error: Loading Client Assemblies {ex}"); + Debug.WriteLine($"Error Loading Client Assemblies From {apiurl} - {ex}"); } } diff --git a/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs index f0e65998..7b8696fa 100644 --- a/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs @@ -37,20 +37,46 @@ namespace Oqtane.Infrastructure var identity = jwtManager.ValidateToken(token, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", "")); if (identity != null && identity.Claims.Any()) { - // create user identity using jwt claims (note the difference in claimtype names) - var user = new User + var idclaim = "nameid"; + var nameclaim = "unique_name"; + var legacynameclaim = "name"; // this was a breaking change in System.IdentityModel.Tokens.Jwt in .NET 7 + + // get jwt claims for userid and username + var userid = identity.Claims.FirstOrDefault(item => item.Type == idclaim)?.Value; + if (userid != null) { - UserId = int.Parse(identity.Claims.FirstOrDefault(item => item.Type == "nameid")?.Value), - Username = identity.Claims.FirstOrDefault(item => item.Type == "name")?.Value - }; - // jwt already contains the roles - we are reloading to ensure most accurate permissions - var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository; + if (!int.TryParse(userid, out _)) + { + userid = null; + } + } + var username = identity.Claims.FirstOrDefault(item => item.Type == nameclaim)?.Value; + if (username == null) + { + // fallback for legacy clients + username = identity.Claims.FirstOrDefault(item => item.Type == legacynameclaim)?.Value; + } - // set claims identity - var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList()); - context.User = new ClaimsPrincipal(claimsidentity); + if (userid != null && username != null) + { + // create user identity + var user = new User + { + UserId = int.Parse(userid), + Username = username + }; - logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For User {Username}", user.Username); + // set claims identity (note jwt already contains the roles - we are reloading to ensure most accurate permissions) + var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository; + var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList()); + context.User = new ClaimsPrincipal(claimsidentity); + + logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For UserId {UserId} And Username {Username}", user.UserId, user.Username); + } + else + { + logger.Log(alias.SiteId, LogLevel.Error, "TokenValidation", Enums.LogFunction.Security, "Token Validated But Could Not Locate UserId Or Username In Claims {Claims}", identity.Claims.ToString()); + } } else { diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index c4d5e91f..3af4b4b7 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -231,7 +231,7 @@ namespace Oqtane.Infrastructure new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.Edit, RoleNames.Admin, true) }, - Content = "

The page you requested does not exist.

" + Content = "

The page you requested does not exist or you do not have sufficient rights to view it.

" } } }); diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 92677c78..e7d99d51 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -130,14 +130,14 @@ namespace Oqtane.Managers if (!user.EmailConfirmed) { string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - string url = alias.Protocol + "://" + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; var notification = new Notification(user.SiteId, User, "User Account Verification", body); _notifications.AddNotification(notification); } else { - string url = alias.Protocol + "://" + alias.Name; + string url = alias.Protocol + alias.Name; string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You. Please Use The Following Link To Access The Site:\n\n" + url + "\n\nThank You!"; var notification = new Notification(user.SiteId, User, "User Account Notification", body); _notifications.AddNotification(notification); @@ -299,7 +299,7 @@ namespace Oqtane.Managers var alias = _tenantManager.GetAlias(); user = _users.GetUser(user.Username); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); - string url = alias.Protocol + "://" + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url + "\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." + "\n\nThank You!"; @@ -348,7 +348,7 @@ namespace Oqtane.Managers var alias = _tenantManager.GetAlias(); user = _users.GetUser(user.Username); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); - string url = alias.Protocol + "://" + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url + "\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." + "\n\nIf you did not request to reset your password you can safely ignore this message." + diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index e75738ae..72e07bc7 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -640,7 +640,7 @@ namespace Oqtane.Repository new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.Edit, RoleNames.Admin, true) }, - Content = "

The page you requested does not exist.

" + Content = "

The page you requested does not exist or you do not have sufficient rights to view it.

" } } });