completed antiforgery implementation, improved external login claim mapping, principal construction, and user experience
This commit is contained in:
parent
391713b84d
commit
e4c648ee92
|
@ -28,7 +28,7 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="pwd" type="password" class="form-control" @bind="@_pwd" />
|
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="pwd" type="password" class="form-control" @bind="@_pwd" />
|
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="pwd" type="password" class="form-control" @bind="@_pwd" />
|
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,13 +62,19 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="password" type="password" class="form-control" @bind="@_hostPassword" />
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
@ -104,6 +110,8 @@
|
||||||
|
|
||||||
private string _hostUsername = string.Empty;
|
private string _hostUsername = string.Empty;
|
||||||
private string _hostPassword = string.Empty;
|
private string _hostPassword = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
private string _confirmPassword = string.Empty;
|
private string _confirmPassword = string.Empty;
|
||||||
private string _hostEmail = string.Empty;
|
private string _hostEmail = string.Empty;
|
||||||
private bool _register = true;
|
private bool _register = true;
|
||||||
|
@ -112,6 +120,7 @@
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
if (_databases.Exists(item => item.IsDefault))
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
{
|
{
|
||||||
|
@ -218,4 +227,18 @@
|
||||||
_message = Localizer["Message.Require.DbInfo"];
|
_message = Localizer["Message.Require.DbInfo"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
|
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
|
||||||
{
|
{
|
||||||
|
@ -188,8 +188,7 @@
|
||||||
|
|
||||||
if (!twofactor)
|
if (!twofactor)
|
||||||
{
|
{
|
||||||
bool setCookie = (PageState.Runtime == Oqtane.Shared.Runtime.WebAssembly);
|
user = await UserService.LoginUserAsync(user);
|
||||||
user = await UserService.LoginUserAsync(user, setCookie, false);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -200,23 +199,14 @@
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
||||||
|
|
||||||
if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
|
// post back to the Login page so that the cookies are set correctly
|
||||||
{
|
|
||||||
// server-side Blazor needs to post to the Login page so that the cookies are set correctly
|
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
|
||||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||||
await interop.SubmitForm(url, fields);
|
await interop.SubmitForm(url, fields);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
if (PageState.Site.Settings["LoginOptions:TwoFactor"] == "required" || user.TwoFactorRequired)
|
||||||
authstateprovider.NotifyAuthenticationChanged();
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (user.TwoFactorRequired)
|
|
||||||
{
|
{
|
||||||
twofactor = true;
|
twofactor = true;
|
||||||
validated = false;
|
validated = false;
|
||||||
|
@ -308,12 +298,12 @@
|
||||||
if (_passwordtype == "password")
|
if (_passwordtype == "password")
|
||||||
{
|
{
|
||||||
_passwordtype = "text";
|
_passwordtype = "text";
|
||||||
_togglepassword = Localizer["HidePassword"];
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_passwordtype = "password";
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,19 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="password" type="password" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
@ -66,12 +72,19 @@ else
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
private string _confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Register()
|
private async Task Register()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
@ -134,4 +147,18 @@ else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,19 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The new password. It must satisfy complexity rules for the site." ResourceKey="Password">Password: </Label>
|
<Label Class="col-sm-3" For="password" HelpText="The new password. It must satisfy complexity rules for the site." ResourceKey="Password">Password: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="password" type="password" class="form-control" @bind="@_password" required />
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter the password again. It must exactly match the password entered above." ResourceKey="Confirm">Confirm: </Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Enter the password again. It must exactly match the password entered above." ResourceKey="Confirm">Confirm: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@_confirm" required />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,12 +42,16 @@
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
private string _confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
|
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
|
||||||
{
|
{
|
||||||
_username = PageState.QueryString["name"];
|
_username = PageState.QueryString["name"];
|
||||||
|
@ -110,4 +120,18 @@
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,7 +341,7 @@
|
||||||
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
||||||
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
||||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||||
_togglesmtppassword = Localizer["Show"];
|
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||||
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
|
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
|
||||||
|
|
||||||
|
@ -656,12 +656,12 @@
|
||||||
if (_smtppasswordtype == "password")
|
if (_smtppasswordtype == "password")
|
||||||
{
|
{
|
||||||
_smtppasswordtype = "text";
|
_smtppasswordtype = "text";
|
||||||
_togglesmtppassword = Localizer["Hide"];
|
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_smtppasswordtype = "password";
|
_smtppasswordtype = "password";
|
||||||
_togglesmtppassword = Localizer["Show"];
|
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ else
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password of an existing host user" ResourceKey="HostPassword">Host Password:</Label>
|
<Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password of an existing host user" ResourceKey="HostPassword">Host Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" required />
|
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" autocomplete="new-password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ else
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = _hostusername;
|
user.Username = _hostusername;
|
||||||
user.Password = _hostpassword;
|
user.Password = _hostpassword;
|
||||||
user = await UserService.LoginUserAsync(user, false, false);
|
user = await UserService.LoginUserAsync(user);
|
||||||
if (user.IsAuthenticated)
|
if (user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
|
|
@ -33,7 +33,7 @@ else
|
||||||
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" @bind="@_password" autocomplete="new-password" />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,10 @@ else
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (allowtwofactor)
|
@if (allowtwofactor)
|
||||||
|
@ -246,10 +249,11 @@ else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
|
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
|
||||||
{
|
{
|
||||||
allowtwofactor = bool.Parse(PageState.Site.Settings["LoginOptions:TwoFactor"]);
|
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PageState.User != null)
|
if (PageState.User != null)
|
||||||
|
@ -455,12 +459,12 @@ else
|
||||||
if (_passwordtype == "password")
|
if (_passwordtype == "password")
|
||||||
{
|
{
|
||||||
_passwordtype = "text";
|
_passwordtype = "text";
|
||||||
_togglepassword = Localizer["HidePassword"];
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_passwordtype = "password";
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" @bind="@_password" required />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,10 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
@ -107,7 +110,7 @@
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
settings = new Dictionary<string, string>();
|
settings = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
@ -204,12 +207,12 @@
|
||||||
if (_passwordtype == "password")
|
if (_passwordtype == "password")
|
||||||
{
|
{
|
||||||
_passwordtype = "text";
|
_passwordtype = "text";
|
||||||
_togglepassword = Localizer["HidePassword"];
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_passwordtype = "password";
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ else
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" @bind="@_password" required />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,10 @@ else
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
@ -162,10 +165,9 @@ else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// OnParametersSetAsync is called when the edit modal is closed - in which case there is no id parameter
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
userid = Int32.Parse(PageState.QueryString["id"]);
|
userid = Int32.Parse(PageState.QueryString["id"]);
|
||||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||||
|
@ -279,12 +281,12 @@ else
|
||||||
if (_passwordtype == "password")
|
if (_passwordtype == "password")
|
||||||
{
|
{
|
||||||
_passwordtype = "text";
|
_passwordtype = "text";
|
||||||
_togglepassword = Localizer["HidePassword"];
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_passwordtype = "password";
|
||||||
_togglepassword = Localizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,28 +94,28 @@ else
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled for this option to work properly." ResourceKey="TwoFactor">Allow Two Factor?</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
|
||||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="false">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (!string.IsNullOrEmpty(PageState.Alias.Path))
|
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="cookietype" HelpText="Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site (this option is only applicable to micro-sites)." ResourceKey="CookieType">Cookie Type:</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?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="cookietype" class="form-select" @bind="@_cookietype">
|
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||||
<option value="domain">@Localizer["Domain"]</option>
|
<option value="false">@Localizer["Disabled"]</option>
|
||||||
<option value="site">@Localizer["Site"]</option>
|
<option value="true">@Localizer["Optional"]</option>
|
||||||
|
<option value="required">@Localizer["Required"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</Section>
|
</Section>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||||
|
@ -274,15 +274,18 @@ else
|
||||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The type name for the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim Type:</Label>
|
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="The name of the unique user identifier claim provided by the provider" ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The name of the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
@ -338,6 +341,7 @@ else
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
@ -353,7 +357,7 @@ else
|
||||||
private string _allowregistration;
|
private string _allowregistration;
|
||||||
private string _allowsitelogin;
|
private string _allowsitelogin;
|
||||||
private string _twofactor;
|
private string _twofactor;
|
||||||
private string _cookietype;
|
private string _cookiename;
|
||||||
|
|
||||||
private string _minimumlength;
|
private string _minimumlength;
|
||||||
private string _uniquecharacters;
|
private string _uniquecharacters;
|
||||||
|
@ -378,6 +382,7 @@ else
|
||||||
private string _scopes;
|
private string _scopes;
|
||||||
private string _pkce;
|
private string _pkce;
|
||||||
private string _redirecturl;
|
private string _redirecturl;
|
||||||
|
private string _identifierclaimtype;
|
||||||
private string _emailclaimtype;
|
private string _emailclaimtype;
|
||||||
private string _domainfilter;
|
private string _domainfilter;
|
||||||
private string _createusers;
|
private string _createusers;
|
||||||
|
@ -401,8 +406,11 @@ else
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
||||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
|
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||||
_cookietype = SettingService.GetSetting(settings, "LoginOptions:CookieType", "domain");
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
|
|
||||||
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||||
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||||
|
@ -423,19 +431,20 @@ else
|
||||||
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
|
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
|
||||||
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
||||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||||
_toggleclientsecret = Localizer["Show"];
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||||
|
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
|
||||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
|
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
|
||||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
|
|
||||||
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
||||||
_togglesecret = Localizer["Show"];
|
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||||
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
|
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
|
||||||
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
|
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
|
||||||
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
|
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UserRole> Search(string search)
|
private List<UserRole> Search(string search)
|
||||||
|
@ -507,8 +516,11 @@ else
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||||
|
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieType", _cookietype, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
|
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||||
|
@ -531,6 +543,7 @@ else
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||||
|
@ -540,6 +553,7 @@ else
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
||||||
|
}
|
||||||
|
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
await SettingService.ClearSiteSettingsCacheAsync();
|
await SettingService.ClearSiteSettingsCacheAsync();
|
||||||
|
@ -561,13 +575,20 @@ else
|
||||||
private void ProviderTypeChanged(ChangeEventArgs e)
|
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_providertype = (string)e.Value;
|
_providertype = (string)e.Value;
|
||||||
|
if (string.IsNullOrEmpty(_providername))
|
||||||
|
{
|
||||||
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
{
|
{
|
||||||
_scopes = "openid,profile,email";
|
_scopes = "openid,profile,email";
|
||||||
|
_identifierclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
|
||||||
|
_emailclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_scopes = "";
|
_scopes = "";
|
||||||
|
_identifierclaimtype = "sub";
|
||||||
|
_emailclaimtype = "email";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
@ -583,12 +604,12 @@ else
|
||||||
if (_clientsecrettype == "password")
|
if (_clientsecrettype == "password")
|
||||||
{
|
{
|
||||||
_clientsecrettype = "text";
|
_clientsecrettype = "text";
|
||||||
_toggleclientsecret = Localizer["Hide"];
|
_toggleclientsecret = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_clientsecrettype = "password";
|
_clientsecrettype = "password";
|
||||||
_toggleclientsecret = Localizer["Show"];
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,12 +618,12 @@ else
|
||||||
if (_secrettype == "password")
|
if (_secrettype == "password")
|
||||||
{
|
{
|
||||||
_secrettype = "text";
|
_secrettype = "text";
|
||||||
_togglesecret = Localizer["Hide"];
|
_togglesecret = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_secrettype = "password";
|
_secrettype = "password";
|
||||||
_togglesecret = Localizer["Show"];
|
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,12 +189,6 @@
|
||||||
<data name="Username.Text" xml:space="preserve">
|
<data name="Username.Text" xml:space="preserve">
|
||||||
<value>Username:</value>
|
<value>Username:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HidePassword" xml:space="preserve">
|
|
||||||
<value>Hide</value>
|
|
||||||
</data>
|
|
||||||
<data name="ShowPassword" xml:space="preserve">
|
|
||||||
<value>Show</value>
|
|
||||||
</data>
|
|
||||||
<data name="Use" xml:space="preserve">
|
<data name="Use" xml:space="preserve">
|
||||||
<value>Use</value>
|
<value>Use</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -225,4 +219,10 @@
|
||||||
<data name="ExternalLoginStatus.VerificationRequired" xml:space="preserve">
|
<data name="ExternalLoginStatus.VerificationRequired" xml:space="preserve">
|
||||||
<value>In Order To Link Your External Login With Your User Account You Must Verify Your Identity. Please Check Your Email For Further Instructions.</value>
|
<value>In Order To Link Your External Login With Your User Account You Must Verify Your Identity. Please Check Your Email For Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ExternalLoginStatus.AccessDenied" xml:space="preserve">
|
||||||
|
<value>Your External Login Was Denied Access. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ExternalLoginStatus.RemoteFailure" xml:space="preserve">
|
||||||
|
<value>Your External Login Failed. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -324,10 +324,4 @@
|
||||||
<data name="Aliases.Heading" xml:space="preserve">
|
<data name="Aliases.Heading" xml:space="preserve">
|
||||||
<value>Aliases</value>
|
<value>Aliases</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Hide" xml:space="preserve">
|
|
||||||
<value>Hide</value>
|
|
||||||
</data>
|
|
||||||
<data name="Show" xml:space="preserve">
|
|
||||||
<value>Show</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
|
@ -219,10 +219,4 @@
|
||||||
<data name="DeleteAllNotifications.Text" xml:space="preserve">
|
<data name="DeleteAllNotifications.Text" xml:space="preserve">
|
||||||
<value>Delete ALL Notifications</value>
|
<value>Delete ALL Notifications</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HidePassword" xml:space="preserve">
|
|
||||||
<value>Hide</value>
|
|
||||||
</data>
|
|
||||||
<data name="ShowPassword" xml:space="preserve">
|
|
||||||
<value>Show</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
@ -168,12 +168,6 @@
|
||||||
<data name="Username.Text" xml:space="preserve">
|
<data name="Username.Text" xml:space="preserve">
|
||||||
<value>Username:</value>
|
<value>Username:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HidePassword" xml:space="preserve">
|
|
||||||
<value>Hide</value>
|
|
||||||
</data>
|
|
||||||
<data name="ShowPassword" xml:space="preserve">
|
|
||||||
<value>Show</value>
|
|
||||||
</data>
|
|
||||||
<data name="Password.Placeholder" xml:space="preserve">
|
<data name="Password.Placeholder" xml:space="preserve">
|
||||||
<value>Password</value>
|
<value>Password</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
@ -183,12 +183,6 @@
|
||||||
<data name="Profile.Heading" xml:space="preserve">
|
<data name="Profile.Heading" xml:space="preserve">
|
||||||
<value>Profile</value>
|
<value>Profile</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HidePassword" xml:space="preserve">
|
|
||||||
<value>Hide</value>
|
|
||||||
</data>
|
|
||||||
<data name="ShowPassword" xml:space="preserve">
|
|
||||||
<value>Show</value>
|
|
||||||
</data>
|
|
||||||
<data name="Password.Placeholder" xml:space="preserve">
|
<data name="Password.Placeholder" xml:space="preserve">
|
||||||
<value>Password</value>
|
<value>Password</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
@ -247,10 +247,10 @@
|
||||||
<value>Domain Filter:</value>
|
<value>Domain Filter:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EmailClaimType.HelpText" xml:space="preserve">
|
<data name="EmailClaimType.HelpText" xml:space="preserve">
|
||||||
<value>The type name for the email address claim provided by the provider</value>
|
<value>The name of the email address claim provided by the provider</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EmailClaimType.Text" xml:space="preserve">
|
<data name="EmailClaimType.Text" xml:space="preserve">
|
||||||
<value>Email Claim Type:</value>
|
<value>Email Claim:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExternalLoginSettings.Heading" xml:space="preserve">
|
<data name="ExternalLoginSettings.Heading" xml:space="preserve">
|
||||||
<value>External Login Settings</value>
|
<value>External Login Settings</value>
|
||||||
|
@ -318,11 +318,11 @@
|
||||||
<data name="UserSettings.Heading" xml:space="preserve">
|
<data name="UserSettings.Heading" xml:space="preserve">
|
||||||
<value>User Settings</value>
|
<value>User Settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CookieType.HelpText" xml:space="preserve">
|
<data name="CookieName.HelpText" xml:space="preserve">
|
||||||
<value>Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site (this option is only applicable to micro-sites).</value>
|
<value>You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CookieType.Text" xml:space="preserve">
|
<data name="CookieName.Text" xml:space="preserve">
|
||||||
<value>Login Cookie Type:</value>
|
<value>Cookie Name:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CreateToken" xml:space="preserve">
|
<data name="CreateToken" xml:space="preserve">
|
||||||
<value>Create Token</value>
|
<value>Create Token</value>
|
||||||
|
@ -355,16 +355,19 @@
|
||||||
<value>Token Settings</value>
|
<value>Token Settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoFactor.HelpText" xml:space="preserve">
|
<data name="TwoFactor.HelpText" xml:space="preserve">
|
||||||
<value>Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled and your SMTP options need to be configured in Site Settings for this option to work properly.</value>
|
<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>
|
||||||
<data name="TwoFactor.Text" xml:space="preserve">
|
<data name="TwoFactor.Text" xml:space="preserve">
|
||||||
<value>Allow Two Factor?</value>
|
<value>Two Factor?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Hide" xml:space="preserve">
|
<data name="Disabled" xml:space="preserve">
|
||||||
<value>Hide</value>
|
<value>Disabled</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Show" xml:space="preserve">
|
<data name="Optional" xml:space="preserve">
|
||||||
<value>Show</value>
|
<value>Optional</value>
|
||||||
|
</data>
|
||||||
|
<data name="Required" xml:space="preserve">
|
||||||
|
<value>Required</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CreatedOn" xml:space="preserve">
|
<data name="CreatedOn" xml:space="preserve">
|
||||||
<value>Created On</value>
|
<value>Created On</value>
|
||||||
|
@ -375,4 +378,10 @@
|
||||||
<data name="LastLoginOn" xml:space="preserve">
|
<data name="LastLoginOn" xml:space="preserve">
|
||||||
<value>Last Login</value>
|
<value>Last Login</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="IdentifierClaimType.HelpText" xml:space="preserve">
|
||||||
|
<value>The name of the unique user identifier claim provided by the provider</value>
|
||||||
|
</data>
|
||||||
|
<data name="IdentifierClaimType.Text" xml:space="preserve">
|
||||||
|
<value>Identifier Claim:</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -321,4 +321,10 @@
|
||||||
<data name="Settings" xml:space="preserve">
|
<data name="Settings" xml:space="preserve">
|
||||||
<value>Settings</value>
|
<value>Settings</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="HidePassword" xml:space="preserve">
|
||||||
|
<value>Hide</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShowPassword" xml:space="preserve">
|
||||||
|
<value>Show</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -54,10 +54,8 @@ namespace Oqtane.Services
|
||||||
/// Note that this will probably not be a real User, but a user object where the `Username` and `Password` have been filled.
|
/// Note that this will probably not be a real User, but a user object where the `Username` and `Password` have been filled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">A <see cref="User"/> object which should have at least the <see cref="User.Username"/> and <see cref="User.Password"/> set.</param>
|
/// <param name="user">A <see cref="User"/> object which should have at least the <see cref="User.Username"/> and <see cref="User.Password"/> set.</param>
|
||||||
/// <param name="setCookie">Determines if the login should be stored in the cookie.</param>
|
|
||||||
/// <param name="isPersistent">Determines if the login should be persisted in the cookie for a long time.</param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent);
|
Task<User> LoginUserAsync(User user);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logout a <see cref="User"/>
|
/// Logout a <see cref="User"/>
|
||||||
|
|
|
@ -22,10 +22,15 @@ namespace Oqtane.Services
|
||||||
|
|
||||||
private HttpClient GetHttpClient()
|
private HttpClient GetHttpClient()
|
||||||
{
|
{
|
||||||
var httpClient = _httpClientFactory.CreateClient("Remote");
|
return GetHttpClient(_siteState?.AuthorizationToken);
|
||||||
if (!httpClient.DefaultRequestHeaders.Contains(HeaderNames.Authorization) && _siteState != null && !string.IsNullOrEmpty(_siteState.AuthorizationToken))
|
}
|
||||||
|
|
||||||
|
private HttpClient GetHttpClient(string AuthorizationToken)
|
||||||
{
|
{
|
||||||
httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + _siteState.AuthorizationToken);
|
var httpClient = _httpClientFactory.CreateClient("Remote");
|
||||||
|
if (!httpClient.DefaultRequestHeaders.Contains(HeaderNames.Authorization) && !string.IsNullOrEmpty(AuthorizationToken))
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + AuthorizationToken);
|
||||||
}
|
}
|
||||||
return httpClient;
|
return httpClient;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,9 @@ namespace Oqtane.Services
|
||||||
await DeleteAsync($"{Apiurl}/{userId}?siteid={siteId}");
|
await DeleteAsync($"{Apiurl}/{userId}?siteid={siteId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent)
|
public async Task<User> LoginUserAsync(User user)
|
||||||
{
|
{
|
||||||
return await PostJsonAsync<User>($"{Apiurl}/login?setcookie={setCookie}&persistent={isPersistent}", user);
|
return await PostJsonAsync<User>($"{Apiurl}/login", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogoutUserAsync(User user)
|
public async Task LogoutUserAsync(User user)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Providers;
|
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
@ -33,28 +31,19 @@ namespace Oqtane.Themes.Controls
|
||||||
|
|
||||||
protected async Task LogoutUser()
|
protected async Task LogoutUser()
|
||||||
{
|
{
|
||||||
await UserService.LogoutUserAsync(PageState.User);
|
|
||||||
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username);
|
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username);
|
||||||
PageState.User = null;
|
|
||||||
|
|
||||||
|
// check if anonymous user can access page
|
||||||
var url = PageState.Alias.Path + "/" + PageState.Page.Path;
|
var url = PageState.Alias.Path + "/" + PageState.Page.Path;
|
||||||
if (!UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, PageState.Page.Permissions))
|
if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.Permissions))
|
||||||
{
|
{
|
||||||
url = PageState.Alias.Path;
|
url = PageState.Alias.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PageState.Runtime == Shared.Runtime.Server)
|
// post to the Logout page to complete the logout process
|
||||||
{
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url };
|
||||||
// server-side Blazor needs to redirect to the Logout page
|
var interop = new Interop(jsRuntime);
|
||||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/logout/") + "?returnurl=" + WebUtility.UrlEncode(url), true);
|
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// client-side Blazor
|
|
||||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
|
||||||
authstateprovider.NotifyAuthenticationChanged();
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl(url, true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,20 +84,30 @@
|
||||||
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
|
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
|
||||||
var querystring = ParseQueryString(route.Query);
|
var querystring = ParseQueryString(route.Query);
|
||||||
|
|
||||||
// reload the client application if there is a forced reload or the user navigated to a site with a different alias
|
// reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias
|
||||||
if (querystring.ContainsKey("reload") || (!route.AbsolutePath.Substring(1).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
|
if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
|
if (querystring["reload"] == "post")
|
||||||
|
{
|
||||||
|
// post back so that the cookies are set correctly - required on any change to the principal
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) };
|
||||||
|
string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// the refresh parameter is used to refresh the PageState
|
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the refresh parameter is used to refresh the client-side PageState
|
||||||
if (querystring.ContainsKey("refresh"))
|
if (querystring.ContainsKey("refresh"))
|
||||||
{
|
{
|
||||||
refresh = UI.Refresh.Site;
|
refresh = UI.Refresh.Site;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (PageState != null)
|
if (PageState != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,6 +52,7 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
results.Add(new Dictionary<string, string>() { { "Error", ex.Message } });
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
|
_logger.Log(LogLevel.Error, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
|
||||||
}
|
}
|
||||||
sqlquery.Results = results;
|
sqlquery.Results = results;
|
||||||
|
|
|
@ -316,7 +316,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// POST api/<controller>/login
|
// POST api/<controller>/login
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<User> Login([FromBody] User user, bool setCookie, bool isPersistent)
|
public async Task<User> Login([FromBody] User user)
|
||||||
{
|
{
|
||||||
User loginUser = new User { SiteId = user.SiteId, Username = user.Username, IsAuthenticated = false };
|
User loginUser = new User { SiteId = user.SiteId, Username = user.Username, IsAuthenticated = false };
|
||||||
|
|
||||||
|
@ -357,10 +357,6 @@ namespace Oqtane.Controllers
|
||||||
loginUser.LastIPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
|
loginUser.LastIPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
|
||||||
_users.UpdateUser(loginUser);
|
_users.UpdateUser(loginUser);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", user.Username);
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", user.Username);
|
||||||
if (setCookie)
|
|
||||||
{
|
|
||||||
await _identitySignInManager.SignInAsync(identityuser, isPersistent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,9 +16,9 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
namespace Oqtane.Extensions
|
namespace Oqtane.Extensions
|
||||||
{
|
{
|
||||||
|
@ -29,15 +29,7 @@ namespace Oqtane.Extensions
|
||||||
// site cookie authentication options
|
// site cookie authentication options
|
||||||
builder.AddSiteOptions<CookieAuthenticationOptions>((options, alias, sitesettings) =>
|
builder.AddSiteOptions<CookieAuthenticationOptions>((options, alias, sitesettings) =>
|
||||||
{
|
{
|
||||||
if (sitesettings.GetValue("LoginOptions:CookieType", "domain") == "domain")
|
options.Cookie.Name = sitesettings.GetValue("LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
{
|
|
||||||
options.Cookie.Name = ".AspNetCore.Identity.Application";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// use unique cookie name for site
|
|
||||||
options.Cookie.Name = ".AspNetCore.Identity.Application" + alias.SiteKey;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// site OpenId Connect options
|
// site OpenId Connect options
|
||||||
|
@ -105,6 +97,7 @@ namespace Oqtane.Extensions
|
||||||
|
|
||||||
// oauth2 events
|
// oauth2 events
|
||||||
options.Events.OnCreatingTicket = OnCreatingTicket;
|
options.Events.OnCreatingTicket = OnCreatingTicket;
|
||||||
|
options.Events.OnTicketReceived = OnTicketReceived;
|
||||||
options.Events.OnAccessDenied = OnAccessDenied;
|
options.Events.OnAccessDenied = OnAccessDenied;
|
||||||
options.Events.OnRemoteFailure = OnRemoteFailure;
|
options.Events.OnRemoteFailure = OnRemoteFailure;
|
||||||
}
|
}
|
||||||
|
@ -117,10 +110,13 @@ namespace Oqtane.Extensions
|
||||||
{
|
{
|
||||||
// OAuth 2.0
|
// OAuth 2.0
|
||||||
var email = "";
|
var email = "";
|
||||||
|
var id = "";
|
||||||
|
|
||||||
if (context.Options.UserInformationEndpoint != "")
|
if (context.Options.UserInformationEndpoint != "")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// call user information endpoint
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
|
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
|
||||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
|
@ -129,17 +125,34 @@ namespace Oqtane.Extensions
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var output = await response.Content.ReadAsStringAsync();
|
var output = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// get email address using Regex on the raw output (could be json or html)
|
// parse json output
|
||||||
var regex = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase);
|
var idClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:IdentifierClaimType", "");
|
||||||
foreach (Match match in regex.Matches(output))
|
var emailClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", "");
|
||||||
|
if (!output.StartsWith("[") && !output.EndsWith("]"))
|
||||||
{
|
{
|
||||||
if (EmailValid(match.Value, context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", "")))
|
output = "[" + output + "]"; // convert to json array
|
||||||
|
}
|
||||||
|
JsonNode items = JsonNode.Parse(output)!;
|
||||||
|
foreach(var item in items.AsArray())
|
||||||
{
|
{
|
||||||
email = match.Value.ToLower();
|
if (item[emailClaimType] != null)
|
||||||
|
{
|
||||||
|
if (EmailValid(item[emailClaimType].ToString(), context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", "")))
|
||||||
|
{
|
||||||
|
email = item[emailClaimType].ToString().ToLower();
|
||||||
|
if (item[idClaimType] != null)
|
||||||
|
{
|
||||||
|
id = item[idClaimType].ToString();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
id = email;
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||||
|
@ -147,29 +160,51 @@ namespace Oqtane.Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// login user
|
// validate user
|
||||||
var status = await LoginUser(email, context.HttpContext, context.Principal);
|
var identity = await ValidateUser(email, id, context.HttpContext);
|
||||||
|
if (identity.Label == ExternalLoginStatus.Success)
|
||||||
|
{
|
||||||
|
identity.AddClaim(new Claim("access_token", context.AccessToken));
|
||||||
|
context.Principal = new ClaimsPrincipal(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass properties to OnTicketReceived
|
||||||
|
context.Properties.SetParameter("status", identity.Label);
|
||||||
|
context.Properties.SetParameter("redirecturl", context.Properties.RedirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task OnTicketReceived(TicketReceivedContext context)
|
||||||
|
{
|
||||||
|
// OAuth 2.0
|
||||||
|
var status = context.Properties.GetParameter<string>("status");
|
||||||
if (status != ExternalLoginStatus.Success)
|
if (status != ExternalLoginStatus.Success)
|
||||||
{
|
{
|
||||||
// redirect to login page and pass status
|
// redirect to login page and pass status
|
||||||
var alias = context.HttpContext.GetAlias();
|
context.Response.Redirect(Utilities.TenantUrl(context.HttpContext.GetAlias(), $"/login?status={status}&returnurl={context.Properties.GetParameter<string>("redirecturl")}"), true);
|
||||||
context.Response.Redirect($"{alias.Path}/login?status={status}&returnurl={context.Properties.RedirectUri}", true);
|
context.HandleResponse();
|
||||||
};
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task OnTokenValidated(TokenValidatedContext context)
|
private static async Task OnTokenValidated(TokenValidatedContext context)
|
||||||
{
|
{
|
||||||
// OpenID Connect
|
// OpenID Connect
|
||||||
|
var idClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:IdentifierClaimType", "");
|
||||||
|
var id = context.Principal.FindFirstValue(idClaimType);
|
||||||
var emailClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", "");
|
var emailClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", "");
|
||||||
var email = context.Principal.FindFirstValue(emailClaimType);
|
var email = context.Principal.FindFirstValue(emailClaimType);
|
||||||
|
|
||||||
// login user
|
// validate user
|
||||||
var status = await LoginUser(email, context.HttpContext, context.Principal);
|
var identity = await ValidateUser(email, id, context.HttpContext);
|
||||||
if (status != ExternalLoginStatus.Success)
|
if (identity.Label == ExternalLoginStatus.Success)
|
||||||
|
{
|
||||||
|
identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData));
|
||||||
|
context.Principal = new ClaimsPrincipal(identity);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// redirect to login page and pass status
|
// redirect to login page and pass status
|
||||||
var alias = context.HttpContext.GetAlias();
|
context.Response.Redirect(Utilities.TenantUrl(context.HttpContext.GetAlias(), $"/login?status={identity.Label}&returnurl={context.Properties.RedirectUri}"), true);
|
||||||
context.Response.Redirect($"{alias.Path}/login?status={status}&returnurl={context.Properties.RedirectUri}", true);
|
|
||||||
context.HandleResponse();
|
context.HandleResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,8 +214,7 @@ namespace Oqtane.Extensions
|
||||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External Login Access Denied - User May Have Cancelled Their External Login Attempt");
|
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External Login Access Denied - User May Have Cancelled Their External Login Attempt");
|
||||||
// redirect to login page
|
// redirect to login page
|
||||||
var alias = context.HttpContext.GetAlias();
|
context.Response.Redirect(Utilities.TenantUrl(context.HttpContext.GetAlias(), $"/login?status={ExternalLoginStatus.AccessDenied}&returnurl={context.Properties.RedirectUri}"), true);
|
||||||
context.Response.Redirect($"{alias.Path}/login?returnurl={context.Properties.RedirectUri}", true);
|
|
||||||
context.HandleResponse();
|
context.HandleResponse();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -190,16 +224,16 @@ namespace Oqtane.Extensions
|
||||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External Login Remote Failure - {Error}", context.Failure.Message);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External Login Remote Failure - {Error}", context.Failure.Message);
|
||||||
// redirect to login page
|
// redirect to login page
|
||||||
var alias = context.HttpContext.GetAlias();
|
context.Response.Redirect(Utilities.TenantUrl(context.HttpContext.GetAlias(), $"/login?status={ExternalLoginStatus.RemoteFailure}"), true);
|
||||||
context.Response.Redirect($"{alias.Path}/login", true);
|
|
||||||
context.HandleResponse();
|
context.HandleResponse();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ExternalLoginStatus> LoginUser(string email, HttpContext httpContext, ClaimsPrincipal claimsPrincipal)
|
private static async Task<ClaimsIdentity> ValidateUser(string email, string id, HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var _logger = httpContext.RequestServices.GetRequiredService<ILogManager>();
|
var _logger = httpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||||
var status = ExternalLoginStatus.Success;
|
ClaimsIdentity identity = new ClaimsIdentity(Constants.AuthenticationScheme);
|
||||||
|
// use identity.Label as a temporary location to store validation status information
|
||||||
|
|
||||||
if (EmailValid(email, httpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", "")))
|
if (EmailValid(email, httpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", "")))
|
||||||
{
|
{
|
||||||
|
@ -209,11 +243,6 @@ namespace Oqtane.Extensions
|
||||||
var alias = httpContext.GetAlias();
|
var alias = httpContext.GetAlias();
|
||||||
var providerType = httpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", "");
|
var providerType = httpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", "");
|
||||||
var providerName = httpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderName", "");
|
var providerName = httpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderName", "");
|
||||||
var providerKey = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
||||||
if (providerKey == null)
|
|
||||||
{
|
|
||||||
providerKey = email; // OAuth2 does not pass claims
|
|
||||||
}
|
|
||||||
User user = null;
|
User user = null;
|
||||||
|
|
||||||
bool duplicates = false;
|
bool duplicates = false;
|
||||||
|
@ -231,7 +260,7 @@ namespace Oqtane.Extensions
|
||||||
{
|
{
|
||||||
if (duplicates)
|
if (duplicates)
|
||||||
{
|
{
|
||||||
status = ExternalLoginStatus.DuplicateEmail;
|
identity.Label = ExternalLoginStatus.DuplicateEmail;
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Multiple Users Exist With Email Address {Email}. Login Denied.", email);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Multiple Users Exist With Email Address {Email}. Login Denied.", email);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -265,25 +294,25 @@ namespace Oqtane.Extensions
|
||||||
_notifications.AddNotification(notification);
|
_notifications.AddNotification(notification);
|
||||||
|
|
||||||
// add user login
|
// add user login
|
||||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType, providerKey, ""));
|
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType, id, ""));
|
||||||
|
|
||||||
_logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user);
|
_logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
status = ExternalLoginStatus.UserNotCreated;
|
identity.Label = ExternalLoginStatus.UserNotCreated;
|
||||||
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add User {Email}", email);
|
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add User {Email}", email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
status = ExternalLoginStatus.UserNotCreated;
|
identity.Label = ExternalLoginStatus.UserNotCreated;
|
||||||
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add Identity User {Email} {Error}", email, result.Errors.ToString());
|
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add Identity User {Email} {Error}", email, result.Errors.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
status = ExternalLoginStatus.UserDoesNotExist;
|
identity.Label = ExternalLoginStatus.UserDoesNotExist;
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled For This Site. User With Email Address {Email} Will First Need To Be Registered On The Site.", email);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled For This Site. User With Email Address {Email} Will First Need To Be Registered On The Site.", email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,14 +323,14 @@ namespace Oqtane.Extensions
|
||||||
var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString()));
|
var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString()));
|
||||||
if (login != null)
|
if (login != null)
|
||||||
{
|
{
|
||||||
if (login.ProviderKey == providerKey)
|
if (login.ProviderKey == id)
|
||||||
{
|
{
|
||||||
user = _users.GetUser(identityuser.UserName);
|
user = _users.GetUser(identityuser.UserName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// provider keys do not match
|
// provider keys do not match
|
||||||
status = ExternalLoginStatus.ProviderKeyMismatch;
|
identity.Label = ExternalLoginStatus.ProviderKeyMismatch;
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,23 +340,22 @@ namespace Oqtane.Extensions
|
||||||
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
|
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
|
||||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||||
string url = httpContext.Request.Scheme + "://" + alias.Name;
|
string url = httpContext.Request.Scheme + "://" + alias.Name;
|
||||||
url += $"/login?name={identityuser.UserName}&token={WebUtility.UrlEncode(token)}&key={WebUtility.UrlEncode(providerKey)}";
|
url += $"/login?name={identityuser.UserName}&token={WebUtility.UrlEncode(token)}&key={WebUtility.UrlEncode(id)}";
|
||||||
string body = $"You Recently Signed In To Our Site With {providerName} Using The Email Address {email}. ";
|
string body = $"You Recently Signed In To Our Site With {providerName} Using The Email Address {email}. ";
|
||||||
body += "In Order To Complete The Linkage Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
body += "In Order To Complete The Linkage Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
||||||
var notification = new Notification(alias.SiteId, email, email, "External Login Linkage", body);
|
var notification = new Notification(alias.SiteId, email, email, "External Login Linkage", body);
|
||||||
_notifications.AddNotification(notification);
|
_notifications.AddNotification(notification);
|
||||||
status = ExternalLoginStatus.VerificationRequired;
|
identity.Label = ExternalLoginStatus.VerificationRequired;
|
||||||
_logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email);
|
_logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add claims to principal
|
// manage user
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
var principal = (ClaimsIdentity)claimsPrincipal.Identity;
|
// create claims identity
|
||||||
UserSecurity.ResetClaimsIdentity(principal);
|
identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList());
|
||||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList());
|
identity.Label = ExternalLoginStatus.Success;
|
||||||
principal.AddClaims(identity.Claims);
|
|
||||||
|
|
||||||
// update user
|
// update user
|
||||||
user.LastLoginOn = DateTime.UtcNow;
|
user.LastLoginOn = DateTime.UtcNow;
|
||||||
|
@ -338,25 +366,17 @@ namespace Oqtane.Extensions
|
||||||
}
|
}
|
||||||
else // email invalid
|
else // email invalid
|
||||||
{
|
{
|
||||||
status = ExternalLoginStatus.InvalidEmail;
|
identity.Label = ExternalLoginStatus.InvalidEmail;
|
||||||
if (!string.IsNullOrEmpty(email))
|
if (!string.IsNullOrEmpty(email))
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The Email Address {Email} Is Invalid Or Does Not Match The Domain Filter Criteria. Login Denied.", email);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The Email Address {Email} Is Invalid Or Does Not Match The Domain Filter Criteria. Login Denied.", email);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
var emailclaimtype = claimsPrincipal.Claims.FirstOrDefault(item => item.Value.Contains("@") && item.Value.Contains("."));
|
|
||||||
if (emailclaimtype != null)
|
|
||||||
{
|
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Please Verify If \"{ClaimType}\" Is A Valid Email Claim Type For The Provider And Update Your External Login Settings Accordingly", emailclaimtype.Type);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return An Email To Uniquely Identify The User.");
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return An Email To Uniquely Identify The User.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return identity;
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool EmailValid(string email, string domainfilter)
|
private static bool EmailValid(string email, string domainfilter)
|
||||||
|
|
|
@ -14,5 +14,14 @@ namespace Oqtane.Extensions
|
||||||
|
|
||||||
return list.Any(f => s.StartsWith(f));
|
return list.Any(f => s.StartsWith(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ReplaceMultiple(this string s, string[] oldValues, string newValue)
|
||||||
|
{
|
||||||
|
foreach(string value in oldValues)
|
||||||
|
{
|
||||||
|
s = s.Replace(value, newValue);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,12 +45,11 @@ namespace Oqtane.Infrastructure
|
||||||
};
|
};
|
||||||
// jwt already contains the roles - we are reloading to ensure most accurate permissions
|
// jwt already contains the roles - we are reloading to ensure most accurate permissions
|
||||||
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
||||||
identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList());
|
|
||||||
|
|
||||||
// populate principal
|
// set claims identity
|
||||||
var principal = (ClaimsIdentity)context.User.Identity;
|
var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList());
|
||||||
UserSecurity.ResetClaimsIdentity(principal);
|
context.User = new ClaimsPrincipal(claimsidentity);
|
||||||
principal.AddClaims(identity.Claims);
|
|
||||||
logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For User {Username}", user.Username);
|
logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For User {Username}", user.Username);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Oqtane.Extensions;
|
using Oqtane.Extensions;
|
||||||
|
|
||||||
namespace Oqtane.Pages
|
namespace Oqtane.Pages
|
||||||
{
|
{
|
||||||
|
[AllowAnonymous]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
public class ExternalModel : PageModel
|
public class ExternalModel : PageModel
|
||||||
{
|
{
|
||||||
public IActionResult OnGetAsync(string returnurl)
|
public IActionResult OnGetAsync(string returnurl)
|
||||||
|
@ -16,7 +19,7 @@ namespace Oqtane.Pages
|
||||||
var providertype = HttpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", "");
|
var providertype = HttpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", "");
|
||||||
if (providertype != "")
|
if (providertype != "")
|
||||||
{
|
{
|
||||||
return new ChallengeResult(providertype, new AuthenticationProperties { RedirectUri = returnurl });
|
return new ChallengeResult(providertype, new AuthenticationProperties { RedirectUri = returnurl + (returnurl.Contains("?") ? "&" : "?") + "reload=post" });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -24,5 +27,22 @@ namespace Oqtane.Pages
|
||||||
return new EmptyResult();
|
return new EmptyResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IActionResult OnPostAsync(string returnurl)
|
||||||
|
{
|
||||||
|
if (returnurl == null)
|
||||||
|
{
|
||||||
|
returnurl = "";
|
||||||
|
}
|
||||||
|
if (!returnurl.StartsWith("/"))
|
||||||
|
{
|
||||||
|
returnurl = "/" + returnurl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove reload parameter
|
||||||
|
returnurl = returnurl.ReplaceMultiple(new string[] { "?reload=post", "&reload=post" }, "");
|
||||||
|
|
||||||
|
return LocalRedirect(Url.Content("~" + returnurl));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Oqtane.Pages
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(string username, string password, bool remember, string returnurl)
|
public async Task<IActionResult> OnPostAsync(string username, string password, bool remember, string returnurl)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
|
if (!User.Identity.IsAuthenticated && !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
bool validuser = false;
|
bool validuser = false;
|
||||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(username);
|
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(username);
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Oqtane.Extensions;
|
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Pages
|
namespace Oqtane.Pages
|
||||||
|
@ -13,7 +10,7 @@ namespace Oqtane.Pages
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class LogoutModel : PageModel
|
public class LogoutModel : PageModel
|
||||||
{
|
{
|
||||||
public async Task<IActionResult> OnGetAsync(string returnurl)
|
public async Task<IActionResult> OnPostAsync(string returnurl)
|
||||||
{
|
{
|
||||||
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
||||||
|
|
||||||
|
|
|
@ -159,8 +159,8 @@ namespace Oqtane.Repository
|
||||||
moduledefinition.Name = (!string.IsNullOrEmpty(moduledef.Name)) ? moduledef.Name : moduledefinition.Name;
|
moduledefinition.Name = (!string.IsNullOrEmpty(moduledef.Name)) ? moduledef.Name : moduledefinition.Name;
|
||||||
moduledefinition.Description = (!string.IsNullOrEmpty(moduledef.Description)) ? moduledef.Description : moduledefinition.Description;
|
moduledefinition.Description = (!string.IsNullOrEmpty(moduledef.Description)) ? moduledef.Description : moduledefinition.Description;
|
||||||
moduledefinition.Categories = (!string.IsNullOrEmpty(moduledef.Categories)) ? moduledef.Categories : moduledefinition.Categories;
|
moduledefinition.Categories = (!string.IsNullOrEmpty(moduledef.Categories)) ? moduledef.Categories : moduledefinition.Categories;
|
||||||
// manage versioning
|
// manage releaseversions in cases where it was not provided or is lower than the module version
|
||||||
if (string.IsNullOrEmpty(moduledefinition.ReleaseVersions))
|
if (string.IsNullOrEmpty(moduledefinition.ReleaseVersions) || Version.Parse(moduledefinition.Version).CompareTo(Version.Parse(moduledefinition.ReleaseVersions.Split(',').Last())) > 0)
|
||||||
{
|
{
|
||||||
moduledefinition.ReleaseVersions = moduledefinition.Version;
|
moduledefinition.ReleaseVersions = moduledefinition.Version;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ namespace Oqtane
|
||||||
|
|
||||||
services.AddMvc(options =>
|
services.AddMvc(options =>
|
||||||
{
|
{
|
||||||
//options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
|
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
|
||||||
})
|
})
|
||||||
.AddNewtonsoftJson()
|
.AddNewtonsoftJson()
|
||||||
.AddOqtaneApplicationParts() // register any Controllers from custom modules
|
.AddOqtaneApplicationParts() // register any Controllers from custom modules
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
namespace Oqtane.Shared
|
|
||||||
{
|
|
||||||
public enum ExternalLoginStatus
|
|
||||||
{
|
|
||||||
Success,
|
|
||||||
InvalidEmail,
|
|
||||||
DuplicateEmail,
|
|
||||||
UserNotCreated,
|
|
||||||
UserDoesNotExist,
|
|
||||||
ProviderKeyMismatch,
|
|
||||||
VerificationRequired
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -152,14 +152,5 @@ namespace Oqtane.Security
|
||||||
}
|
}
|
||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ResetClaimsIdentity(ClaimsIdentity identity)
|
|
||||||
{
|
|
||||||
var claims = identity.Claims.ToList(); // clone
|
|
||||||
foreach (var claim in claims)
|
|
||||||
{
|
|
||||||
identity.RemoveClaim(claim);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
Oqtane.Shared/Shared/ExternalLoginStatus.cs
Normal file
13
Oqtane.Shared/Shared/ExternalLoginStatus.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Oqtane.Shared {
|
||||||
|
public class ExternalLoginStatus {
|
||||||
|
public const string Success = "Success";
|
||||||
|
public const string InvalidEmail = "InvalidEmail";
|
||||||
|
public const string DuplicateEmail = "DuplicateEmail";
|
||||||
|
public const string UserNotCreated = "UserNotCreated";
|
||||||
|
public const string UserDoesNotExist = "UserDoesNotExist";
|
||||||
|
public const string ProviderKeyMismatch = "ProviderKeyMismatch";
|
||||||
|
public const string VerificationRequired = "VerificationRequired";
|
||||||
|
public const string AccessDenied = "AccessDenied";
|
||||||
|
public const string RemoteFailure = "RemoteFailure";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user