v14: login app (#14318)
* ignore output files * add new umb-login element * allow to build and clean 'login' assets * remove unused AuthUrl since this is now coded into the frontend code for each context * ensure the ReturnUrl has a fallback to the default installation directory, since if you accidentally hit the login page and login, nothing happens if there is no return url * switch to DependsOnTargets to account for if this is the only target being run (we need node_modules installed) * add UmbracoUrl property * add taghelper to use asp-append-version on login static assets
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -73,6 +73,7 @@ preserve.belle
|
|||||||
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/js
|
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/js
|
||||||
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/lib
|
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/lib
|
||||||
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/views
|
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/views
|
||||||
|
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/login
|
||||||
|
|
||||||
# Environment specific data
|
# Environment specific data
|
||||||
/src/Umbraco.Web.UI.Client/[Bb]uild/
|
/src/Umbraco.Web.UI.Client/[Bb]uild/
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Umbraco.Cms.Core.Configuration.Models;
|
||||||
|
using Umbraco.Cms.Core.Hosting;
|
||||||
|
|
||||||
namespace Umbraco.Cms.Api.Management;
|
namespace Umbraco.Cms.Api.Management;
|
||||||
|
|
||||||
@@ -7,27 +9,45 @@ namespace Umbraco.Cms.Api.Management;
|
|||||||
public class
|
public class
|
||||||
BackOfficeLoginModel
|
BackOfficeLoginModel
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value of the "ReturnUrl" query parameter or defaults to the configured Umbraco directory.
|
||||||
|
/// </summary>
|
||||||
[FromQuery(Name = "ReturnUrl")]
|
[FromQuery(Name = "ReturnUrl")]
|
||||||
public string? ReturnUrl { get; set; }
|
public string? ReturnUrl { get; set; }
|
||||||
|
|
||||||
public string AuthUrl { get; set; } = string.Empty;
|
/// <summary>
|
||||||
|
/// The configured Umbraco directory.
|
||||||
|
/// </summary>
|
||||||
|
public string? UmbracoUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[ApiExplorerSettings(IgnoreApi=true)]
|
[ApiExplorerSettings(IgnoreApi=true)]
|
||||||
[Route("/umbraco/login")]
|
[Route("/umbraco/login")]
|
||||||
public class BackOfficeLoginController : Controller
|
public class BackOfficeLoginController : Controller
|
||||||
{
|
{
|
||||||
private readonly LinkGenerator _linkGenerator;
|
private readonly IHostingEnvironment _hostingEnvironment;
|
||||||
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
|
||||||
public BackOfficeLoginController(LinkGenerator linkGenerator)
|
public BackOfficeLoginController(
|
||||||
|
IOptionsSnapshot<GlobalSettings> globalSettings,
|
||||||
|
IHostingEnvironment hostingEnvironment)
|
||||||
{
|
{
|
||||||
_linkGenerator = linkGenerator;
|
_hostingEnvironment = hostingEnvironment;
|
||||||
|
_globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
public IActionResult Index(BackOfficeLoginModel model)
|
public IActionResult Index(BackOfficeLoginModel model)
|
||||||
{
|
{
|
||||||
model.AuthUrl = "/umbraco/management/api/v1.0/security/back-office";
|
if (string.IsNullOrEmpty(model.UmbracoUrl))
|
||||||
|
{
|
||||||
|
model.UmbracoUrl = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(model.ReturnUrl))
|
||||||
|
{
|
||||||
|
model.ReturnUrl = model.UmbracoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
return View("/umbraco/UmbracoLogin/Index.cshtml", model);
|
return View("/umbraco/UmbracoLogin/Index.cshtml", model);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<BasePath>$(ProjectDir)wwwroot\umbraco</BasePath>
|
<BasePath>$(ProjectDir)wwwroot\umbraco</BasePath>
|
||||||
<BellePath>$(BasePath)\lib</BellePath>
|
<BellePath>$(BasePath)\lib</BellePath>
|
||||||
<BackofficePath>$(BasePath)\backoffice</BackofficePath>
|
<BackofficePath>$(BasePath)\backoffice</BackofficePath>
|
||||||
|
<LoginPath>$(BasePath)\login</LoginPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="BuildBellePreconditions" BeforeTargets="Build">
|
<Target Name="BuildBellePreconditions" BeforeTargets="Build">
|
||||||
@@ -32,6 +33,11 @@
|
|||||||
<Message Text="Skip BuildBackOffice target because '$(BackofficePath)' already exists" Importance="high" Condition="Exists('$(BackofficePath)')" />
|
<Message Text="Skip BuildBackOffice target because '$(BackofficePath)' already exists" Importance="high" Condition="Exists('$(BackofficePath)')" />
|
||||||
<Message Text="Call BuildBackOffice target because UmbracoBuild is empty (this is Visual Studio) and '$(BackofficePath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficePath)')" />
|
<Message Text="Call BuildBackOffice target because UmbracoBuild is empty (this is Visual Studio) and '$(BackofficePath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficePath)')" />
|
||||||
<CallTarget Targets="BuildBackOffice" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficePath)')" />
|
<CallTarget Targets="BuildBackOffice" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficePath)')" />
|
||||||
|
|
||||||
|
<Message Text="Skip BuildLogin target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||||
|
<Message Text="Skip BuildLogin target because '$(LoginPath)' already exists" Importance="high" Condition="Exists('$(LoginPath)')" />
|
||||||
|
<Message Text="Call BuildLogin target because UmbracoBuild is empty (this is Visual Studio) and '$(LoginPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginPath)')" />
|
||||||
|
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginPath)')" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Name="BuildBelle">
|
<Target Name="BuildBelle">
|
||||||
@@ -44,6 +50,10 @@
|
|||||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.New.Client\" Command="npm run build:for:cms" />
|
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.New.Client\" Command="npm run build:for:cms" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="BuildLogin" DependsOnTargets="BuildBackOffice">
|
||||||
|
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.New.Client\apps\auth" Command="npm run build" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
<Target Name="CleanBellePreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
<Target Name="CleanBellePreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||||
<Message Text="Skip CleanBelle target because '$(BellePath)' doesn't exist" Importance="high" Condition="!Exists('$(BellePath)')" />
|
<Message Text="Skip CleanBelle target because '$(BellePath)' doesn't exist" Importance="high" Condition="!Exists('$(BellePath)')" />
|
||||||
<Message Text="Skip CleanBelle target because preserve.belle marker file exists" Importance="high" Condition="Exists('$(BellePath)') and Exists('$(SolutionDir)preserve.belle')" />
|
<Message Text="Skip CleanBelle target because preserve.belle marker file exists" Importance="high" Condition="Exists('$(BellePath)') and Exists('$(SolutionDir)preserve.belle')" />
|
||||||
@@ -55,6 +65,11 @@
|
|||||||
<Message Text="Call CleanBackoffice target because '$(BackofficePath)' exists and preserve.belle marker file doesn't exist" Importance="high" Condition="Exists('$(BackofficePath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
<Message Text="Call CleanBackoffice target because '$(BackofficePath)' exists and preserve.belle marker file doesn't exist" Importance="high" Condition="Exists('$(BackofficePath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
||||||
<CallTarget Targets="CleanBackoffice" Condition="Exists('$(BackofficePath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
<CallTarget Targets="CleanBackoffice" Condition="Exists('$(BackofficePath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
||||||
|
|
||||||
|
<Message Text="Skip CleanLogin target because '$(LoginPath)' doesn't exist" Importance="high" Condition="!Exists('$(LoginPath)')" />
|
||||||
|
<Message Text="Skip CleanLogin target because preserve.belle marker file exists" Importance="high" Condition="Exists('$(LoginPath)') and Exists('$(SolutionDir)preserve.belle')" />
|
||||||
|
<Message Text="Call CleanLogin target because '$(LoginPath)' exists and preserve.belle marker file doesn't exist" Importance="high" Condition="Exists('$(LoginPath)') and !Exists('$(SolutionDir)preserve.belle')" />
|
||||||
|
<CallTarget Targets="CleanLogin" Condition="Exists('$(LoginPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||||
|
|
||||||
|
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
@@ -71,4 +86,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<RemoveDir Directories="@(BackofficeDirectories)" />
|
<RemoveDir Directories="@(BackofficeDirectories)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="CleanLogin">
|
||||||
|
<ItemGroup>
|
||||||
|
<LoginDirectories Include="$(LoginPath);" />
|
||||||
|
</ItemGroup>
|
||||||
|
<RemoveDir Directories="@(LoginDirectories)" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,85 +1,19 @@
|
|||||||
@model Umbraco.Cms.Api.Management.BackOfficeLoginModel
|
@model Umbraco.Cms.Api.Management.BackOfficeLoginModel
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Umbraco</title>
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="~/umbraco/login/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Umbraco</title>
|
||||||
|
<script type="module" src="~/umbraco/login/main.js" asp-append-version="true"></script>
|
||||||
|
<link rel="stylesheet" href="~/umbraco/login/style.css" asp-append-version="true" />
|
||||||
|
<base href="@Model.UmbracoUrl/login/" />
|
||||||
|
</head>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/blocks.css/dist/blocks.min.css"/>
|
<body class="uui-font uui-text" style="margin: 0; padding: 0; overflow: hidden">
|
||||||
|
<umb-login return-url="@Model.ReturnUrl"></umb-login>
|
||||||
<style>
|
</body>
|
||||||
body {
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="card fixed block">
|
|
||||||
<h1>Umbraco login universe</h1>
|
|
||||||
<p>We have not yet implemented the new login page, so for your pleasure,<br/>we present you a blocky experience that just works™</p>
|
|
||||||
<form id="loginform" method="post">
|
|
||||||
<div>
|
|
||||||
<label>Username: <input class="round block" type="text" name="username" autocomplete="username"></label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Password: <input class="round block" type="password" name="password" autocomplete="current-password"></label>
|
|
||||||
</div>
|
|
||||||
<button id="login" class="accent block">LOGIN</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
!(function () {
|
|
||||||
const authUrlLogin = "@Model.AuthUrl/login"
|
|
||||||
const returnUrl = "@Model.ReturnUrl".replaceAll("&", "&")
|
|
||||||
|
|
||||||
console.log('urls', {authUrlLogin, returnUrl})
|
|
||||||
|
|
||||||
const form = document.getElementById("loginform");
|
|
||||||
|
|
||||||
form.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const formData = new FormData(e.target)
|
|
||||||
const userName = formData.get("username")
|
|
||||||
const password = formData.get("password")
|
|
||||||
console.log('username', userName)
|
|
||||||
|
|
||||||
const res = await fetch(authUrlLogin, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
userName,
|
|
||||||
password
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const content = await res.text()
|
|
||||||
console.log('login success', content)
|
|
||||||
|
|
||||||
if (returnUrl) {
|
|
||||||
location.href = returnUrl
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
alert('Could not login: ' + err.message)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user