@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>10.0.1</Version>
|
||||
<Version>10.0.2</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Client" Version="10.0.1" />
|
||||
<PackageReference Include="Oqtane.Client" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Application.Template</id>
|
||||
<version>10.0.1</version>
|
||||
<version>10.0.2</version>
|
||||
<title>Oqtane Application Template For Blazor</title>
|
||||
<authors>Shaun Walker</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Server" Version="10.0.1" />
|
||||
<PackageReference Include="Oqtane.Server" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Shared" Version="10.0.1" />
|
||||
<PackageReference Include="Oqtane.Shared" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -127,6 +127,7 @@ else
|
||||
private bool _allowsitelogin = true;
|
||||
private bool _allowloginlink = false;
|
||||
private bool _allowpasskeys = false;
|
||||
private string _returnurl = string.Empty;
|
||||
|
||||
private ElementReference login;
|
||||
private bool validated = false;
|
||||
@ -169,6 +170,9 @@ else
|
||||
_registerurl = NavigateUrl("register");
|
||||
}
|
||||
|
||||
// PageState.ReturnUrl is not specified if user navigated directly to login page
|
||||
_returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
if (PageState.QueryString.ContainsKey("name"))
|
||||
@ -216,7 +220,7 @@ else
|
||||
{
|
||||
if (PageState.QueryString.ContainsKey("status"))
|
||||
{
|
||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,7 +256,7 @@ else
|
||||
|
||||
private void ExternalLogin()
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(_returnurl)), true);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
@ -294,20 +298,17 @@ else
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||
|
||||
// return url is not specified if user navigated directly to login page
|
||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||
|
||||
if (hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||
NavigationManager.NavigateTo(NavigateUrl(_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 = WebUtility.UrlEncode(returnurl) };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(_returnurl) };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
@ -349,14 +350,14 @@ else
|
||||
|
||||
private void CancelLogin()
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
NavigationManager.NavigateTo(_returnurl);
|
||||
}
|
||||
|
||||
private async Task PasskeyLogin()
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var interop = new Interop(JSRuntime);
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = NavigateUrl() };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = _returnurl };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
@ -423,7 +424,7 @@ else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_email))
|
||||
{
|
||||
if (await UserService.SendLoginLinkAsync(_email))
|
||||
if (await UserService.SendLoginLinkAsync(_email, _returnurl))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info);
|
||||
await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email);
|
||||
@ -457,8 +458,7 @@ else
|
||||
if (!string.IsNullOrEmpty(credential))
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path + "/";
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = returnurl };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = _returnurl };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
@ -497,7 +497,7 @@ else
|
||||
// redirect logged in user to specified page
|
||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
NavigationManager.NavigateTo(_returnurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@ -81,21 +81,9 @@
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -110,27 +98,6 @@
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -141,15 +108,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
||||
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -181,6 +141,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@ -98,21 +98,9 @@
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -127,27 +115,6 @@
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -158,14 +125,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||
<Section Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -200,6 +161,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
|
||||
@ -36,6 +36,7 @@ else
|
||||
<th>@Localizer["Url"]</th>
|
||||
<th>@Localizer["Requests"]</th>
|
||||
<th>@Localizer["Requested"]</th>
|
||||
<th>@Localizer["Referrer"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||
@ -49,7 +50,8 @@ else
|
||||
</td>
|
||||
<td>@context.Requests</td>
|
||||
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||
</Row>
|
||||
<td>@context.Referrer</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
|
||||
@ -114,9 +114,9 @@
|
||||
}
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddPasskey">@SharedLocalizer["Add"]</button>
|
||||
@if (_passkeys != null && _passkeys.Count > 0)
|
||||
@if (_passkeys.Count > 0)
|
||||
{
|
||||
<Pager Items="@_passkeys">
|
||||
<Header>
|
||||
@ -142,15 +142,15 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||
@if (_logins != null && _logins.Count > 0)
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||
@if (_logins.Count > 0)
|
||||
{
|
||||
<Pager Items="@_logins">
|
||||
<Header>
|
||||
@ -165,7 +165,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Logins.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
|
||||
@ -106,8 +106,8 @@
|
||||
<br /><br />
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||
@if (_passkeys != null && _passkeys.Count > 0)
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||
@if (_passkeys.Count > 0)
|
||||
{
|
||||
<Pager Items="@_passkeys">
|
||||
<Header>
|
||||
@ -122,15 +122,15 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||
@if (_logins != null && _logins.Count > 0)
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||
@if (_logins.Count > 0)
|
||||
{
|
||||
<Pager Items="@_logins">
|
||||
<Header>
|
||||
@ -145,7 +145,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Logins.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
|
||||
@ -246,6 +246,9 @@
|
||||
<data name="ExternalLoginStatus.LoginLinkFailed" xml:space="preserve">
|
||||
<value>Login Links Are Time Sensitive. Please Request Another Login Link To Complete The Login Process.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.PasskeyFailed" xml:space="preserve">
|
||||
<value>Passkey Login Was Unsuccessful. Please Ensure You Selected The Correct Passkey For This Site.</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>Register as new user?</value>
|
||||
</data>
|
||||
|
||||
@ -225,9 +225,6 @@
|
||||
<data name="Personalizable.Text" xml:space="preserve">
|
||||
<value>Personalizable? </value>
|
||||
</data>
|
||||
<data name="Appearance.Name" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="HeadContent.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
|
||||
</data>
|
||||
@ -253,7 +250,7 @@
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="Theme.Heading" xml:space="preserve">
|
||||
<value>Theme Settings</value>
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this page is active</value>
|
||||
@ -267,4 +264,7 @@
|
||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||
<value>Expiry Date: </value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Appearance.Heading" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -309,4 +309,7 @@
|
||||
<data name="UpdateModulePermissions.HelpText" xml:space="preserve">
|
||||
<value>Specify if changes made to page permissions should be propagated to the modules on this page</value>
|
||||
</data>
|
||||
<data name="Theme.Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -224,7 +224,7 @@ namespace Oqtane.Services
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SendLoginLinkAsync(string email);
|
||||
Task<bool> SendLoginLinkAsync(string email, string returnurl);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
@ -386,9 +386,9 @@ namespace Oqtane.Services
|
||||
await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}");
|
||||
}
|
||||
|
||||
public async Task<bool> SendLoginLinkAsync(string email)
|
||||
public async Task<bool> SendLoginLinkAsync(string email, string returnurl)
|
||||
{
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/loginlink/{WebUtility.UrlEncode(email)}");
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/loginlink/{WebUtility.UrlEncode(email)}/{WebUtility.UrlEncode(returnurl)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,36 @@
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@namespace Oqtane.Themes.Controls
|
||||
|
||||
@switch (Orientation)
|
||||
@if (_menuType != null)
|
||||
{
|
||||
case "Horizontal":
|
||||
<MenuHorizontal/>
|
||||
break;
|
||||
default: // Vertical
|
||||
{
|
||||
<MenuVertical/>
|
||||
break;
|
||||
}
|
||||
<DynamicComponent Type="@_menuType" Parameters="@Attributes"></DynamicComponent>
|
||||
}
|
||||
|
||||
@code{
|
||||
|
||||
[Parameter]
|
||||
public string Orientation { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string MenuType { get; set; }
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
private Type _menuType;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (string.IsNullOrEmpty(MenuType) && !string.IsNullOrEmpty(Orientation))
|
||||
{
|
||||
if (Orientation == "Horizontal")
|
||||
{
|
||||
MenuType = "Oqtane.Themes.Controls.MenuHorizontal, Oqtane.Client";
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuType = "Oqtane.Themes.Controls.MenuVertical, Oqtane.Client";
|
||||
}
|
||||
}
|
||||
|
||||
_menuType = Type.GetType(MenuType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
|
||||
<main role="main">
|
||||
<nav class="navbar navbar-dark bg-primary fixed-top">
|
||||
<Logo UseSiteNameAsFallback="true" /><Menu Orientation="Horizontal" />
|
||||
<Logo UseSiteNameAsFallback="true" />
|
||||
<Menu MenuType="Oqtane.Themes.Controls.MenuHorizontal, Oqtane.Client" />
|
||||
<div class="controls ms-auto">
|
||||
<div class="controls-group">
|
||||
<Search CssClass="me-3 text-center bg-primary" />
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>10.0.1</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>10.0.2</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
|
||||
@ -273,6 +273,11 @@ app {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.app-editor-resizable {
|
||||
resize: vertical;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app-logo .navbar-brand {
|
||||
padding: 5px 20px 5px 20px;
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
},
|
||||
includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
|
||||
var script;
|
||||
var script = null;
|
||||
if (src !== "") {
|
||||
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
|
||||
}
|
||||
@ -140,7 +140,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (script !== null) {
|
||||
if (script instanceof HTMLScriptElement) {
|
||||
script.remove();
|
||||
script = null;
|
||||
}
|
||||
@ -516,5 +516,17 @@ Oqtane.Interop = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createCredential: async function (optionsResponse) {
|
||||
const optionsJson = JSON.parse(optionsResponse);
|
||||
const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
|
||||
const credential = await navigator.credentials.create({ publicKey: options });
|
||||
return JSON.stringify(credential);
|
||||
},
|
||||
requestCredential: async function (optionsResponse) {
|
||||
const optionsJson = JSON.parse(optionsResponse);
|
||||
const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
|
||||
const credential = await navigator.credentials.get({ publicKey: options, undefined });
|
||||
return JSON.stringify(credential);
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>10.0.1</version>
|
||||
<version>10.0.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>10.0.1</version>
|
||||
<version>10.0.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,8 +11,8 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.0.1/Oqtane.Framework.10.0.1.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.0.2/Oqtane.Framework.10.0.2.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>10.0.1</version>
|
||||
<version>10.0.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>10.0.1</version>
|
||||
<version>10.0.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>10.0.1</version>
|
||||
<version>10.0.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
||||
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.1.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.2.Install.zip" -Force
|
||||
|
||||
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.1.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.2.Upgrade.zip" -Force
|
||||
|
||||
@ -294,8 +294,11 @@
|
||||
|
||||
private void HandlePageNotFound(Site site, Page page, Route route)
|
||||
{
|
||||
// referrer will only be set if the link originated externally
|
||||
string referrer = (Context.Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.Referer] : "";
|
||||
|
||||
// page not found - look for url mapping
|
||||
var urlMapping = UrlMappingRepository.GetUrlMapping(site.SiteId, route.PagePath);
|
||||
var urlMapping = UrlMappingRepository.GetUrlMapping(site.SiteId, route.PagePath, referrer);
|
||||
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
{
|
||||
// redirect to mapped url
|
||||
|
||||
@ -90,7 +90,7 @@ namespace Oqtane.Controllers
|
||||
else
|
||||
{
|
||||
// suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies or private browsing sessions
|
||||
if (entityName != EntityNames.Visitor)
|
||||
if (FormatName(entityName) != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings For EntityName {EntityName} And EntityId {EntityId}", entityName, entityId);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
@ -114,7 +114,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (setting != null && entityName != EntityNames.Visitor)
|
||||
if (setting != null && FormatName(entityName) != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access SettingId {SettingId} For EntityName {EntityName} ", id, entityName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
@ -139,7 +139,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (setting.EntityName != EntityNames.Visitor)
|
||||
if (FormatName(setting.EntityName) != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
@ -161,7 +161,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (setting.EntityName != EntityNames.Visitor)
|
||||
if (FormatName(setting.EntityName) != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
@ -261,7 +261,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entityName != EntityNames.Visitor)
|
||||
if (FormatName(entityName) != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For EntityName {EntityName} EntityId {EntityId} SettingName {SettingName}", entityName, entityId, settingName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
@ -282,7 +282,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entityName != EntityNames.Visitor)
|
||||
if (FormatName(entityName) != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For SettingId {SettingId} For EntityName {EntityName} ", id, entityName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
@ -408,19 +408,21 @@ namespace Oqtane.Controllers
|
||||
private bool IsAuthorized(string entityName, int entityId, string permissionName)
|
||||
{
|
||||
bool authorized = false;
|
||||
|
||||
if (entityName == EntityNames.PageModule)
|
||||
{
|
||||
entityName = EntityNames.Module;
|
||||
entityId = _pageModules.GetPageModule(entityId).ModuleId;
|
||||
}
|
||||
switch (entityName)
|
||||
|
||||
switch (FormatName(entityName))
|
||||
{
|
||||
case EntityNames.Tenant:
|
||||
case EntityNames.ModuleDefinition:
|
||||
case EntityNames.Host:
|
||||
case EntityNames.Job:
|
||||
case EntityNames.Theme:
|
||||
if (permissionName == PermissionNames.Edit)
|
||||
if (FormatName(permissionName) == PermissionNames.Edit)
|
||||
{
|
||||
authorized = User.IsInRole(RoleNames.Host);
|
||||
}
|
||||
@ -431,7 +433,7 @@ namespace Oqtane.Controllers
|
||||
break;
|
||||
case EntityNames.Site:
|
||||
case EntityNames.Role:
|
||||
if (permissionName == PermissionNames.Edit)
|
||||
if (FormatName(permissionName) == PermissionNames.Edit)
|
||||
{
|
||||
authorized = User.IsInRole(RoleNames.Admin);
|
||||
}
|
||||
@ -458,7 +460,7 @@ namespace Oqtane.Controllers
|
||||
break;
|
||||
default: // custom entity
|
||||
authorized = true;
|
||||
if (permissionName == PermissionNames.Edit)
|
||||
if (FormatName(permissionName) == PermissionNames.Edit)
|
||||
{
|
||||
if (entityId == -1)
|
||||
{
|
||||
@ -477,7 +479,7 @@ namespace Oqtane.Controllers
|
||||
private bool FilterPrivate(string entityName, int entityId)
|
||||
{
|
||||
bool filter = false;
|
||||
switch (entityName)
|
||||
switch (FormatName(entityName))
|
||||
{
|
||||
case EntityNames.Tenant:
|
||||
case EntityNames.ModuleDefinition:
|
||||
@ -526,9 +528,9 @@ namespace Oqtane.Controllers
|
||||
|
||||
private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias, EntityName + "Setting", SettingId, Action);
|
||||
_syncManager.AddSyncEvent(_alias, FormatName(EntityName) + "Setting", SettingId, Action);
|
||||
|
||||
switch (EntityName)
|
||||
switch (FormatName(EntityName))
|
||||
{
|
||||
case EntityNames.Module:
|
||||
case EntityNames.Page:
|
||||
@ -540,5 +542,15 @@ namespace Oqtane.Controllers
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatName(string name)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
// entity names and permission names are case sensitive
|
||||
name = name.Substring(0, 1).ToUpper() + name.Substring(1).ToLower();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -563,11 +563,11 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// GET api/<controller>/loginlink/x
|
||||
[HttpGet("loginlink/{email}")]
|
||||
public async Task<bool> SendLoginLink(string email)
|
||||
// GET api/<controller>/loginlink/x/y
|
||||
[HttpGet("loginlink/{email}/{returnurl}")]
|
||||
public async Task<bool> SendLoginLink(string email, string returnurl)
|
||||
{
|
||||
return await _userManager.SendLoginLink(email);
|
||||
return await _userManager.SendLoginLink(email, returnurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using EFCore.NamingConventions.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@ -108,36 +109,40 @@ namespace Oqtane.Database.PostgreSQL
|
||||
|
||||
public override void UpdateIdentityStoreTableNames(ModelBuilder builder)
|
||||
{
|
||||
foreach(var entity in builder.Model.GetEntityTypes())
|
||||
foreach (var entity in builder.Model.GetEntityTypes())
|
||||
{
|
||||
var tableName = entity.GetTableName();
|
||||
if (tableName.StartsWith("AspNetUser"))
|
||||
// the IdentityPasskeyData entity was introduced in .NET 10 and is not mapped to a database table so should be ignored
|
||||
if (entity.ClrType.Name != "IdentityPasskeyData")
|
||||
{
|
||||
// replace table name
|
||||
entity.SetTableName(RewriteName(entity.GetTableName()));
|
||||
|
||||
// replace column names
|
||||
foreach(var property in entity.GetProperties())
|
||||
var tableName = entity.GetTableName();
|
||||
if (tableName.StartsWith("AspNetUser"))
|
||||
{
|
||||
property.SetColumnName(RewriteName(property.Name));
|
||||
}
|
||||
// replace table name
|
||||
entity.SetTableName(RewriteName(entity.GetTableName()));
|
||||
|
||||
// replace key names
|
||||
foreach(var key in entity.GetKeys())
|
||||
{
|
||||
key.SetName(RewriteName(key.GetName()));
|
||||
}
|
||||
// replace column names
|
||||
foreach (var property in entity.GetProperties())
|
||||
{
|
||||
property.SetColumnName(RewriteName(property.Name));
|
||||
}
|
||||
|
||||
// replace foreign key names
|
||||
foreach (var key in entity.GetForeignKeys())
|
||||
{
|
||||
key.PrincipalKey.SetName(RewriteName(key.PrincipalKey.GetName()));
|
||||
}
|
||||
// replace key names
|
||||
foreach (var key in entity.GetKeys())
|
||||
{
|
||||
key.SetName(RewriteName(key.GetName()));
|
||||
}
|
||||
|
||||
// replace index names
|
||||
foreach (var index in entity.GetIndexes())
|
||||
{
|
||||
index.SetDatabaseName(RewriteName(index.GetDatabaseName()));
|
||||
// replace foreign key names
|
||||
foreach (var key in entity.GetForeignKeys())
|
||||
{
|
||||
key.PrincipalKey.SetName(RewriteName(key.PrincipalKey.GetName()));
|
||||
}
|
||||
|
||||
// replace index names
|
||||
foreach (var index in entity.GetIndexes())
|
||||
{
|
||||
index.SetDatabaseName(RewriteName(index.GetDatabaseName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,12 +31,15 @@ namespace Oqtane.Database.Sqlite
|
||||
|
||||
public override void DropColumn(MigrationBuilder builder, string name, string table)
|
||||
{
|
||||
// not implemented as SQLite does not support dropping columns
|
||||
// SQLite supports dropping columns starting with version 3.35.0 but EF Core does not implement it yet
|
||||
// note that a column cannot be dropped if it has a UNIQUE constraint, is part of a PRIMARY KEY, is indexed, or is referenced by other parts of the schema
|
||||
builder.Sql($"ALTER TABLE {table} DROP COLUMN {name};");
|
||||
}
|
||||
|
||||
public override void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index)
|
||||
{
|
||||
// not implemented as SQLite does not support altering columns
|
||||
// note that column length does not need to be modified as SQLite uses a TEXT type which utilizes variable length strings
|
||||
}
|
||||
|
||||
public override string ConcatenateSql(params string[] values)
|
||||
|
||||
@ -44,7 +44,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
// process forwarded headers on load balancers and proxy servers
|
||||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
|
||||
options.KnownIPNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
});
|
||||
|
||||
// register localization services
|
||||
|
||||
@ -449,8 +449,6 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
private Installation MigrateModules(InstallConfig install)
|
||||
{
|
||||
var result = new Installation { Success = false, Message = string.Empty };
|
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var moduleDefinitions = scope.ServiceProvider.GetRequiredService<IModuleDefinitionRepository>();
|
||||
@ -464,6 +462,8 @@ namespace Oqtane.Infrastructure
|
||||
var versions = moduleDefinition.ReleaseVersions.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
using (var db = GetInstallationContext())
|
||||
{
|
||||
var message = "";
|
||||
|
||||
if (!string.IsNullOrEmpty(moduleDefinition.ServerManagerType))
|
||||
{
|
||||
var moduleType = Type.GetType(moduleDefinition.ServerManagerType);
|
||||
@ -488,20 +488,23 @@ namespace Oqtane.Infrastructure
|
||||
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable;
|
||||
if (moduleObject == null || !moduleObject.Install(tenant, versions[i]))
|
||||
{
|
||||
result.Message = "An Error Occurred Executing IInstallable Interface For " + moduleDefinition.ServerManagerType;
|
||||
message = "An Error Occurred Executing IInstallable Interface For " + moduleDefinition.ServerManagerType + " On Tenant " + tenant.Name;
|
||||
_filelogger.LogError(Utilities.LogMessage(this, message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!sql.ExecuteScript(tenant, moduleType.Assembly, Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"))
|
||||
{
|
||||
result.Message = "An Error Occurred Executing Database Script " + Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql";
|
||||
message = "An Error Occurred Executing Database Script " + Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql On Tenant " + tenant.Name;
|
||||
_filelogger.LogError(Utilities.LogMessage(this, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " On Tenant " + tenant.Name + " - " + ex.ToString();
|
||||
message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " On Tenant " + tenant.Name + " - " + ex.ToString();
|
||||
_filelogger.LogError(Utilities.LogMessage(this, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -509,11 +512,13 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " - ServerManagerType " + moduleDefinition.ServerManagerType + " Does Not Exist";
|
||||
message = "An Error Occurred Installing " + moduleDefinition.Name + " - ServerManagerType " + moduleDefinition.ServerManagerType + " Does Not Exist";
|
||||
_filelogger.LogError(Utilities.LogMessage(this, message));
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1])
|
||||
// update module if all migrations were successful and version is not current
|
||||
if (string.IsNullOrEmpty(message) && moduleDefinition.Version != versions[versions.Length - 1])
|
||||
{
|
||||
// get module definition from database to retain user customizable property values
|
||||
var moduledef = db.ModuleDefinition.AsNoTracking().FirstOrDefault(item => item.ModuleDefinitionId == moduleDefinition.ModuleDefinitionId);
|
||||
@ -531,16 +536,8 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(result.Message))
|
||||
{
|
||||
result.Success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, result.Message));
|
||||
}
|
||||
|
||||
return result;
|
||||
// module migration issues are logged and should not prevent the framework from starting up
|
||||
return new Installation { Success = true, Message = string.Empty };
|
||||
}
|
||||
|
||||
private Installation CreateSite(InstallConfig install)
|
||||
@ -596,6 +593,7 @@ namespace Oqtane.Infrastructure
|
||||
Runtime = runtime,
|
||||
Prerender = (rendermode == RenderModes.Interactive),
|
||||
Hybrid = false,
|
||||
EnhancedNavigation = true,
|
||||
TenantId = tenant.TenantId
|
||||
};
|
||||
site = sites.AddSite(site);
|
||||
|
||||
@ -41,7 +41,7 @@ namespace Oqtane.Managers
|
||||
Task<List<UserLogin>> GetLogins(int userId, int siteId);
|
||||
Task<User> AddLogin(User user, string token, string type, string key, string name);
|
||||
Task DeleteLogin(int userId, string provider, string key);
|
||||
Task<bool> SendLoginLink(string email);
|
||||
Task<bool> SendLoginLink(string email, string returnurl);
|
||||
}
|
||||
|
||||
public class UserManager : IUserManager
|
||||
@ -960,7 +960,7 @@ namespace Oqtane.Managers
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendLoginLink(string email)
|
||||
public async Task<bool> SendLoginLink(string email, string returnurl)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -973,7 +973,7 @@ namespace Oqtane.Managers
|
||||
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var user = GetUser(identityuser.UserName, alias.SiteId);
|
||||
string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token) + "&returnurl=" + WebUtility.UrlEncode(returnurl);
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["LoginLinkEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
|
||||
@ -16,15 +16,18 @@ namespace Oqtane.Migrations.Tenant
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
folderEntityBuilder.DropColumn("DeletedBy");
|
||||
folderEntityBuilder.DropColumn("DeletedOn");
|
||||
folderEntityBuilder.DropColumn("IsDeleted");
|
||||
if (ActiveDatabase.Name != "Sqlite")
|
||||
{
|
||||
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
folderEntityBuilder.DropColumn("DeletedBy");
|
||||
folderEntityBuilder.DropColumn("DeletedOn");
|
||||
folderEntityBuilder.DropColumn("IsDeleted");
|
||||
|
||||
var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
fileEntityBuilder.DropColumn("DeletedBy");
|
||||
fileEntityBuilder.DropColumn("DeletedOn");
|
||||
fileEntityBuilder.DropColumn("IsDeleted");
|
||||
var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
fileEntityBuilder.DropColumn("DeletedBy");
|
||||
fileEntityBuilder.DropColumn("DeletedOn");
|
||||
fileEntityBuilder.DropColumn("IsDeleted");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
|
||||
@ -16,7 +16,7 @@ namespace Oqtane.Migrations.Tenant
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// IsDeleted columns were removed in 3.2.2 however SQLite does not support column removal so they had to be restored
|
||||
// IsDeleted columns were removed in 3.2.2 however SQLite did not support column removal so they had to be restored
|
||||
if (ActiveDatabase.Name != "Sqlite")
|
||||
{
|
||||
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
||||
@ -16,8 +16,11 @@ namespace Oqtane.Migrations.Tenant
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
languageEntityBuilder.DropColumn("Name");
|
||||
if (ActiveDatabase.Name != "Sqlite")
|
||||
{
|
||||
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
languageEntityBuilder.DropColumn("Name");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
|
||||
@ -16,7 +16,7 @@ namespace Oqtane.Migrations.Tenant
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Name column was removed in 5.2.4 however SQLite does not support column removal so it had to be restored
|
||||
// Name column was removed in 5.2.4 however SQLite did not support column removal so it had to be restored
|
||||
if (ActiveDatabase.Name != "Sqlite")
|
||||
{
|
||||
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
||||
@ -18,7 +18,10 @@ namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
siteEntityBuilder.DropIndex("IX_Site"); // TenantId, Name
|
||||
siteEntityBuilder.DropColumn("TenantId");
|
||||
if (ActiveDatabase.Name != "Sqlite")
|
||||
{
|
||||
siteEntityBuilder.DropColumn("TenantId");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.10.00.02.01")]
|
||||
public class RemoveDeprecatedColumns : MultiDatabaseMigration
|
||||
{
|
||||
public RemoveDeprecatedColumns(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Oqtane 10.0.2 includes support for column removal in SQLite, so we can now clean up deprecated columns
|
||||
|
||||
// Folder columns were deprecated in Oqtane 3.2.2
|
||||
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
folderEntityBuilder.DropColumn("IsDeleted");
|
||||
if (ActiveDatabase.Name == "Sqlite")
|
||||
{
|
||||
/// the following columns were not added back in 3.2.3 but they still exist in SQLite databases
|
||||
folderEntityBuilder.DropColumn("DeletedBy");
|
||||
folderEntityBuilder.DropColumn("DeletedOn");
|
||||
}
|
||||
|
||||
// File columns were deprecated in Oqtane 3.2.2
|
||||
var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
// IsDeleted was added back in 3.2.3 for non-SQLLite databases
|
||||
fileEntityBuilder.DropColumn("IsDeleted");
|
||||
if (ActiveDatabase.Name == "Sqlite")
|
||||
{
|
||||
/// the following columns were not added back in 3.2.3 but they still exist in SQLite databases
|
||||
fileEntityBuilder.DropColumn("DeletedBy");
|
||||
fileEntityBuilder.DropColumn("DeletedOn");
|
||||
}
|
||||
|
||||
// Language columns were deprecated in Oqtane 5.2.4
|
||||
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
languageEntityBuilder.DropColumn("Name");
|
||||
|
||||
// Site columns were deprecated in Oqtane 10.0.1
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
if (ActiveDatabase.Name == "Sqlite")
|
||||
{
|
||||
/// the following column was removed for non-SQLite databases in 10.0.1
|
||||
siteEntityBuilder.DropColumn("TenantId");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Oqtane.Server/Migrations/Tenant/10000202_ExpandPageName.cs
Normal file
28
Oqtane.Server/Migrations/Tenant/10000202_ExpandPageName.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.10.00.02.02")]
|
||||
public class ExpandPageName : MultiDatabaseMigration
|
||||
{
|
||||
public ExpandPageName(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
pageEntityBuilder.AlterStringColumn("Name", 100);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.10.00.02.03")]
|
||||
public class AddUrlMappingReferrer : MultiDatabaseMigration
|
||||
{
|
||||
public AddUrlMappingReferrer(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
urlMappingEntityBuilder.AddStringColumn("Referrer", 2048);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,6 @@ namespace Oqtane.Modules.Admin.Files.Manager
|
||||
if (folder.ModifiedOn >= lastIndexedOn)
|
||||
{
|
||||
changed = true;
|
||||
removed = folder.IsDeleted.Value;
|
||||
}
|
||||
|
||||
var files = _fileRepository.GetFiles(folder.FolderId);
|
||||
@ -78,7 +77,7 @@ namespace Oqtane.Modules.Admin.Files.Manager
|
||||
Permissions = $"{EntityNames.Folder}:{folder.FolderId}",
|
||||
ContentModifiedBy = file.ModifiedBy,
|
||||
ContentModifiedOn = file.ModifiedOn,
|
||||
IsDeleted = (removed || file.IsDeleted.Value)
|
||||
IsDeleted = (removed)
|
||||
};
|
||||
searchContents.Add(searchContent);
|
||||
}
|
||||
|
||||
@ -27,38 +27,45 @@ namespace Oqtane.Pages
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string name, string token)
|
||||
public async Task<IActionResult> OnGetAsync(string name, string token, string returnurl)
|
||||
{
|
||||
var returnurl = "/login";
|
||||
returnurl = (returnurl == null) ? "" : WebUtility.UrlDecode(returnurl);
|
||||
|
||||
if (bool.Parse(HttpContext.GetSiteSettings().GetValue("LoginOptions:LoginLink", "false")) &&
|
||||
!User.Identity.IsAuthenticated && !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(token))
|
||||
!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
var validuser = false;
|
||||
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(name);
|
||||
if (identityuser != null)
|
||||
if (!User.Identity.IsAuthenticated)
|
||||
{
|
||||
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
|
||||
if (result.Succeeded)
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(name);
|
||||
if (identityuser != null)
|
||||
{
|
||||
await _identitySignInManager.SignInAsync(identityuser, false);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Successful For User {Username}", name);
|
||||
validuser = true;
|
||||
returnurl = "/";
|
||||
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
await _identitySignInManager.SignInAsync(identityuser, false);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Successful For User {Username}", name);
|
||||
validuser = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!validuser)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Failed For User {Username}", name);
|
||||
returnurl += $"?status={ExternalLoginStatus.LoginLinkFailed}";
|
||||
returnurl = HttpContext.GetAlias().Path + $"/login?status={ExternalLoginStatus.LoginLinkFailed}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Login Link Attempt For User {Username}", name);
|
||||
returnurl = "/";
|
||||
returnurl = HttpContext.GetAlias().Path;
|
||||
}
|
||||
|
||||
if (!returnurl.StartsWith("/"))
|
||||
{
|
||||
returnurl = "/" + returnurl;
|
||||
}
|
||||
|
||||
return LocalRedirect(Url.Content("~" + returnurl));
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
@ -11,7 +9,6 @@ using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Managers;
|
||||
using Oqtane.Shared;
|
||||
using Radzen.Blazor.Markdown;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
|
||||
@ -10,6 +10,7 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Managers;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
@ -103,7 +104,7 @@ namespace Oqtane.Pages
|
||||
{
|
||||
identityuser = null;
|
||||
var requestOptionsJson = await _identitySignInManager.MakePasskeyRequestOptionsAsync(identityuser);
|
||||
returnurl += $"?options={WebUtility.UrlEncode(requestOptionsJson)}";
|
||||
returnurl = HttpContext.GetAlias().Path + $"/login?options={WebUtility.UrlEncode(requestOptionsJson)}&returnurl={WebUtility.UrlEncode(returnurl)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -129,6 +130,7 @@ namespace Oqtane.Pages
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Passkey Login Failed - Invalid Credential");
|
||||
returnurl = HttpContext.GetAlias().Path + $"/login?status={ExternalLoginStatus.PasskeyFailed}&returnurl={WebUtility.UrlEncode(returnurl)}";
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@ -72,7 +72,6 @@ namespace Oqtane.Repository
|
||||
public File AddFile(File file)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
file.IsDeleted = false;
|
||||
db.File.Add(file);
|
||||
db.SaveChanges();
|
||||
file.Folder = _folderRepository.GetFolder(file.FolderId);
|
||||
|
||||
@ -51,7 +51,6 @@ namespace Oqtane.Repository
|
||||
public Folder AddFolder(Folder folder)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
folder.IsDeleted = false;
|
||||
db.Folder.Add(folder);
|
||||
db.SaveChanges();
|
||||
_permissions.UpdatePermissions(folder.SiteId, EntityNames.Folder, folder.FolderId, folder.PermissionList);
|
||||
|
||||
@ -14,6 +14,7 @@ namespace Oqtane.Repository
|
||||
UrlMapping GetUrlMapping(int urlMappingId);
|
||||
UrlMapping GetUrlMapping(int urlMappingId, bool tracking);
|
||||
UrlMapping GetUrlMapping(int siteId, string url);
|
||||
UrlMapping GetUrlMapping(int siteId, string url, string referrer);
|
||||
void DeleteUrlMapping(int urlMappingId);
|
||||
int DeleteUrlMappings(int siteId, int age);
|
||||
}
|
||||
@ -78,6 +79,11 @@ namespace Oqtane.Repository
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int siteId, string url)
|
||||
{
|
||||
return GetUrlMapping(siteId, url, "");
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int siteId, string url, string referrer)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
url = (url.StartsWith("/")) ? url.Substring(1) : url;
|
||||
@ -93,6 +99,7 @@ namespace Oqtane.Repository
|
||||
urlMapping.Url = url;
|
||||
urlMapping.MappedUrl = "";
|
||||
urlMapping.Requests = 1;
|
||||
urlMapping.Referrer = referrer;
|
||||
urlMapping.CreatedOn = DateTime.UtcNow;
|
||||
urlMapping.RequestedOn = DateTime.UtcNow;
|
||||
try
|
||||
@ -109,6 +116,10 @@ namespace Oqtane.Repository
|
||||
{
|
||||
urlMapping.Requests += 1;
|
||||
urlMapping.RequestedOn = DateTime.UtcNow;
|
||||
if (!string.IsNullOrEmpty(referrer))
|
||||
{
|
||||
urlMapping.Referrer = referrer;
|
||||
}
|
||||
urlMapping = UpdateUrlMapping(urlMapping);
|
||||
}
|
||||
return urlMapping;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Models
|
||||
@ -55,13 +56,6 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated
|
||||
/// Note that this property still exists in the database because columns cannot be dropped in SQLite
|
||||
/// Therefore the property must be retained/mapped even though the framework no longer uses it
|
||||
/// </summary>
|
||||
public bool? IsDeleted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object reference to the <see cref="Folder"/> object.
|
||||
/// Use this if you need to determine what <see cref="Site"/> the file belongs to.
|
||||
@ -74,5 +68,16 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public string Url { get; set; }
|
||||
|
||||
#region Deprecated Properties
|
||||
|
||||
[Obsolete("The IsDeleted property is deprecated. Soft delete of files is not supported.", false)]
|
||||
[NotMapped]
|
||||
[JsonIgnore] // exclude from API payload
|
||||
public bool? IsDeleted { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,13 +67,6 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public string CacheControl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated
|
||||
/// Note that this property still exists in the database because columns cannot be dropped in SQLite
|
||||
/// Therefore the property must be retained/mapped even though the framework no longer uses it
|
||||
/// </summary>
|
||||
public bool? IsDeleted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: todoc what would this contain?
|
||||
/// </summary>
|
||||
@ -110,6 +103,11 @@ namespace Oqtane.Models
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("The IsDeleted property is deprecated. Soft delete of folders is not supported.", false)]
|
||||
[NotMapped]
|
||||
[JsonIgnore] // exclude from API payload
|
||||
public bool? IsDeleted { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,9 +31,8 @@ namespace Oqtane.Models
|
||||
|
||||
/// <summary>
|
||||
/// Language Name - corresponds to <see cref="Culture.DisplayName"/>, _not_ <see cref="Culture.Name"/>
|
||||
/// Note that this property still exists in the database because columns cannot be dropped in SQLite
|
||||
/// Therefore the property must be retained/mapped even though the framework populates it from the Culture API
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public string Name { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
|
||||
@ -33,6 +33,11 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public int Requests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last referrer to the Url (only set if linked to externally)
|
||||
/// </summary>
|
||||
public string Referrer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date when the url was first requested for the site
|
||||
/// </summary>
|
||||
|
||||
@ -4,8 +4,8 @@ namespace Oqtane.Shared
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public static readonly string Version = "10.0.1";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1";
|
||||
public static readonly string Version = "10.0.2";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1,10.0.2";
|
||||
public const string PackageId = "Oqtane.Framework";
|
||||
public const string ClientId = "Oqtane.Client";
|
||||
public const string UpdaterPackageId = "Oqtane.Updater";
|
||||
|
||||
@ -11,5 +11,6 @@ namespace Oqtane.Shared {
|
||||
public const string RemoteFailure = "RemoteFailure";
|
||||
public const string ReviewClaims = "ReviewClaims";
|
||||
public const string LoginLinkFailed = "LoginLinkFailed";
|
||||
public const string PasskeyFailed = "PasskeyFailed";
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline
|
||||
|
||||
# Latest Release
|
||||
|
||||
[10.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1) was released on December 15, 2025 and is a major release including 38 pull requests by 5 different contributors, pushing the total number of project commits all-time over 7400. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||
[10.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2) was released on December 23, 2025 and is a maintenance release including 19 pull requests by 2 different contributors, pushing the total number of project commits all-time to nearly 7500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||
|
||||
# Try It Now!
|
||||
|
||||
@ -111,6 +111,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan
|
||||
# Roadmap
|
||||
This project is open source, and therefore is a work in progress...
|
||||
|
||||
[10.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2) (Dec 23, 2025)
|
||||
- [x] Stabilization improvements
|
||||
|
||||
[10.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1) (Dec 15, 2025)
|
||||
- [x] Stabilization improvements
|
||||
|
||||
|
||||
@ -220,7 +220,7 @@
|
||||
"apiVersion": "2024-04-01",
|
||||
"name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]",
|
||||
"properties": {
|
||||
"packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.1/Oqtane.Framework.10.0.1.Install.zip"
|
||||
"packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.2/Oqtane.Framework.10.0.2.Install.zip"
|
||||
},
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
|
||||
|
||||
Reference in New Issue
Block a user