Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@@ -21,9 +21,7 @@ else
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
@@ -49,15 +47,11 @@ else
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ else
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@@ -114,7 +115,7 @@
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_timezones = Utilities.GetTimeZones();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
@inject IAliasService AliasService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject INotificationService NotificationService
|
||||
@@ -193,80 +194,125 @@
|
||||
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SmtpEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="host" class="form-control" @bind="@_smtphost" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="port" class="form-control" @bind="@_smtpport" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
|
||||
<select id="smtpenabled" class="form-select" value="@_smtpenabled" @onchange="(e => SMTPEnabledChanged(e))">
|
||||
<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="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||
@if (_smtpenabled == "True")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmtpSender">Email Sender: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="host" class="form-control" @bind="@_smtphost" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="port" class="form-control" @bind="@_smtpport" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="SmtpSSL">SSL Required: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpauthentication" HelpText="Specify the SMTP authentication type" ResourceKey="SMTPAuthentication">Authentication: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpauthentication" class="form-select" value="@_smtpauthentication" @onchange="(e => SMTPAuthenticationChanged(e))">
|
||||
<option value="Basic">@Localizer["Basic"]</option>
|
||||
<option value="OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||
<br /><br />
|
||||
@if (_smtpauthentication == "Basic")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified below." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpauthority" HelpText="The Authority Url for the SMTP provider" ResourceKey="SmtpAuthority">Authority Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="smtpauthority" class="form-control" @bind="@_smtpauthority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpclientid" HelpText="The Client ID for the SMTP provider" ResourceKey="SmtpClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="smtpclientid" class="form-control" @bind="@_smtpclientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpclientsecret" HelpText="The Client Secret for the SMTP provider" ResourceKey="SmtpClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_smtpclientsecrettype" id="smtpclientsecret" class="form-control" @bind="@_smtpclientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSmtpClientSecret">@_togglesmtpclientsecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpscopes" HelpText="A list of Scopes for the SMTP provider (separated by commas)" ResourceKey="SmtpScopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="smtpscopes" class="form-control" @bind="@_smtpscopes" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="sender" HelpText="Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP provider." ResourceKey="SmtpSender">Email Sender: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||
<br /><br />
|
||||
}
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
|
||||
@@ -454,16 +500,23 @@
|
||||
private string _headcontent = string.Empty;
|
||||
private string _bodycontent = string.Empty;
|
||||
|
||||
private string _smtpenabled = "False";
|
||||
private string _smtpauthentication = "Basic";
|
||||
private string _smtphost = string.Empty;
|
||||
private string _smtpport = string.Empty;
|
||||
private string _smtpssl = "False";
|
||||
private string _smtpssl = "True";
|
||||
private string _smtpusername = string.Empty;
|
||||
private string _smtppassword = string.Empty;
|
||||
private string _smtppasswordtype = "password";
|
||||
private string _togglesmtppassword = string.Empty;
|
||||
private string _smtpauthority = string.Empty;
|
||||
private string _smtpclientid = string.Empty;
|
||||
private string _smtpclientsecret = string.Empty;
|
||||
private string _smtpclientsecrettype = "password";
|
||||
private string _togglesmtpclientsecret = string.Empty;
|
||||
private string _smtpscopes = string.Empty;
|
||||
private string _smtpsender = string.Empty;
|
||||
private string _smtprelay = "False";
|
||||
private string _smtpenabled = "True";
|
||||
private int _retention = 30;
|
||||
|
||||
private string _pwaisenabled;
|
||||
@@ -507,7 +560,7 @@
|
||||
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
||||
if (site != null)
|
||||
{
|
||||
_timezones = Utilities.GetTimeZones();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
@@ -555,15 +608,21 @@
|
||||
_bodycontent = site.BodyContent;
|
||||
|
||||
// SMTP
|
||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "False");
|
||||
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
||||
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
||||
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
||||
_smtpauthentication = SettingService.GetSetting(settings, "SMTPAuthentication", "Basic");
|
||||
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
_smtpauthority = SettingService.GetSetting(settings, "SMTPAuthority", string.Empty);
|
||||
_smtpclientid = SettingService.GetSetting(settings, "SMTPClientId", string.Empty);
|
||||
_smtpclientsecret = SettingService.GetSetting(settings, "SMTPClientSecret", string.Empty);
|
||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||
|
||||
// PWA
|
||||
@@ -744,8 +803,13 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||
@@ -812,6 +876,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void SMTPAuthenticationChanged(ChangeEventArgs e)
|
||||
{
|
||||
_smtpauthentication = (string)e.Value;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void SMTPEnabledChanged(ChangeEventArgs e)
|
||||
{
|
||||
_smtpenabled = (string)e.Value;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ToggleSMTPPassword()
|
||||
{
|
||||
if (_smtppasswordtype == "password")
|
||||
{
|
||||
_smtppasswordtype = "text";
|
||||
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtppasswordtype = "password";
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSmtpClientSecret()
|
||||
{
|
||||
if (_smtpclientsecrettype == "password")
|
||||
{
|
||||
_smtpclientsecrettype = "text";
|
||||
_togglesmtpclientsecret = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtpclientsecrettype = "password";
|
||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEmail()
|
||||
{
|
||||
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
|
||||
@@ -822,8 +926,13 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site SMTP Settings Saved");
|
||||
@@ -844,20 +953,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSMTPPassword()
|
||||
{
|
||||
if (_smtppasswordtype == "password")
|
||||
{
|
||||
_smtppasswordtype = "text";
|
||||
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtppasswordtype = "password";
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetAliases()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
|
||||
<Pager Items="@_themes">
|
||||
<Header>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@inject IUserService UserService
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IStringLocalizer<Add> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -27,6 +28,15 @@
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<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="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -119,6 +129,7 @@
|
||||
private bool _initialized = false;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = "True";
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _notify = "True";
|
||||
@@ -132,7 +143,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_timezones = Utilities.GetTimeZones();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
_settings = new Dictionary<string, string>();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
@@ -168,6 +179,7 @@
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.PhotoFileId = null;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IFileService FileService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@@ -47,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
@@ -158,7 +159,7 @@
|
||||
}
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
||||
{
|
||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
}
|
||||
<br /><br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
@@ -203,7 +204,7 @@
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||
_timezones = Utilities.GetTimeZones();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||
{
|
||||
|
||||
@@ -74,10 +74,19 @@ else
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor Authentication?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
@@ -490,6 +499,7 @@ else
|
||||
private string _allowregistration;
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requireconfirmedemail;
|
||||
private string _twofactor;
|
||||
private string _cookiename;
|
||||
private string _cookieexpiration;
|
||||
@@ -560,6 +570,7 @@ else
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
@@ -685,6 +696,7 @@ else
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Error.Login.Fail" xml:space="preserve">
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That New User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification Containing Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||
<value>Please Provide All Required Fields</value>
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
<data name="Port.HelpText" xml:space="preserve">
|
||||
<value>Enter the port number for the SMTP server. Please note this field is required if you provide a host name.</value>
|
||||
</data>
|
||||
<data name="UseSsl.HelpText" xml:space="preserve">
|
||||
<data name="SmtpSSL.HelpText" xml:space="preserve">
|
||||
<value>Specify if SSL is required for your SMTP server</value>
|
||||
</data>
|
||||
<data name="SmtpUsername.HelpText" xml:space="preserve">
|
||||
@@ -202,7 +202,7 @@
|
||||
<value>Enter the password for your SMTP account</value>
|
||||
</data>
|
||||
<data name="SmtpSender.HelpText" xml:space="preserve">
|
||||
<value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value>
|
||||
<value>Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP server.</value>
|
||||
</data>
|
||||
<data name="EnablePWA.HelpText" xml:space="preserve">
|
||||
<value>Select whether you would like this site to be available as a Progressive Web Application (PWA)</value>
|
||||
@@ -240,8 +240,8 @@
|
||||
<data name="Port.Text" xml:space="preserve">
|
||||
<value>Port: </value>
|
||||
</data>
|
||||
<data name="UseSsl.Text" xml:space="preserve">
|
||||
<value>SSL Enabled: </value>
|
||||
<data name="SmtpSSL.Text" xml:space="preserve">
|
||||
<value>SSL Required: </value>
|
||||
</data>
|
||||
<data name="SmtpUsername.Text" xml:space="preserve">
|
||||
<value>Username: </value>
|
||||
@@ -372,10 +372,10 @@
|
||||
<data name="PageContent.Heading" xml:space="preserve">
|
||||
<value>Page Content</value>
|
||||
</data>
|
||||
<data name="SMTPEnabled.HelpText" xml:space="preserve">
|
||||
<data name="SmtpEnabled.HelpText" xml:space="preserve">
|
||||
<value>Specify if SMTP is enabled for this site</value>
|
||||
</data>
|
||||
<data name="SMTPEnabled.Text" xml:space="preserve">
|
||||
<data name="SmtpEnabled.Text" xml:space="preserve">
|
||||
<value>Enabled?</value>
|
||||
</data>
|
||||
<data name="Version.HelpText" xml:space="preserve">
|
||||
@@ -453,4 +453,40 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>The default time zone for the site</value>
|
||||
</data>
|
||||
<data name="Basic" xml:space="preserve">
|
||||
<value>Basic</value>
|
||||
</data>
|
||||
<data name="OAuth2" xml:space="preserve">
|
||||
<value>OAuth 2.0 (OAuth2)</value>
|
||||
</data>
|
||||
<data name="SmtpAuthentication.Text" xml:space="preserve">
|
||||
<value>Authentication:</value>
|
||||
</data>
|
||||
<data name="SmtpAuthentication.HelpText" xml:space="preserve">
|
||||
<value>Specify the SMTP authentication type</value>
|
||||
</data>
|
||||
<data name="SmtpClientID.Text" xml:space="preserve">
|
||||
<value>Client ID:</value>
|
||||
</data>
|
||||
<data name="SmtpClientID.HelpText" xml:space="preserve">
|
||||
<value>The Client ID for the SMTP provider</value>
|
||||
</data>
|
||||
<data name="SmtpClientSecret.Text" xml:space="preserve">
|
||||
<value>Client Secret:</value>
|
||||
</data>
|
||||
<data name="SmtpClientSecret.HelpText" xml:space="preserve">
|
||||
<value>The Client Secret for the SMTP provider</value>
|
||||
</data>
|
||||
<data name="SmtpScopes.Text" xml:space="preserve">
|
||||
<value>Scopes:</value>
|
||||
</data>
|
||||
<data name="SmtpScopes.HelpText" xml:space="preserve">
|
||||
<value>A list of Scopes for the SMTP provider (separated by commas)</value>
|
||||
</data>
|
||||
<data name="SmtpAuthority.Text" xml:space="preserve">
|
||||
<value>Authority Url:</value>
|
||||
</data>
|
||||
<data name="SmtpAuthority.HelpText" xml:space="preserve">
|
||||
<value>The Authority Url for the SMTP provider</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -162,4 +162,10 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>The user's time zone</value>
|
||||
</data>
|
||||
<data name="Confirmed.Text" xml:space="preserve">
|
||||
<value>Verified?</value>
|
||||
</data>
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -217,7 +217,7 @@
|
||||
<value>The user's time zone</value>
|
||||
</data>
|
||||
<data name="Confirmed.Text" xml:space="preserve">
|
||||
<value>Confirmed?</value>
|
||||
<value>Verified?</value>
|
||||
</data>
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
|
||||
@@ -370,7 +370,13 @@
|
||||
<value>Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out.</value>
|
||||
</data>
|
||||
<data name="TwoFactor.Text" xml:space="preserve">
|
||||
<value>Two Factor?</value>
|
||||
<value>Two Factor Authentication?</value>
|
||||
</data>
|
||||
<data name="RequireConfirmedEmail.HelpText" xml:space="preserve">
|
||||
<value>Do you want to require registered users to verify their email address before they are allowed to log in?</value>
|
||||
</data>
|
||||
<data name="RequireConfirmedEmail.Text" xml:space="preserve">
|
||||
<value>Require Verified Email?</value>
|
||||
</data>
|
||||
<data name="Disabled" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
@@ -502,7 +508,7 @@
|
||||
<value>Info</value>
|
||||
</data>
|
||||
<data name="OAuth2" xml:space="preserve">
|
||||
<value>OAuth 2.0</value>
|
||||
<value>OAuth 2.0 (OAuth2)</value>
|
||||
</data>
|
||||
<data name="OIDC" xml:space="preserve">
|
||||
<value>OpenID Connect (OIDC)</value>
|
||||
|
||||
@@ -117,13 +117,4 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Universal" xml:space="preserve">
|
||||
<value>(UTC) Coordinated Universal Time</value>
|
||||
</data>
|
||||
<data name="US/Eastern" xml:space="preserve">
|
||||
<value>(UTC-05:00) Eastern Time (US & Canada)</value>
|
||||
</data>
|
||||
<data name="US/Pacific" xml:space="preserve">
|
||||
<value>(UTC-08:00) Pacific Time (US & Canada)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime.TimeZones;
|
||||
using NodaTime;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using NodaTime.Extensions;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@@ -17,18 +19,40 @@ namespace Oqtane.Services
|
||||
_TimeZoneLocalizer = TimeZoneLocalizer;
|
||||
}
|
||||
|
||||
public List<TimeZone> GetTimeZones()
|
||||
public List<Models.TimeZone> GetTimeZones()
|
||||
{
|
||||
var _timezones = new List<TimeZone>();
|
||||
foreach (var timezone in Utilities.GetTimeZones())
|
||||
var timezones = new List<Models.TimeZone>();
|
||||
|
||||
foreach (var tz in DateTimeZoneProviders.Tzdb.GetAllZones()
|
||||
// only include timezones which have a country code defined or are US timezones
|
||||
.Where(item => !string.IsNullOrEmpty(TzdbDateTimeZoneSource.Default.ZoneLocations.FirstOrDefault(l => l.ZoneId == item.Id)?.CountryCode) || item.Id.ToLower().Contains("us/"))
|
||||
// order by UTC offset (ie. -11:00 to +14:00)
|
||||
.OrderBy(item => item.GetUtcOffset(Instant.FromDateTimeUtc(DateTime.UtcNow)).Ticks))
|
||||
{
|
||||
_timezones.Add(new TimeZone
|
||||
// get localized display name
|
||||
var displayname = _TimeZoneLocalizer[tz.Id].Value;
|
||||
if (displayname == tz.Id)
|
||||
{
|
||||
Id = timezone.Id,
|
||||
DisplayName = _TimeZoneLocalizer[timezone.Id]
|
||||
});
|
||||
// use default "friendly" display format
|
||||
displayname = displayname.Replace("_", " ").Replace("/", " / ");
|
||||
}
|
||||
|
||||
// time zones can be excluded from the list by providing an empty translation in the localization file
|
||||
if (!string.IsNullOrEmpty(displayname))
|
||||
{
|
||||
// include offset prefix
|
||||
var offset = tz.GetUtcOffset(Instant.FromDateTimeUtc(DateTime.UtcNow)).Ticks;
|
||||
displayname = "(UTC" + (offset >= 0 ? "+" : "-") + new DateTime(Math.Abs(offset)).ToString("HH:mm") + ") " + displayname;
|
||||
|
||||
timezones.Add(new Models.TimeZone()
|
||||
{
|
||||
Id = tz.Id,
|
||||
DisplayName = displayname
|
||||
});
|
||||
}
|
||||
}
|
||||
return _timezones.OrderBy(item => item.DisplayName).ToList();
|
||||
|
||||
return timezones;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Oqtane.Themes.OqtaneTheme
|
||||
ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client",
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://www.jsdelivr.com/package/npm/bootswatch
|
||||
new Stylesheet("https://cdn.jsdelivr.net/npm/bootswatch@5.3.5/dist/cyborg/bootstrap.min.css"),
|
||||
// obtained from https://cdnjs.com/libraries/bootswatch
|
||||
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.7/cyborg/bootstrap.min.css", "sha512-/LQFzDeXqysGQ/POl5YOEjgVZH1BmqDHvshhnFIChf50bMGQ470qhUrsecD9MRCUwzwqRoshwAbmA2oTW4I6Yg==", "anonymous"),
|
||||
new Stylesheet("~/Theme.css"),
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
[Parameter]
|
||||
public Module ModuleState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ContainerType { get; set; }
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
return PageState?.RenderId == ModuleState?.RenderId;
|
||||
@@ -44,6 +47,10 @@
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
string container = ModuleState.ContainerType;
|
||||
if (!string.IsNullOrEmpty(ContainerType))
|
||||
{
|
||||
container = ContainerType;
|
||||
}
|
||||
if (PageState.ModuleId != -1 && PageState.Route.Action != "" && ModuleState.UseAdminContainer)
|
||||
{
|
||||
container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||
|
||||
@@ -26,6 +26,9 @@ else
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ContainerType { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
@@ -119,6 +122,7 @@ else
|
||||
{
|
||||
builder.OpenComponent(0, typeof(ContainerBuilder));
|
||||
builder.AddAttribute(1, "ModuleState", module);
|
||||
builder.AddAttribute(2, "ContainerType", ContainerType);
|
||||
builder.SetKey(module.PageModuleId);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
@@ -48,18 +48,12 @@
|
||||
|
||||
private bool _initialized = false;
|
||||
private bool _installed = false;
|
||||
private string _display = "";
|
||||
private string _display = "display: none;"; // prevents flash on initial interactive page load when using prerendering
|
||||
|
||||
private PageState _pageState { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PageState != null && PageState.RenderMode == RenderModes.Interactive && PageState.Site.Prerender)
|
||||
{
|
||||
// prevents flash on initial interactive page load when using prerendering
|
||||
_display = "display: none;";
|
||||
}
|
||||
|
||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||
SiteState.AuthorizationToken = AuthorizationToken;
|
||||
SiteState.Platform = Platform;
|
||||
@@ -89,9 +83,10 @@
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
if (firstRender && _display == "display: none;")
|
||||
{
|
||||
_display = "";
|
||||
StateHasChanged(); // required or else the UI will not refresh
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,18 +239,19 @@ app {
|
||||
.app-form-inline {
|
||||
display: inline;
|
||||
}
|
||||
.app-search{
|
||||
|
||||
.app-search {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.app-search input + button{
|
||||
.app-search input + button {
|
||||
background: none;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.app-search input + button .oi{
|
||||
.app-search input + button .oi {
|
||||
top: 0;
|
||||
}
|
||||
.app-search-noinput {
|
||||
@@ -275,3 +276,13 @@ app {
|
||||
.app-logo .navbar-brand {
|
||||
padding: 5px 20px 5px 20px;
|
||||
}
|
||||
|
||||
/* cookie consent */
|
||||
.gdpr-consent-bar .btn-show {
|
||||
bottom: -3px;
|
||||
left: 5px;
|
||||
}
|
||||
.gdpr-consent-bar .btn-hide {
|
||||
top: 0;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
return files;
|
||||
},
|
||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames) {
|
||||
var success = true;
|
||||
var fileinput = document.getElementById('FileInput_' + id);
|
||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||
@@ -344,16 +344,22 @@ Oqtane.Interop = {
|
||||
const totalParts = Math.ceil(file.size / chunkSize);
|
||||
let partCount = 0;
|
||||
|
||||
let filename = file.name;
|
||||
if (anonymizeuploadfilenames) {
|
||||
filename = crypto.randomUUID() + '.' + filename.split('.').pop();
|
||||
}
|
||||
|
||||
const uploadPart = () => {
|
||||
const start = partCount * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let formdata = new FormData();
|
||||
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||
formdata.append('folder', folder);
|
||||
formdata.append('formfile', chunk, file.name);
|
||||
formdata.append('formfile', chunk, filename);
|
||||
|
||||
var credentials = 'same-origin';
|
||||
var headers = new Headers();
|
||||
|
||||
@@ -165,14 +165,13 @@ namespace Oqtane.Controllers
|
||||
bool allowregistration;
|
||||
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
|
||||
{
|
||||
user.EmailConfirmed = true;
|
||||
user.IsAuthenticated = true;
|
||||
user.IsAuthenticated = true; // admins can add any existing user to a site
|
||||
allowregistration = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.EmailConfirmed = false;
|
||||
user.IsAuthenticated = false;
|
||||
user.EmailConfirmed = false; // standard users cannot specify that their email is verified
|
||||
user.IsAuthenticated = false; // existing users can only be added to a site if they provide a valid username and password
|
||||
allowregistration = _sites.GetSite(user.SiteId).AllowRegistration;
|
||||
}
|
||||
|
||||
|
||||
@@ -228,11 +228,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
options.Lockout.AllowedForNewUsers = false;
|
||||
|
||||
// SignIn settings
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
options.SignIn.RequireConfirmedEmail = false;
|
||||
options.SignIn.RequireConfirmedAccount = false;
|
||||
options.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
// User settings
|
||||
options.User.RequireUniqueEmail = false; // changing to true will cause issues for legacy data
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using MailKit.Net.Smtp;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using Microsoft.Identity.Client;
|
||||
using MimeKit;
|
||||
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Shared;
|
||||
using MailKit.Security;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
@@ -27,7 +26,7 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
|
||||
// job is executed for each tenant in installation
|
||||
public override string ExecuteJob(IServiceProvider provider)
|
||||
public async override Task<string> ExecuteJobAsync(IServiceProvider provider)
|
||||
{
|
||||
string log = "";
|
||||
|
||||
@@ -48,126 +47,175 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
|
||||
{
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPHost", "") != "" &&
|
||||
settingRepository.GetSettingValue(settings, "SMTPPort", "") != "" &&
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "") != "")
|
||||
bool valid = true;
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
|
||||
{
|
||||
// basic
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "<br />";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// oauth
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPAuthority", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPClientId", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPClientSecret", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPScopes", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - Host, Port, Authority, Client ID, Client Secret, Scopes, And Sender Are All Required" + "<br />";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (valid)
|
||||
{
|
||||
// construct SMTP Client
|
||||
using var client = new SmtpClient();
|
||||
|
||||
client.Connect(host: settingRepository.GetSettingValue(settings, "SMTPHost", ""),
|
||||
port: int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")),
|
||||
options: bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) ? MailKit.Security.SecureSocketOptions.StartTls : MailKit.Security.SecureSocketOptions.None);
|
||||
await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""),
|
||||
int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")),
|
||||
bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
|
||||
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
|
||||
{
|
||||
client.Authenticate(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
|
||||
settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
|
||||
// it is possible to use basic without any authentication (not recommended)
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
|
||||
{
|
||||
await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
|
||||
settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// oauth authentication
|
||||
var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(settingRepository.GetSettingValue(settings, "SMTPClientId", ""))
|
||||
.WithAuthority(settingRepository.GetSettingValue(settings, "SMTPAuthority", ""))
|
||||
.WithClientSecret(settingRepository.GetSettingValue(settings, "SMTPClientSecret", ""))
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
var result = await confidentialClientApplication.AcquireTokenForClient(settingRepository.GetSettingValue(settings, "SMTPScopes", "").Split(',')).ExecuteAsync();
|
||||
var oauth2 = new SaslMechanismOAuth2(settingRepository.GetSettingValue(settings, "SMTPSender", ""), result.AccessToken);
|
||||
await client.AuthenticateAsync(oauth2);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - OAuth Token Could Not Be Retrieved From Authority - " + ex.Message + "<br />";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// iterate through undelivered notifications
|
||||
int sent = 0;
|
||||
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
|
||||
foreach (Notification notification in notifications)
|
||||
if (valid)
|
||||
{
|
||||
// get sender and receiver information from user object if not provided
|
||||
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
|
||||
// iterate through undelivered notifications
|
||||
int sent = 0;
|
||||
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
|
||||
foreach (Notification notification in notifications)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.FromUserId.Value);
|
||||
if (user != null)
|
||||
// get sender and receiver information from user object if not provided
|
||||
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
|
||||
{
|
||||
notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail;
|
||||
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName;
|
||||
}
|
||||
}
|
||||
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.ToUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
|
||||
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
// validate recipient
|
||||
if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _))
|
||||
{
|
||||
log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}<br />";
|
||||
notification.IsDeleted = true;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
else
|
||||
{
|
||||
MimeMessage mailMessage = new MimeMessage();
|
||||
|
||||
// sender
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(notification.FromDisplayName))
|
||||
var user = userRepository.GetUser(notification.FromUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail));
|
||||
notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail;
|
||||
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName;
|
||||
}
|
||||
}
|
||||
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.ToUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
|
||||
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
// validate recipient
|
||||
if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _))
|
||||
{
|
||||
log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}<br />";
|
||||
notification.IsDeleted = true;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
else
|
||||
{
|
||||
MimeMessage mailMessage = new MimeMessage();
|
||||
|
||||
// sender
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(notification.FromDisplayName))
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail));
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress("", notification.FromEmail));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress("", notification.FromEmail));
|
||||
mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name,
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "")));
|
||||
}
|
||||
|
||||
// recipient
|
||||
if (!string.IsNullOrEmpty(notification.ToDisplayName))
|
||||
{
|
||||
mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail));
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.To.Add(new MailboxAddress("", notification.ToEmail));
|
||||
}
|
||||
|
||||
// subject
|
||||
mailMessage.Subject = notification.Subject;
|
||||
|
||||
//body
|
||||
var bodyText = notification.Body;
|
||||
|
||||
if (!bodyText.Contains('<') || !bodyText.Contains('>'))
|
||||
{
|
||||
// plain text messages should convert line breaks to HTML tags to preserve formatting
|
||||
bodyText = bodyText.Replace("\n", "<br />");
|
||||
}
|
||||
|
||||
mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8)
|
||||
{
|
||||
Text = bodyText
|
||||
};
|
||||
|
||||
// send mail
|
||||
try
|
||||
{
|
||||
await client.SendAsync(mailMessage);
|
||||
sent++;
|
||||
notification.IsDelivered = true;
|
||||
notification.DeliveredOn = DateTime.UtcNow;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// error
|
||||
log += $"NotificationId: {notification.NotificationId} - {ex.Message}<br />";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name,
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "")));
|
||||
}
|
||||
|
||||
// recipient
|
||||
if (!string.IsNullOrEmpty(notification.ToDisplayName))
|
||||
{
|
||||
mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail));
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.To.Add(new MailboxAddress("", notification.ToEmail));
|
||||
}
|
||||
|
||||
// subject
|
||||
mailMessage.Subject = notification.Subject;
|
||||
|
||||
//body
|
||||
var bodyText = notification.Body;
|
||||
|
||||
if (!bodyText.Contains('<') || !bodyText.Contains('>'))
|
||||
{
|
||||
// plain text messages should convert line breaks to HTML tags to preserve formatting
|
||||
bodyText = bodyText.Replace("\n", "<br />");
|
||||
}
|
||||
|
||||
mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8)
|
||||
{
|
||||
Text = bodyText
|
||||
};
|
||||
|
||||
// send mail
|
||||
try
|
||||
{
|
||||
client.Send(mailMessage);
|
||||
sent++;
|
||||
notification.IsDelivered = true;
|
||||
notification.DeliveredOn = DateTime.UtcNow;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// error
|
||||
log += $"NotificationId: {notification.NotificationId} - {ex.Message}<br />";
|
||||
}
|
||||
}
|
||||
await client.DisconnectAsync(true);
|
||||
log += "Notifications Delivered: " + sent + "<br />";
|
||||
}
|
||||
client.Disconnect(true);
|
||||
log += "Notifications Delivered: " + sent + "<br />";
|
||||
}
|
||||
else
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "<br />";
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Oqtane.Managers
|
||||
if (User != null)
|
||||
{
|
||||
string siteName = _sites.GetSite(user.SiteId).Name;
|
||||
if (!user.EmailConfirmed)
|
||||
if (!user.EmailConfirmed && bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")))
|
||||
{
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
@@ -252,29 +252,32 @@ namespace Oqtane.Managers
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
}
|
||||
|
||||
if (user.EmailConfirmed)
|
||||
if (bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")))
|
||||
{
|
||||
if (!identityuser.EmailConfirmed)
|
||||
if (user.EmailConfirmed)
|
||||
{
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
if (!identityuser.EmailConfirmed)
|
||||
{
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
|
||||
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
identityuser.EmailConfirmed = false;
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To 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
|
||||
{
|
||||
identityuser.EmailConfirmed = false;
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To 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);
|
||||
}
|
||||
|
||||
user = _users.UpdateUser(user);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
@@ -354,15 +357,14 @@ namespace Oqtane.Managers
|
||||
if (!user.IsDeleted)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
var twoFactorRequired = _settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired;
|
||||
if (twoFactorRequired)
|
||||
{
|
||||
var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email");
|
||||
user.TwoFactorCode = token;
|
||||
user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10);
|
||||
_users.UpdateUser(user);
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["TwoFactorEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["TwoFactorEmailBody"].Value;
|
||||
@@ -377,7 +379,7 @@ namespace Oqtane.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||
if (!bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")) || await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||
{
|
||||
user = GetUser(identityuser.UserName, alias.SiteId);
|
||||
if (user != null)
|
||||
@@ -400,13 +402,25 @@ namespace Oqtane.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Denied - User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Email Address Not Verified {Username}", user.Username);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Denied - User Email Address Not Verified For {Username}", user.Username);
|
||||
|
||||
// send verification email again
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string subject = _localizer["VerificationEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["VerificationEmailBody"].Value;
|
||||
body = body.Replace("[UserDisplayName]", user.DisplayName);
|
||||
body = body.Replace("[URL]", url);
|
||||
body = body.Replace("[SiteName]", siteName);
|
||||
var notification = new Notification(alias.SiteId, user, subject, body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,8 +552,7 @@ namespace Oqtane.Managers
|
||||
if (user != null)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||
var twoFactorRequired = _settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired;
|
||||
if (twoFactorRequired && user.TwoFactorCode == token && DateTime.UtcNow < user.TwoFactorExpiry)
|
||||
{
|
||||
user.IsAuthenticated = true;
|
||||
|
||||
@@ -16,5 +16,6 @@ namespace Oqtane.Repository
|
||||
void DeleteSetting(string entityName, int settingId);
|
||||
void DeleteSettings(string entityName, int entityId);
|
||||
string GetSettingValue(IEnumerable<Setting> settings, string settingName, string defaultValue);
|
||||
string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules.Admin.Users;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
@@ -71,6 +73,19 @@ namespace Oqtane.Repository
|
||||
public void DeleteRole(int roleId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
|
||||
// remove userroles for role
|
||||
foreach (var userrole in db.UserRole.Where(item => item.RoleId == roleId))
|
||||
{
|
||||
db.UserRole.Remove(userrole);
|
||||
}
|
||||
|
||||
// remove permissions for role
|
||||
foreach (var permission in db.Permission.Where(item => item.RoleId == roleId))
|
||||
{
|
||||
db.Permission.Remove(permission);
|
||||
}
|
||||
|
||||
Role role = db.Role.Find(roleId);
|
||||
db.Role.Remove(role);
|
||||
db.SaveChanges();
|
||||
|
||||
@@ -180,6 +180,19 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
|
||||
public string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue)
|
||||
{
|
||||
var setting = GetSetting(entityName, entityId, settingName);
|
||||
if (setting != null)
|
||||
{
|
||||
return setting.SettingValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMaster(string EntityName)
|
||||
{
|
||||
return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules.Admin.Users;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
@@ -131,6 +132,13 @@ namespace Oqtane.Repository
|
||||
public void DeleteUser(int userId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
|
||||
// remove permissions for user
|
||||
foreach (var permission in db.Permission.Where(item => item.UserId == userId))
|
||||
{
|
||||
db.Permission.Remove(permission);
|
||||
}
|
||||
|
||||
var user = db.User.Find(userId);
|
||||
db.User.Remove(user);
|
||||
db.SaveChanges();
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace [Owner].Theme.[Theme]
|
||||
ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://www.jsdelivr.com/
|
||||
new Script(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new StyleSheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
|
||||
|
||||
@@ -278,11 +278,11 @@ app {
|
||||
}
|
||||
|
||||
/* cookie consent */
|
||||
.gdpr-consent-bar .btn-show{
|
||||
.gdpr-consent-bar .btn-show {
|
||||
bottom: -3px;
|
||||
left: 5px;
|
||||
}
|
||||
.gdpr-consent-bar .btn-hide{
|
||||
.gdpr-consent-bar .btn-hide {
|
||||
top: 0;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
@@ -85,11 +85,11 @@ namespace Oqtane.Shared
|
||||
|
||||
public static readonly string[] InternalPagePaths = { "login", "register", "reset", "404" };
|
||||
public const string DefaultTextEditor = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
|
||||
|
||||
public const string BootstrapScriptUrl = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js";
|
||||
public const string BootstrapScriptIntegrity = "sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq";
|
||||
public const string BootstrapStylesheetUrl = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css";
|
||||
public const string BootstrapStylesheetIntegrity = "sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7";
|
||||
//Obtained from https://cdnjs.com/libraries/bootstrap
|
||||
public const string BootstrapScriptUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/js/bootstrap.bundle.min.js";
|
||||
public const string BootstrapScriptIntegrity = "sha512-Tc0i+vRogmX4NN7tuLbQfBxa8JkfUSAxSFVzmU31nVdHyiHElPPy2cWfFacmCJKw0VqovrzKhdd2TSTMdAxp2g==";
|
||||
public const string BootstrapStylesheetUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css";
|
||||
public const string BootstrapStylesheetIntegrity = "sha512-fw7f+TcMjTb7bpbLJZlP8g2Y4XcCyFZW8uy8HsRZsH/SwbMw0plKHFHr99DN3l04VsYNwvzicUX/6qurvIxbxw==";
|
||||
|
||||
public const string CookieConsentCookieName = "Oqtane.CookieConsent";
|
||||
public const string CookieConsentCookieValue = "yes";
|
||||
|
||||
@@ -692,16 +692,6 @@ namespace Oqtane.Shared
|
||||
return (localDateTime?.Date, localTime);
|
||||
}
|
||||
|
||||
public static List<TimeZone> GetTimeZones()
|
||||
{
|
||||
return [.. DateTimeZoneProviders.Tzdb.GetAllZones()
|
||||
.Select(tz => new TimeZone()
|
||||
{
|
||||
Id = tz.Id,
|
||||
DisplayName = tz.Id
|
||||
})];
|
||||
}
|
||||
|
||||
public static bool IsEffectiveAndNotExpired(DateTime? effectiveDate, DateTime? expiryDate)
|
||||
{
|
||||
DateTime currentUtcTime = DateTime.UtcNow;
|
||||
|
||||
13
README.md
13
README.md
@@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline
|
||||
|
||||
# Latest Release
|
||||
|
||||
[6.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3) was released on May 29, 2025 and is a maintenance release including 59 pull requests by 5 different contributors, pushing the total number of project commits all-time to over 6600. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||
[6.1.4](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4) was released on July 30, 2025 and is a maintenance release including 49 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 6700. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||
|
||||
# Try It Now!
|
||||
|
||||
@@ -26,7 +26,7 @@ A free ASP.NET hosting account. No hidden fees. No credit card required.
|
||||
|
||||
**Installing using source code from the Dev/Master branch:**
|
||||
|
||||
- Install **[.NET 9.0.5 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**.
|
||||
- Install **[.NET 9.0.7 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**.
|
||||
|
||||
- Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
|
||||
|
||||
@@ -92,8 +92,15 @@ 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...
|
||||
|
||||
[6.1.4](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4) (Jul 30, 2025)
|
||||
- [x] Stabilization improvements
|
||||
- [x] SMTP OAuth2 Support
|
||||
|
||||
[6.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3) (May 29, 2025)
|
||||
- [x] Stabilization improvements
|
||||
- [x] Stabilization improvements
|
||||
- [x] Time zone support
|
||||
- [x] Module header/footer content
|
||||
- [x] Module import/export from files
|
||||
|
||||
[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) (Apr 10, 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/v6.1.3/Oqtane.Framework.6.1.3.Install.zip"
|
||||
"packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.4/Oqtane.Framework.6.1.4.Install.zip"
|
||||
},
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
|
||||
|
||||
Reference in New Issue
Block a user