fix #4638 - add Logout Everywhere option to User Profile
This commit is contained in:
parent
8cdcdaf6d9
commit
b7928a5255
|
@ -209,7 +209,7 @@ else
|
||||||
|
|
||||||
if (user != null && user.IsAuthenticated)
|
if (user != null && user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||||
|
|
||||||
// return url is not specified if user navigated directly to login page
|
// return url is not specified if user navigated directly to login page
|
||||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
|
@inject IJSRuntime jsRuntime
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@ -84,6 +86,7 @@
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -518,6 +521,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Logout()
|
||||||
|
{
|
||||||
|
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||||
|
|
||||||
|
var url = NavigateUrl(""); // home page
|
||||||
|
|
||||||
|
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||||
|
{
|
||||||
|
if (PageState.User != null)
|
||||||
|
{
|
||||||
|
// hybrid apps utilize an interactive logout
|
||||||
|
await UserService.LogoutUserEverywhereAsync(PageState.User);
|
||||||
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// post to the Logout page to complete the logout process
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url, everywhere = true };
|
||||||
|
var interop = new Interop(jsRuntime);
|
||||||
|
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in profiles)
|
foreach (Profile profile in profiles)
|
||||||
|
|
|
@ -243,4 +243,7 @@
|
||||||
<data name="NoNotificationsSent.Text" xml:space="preserve">
|
<data name="NoNotificationsSent.Text" xml:space="preserve">
|
||||||
<value>No notifications have been sent</value>
|
<value>No notifications have been sent</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Logout Everywhere" xml:space="preserve">
|
||||||
|
<value>Logout Everywhere</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -75,6 +75,13 @@ namespace Oqtane.Services
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task LogoutUserAsync(User user);
|
Task LogoutUserAsync(User user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logout a <see cref="User"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task LogoutUserEverywhereAsync(User user);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update e-mail verification status of a user.
|
/// Update e-mail verification status of a user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -61,10 +61,14 @@ namespace Oqtane.Services
|
||||||
|
|
||||||
public async Task LogoutUserAsync(User user)
|
public async Task LogoutUserAsync(User user)
|
||||||
{
|
{
|
||||||
// best practices recommend post is preferrable to get for logout
|
|
||||||
await PostJsonAsync($"{Apiurl}/logout", user);
|
await PostJsonAsync($"{Apiurl}/logout", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task LogoutUserEverywhereAsync(User user)
|
||||||
|
{
|
||||||
|
await PostJsonAsync($"{Apiurl}/logouteverywhere", user);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<User> VerifyEmailAsync(User user, string token)
|
public async Task<User> VerifyEmailAsync(User user, string token)
|
||||||
{
|
{
|
||||||
return await PostJsonAsync<User>($"{Apiurl}/verify?token={token}", user);
|
return await PostJsonAsync<User>($"{Apiurl}/verify?token={token}", user);
|
||||||
|
|
|
@ -61,7 +61,6 @@
|
||||||
{
|
{
|
||||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||||
SiteState.AuthorizationToken = AuthorizationToken;
|
SiteState.AuthorizationToken = AuthorizationToken;
|
||||||
SiteState.RemoteIPAddress = (_pageState != null) ? _pageState.RemoteIPAddress : "";
|
|
||||||
SiteState.Platform = Platform;
|
SiteState.Platform = Platform;
|
||||||
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
|
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
|
||||||
|
|
||||||
|
@ -80,6 +79,7 @@
|
||||||
{
|
{
|
||||||
_pageState = PageState;
|
_pageState = PageState;
|
||||||
SiteState.Alias = PageState.Alias;
|
SiteState.Alias = PageState.Alias;
|
||||||
|
SiteState.RemoteIPAddress = (PageState != null) ? PageState.RemoteIPAddress : "";
|
||||||
_installed = true;
|
_installed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,10 +262,26 @@ namespace Oqtane.Controllers
|
||||||
[HttpPost("logout")]
|
[HttpPost("logout")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task Logout([FromBody] User user)
|
public async Task Logout([FromBody] User user)
|
||||||
|
{
|
||||||
|
if (_userPermissions.GetUser(User).UserId == user.UserId)
|
||||||
{
|
{
|
||||||
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/<controller>/logout
|
||||||
|
[HttpPost("logouteverywhere")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task LogoutEverywhere([FromBody] User user)
|
||||||
|
{
|
||||||
|
if (_userPermissions.GetUser(User).UserId == user.UserId)
|
||||||
|
{
|
||||||
|
await _userManager.LogoutUserEverywhere(user);
|
||||||
|
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// POST api/<controller>/verify
|
// POST api/<controller>/verify
|
||||||
[HttpPost("verify")]
|
[HttpPost("verify")]
|
||||||
|
|
|
@ -645,7 +645,7 @@ namespace Oqtane.Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} Using Provider {Provider}", user.Username, providerName);
|
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress, providerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // claims invalid
|
else // claims invalid
|
||||||
|
|
|
@ -165,17 +165,11 @@ namespace Oqtane.Infrastructure
|
||||||
names.Add(message.Substring(index + 1, message.IndexOf("}", index) - index - 1));
|
names.Add(message.Substring(index + 1, message.IndexOf("}", index) - index - 1));
|
||||||
if (values.Length > (names.Count - 1))
|
if (values.Length > (names.Count - 1))
|
||||||
{
|
{
|
||||||
if (values[names.Count - 1] == null)
|
var value = (values[names.Count - 1] == null) ? "null" : values[names.Count - 1].ToString();
|
||||||
{
|
message = message.Replace("{" + names[names.Count - 1] + "}", value);
|
||||||
message = message.Replace("{" + names[names.Count - 1] + "}", "null");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message = message.Replace("{" + names[names.Count - 1] + "}", values[names.Count - 1].ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
index = (index < message.Length - 1) ? message.IndexOf("{", index + 1) : -1;
|
||||||
index = message.IndexOf("{", index + 1);
|
|
||||||
}
|
}
|
||||||
// rebuild properties into dictionary
|
// rebuild properties into dictionary
|
||||||
Dictionary<string, object> propertyDictionary = new Dictionary<string, object>();
|
Dictionary<string, object> propertyDictionary = new Dictionary<string, object>();
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace Oqtane.Managers
|
||||||
Task<User> UpdateUser(User user);
|
Task<User> UpdateUser(User user);
|
||||||
Task DeleteUser(int userid, int siteid);
|
Task DeleteUser(int userid, int siteid);
|
||||||
Task<User> LoginUser(User user, bool setCookie, bool isPersistent);
|
Task<User> LoginUser(User user, bool setCookie, bool isPersistent);
|
||||||
|
Task LogoutUserEverywhere(User user);
|
||||||
Task<User> VerifyEmail(User user, string token);
|
Task<User> VerifyEmail(User user, string token);
|
||||||
Task ForgotPassword(User user);
|
Task ForgotPassword(User user);
|
||||||
Task<User> ResetPassword(User user, string token);
|
Task<User> ResetPassword(User user, string token);
|
||||||
|
|
|
@ -265,7 +265,6 @@ namespace Oqtane.Managers
|
||||||
user = _users.UpdateUser(user);
|
user = _users.UpdateUser(user);
|
||||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
|
||||||
_cache.Remove($"user:{user.UserId}:{alias.SiteKey}");
|
|
||||||
user.Password = ""; // remove sensitive information
|
user.Password = ""; // remove sensitive information
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
||||||
}
|
}
|
||||||
|
@ -373,7 +372,7 @@ namespace Oqtane.Managers
|
||||||
user.LastLoginOn = DateTime.UtcNow;
|
user.LastLoginOn = DateTime.UtcNow;
|
||||||
user.LastIPAddress = LastIPAddress;
|
user.LastIPAddress = LastIPAddress;
|
||||||
_users.UpdateUser(user);
|
_users.UpdateUser(user);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", user.Username);
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress);
|
||||||
|
|
||||||
if (setCookie)
|
if (setCookie)
|
||||||
{
|
{
|
||||||
|
@ -420,6 +419,16 @@ namespace Oqtane.Managers
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
public async Task LogoutUserEverywhere(User user)
|
||||||
|
{
|
||||||
|
var identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||||
|
if (identityuser != null)
|
||||||
|
{
|
||||||
|
await _identityUserManager.UpdateSecurityStampAsync(identityuser);
|
||||||
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||||
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<User> VerifyEmail(User user, string token)
|
public async Task<User> VerifyEmail(User user, string token)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace Oqtane.Pages
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(string returnurl)
|
public async Task<IActionResult> OnPostAsync(string returnurl, string everywhere)
|
||||||
{
|
{
|
||||||
if (HttpContext.User != null)
|
if (HttpContext.User != null)
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,10 @@ namespace Oqtane.Pages
|
||||||
var user = _userManager.GetUser(HttpContext.User.Identity.Name, alias.SiteId);
|
var user = _userManager.GetUser(HttpContext.User.Identity.Name, alias.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
|
if (everywhere == "true")
|
||||||
|
{
|
||||||
|
await _userManager.LogoutUserEverywhere(user);
|
||||||
|
}
|
||||||
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload);
|
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user