Merge remote-tracking branch 'oqtane/dev' into dev
This commit is contained in:
commit
aa3a4dca65
|
@ -11,9 +11,6 @@
|
|||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
<div>@Localizer["Info.SignedIn"]</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@if (!twofactor)
|
||||
{
|
||||
|
@ -174,6 +171,12 @@
|
|||
{
|
||||
await username.FocusAsync();
|
||||
}
|
||||
|
||||
// redirect logged in user to specified page
|
||||
if (PageState.User != null)
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Login()
|
||||
|
|
|
@ -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<NavigationManager>();
|
||||
var urlpath = GetUrlPath(navigationManager.Uri);
|
||||
var json = await http.GetStringAsync($"api/Installation/installed/?path={WebUtility.UrlEncode(urlpath)}");
|
||||
var installation = JsonSerializer.Deserialize<Installation>(json, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
urlpath = installation.Alias.Path;
|
||||
urlpath = (!string.IsNullOrEmpty(urlpath)) ? urlpath + "/" : urlpath;
|
||||
|
||||
var dlls = new Dictionary<string, byte[]>();
|
||||
var pdbs = new Dictionary<string, byte[]>();
|
||||
|
@ -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<List<string>>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
@using System.Text.Json;
|
||||
@using System.Text.Json.Nodes;
|
||||
|
||||
@if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
<DynamicComponent Type="@ComponentType" Parameters="@Parameters"></DynamicComponent>
|
||||
}
|
||||
else
|
||||
{
|
||||
<br /><br /><center>@message</center>
|
||||
}
|
||||
|
||||
@code {
|
||||
Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client");
|
||||
private IDictionary<string, object> Parameters { get; set; }
|
||||
private string message = "";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
|
@ -13,6 +24,29 @@
|
|||
Parameters.Add(new KeyValuePair<string, object>("VisitorId", -1));
|
||||
Parameters.Add(new KeyValuePair<string, object>("RemoteIPAddress", ""));
|
||||
Parameters.Add(new KeyValuePair<string, object>("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<JsonObject>(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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
Oqtane.Maui/MauiConstants.cs
Normal file
13
Oqtane.Maui/MauiConstants.cs
Normal file
|
@ -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;
|
||||
}
|
|
@ -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,8 +26,10 @@ public static class MauiProgram
|
|||
builder.Services.AddBlazorWebViewDeveloperTools();
|
||||
#endif
|
||||
|
||||
LoadAppSettings();
|
||||
var apiurl = LoadAppSettings();
|
||||
|
||||
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("/", ""));
|
||||
|
@ -42,7 +37,8 @@ public static class MauiProgram
|
|||
builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
|
||||
|
||||
// dynamically load client assemblies
|
||||
LoadClientAssemblies(httpClient);
|
||||
LoadClientAssemblies(httpClient, apiurl);
|
||||
}
|
||||
|
||||
// register localization services
|
||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||
|
@ -67,13 +63,11 @@ public static class MauiProgram
|
|||
}
|
||||
|
||||
|
||||
private static void LoadAppSettings()
|
||||
private static string LoadAppSettings()
|
||||
{
|
||||
var url = MauiConstants.ApiUrl;
|
||||
if (MauiConstants.UseAppSettings)
|
||||
{
|
||||
// appsettings.json file format
|
||||
// {
|
||||
// "Url": "http://localhost:44357/"
|
||||
// }
|
||||
|
||||
string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json");
|
||||
if (File.Exists(file))
|
||||
{
|
||||
|
@ -83,12 +77,22 @@ public static class MauiProgram
|
|||
var obj = JsonSerializer.Deserialize<JsonObject>(content)!;
|
||||
if (!string.IsNullOrEmpty((string)obj["Url"]))
|
||||
{
|
||||
apiurl = (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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (userid != null && username != null)
|
||||
{
|
||||
// create user identity
|
||||
var user = new User
|
||||
{
|
||||
UserId = int.Parse(identity.Claims.FirstOrDefault(item => item.Type == "nameid")?.Value),
|
||||
Username = identity.Claims.FirstOrDefault(item => item.Type == "name")?.Value
|
||||
UserId = int.Parse(userid),
|
||||
Username = username
|
||||
};
|
||||
// jwt already contains the roles - we are reloading to ensure most accurate permissions
|
||||
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
||||
|
||||
// set claims identity
|
||||
// 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 User {Username}", user.Username);
|
||||
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
|
||||
{
|
||||
|
|
|
@ -231,7 +231,7 @@ namespace Oqtane.Infrastructure
|
|||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
},
|
||||
Content = "<p>The page you requested does not exist.</p>"
|
||||
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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." +
|
||||
|
|
|
@ -640,7 +640,7 @@ namespace Oqtane.Repository
|
|||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
},
|
||||
Content = "<p>The page you requested does not exist.</p>"
|
||||
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user