V14: Redirect to login screen (#16051)

* Redirect to login screen after flows complete

* Revoke tokens after completing flow

* Use Ok not Redirect

* skip length check
This commit is contained in:
Nikolaj Geisle
2024-04-19 09:42:13 +02:00
committed by GitHub
parent 4dfd26e59e
commit cceb4180f2
5 changed files with 54 additions and 23 deletions

View File

@@ -54,16 +54,16 @@ public class BackOfficeLoginController : Controller
model.UmbracoUrl = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath);
}
if ( Uri.TryCreate(model.ReturnUrl, UriKind.Relative, out _) is false) // Needs to test for relative and not absolute, as /whatever/ is an absolute path on linux
{
return BadRequest("ReturnUrl must be a relative path.");
}
if (string.IsNullOrEmpty(model.ReturnUrl))
{
model.ReturnUrl = model.UmbracoUrl;
}
if ( Uri.TryCreate(model.ReturnUrl, UriKind.Relative, out _) is false) // Needs to test for relative and not absolute, as /whatever/ is an absolute path on linux
{
return BadRequest("ReturnUrl must be a relative path.");
}
return View("/umbraco/UmbracoLogin/Index.cshtml", model);
}
}

View File

@@ -2,6 +2,7 @@ using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Management.Filters;
using Umbraco.Cms.Api.Management.ViewModels.Security;
@@ -10,6 +11,7 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Security;
@@ -18,8 +20,13 @@ namespace Umbraco.Cms.Api.Management.Controllers.Security;
public class ResetPasswordTokenController : SecurityControllerBase
{
private readonly IUserService _userService;
private readonly IOpenIddictTokenManager _tokenManager;
public ResetPasswordTokenController(IUserService userService) => _userService = userService;
public ResetPasswordTokenController(IUserService userService, IOpenIddictTokenManager tokenManager)
{
_userService = userService;
_tokenManager = tokenManager;
}
[HttpPost("forgot-password/reset")]
[MapToApiVersion("1.0")]
@@ -31,8 +38,13 @@ public class ResetPasswordTokenController : SecurityControllerBase
{
Attempt<PasswordChangedModel, UserOperationStatus> result = await _userService.ResetPasswordAsync(model.User.Id, model.ResetCode, model.Password);
return result.Success
? NoContent()
: UserOperationStatusResult(result.Status, result.Result);
if (result.Success is false)
{
return UserOperationStatusResult(result.Status, result.Result);
}
await _tokenManager.RevokeUmbracoUserTokens(model.User.Id);
return Ok();
}
}

View File

@@ -2,12 +2,14 @@ using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using Umbraco.Cms.Api.Management.ViewModels.User;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.User;
@@ -16,8 +18,13 @@ namespace Umbraco.Cms.Api.Management.Controllers.User;
public class CreateInitialPasswordUserController : UserControllerBase
{
private readonly IUserService _userService;
private readonly IOpenIddictTokenManager _tokenManager;
public CreateInitialPasswordUserController(IUserService userService) => _userService = userService;
public CreateInitialPasswordUserController(IUserService userService, IOpenIddictTokenManager tokenManager)
{
_userService = userService;
_tokenManager = tokenManager;
}
[AllowAnonymous]
[HttpPost("invite/create-password")]
@@ -31,8 +38,12 @@ public class CreateInitialPasswordUserController : UserControllerBase
{
Attempt<PasswordChangedModel, UserOperationStatus> response = await _userService.CreateInitialPasswordAsync(model.User.Id, model.Token, model.Password);
return response.Success
? Ok()
: UserOperationStatusResult(response.Status, response.Result);
if (response.Success is false)
{
return UserOperationStatusResult(response.Status, response.Result);
}
await _tokenManager.RevokeUmbracoUserTokens(model.User.Id);
return Ok();
}
}

View File

@@ -0,0 +1,16 @@
using OpenIddict.Abstractions;
namespace Umbraco.Extensions;
public static class OpenIdDictTokenManagerExtensions
{
public static async Task RevokeUmbracoUserTokens(this IOpenIddictTokenManager openIddictTokenManager, Guid userKey)
{
var tokens = await openIddictTokenManager.FindBySubjectAsync(userKey.ToString()).ToArrayAsync();
foreach (var token in tokens)
{
await openIddictTokenManager.DeleteAsync(token);
}
}
}

View File

@@ -215,17 +215,9 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
private async Task RevokeTokensAsync(IUser user)
{
var tokens = await _tokenManager.FindBySubjectAsync(user.Key.ToString()).ToArrayAsync();
if (tokens.Length == 0)
{
return;
}
_logger.LogInformation("Revoking active tokens for user with ID {id}", user.Id);
_logger.LogInformation("Revoking {count} active tokens for user with ID {id}", tokens.Length, user.Id);
foreach (var token in tokens)
{
await _tokenManager.DeleteAsync(token);
}
await _tokenManager.RevokeUmbracoUserTokens(user.Key);
}
private async Task<IUser?> FindUserFromString(string userId)