Merge branch 'main' into feature/document-user-permission
@@ -27,10 +27,18 @@ Third-party licenses
|
||||
---
|
||||
|
||||
Lucide License
|
||||
ISC License
|
||||
ISC License <https://lucide.dev/license>
|
||||
|
||||
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
Simple Icons
|
||||
CC0 1.0 Universal license <https://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
|
||||
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('login', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Fill input[name="email"]
|
||||
await page.locator('input[name="email"]').fill('test@umbraco.com');
|
||||
|
||||
// Fill input[name="password"]
|
||||
await page.locator('input[name="password"]').fill('test123456');
|
||||
|
||||
// Wait for console.log to be called
|
||||
page.on('console', (message) => {
|
||||
expect(message.text()).toBe('login');
|
||||
});
|
||||
|
||||
// Click [aria-label="Login"]
|
||||
await page.locator('[aria-label="Login"]').click();
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Umbraco Auth</title>
|
||||
<script type="module" src="/src/index.ts"></script>
|
||||
<base href="/" />
|
||||
</head>
|
||||
|
||||
<body class="uui-font uui-text" style="margin: 0; padding: 0; overflow: hidden">
|
||||
<umb-login></umb-login>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "@umbraco-cms/login",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Umbraco HQ",
|
||||
"email": "backoffice@umbraco.com"
|
||||
},
|
||||
"type": "module",
|
||||
"module": "dist/main.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview"
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: './e2e',
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 30 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000,
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
...devices['Desktop Firefox'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: {
|
||||
...devices['Desktop Safari'],
|
||||
},
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: {
|
||||
// ...devices['Pixel 5'],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: {
|
||||
// ...devices['iPhone 12'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: {
|
||||
// channel: 'msedge',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: {
|
||||
// channel: 'chrome',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
// outputDir: 'test-results/',
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
port: 5173,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#3544B1;}
|
||||
.st1{fill:#3544B1;}
|
||||
</style>
|
||||
<path class="st1" d="M0,20C0,8.9,9,0,20,0s20,9,20,20s-9,20-20,20C8.9,40,0,31,0,20L0,20z M19.6,26.8c-1.6,0-3.1-0.1-4.6-0.4
|
||||
c-1.1-0.2-2.1-1-2.5-2c-0.5-1-0.7-2.6-0.7-4.8c0-1.1,0.1-2.3,0.2-3.4c0.1-1.1,0.3-2,0.4-2.7l0.1-0.7c0,0,0,0,0-0.1
|
||||
c0-0.2-0.1-0.4-0.3-0.4l-2.6-0.4H9.6c-0.2,0-0.4,0.1-0.4,0.3c0,0.2-0.1,0.3-0.1,0.7c-0.1,0.8-0.3,1.5-0.4,2.6
|
||||
c-0.2,1.2-0.3,2.4-0.3,3.5c-0.1,0.8-0.1,1.6,0,2.5c0.1,2.2,0.4,3.9,1.1,5.2c0.7,1.3,1.9,2.2,3.5,2.8c1.6,0.6,3.9,0.9,6.9,0.8h0.4
|
||||
c2.9,0,5.2-0.3,6.9-0.8c1.6-0.6,2.8-1.5,3.5-2.8c0.7-1.3,1.1-3.1,1.1-5.2c0.1-0.8,0.1-1.6,0-2.5c0-1.2-0.1-2.4-0.3-3.5
|
||||
c-0.1-1.1-0.3-1.8-0.4-2.6c-0.1-0.4-0.1-0.5-0.1-0.7c0-0.2-0.2-0.3-0.4-0.3h-0.1l-2.6,0.4c-0.2,0-0.3,0.2-0.3,0.4c0,0,0,0,0,0.1
|
||||
l0.1,0.7c0.1,0.7,0.3,1.6,0.4,2.7c0.1,1.1,0.2,2.3,0.2,3.4c0,2.2-0.2,3.8-0.7,4.8c-0.5,1-1.4,1.8-2.5,2c-1.5,0.3-3.1,0.5-4.6,0.4
|
||||
L19.6,26.8z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 30 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 316"><path d="M.2 157.8C.3 70.6 71.1-.1 158.3.1S316.2 71 316.1 158.2s-70.8 157.7-157.9 157.7C70.8 315.9.1 245.1.2 157.8zm154.7 54.1c-12.3.4-24.5-.7-36.5-3.3-8.8-1.8-16.3-7.8-19.9-16-3.6-8.2-5.3-20.9-5.2-38.1.1-9 .6-17.9 1.7-26.8 1-8.7 2.1-15.8 3.1-21.5l1.1-5.6v-.5c0-1.6-1.1-2.9-2.6-3.2l-20.4-3.2h-.4c-1.5 0-2.8 1-3.1 2.5-.3 1.3-.6 2.3-1.2 5.4-1.2 6-2.2 11.8-3.4 20.4-1.3 9.3-2 18.6-2.3 27.9-.4 6.5-.4 13 0 19.6.5 17.3 3.4 31.1 8.9 41.4 5.5 10.3 14.7 17.8 27.7 22.3s31.2 6.8 54.4 6.7h2.9c23.3.1 41.4-2.1 54.4-6.7 13-4.5 22.2-12 27.7-22.3s8.4-24.1 8.9-41.4c.4-6.5.4-13 0-19.6-.3-9.3-1-18.7-2.3-27.9-1.2-8.4-2.3-14.3-3.4-20.4-.6-3.1-.8-4.1-1.2-5.4-.3-1.4-1.6-2.5-3.1-2.5h-.5l-20.4 3.2c-1.6.3-2.7 1.6-2.7 3.2v.5l1.1 5.6c1 5.6 2.1 12.8 3.1 21.5 1 8.9 1.6 17.9 1.7 26.8.2 17.1-1.6 29.8-5.2 38.1-3.6 8.2-11 14.2-19.8 16.1-12 2.5-24.2 3.6-36.5 3.3l-6.6-.1zm932.3-43.9c0-30.4 8.6-51.7 43.8-51.7s43.8 21.3 43.8 51.7-8.6 51.7-43.8 51.7-43.8-21.3-43.8-51.7zm65.3 0c0-21.1-2.7-33.1-21.5-33.1s-21.5 12-21.5 33.1 2.8 33.1 21.5 33.1c18.8 0 21.5-12 21.5-33.1zm-672.1 47.8c.5.9 1.5 1.5 2.5 1.4h8.2c1.6 0 2.9-1.3 2.9-2.9v-92.7c0-1.6-1.3-2.9-2.9-2.9h-16.3c-1.6 0-2.9 1.3-2.9 2.9v73.6c-7 3.9-14.9 5.9-22.8 5.8-10.4 0-15.6-4.5-15.6-14.6v-64.8c0-1.6-1.3-2.9-2.9-2.9h-16.4c-1.6 0-2.9 1.3-2.9 2.9v66.7c0 18.9 8.9 31.3 33.9 31.3 11.4-.1 22.6-3.7 32-10.2l2.9 6.5.3-.1zm184.1-68.1c0-18.7-9.3-31.4-32.6-31.4-11.3 0-22.3 3.4-31.6 9.8-4.1-6.1-12-9.8-25.3-9.8-10.7.2-21.1 3.8-29.7 10.2l-2.9-6.5c-.5-.9-1.5-1.5-2.5-1.4h-8.3c-1.6 0-2.9 1.3-2.9 2.9v92.8c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9v-73.5c6.2-3.8 13.4-5.8 20.7-5.8 8.9 0 14 3.3 14 12.6v66.7c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9v-73.6c6.2-3.9 13.4-5.9 20.7-5.8 8.6 0 14 3.3 14 12.6v66.7c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9l.1-66.5zm50.4 61.7c9.3 6.9 20.5 10.5 32 10.2 28.8 0 39.4-19.3 39.4-51.7s-10.7-51.7-39.4-51.7c-9.4 0-18.5 2.7-26.4 7.7V94.2c0-1.6-1.2-2.9-2.8-3h-16.6c-1.6 0-2.9 1.3-2.9 2.9v120.3c0 1.6 1.3 2.9 2.9 2.9h8.2c1 0 2-.5 2.5-1.4l3.1-6.5zm26.8-8.5c-7.5 0-14.8-2-21.3-5.8v-54.4c6.5-3.8 13.8-5.8 21.3-5.8 19.3 0 22.3 14.8 22.3 32.9s-2.8 33.1-22.3 33.1zM868 135.7c-2.5-.3-5.1-.5-7.7-.5-8.8-.4-17.5 1.7-25.3 5.9v73.2c0 1.6-1.3 2.9-2.9 2.9h-16.3c-1.6 0-2.9-1.3-2.9-2.9v-92.7c0-1.6 1.3-2.9 2.9-2.9h8.2c1 0 2 .5 2.5 1.4l2.9 6.5c8.9-6.8 19.9-10.4 31.2-10.2 2.6 0 5.2.2 7.7.6 1.4 0 2.7 2.4 2.7 4v11.8c0 1.6-1.3 2.9-2.9 2.9h-.2m56.7 36.1c-9.8 1.2-15.6 4.9-15.6 15.2 0 7.5 3.3 14.6 15.2 14.6 7.5.1 14.9-2.2 21.1-6.5v-25.5l-20.7 2.2zm26.1 37.6c-8.5 6.7-19 10.3-29.8 10.2-25.5 0-33.9-15.8-33.9-31.6 0-21.3 13.8-30.4 36.1-32.1l22.1-1.8v-4.9c0-10.1-4.7-14-19.3-14-9.2 0-18.3 1.5-26.9 4.5h-.9c-1.6 0-2.9-1.3-2.9-2.9v-13.1c0-1.2.7-2.4 1.9-2.8 9.8-3.3 20.1-5 30.5-4.9 32.3 0 39.8 14.2 39.8 35.1V214c0 1.6-1.3 2.9-2.9 2.9h-8.2c-1 0-2-.5-2.5-1.4l-3.1-6.1zM1063 197h.9c1.6 0 2.9 1.3 2.9 2.9V213c0 1.2-.7 2.3-1.8 2.7-8.1 2.9-16.7 4.3-25.4 4.1-34.9 0-45.7-20.9-45.7-51.7s10.7-51.7 45.7-51.7c8.6-.2 17.1 1.1 25.2 4 1.1.4 1.9 1.5 1.8 2.7v13.1c0 1.6-1.3 2.9-2.9 2.9h-.9c-7.1-2.2-14.6-3.3-22-3.1-19.1 0-24.7 13.1-24.7 32.2s5.5 32.1 24.7 32.1c7.5.1 14.9-1 22-3.3" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 315.89 315.89"><path fill="#fff" d="M0 157.74a157.95 157.95 0 11158 158.15A157.95 157.95 0 010 157.74zm154.74 54.09a155.41 155.41 0 01-36.5-3.29 27.92 27.92 0 01-19.94-16q-5.35-12.34-5.21-38.1a243 243 0 011.69-26.84q1.55-13 3.09-21.46l1.07-5.59a2 2 0 000-.49 3.2 3.2 0 00-2.65-3.17l-20.37-3.22h-.44a3.19 3.19 0 00-3.11 2.48c-.35 1.31-.56 2.27-1.17 5.38-1.16 6-2.24 11.85-3.43 20.38a264.17 264.17 0 00-2.3 27.94 145.24 145.24 0 000 19.57q.72 25.94 8.9 41.42t27.72 22.3q19.53 6.81 54.43 6.66h2.91q34.94.15 54.41-6.66t27.71-22.3q8.17-15.53 8.91-41.42a145.24 145.24 0 000-19.57 266.84 266.84 0 00-2.3-27.94c-1.2-8.44-2.27-14.26-3.44-20.38-.61-3.11-.81-4.07-1.16-5.38a3.21 3.21 0 00-3.12-2.48h-.52l-20.38 3.18a3.2 3.2 0 00-2.68 3.17 4 4 0 000 .49l1.08 5.59q1.55 8.48 3.12 21.46a245.68 245.68 0 011.65 26.84q.27 25.69-5.21 38.07a27.9 27.9 0 01-19.76 16.07 155.19 155.19 0 01-36.48 3.29z"/></svg>
|
||||
|
Before Width: | Height: | Size: 942 B |
@@ -1,72 +0,0 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-auth-layout')
|
||||
export class UmbAuthLayoutElement extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
<div id="background"></div>
|
||||
|
||||
<div id="logo">
|
||||
<img src="umbraco_logomark_white.svg" alt="Umbraco" />
|
||||
</div>
|
||||
|
||||
<div id="container">
|
||||
<uui-box id="box">
|
||||
<slot></slot>
|
||||
</uui-box>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
css`
|
||||
#background {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-image: url('login.jpeg');
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#logo {
|
||||
position: fixed;
|
||||
top: var(--uui-size-space-5);
|
||||
left: var(--uui-size-space-5);
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#logo img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#box {
|
||||
width: 500px;
|
||||
padding: var(--uui-size-space-6) var(--uui-size-space-5) var(--uui-size-space-5) var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
#email,
|
||||
#password {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-auth-layout': UmbAuthLayoutElement;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { LoginRequestModel, IUmbAuthContext, LoginResponse } from './types.js';
|
||||
|
||||
export class UmbAuthLegacyContext implements IUmbAuthContext {
|
||||
readonly #AUTH_URL = '/umbraco/backoffice/umbracoapi/authentication/postlogin';
|
||||
readonly supportsPersistLogin = true;
|
||||
|
||||
login(data: LoginRequestModel): Promise<LoginResponse> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { UmbAuthRepository } from './auth.repository.js';
|
||||
import { LoginRequestModel, IUmbAuthContext } from './types.js';
|
||||
|
||||
export class UmbAuthContext implements IUmbAuthContext {
|
||||
readonly #AUTH_URL = '/umbraco/management/api/v1/security/back-office';
|
||||
readonly supportsPersistLogin = false;
|
||||
|
||||
#authRepository = new UmbAuthRepository(this.#AUTH_URL);
|
||||
|
||||
public async login(data: LoginRequestModel) {
|
||||
return this.#authRepository.login(data);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { LoginRequestModel } from './types.js';
|
||||
|
||||
export class UmbAuthRepository {
|
||||
#authURL = '';
|
||||
|
||||
constructor(authUrl: string) {
|
||||
this.#authURL = authUrl;
|
||||
}
|
||||
|
||||
public async login(data: LoginRequestModel) {
|
||||
const request = new Request(this.#authURL + '/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
//TODO: What kind of data does the old backoffice expect?
|
||||
//NOTE: this conditionally adds error and data to the response object
|
||||
return {
|
||||
status: response.status,
|
||||
...(!response.ok && { error: this.#getErrorText(response) }),
|
||||
...(response.ok && { data: 'WHAT DATA SHOULD BE RETURNED?' }),
|
||||
};
|
||||
}
|
||||
|
||||
#getErrorText(response: Response) {
|
||||
switch (response.status) {
|
||||
case 401:
|
||||
return 'Oops! It seems like your login credentials are invalid or expired. Please double-check your username and password and try again.';
|
||||
|
||||
case 500:
|
||||
return "We're sorry, but the server encountered an unexpected error. Please refresh the page or try again later..";
|
||||
|
||||
default:
|
||||
return response.statusText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-external-login-provider-test')
|
||||
export class UmbExternalLoginProviderTestElement extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
<b>Custom External Login Provider</b>
|
||||
<p>This is an example of a custom external login provider using the external login provider extension point</p>
|
||||
<uui-button label="My custom login provider" look="primary"></uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
padding: var(--uui-size-space-5);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
background: var(--uui-color-surface-alt);
|
||||
border-radius: var(--uui-border-radius);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbExternalLoginProviderTestElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-external-login-provider-test': UmbExternalLoginProviderTestElement;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-external-login-provider-test2')
|
||||
export class UmbExternalLoginProviderTest2Element extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
<b>Another Custom External Login Provider</b>
|
||||
<p>This is an example of another custom external login provider</p>
|
||||
<uui-form-layout-item>
|
||||
<uui-label id="emailLabel" for="email" slot="label" required>Email</uui-label>
|
||||
<uui-input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Enter your email..."
|
||||
required
|
||||
required-message="Email is required"></uui-input>
|
||||
</uui-form-layout-item>
|
||||
<uui-button label="Custom login" look="primary"></uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
padding: var(--uui-size-space-5);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
background: var(--uui-color-surface-alt);
|
||||
border-radius: var(--uui-border-radius);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbExternalLoginProviderTest2Element;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-external-login-provider-test2': UmbExternalLoginProviderTest2Element;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// TODO: could these be renamed as login providers?
|
||||
import type { ManifestExternalLoginProvider } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestExternalLoginProvider> = [
|
||||
{
|
||||
type: 'externalLoginProvider',
|
||||
alias: 'Umb.ExternalLoginProvider.Test',
|
||||
name: 'Test External Login Provider',
|
||||
elementName: 'umb-external-login-provider-test',
|
||||
element: () => import('./external-login-provider-test.element.js'),
|
||||
weight: 2,
|
||||
meta: {
|
||||
label: 'Test External Login Provider',
|
||||
pathname: 'test/test/test',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'externalLoginProvider',
|
||||
alias: 'Umb.ExternalLoginProvider.Test2',
|
||||
name: 'Test External Login Provider 2',
|
||||
elementName: 'umb-external-login-provider-test2',
|
||||
element: () => import('./external-login-provider-test2.element.js'),
|
||||
weight: 1,
|
||||
meta: {
|
||||
label: 'Test External Login Provider 2',
|
||||
pathname: 'test/test/test',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,7 +0,0 @@
|
||||
import 'element-internals-polyfill';
|
||||
|
||||
import '@umbraco-ui/uui-css/dist/uui-css.css';
|
||||
import '../../../src/css/umb-css.css';
|
||||
import '@umbraco-ui/uui';
|
||||
|
||||
import './login.element.js';
|
||||
@@ -1,148 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
|
||||
import type { UUIButtonState } from '@umbraco-ui/uui';
|
||||
import type { IUmbAuthContext } from './types.js';
|
||||
import { UmbAuthLegacyContext } from './auth-legacy.context.js';
|
||||
import { UmbAuthContext } from './auth.context.js';
|
||||
|
||||
import './auth-layout.element.js';
|
||||
|
||||
@customElement('umb-login')
|
||||
export default class UmbLoginElement extends LitElement {
|
||||
#authContext: IUmbAuthContext;
|
||||
|
||||
@property({ type: String, attribute: 'return-url' })
|
||||
returnUrl = '';
|
||||
|
||||
@property({ type: Boolean })
|
||||
isLegacy = false;
|
||||
|
||||
@state()
|
||||
private _loginState: UUIButtonState = undefined;
|
||||
|
||||
@state()
|
||||
private _loginError = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (this.isLegacy) {
|
||||
this.#authContext = new UmbAuthLegacyContext();
|
||||
} else {
|
||||
this.#authContext = new UmbAuthContext();
|
||||
}
|
||||
}
|
||||
|
||||
#handleSubmit = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const form = e.target as HTMLFormElement;
|
||||
if (!form) return;
|
||||
|
||||
if (!form.checkValidity()) return;
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
const username = formData.get('email') as string;
|
||||
const password = formData.get('password') as string;
|
||||
const persist = formData.has('persist');
|
||||
|
||||
this._loginState = 'waiting';
|
||||
|
||||
const response = await this.#authContext.login({ username, password, persist });
|
||||
|
||||
this._loginError = response.error || '';
|
||||
this._loginState = response.error ? 'failed' : 'success';
|
||||
|
||||
if (response.error) return;
|
||||
|
||||
location.href = this.returnUrl;
|
||||
};
|
||||
|
||||
get #greeting() {
|
||||
return [
|
||||
'Happy super Sunday',
|
||||
'Happy marvelous Monday',
|
||||
'Happy tubular Tuesday',
|
||||
'Happy wonderful Wednesday',
|
||||
'Happy thunderous Thursday',
|
||||
'Happy funky Friday',
|
||||
'Happy Saturday',
|
||||
][new Date().getDay()];
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-auth-layout>
|
||||
<div class="uui-text">
|
||||
<h1 class="uui-h3">${this.#greeting}</h1>
|
||||
<uui-form>
|
||||
<form id="LoginForm" name="login" @submit="${this.#handleSubmit}">
|
||||
<uui-form-layout-item>
|
||||
<uui-label id="emailLabel" for="email" slot="label" required>Email</uui-label>
|
||||
<uui-input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
label="Email"
|
||||
required
|
||||
required-message="Email is required"></uui-input>
|
||||
</uui-form-layout-item>
|
||||
|
||||
<uui-form-layout-item>
|
||||
<uui-label id="passwordLabel" for="password" slot="label" required>Password</uui-label>
|
||||
<uui-input-password
|
||||
id="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
required
|
||||
required-message="Password is required"></uui-input-password>
|
||||
</uui-form-layout-item>
|
||||
|
||||
${this.#authContext.supportsPersistLogin
|
||||
? html`<uui-form-layout-item>
|
||||
<uui-checkbox name="persist" label="Remember me">Remember me</uui-checkbox>
|
||||
</uui-form-layout-item>`
|
||||
: nothing}
|
||||
|
||||
<uui-form-layout-item>${this.#renderErrorMessage()}</uui-form-layout-item>
|
||||
|
||||
<uui-button
|
||||
type="submit"
|
||||
label="Login"
|
||||
look="primary"
|
||||
color="positive"
|
||||
state=${this._loginState}></uui-button>
|
||||
</form>
|
||||
</uui-form>
|
||||
</div>
|
||||
</umb-auth-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderErrorMessage() {
|
||||
if (!this._loginError || this._loginState !== 'failed') return nothing;
|
||||
|
||||
return html`<p class="text-danger">${this._loginError}</p>`;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#email,
|
||||
#password {
|
||||
width: 100%;
|
||||
}
|
||||
.text-danger {
|
||||
color: var(--uui-color-danger-standalone);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-login': UmbLoginElement;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils';
|
||||
import UmbLoginElement from './login.element.js';
|
||||
|
||||
describe('UmbLogin', () => {
|
||||
let element: UmbLoginElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html`<umb-login></umb-login>`);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbLoginElement);
|
||||
});
|
||||
|
||||
if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) {
|
||||
it('passes the a11y audit', async () => {
|
||||
await expect(element).to.be.accessible(defaultA11yConfig);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
export type LoginRequestModel = {
|
||||
username: string;
|
||||
password: string;
|
||||
persist: boolean;
|
||||
};
|
||||
|
||||
export interface IUmbAuthContext {
|
||||
login(data: LoginRequestModel): Promise<LoginResponse>;
|
||||
supportsPersistLogin: boolean;
|
||||
}
|
||||
|
||||
export type LoginResponse = {
|
||||
data?: string;
|
||||
error?: string;
|
||||
status: number;
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
/*
|
||||
interface ImportMetaEnv {
|
||||
}
|
||||
*/
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import viteTSConfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
formats: ['es'],
|
||||
fileName: 'main',
|
||||
},
|
||||
target: 'esnext',
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
external: [/^@umbraco-cms\/backoffice\//],
|
||||
output: {
|
||||
manualChunks: {
|
||||
uui: ['@umbraco-ui/uui'],
|
||||
},
|
||||
},
|
||||
},
|
||||
outDir: '../../../Umbraco.Cms.StaticAssets/wwwroot/umbraco/auth',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
// Allow serving files from the global node_modules folder
|
||||
allow: ['.', '../../node_modules'],
|
||||
},
|
||||
},
|
||||
plugins: [viteTSConfigPaths()],
|
||||
});
|
||||
@@ -6,12 +6,13 @@ const path = pathModule.default;
|
||||
const getDirName = path.dirname;
|
||||
const glob = globModule.default;
|
||||
|
||||
const moduleDirectory = 'src/shared/icon-registry';
|
||||
const moduleDirectory = 'src/packages/core/icon-registry';
|
||||
const iconsOutputDirectory = `${moduleDirectory}/icons`;
|
||||
const umbracoSvgDirectory = `${moduleDirectory}/svgs`;
|
||||
const iconMapJson = `${moduleDirectory}/icon-dictionary.json`;
|
||||
|
||||
const lucideSvgDirectory = 'node_modules/lucide-static/icons';
|
||||
const simpleIconsSvgDirectory = 'node_modules/simple-icons/icons';
|
||||
|
||||
const run = async () => {
|
||||
var icons = await collectDictionaryIcons();
|
||||
@@ -30,7 +31,7 @@ const collectDictionaryIcons = async () => {
|
||||
|
||||
// Lucide:
|
||||
fileJSON.lucide.forEach((iconDef) => {
|
||||
if(iconDef.file && iconDef.name) {
|
||||
if (iconDef.file && iconDef.name) {
|
||||
const path = lucideSvgDirectory + "/" + iconDef.file;
|
||||
|
||||
try {
|
||||
@@ -50,15 +51,46 @@ const collectDictionaryIcons = async () => {
|
||||
};
|
||||
|
||||
icons.push(icon);
|
||||
} catch(e) {
|
||||
console.log(`Could not load file: '${path}'`);
|
||||
} catch (e) {
|
||||
console.log(`[Lucide] Could not load file: '${path}'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// SimpleIcons:
|
||||
fileJSON.simpleIcons.forEach((iconDef) => {
|
||||
if (iconDef.file && iconDef.name) {
|
||||
const path = simpleIconsSvgDirectory + "/" + iconDef.file;
|
||||
|
||||
try {
|
||||
const rawData = readFileSync(path);
|
||||
let svg = rawData.toString()
|
||||
const iconFileName = iconDef.name;
|
||||
|
||||
// SimpleIcons need to use fill="currentColor"
|
||||
const pattern = /fill=/g;
|
||||
if (!pattern.test(svg)) {
|
||||
svg = svg.replace(/<path/g, '<path fill="currentColor"');
|
||||
}
|
||||
|
||||
const icon = {
|
||||
name: iconDef.name,
|
||||
legacy: iconDef.legacy,
|
||||
fileName: iconFileName,
|
||||
svg,
|
||||
output: `${iconsOutputDirectory}/${iconFileName}.js`,
|
||||
};
|
||||
|
||||
icons.push(icon);
|
||||
} catch (e) {
|
||||
console.log(`[SimpleIcons] Could not load file: '${path}'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Umbraco:
|
||||
fileJSON.umbraco.forEach((iconDef) => {
|
||||
if(iconDef.file && iconDef.name) {
|
||||
if (iconDef.file && iconDef.name) {
|
||||
const path = umbracoSvgDirectory + "/" + iconDef.file;
|
||||
|
||||
try {
|
||||
@@ -75,8 +107,8 @@ const collectDictionaryIcons = async () => {
|
||||
};
|
||||
|
||||
icons.push(icon);
|
||||
} catch(e) {
|
||||
console.log(`Could not load file: '${path}'`);
|
||||
} catch (e) {
|
||||
console.log(`[Umbraco] Could not load file: '${path}'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -104,7 +136,7 @@ const collectDiskIcons = async (icons) => {
|
||||
const iconName = iconFileName;
|
||||
|
||||
// Only append not already defined icons:
|
||||
if(!icons.find(x => x.name === iconName)) {
|
||||
if (!icons.find(x => x.name === iconName)) {
|
||||
|
||||
const icon = {
|
||||
name: iconName,
|
||||
|
||||
610
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -62,6 +62,7 @@
|
||||
"./property-action": "./dist-cms/packages/core/property-action/index.js",
|
||||
"./property-editor": "./dist-cms/packages/core/property-editor/index.js",
|
||||
"./property": "./dist-cms/packages/core/property/index.js",
|
||||
"./recycle-bin": "./dist-cms/packages/core/recycle-bin/index.js",
|
||||
"./relation-type": "./dist-cms/packages/relations/relation-types/index.js",
|
||||
"./relations": "./dist-cms/packages/relations/relations/index.js",
|
||||
"./repository": "./dist-cms/packages/core/repository/index.js",
|
||||
@@ -89,6 +90,7 @@
|
||||
"./webhook": "./dist-cms/packages/webhook/index.js",
|
||||
"./workspace": "./dist-cms/packages/core/workspace/index.js",
|
||||
"./external/backend-api": "./dist-cms/external/backend-api/index.js",
|
||||
"./external/base64-js": "./dist-cms/external/base64-js/index.js",
|
||||
"./external/dompurify": "./dist-cms/external/dompurify/index.js",
|
||||
"./external/lit": "./dist-cms/external/lit/index.js",
|
||||
"./external/marked": "./dist-cms/external/marked/index.js",
|
||||
@@ -118,7 +120,6 @@
|
||||
"url": "https://umbraco.com"
|
||||
},
|
||||
"scripts": {
|
||||
"auth:test:e2e": "npx playwright test --config apps/auth/",
|
||||
"backoffice:test:e2e": "npx playwright test",
|
||||
"build-storybook": "npm run wc-analyze && storybook build",
|
||||
"build:for:cms": "npm run build && node ./devops/build/copy-to-cms.js",
|
||||
@@ -147,7 +148,7 @@
|
||||
"preview": "vite preview --open",
|
||||
"storybook:build": "npm run wc-analyze && storybook build",
|
||||
"storybook": "npm run wc-analyze && storybook dev -p 6006",
|
||||
"test:e2e": "npm run auth:test:e2e && npm run backoffice:test:e2e",
|
||||
"test:e2e": "npm run backoffice:test:e2e",
|
||||
"test:dev": "web-test-runner --config ./web-test-runner.dev.config.mjs",
|
||||
"test:dev-watch": "web-test-runner --watch --config ./web-test-runner.dev.config.mjs",
|
||||
"test:watch": "web-test-runner --watch",
|
||||
@@ -164,11 +165,11 @@
|
||||
"npm": ">=10.1 < 11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openid/appauth": "^1.3.1",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@umbraco-ui/uui": "1.7.2",
|
||||
"@umbraco-ui/uui-css": "1.7.2",
|
||||
"base64-js": "^1.5.1",
|
||||
"dompurify": "^3.0.9",
|
||||
"element-internals-polyfill": "^1.3.10",
|
||||
"lit": "^3.1.2",
|
||||
@@ -199,10 +200,10 @@
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"@web/dev-server-esbuild": "^1.0.1",
|
||||
"@web/dev-server-esbuild": "^1.0.2",
|
||||
"@web/dev-server-import-maps": "^0.2.0",
|
||||
"@web/dev-server-rollup": "^0.6.1",
|
||||
"@web/test-runner": "^0.18.0",
|
||||
"@web/test-runner": "^0.18.1",
|
||||
"@web/test-runner-playwright": "^0.11.0",
|
||||
"babel-loader": "^9.1.3",
|
||||
"eslint": "^8.56.0",
|
||||
@@ -223,10 +224,11 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rollup": "^4.12.0",
|
||||
"rollup-plugin-esbuild": "^6.1.0",
|
||||
"rollup-plugin-import-css": "^3.4.0",
|
||||
"rollup": "^4.14.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-import-css": "^3.5.0",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"simple-icons": "^11.11.0",
|
||||
"storybook": "^7.6.17",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"tsc-alias": "^1.8.8",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 316"><path d="M.2 157.8C.3 70.6 71.1-.1 158.3.1S316.2 71 316.1 158.2s-70.8 157.7-157.9 157.7C70.8 315.9.1 245.1.2 157.8zm154.7 54.1c-12.3.4-24.5-.7-36.5-3.3-8.8-1.8-16.3-7.8-19.9-16-3.6-8.2-5.3-20.9-5.2-38.1.1-9 .6-17.9 1.7-26.8 1-8.7 2.1-15.8 3.1-21.5l1.1-5.6v-.5c0-1.6-1.1-2.9-2.6-3.2l-20.4-3.2h-.4c-1.5 0-2.8 1-3.1 2.5-.3 1.3-.6 2.3-1.2 5.4-1.2 6-2.2 11.8-3.4 20.4-1.3 9.3-2 18.6-2.3 27.9-.4 6.5-.4 13 0 19.6.5 17.3 3.4 31.1 8.9 41.4 5.5 10.3 14.7 17.8 27.7 22.3s31.2 6.8 54.4 6.7h2.9c23.3.1 41.4-2.1 54.4-6.7 13-4.5 22.2-12 27.7-22.3s8.4-24.1 8.9-41.4c.4-6.5.4-13 0-19.6-.3-9.3-1-18.7-2.3-27.9-1.2-8.4-2.3-14.3-3.4-20.4-.6-3.1-.8-4.1-1.2-5.4-.3-1.4-1.6-2.5-3.1-2.5h-.5l-20.4 3.2c-1.6.3-2.7 1.6-2.7 3.2v.5l1.1 5.6c1 5.6 2.1 12.8 3.1 21.5 1 8.9 1.6 17.9 1.7 26.8.2 17.1-1.6 29.8-5.2 38.1-3.6 8.2-11 14.2-19.8 16.1-12 2.5-24.2 3.6-36.5 3.3l-6.6-.1zm932.3-43.9c0-30.4 8.6-51.7 43.8-51.7s43.8 21.3 43.8 51.7-8.6 51.7-43.8 51.7-43.8-21.3-43.8-51.7zm65.3 0c0-21.1-2.7-33.1-21.5-33.1s-21.5 12-21.5 33.1 2.8 33.1 21.5 33.1c18.8 0 21.5-12 21.5-33.1zm-672.1 47.8c.5.9 1.5 1.5 2.5 1.4h8.2c1.6 0 2.9-1.3 2.9-2.9v-92.7c0-1.6-1.3-2.9-2.9-2.9h-16.3c-1.6 0-2.9 1.3-2.9 2.9v73.6c-7 3.9-14.9 5.9-22.8 5.8-10.4 0-15.6-4.5-15.6-14.6v-64.8c0-1.6-1.3-2.9-2.9-2.9h-16.4c-1.6 0-2.9 1.3-2.9 2.9v66.7c0 18.9 8.9 31.3 33.9 31.3 11.4-.1 22.6-3.7 32-10.2l2.9 6.5.3-.1zm184.1-68.1c0-18.7-9.3-31.4-32.6-31.4-11.3 0-22.3 3.4-31.6 9.8-4.1-6.1-12-9.8-25.3-9.8-10.7.2-21.1 3.8-29.7 10.2l-2.9-6.5c-.5-.9-1.5-1.5-2.5-1.4h-8.3c-1.6 0-2.9 1.3-2.9 2.9v92.8c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9v-73.5c6.2-3.8 13.4-5.8 20.7-5.8 8.9 0 14 3.3 14 12.6v66.7c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9v-73.6c6.2-3.9 13.4-5.9 20.7-5.8 8.6 0 14 3.3 14 12.6v66.7c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9l.1-66.5zm50.4 61.7c9.3 6.9 20.5 10.5 32 10.2 28.8 0 39.4-19.3 39.4-51.7s-10.7-51.7-39.4-51.7c-9.4 0-18.5 2.7-26.4 7.7V94.2c0-1.6-1.2-2.9-2.8-3h-16.6c-1.6 0-2.9 1.3-2.9 2.9v120.3c0 1.6 1.3 2.9 2.9 2.9h8.2c1 0 2-.5 2.5-1.4l3.1-6.5zm26.8-8.5c-7.5 0-14.8-2-21.3-5.8v-54.4c6.5-3.8 13.8-5.8 21.3-5.8 19.3 0 22.3 14.8 22.3 32.9s-2.8 33.1-22.3 33.1zM868 135.7c-2.5-.3-5.1-.5-7.7-.5-8.8-.4-17.5 1.7-25.3 5.9v73.2c0 1.6-1.3 2.9-2.9 2.9h-16.3c-1.6 0-2.9-1.3-2.9-2.9v-92.7c0-1.6 1.3-2.9 2.9-2.9h8.2c1 0 2 .5 2.5 1.4l2.9 6.5c8.9-6.8 19.9-10.4 31.2-10.2 2.6 0 5.2.2 7.7.6 1.4 0 2.7 2.4 2.7 4v11.8c0 1.6-1.3 2.9-2.9 2.9h-.2m56.7 36.1c-9.8 1.2-15.6 4.9-15.6 15.2 0 7.5 3.3 14.6 15.2 14.6 7.5.1 14.9-2.2 21.1-6.5v-25.5l-20.7 2.2zm26.1 37.6c-8.5 6.7-19 10.3-29.8 10.2-25.5 0-33.9-15.8-33.9-31.6 0-21.3 13.8-30.4 36.1-32.1l22.1-1.8v-4.9c0-10.1-4.7-14-19.3-14-9.2 0-18.3 1.5-26.9 4.5h-.9c-1.6 0-2.9-1.3-2.9-2.9v-13.1c0-1.2.7-2.4 1.9-2.8 9.8-3.3 20.1-5 30.5-4.9 32.3 0 39.8 14.2 39.8 35.1V214c0 1.6-1.3 2.9-2.9 2.9h-8.2c-1 0-2-.5-2.5-1.4l-3.1-6.1zM1063 197h.9c1.6 0 2.9 1.3 2.9 2.9V213c0 1.2-.7 2.3-1.8 2.7-8.1 2.9-16.7 4.3-25.4 4.1-34.9 0-45.7-20.9-45.7-51.7s10.7-51.7 45.7-51.7c8.6-.2 17.1 1.1 25.2 4 1.1.4 1.9 1.5 1.8 2.7v13.1c0 1.6-1.3 2.9-2.9 2.9h-.9c-7.1-2.2-14.6-3.3-22-3.1-19.1 0-24.7 13.1-24.7 32.2s5.5 32.1 24.7 32.1c7.5.1 14.9-1 22-3.3" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
171
src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
UMB_AUTH_CONTEXT,
|
||||
UMB_MODAL_APP_AUTH,
|
||||
UMB_STORAGE_REDIRECT_URL,
|
||||
type UmbUserLoginState,
|
||||
} from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { filter, firstValueFrom, skip } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { ManifestAuthProvider } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export class UmbAppAuthController extends UmbControllerBase {
|
||||
#authContext?: typeof UMB_AUTH_CONTEXT.TYPE;
|
||||
#firstTimeLoggingIn = true;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.consumeContext(UMB_AUTH_CONTEXT, (context) => {
|
||||
this.#authContext = context;
|
||||
|
||||
// Observe the user's authorization state and start the authorization flow if the user is not authorized
|
||||
this.observe(
|
||||
context.isAuthorized.pipe(
|
||||
// Skip the first since it is always false
|
||||
skip(1),
|
||||
// Only continue if the value is false
|
||||
filter((x) => !x),
|
||||
),
|
||||
() => {
|
||||
this.#firstTimeLoggingIn = false;
|
||||
this.makeAuthorizationRequest('timedOut');
|
||||
},
|
||||
'_authState',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is authorized.
|
||||
* If not, the authorization flow is started.
|
||||
*/
|
||||
async isAuthorized(): Promise<boolean> {
|
||||
if (!this.#authContext) {
|
||||
throw new Error('[Fatal] Auth context is not available');
|
||||
}
|
||||
|
||||
const isAuthorized = this.#authContext.getIsAuthorized();
|
||||
|
||||
if (isAuthorized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make a request to the auth server to start the auth flow
|
||||
return this.makeAuthorizationRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the authorization flow.
|
||||
* It will check which providers are available and either redirect directly to the provider or show a provider selection screen.
|
||||
*/
|
||||
async makeAuthorizationRequest(userLoginState: UmbUserLoginState = 'loggingIn'): Promise<boolean> {
|
||||
if (!this.#authContext) {
|
||||
throw new Error('[Fatal] Auth context is not available');
|
||||
}
|
||||
|
||||
// Save location.href so we can redirect to it after login
|
||||
if (location.href !== this.#authContext.getPostLogoutRedirectUrl()) {
|
||||
window.sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, location.href);
|
||||
}
|
||||
|
||||
// Figure out which providers are available
|
||||
const availableProviders = await firstValueFrom(this.#authContext.getAuthProviders());
|
||||
|
||||
// If the user is timed out, we can show the login modal directly
|
||||
if (userLoginState === 'timedOut') {
|
||||
const selected = await this.#showLoginModal(userLoginState, availableProviders);
|
||||
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.#updateState();
|
||||
}
|
||||
|
||||
if (availableProviders.length === 0) {
|
||||
throw new Error('[Fatal] No auth providers available');
|
||||
}
|
||||
|
||||
if (availableProviders.length === 1) {
|
||||
// One provider available (most likely the Umbraco provider), so initiate the authorization request to the default provider
|
||||
this.#authContext.makeAuthorizationRequest(availableProviders[0].forProviderName);
|
||||
return this.#updateState();
|
||||
}
|
||||
|
||||
// Check if any provider is redirecting directly to the provider
|
||||
const redirectProvider =
|
||||
userLoginState === 'loggingIn'
|
||||
? availableProviders.find((provider) => provider.meta?.behavior?.autoRedirect)
|
||||
: undefined;
|
||||
|
||||
if (redirectProvider) {
|
||||
// Redirect directly to the provider
|
||||
this.#authContext.makeAuthorizationRequest(redirectProvider.forProviderName);
|
||||
return this.#updateState();
|
||||
}
|
||||
|
||||
// Show the provider selection screen
|
||||
const selected = await this.#showLoginModal(userLoginState, availableProviders);
|
||||
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.#updateState();
|
||||
}
|
||||
|
||||
async #showLoginModal(
|
||||
userLoginState: UmbUserLoginState,
|
||||
availableProviders: Array<ManifestAuthProvider>,
|
||||
): Promise<boolean> {
|
||||
if (!this.#authContext) {
|
||||
throw new Error('[Fatal] Auth context is not available');
|
||||
}
|
||||
|
||||
// Check if any provider denies local login
|
||||
const denyLocalLogin = availableProviders.some((provider) => provider.meta?.behavior?.denyLocalLogin);
|
||||
if (denyLocalLogin) {
|
||||
// Unregister the Umbraco provider
|
||||
umbExtensionsRegistry.unregister('Umb.AuthProviders.Umbraco');
|
||||
}
|
||||
|
||||
// Show the provider selection screen
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
const selected = await modalManager
|
||||
.open(this._host, UMB_MODAL_APP_AUTH, {
|
||||
data: {
|
||||
userLoginState,
|
||||
},
|
||||
modal: {
|
||||
backdropBackground: this.#firstTimeLoggingIn
|
||||
? 'var(--umb-auth-backdrop, url("/umbraco/backoffice/assets/umbraco_logo_white.svg") 20px 20px / 200px no-repeat, radial-gradient(circle, rgba(2,0,36,1) 0%, rgba(40,58,151,.9) 50%, rgba(0,212,255,1) 100%))'
|
||||
: 'var(--umb-auth-backdrop-timedout, rgba(0,0,0,0.75))',
|
||||
},
|
||||
})
|
||||
.onSubmit()
|
||||
.catch(() => undefined);
|
||||
|
||||
if (!selected?.providerName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.#authContext.makeAuthorizationRequest(selected.providerName, selected.loginHint);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#updateState() {
|
||||
if (!this.#authContext) {
|
||||
throw new Error('[Fatal] Auth context is not available');
|
||||
}
|
||||
|
||||
// Reinitialize the auth flow (load the state from local storage)
|
||||
this.#authContext.setInitialState();
|
||||
|
||||
// The authorization flow is finished, so let the caller know if the user is authorized
|
||||
return this.#authContext.getIsAuthorized();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { onInit } from '../../packages/core/entry-point.js';
|
||||
import type { UmbAppErrorElement } from './app-error.element.js';
|
||||
import { UmbAppContext } from './app.context.js';
|
||||
import { UmbServerConnection } from './server-connection.js';
|
||||
import { UmbAppAuthController } from './app-auth.controller.js';
|
||||
import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UMB_STORAGE_REDIRECT_URL, UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbIconRegistry } from '@umbraco-cms/backoffice/icon';
|
||||
@@ -11,6 +13,8 @@ import type { Guard, UmbRoute } from '@umbraco-cms/backoffice/router';
|
||||
import { pathWithoutBasePath } from '@umbraco-cms/backoffice/router';
|
||||
import { OpenAPI, RuntimeLevelModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbContextDebugController } from '@umbraco-cms/backoffice/debug';
|
||||
import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-app')
|
||||
export class UmbAppElement extends UmbLitElement {
|
||||
@@ -21,7 +25,12 @@ export class UmbAppElement extends UmbLitElement {
|
||||
* @remarks This is the base URL of the Umbraco server, not the base URL of the backoffice.
|
||||
*/
|
||||
@property({ type: String })
|
||||
serverUrl = window.location.origin;
|
||||
set serverUrl(url: string) {
|
||||
OpenAPI.BASE = url;
|
||||
}
|
||||
get serverUrl() {
|
||||
return OpenAPI.BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base path of the backoffice.
|
||||
@@ -29,7 +38,6 @@ export class UmbAppElement extends UmbLitElement {
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String })
|
||||
// TODO: get from base element or maybe move to UmbAuthContext.#getRedirectUrl since it is only used there
|
||||
backofficePath = '/umbraco';
|
||||
|
||||
/**
|
||||
@@ -48,6 +56,13 @@ export class UmbAppElement extends UmbLitElement {
|
||||
component: () => import('../upgrader/upgrader.element.js'),
|
||||
guards: [this.#isAuthorizedGuard()],
|
||||
},
|
||||
{
|
||||
path: 'logout',
|
||||
resolve: () => {
|
||||
this.#authContext?.clearTokenStorage();
|
||||
this.#authController.makeAuthorizationRequest('loggedOut');
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: () => import('../backoffice/backoffice.element.js'),
|
||||
@@ -56,17 +71,18 @@ export class UmbAppElement extends UmbLitElement {
|
||||
];
|
||||
|
||||
#authContext?: typeof UMB_AUTH_CONTEXT.TYPE;
|
||||
#umbIconRegistry = new UmbIconRegistry();
|
||||
#uuiIconRegistry = new UUIIconRegistryEssential();
|
||||
#serverConnection?: UmbServerConnection;
|
||||
#authController = new UmbAppAuthController(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
new UmbContextDebugController(this);
|
||||
OpenAPI.BASE = window.location.origin;
|
||||
|
||||
this.#umbIconRegistry.attach(this);
|
||||
this.#uuiIconRegistry.attach(this);
|
||||
new UmbIconRegistry().attach(this);
|
||||
new UUIIconRegistryEssential().attach(this);
|
||||
|
||||
new UmbContextDebugController(this);
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
@@ -75,19 +91,17 @@ export class UmbAppElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
async #setup() {
|
||||
if (this.serverUrl === undefined) throw new Error('No serverUrl provided');
|
||||
|
||||
/* All requests to the server requires the base URL to be set.
|
||||
We make sure it happens before we get the server status.
|
||||
TODO: find the right place to set this
|
||||
*/
|
||||
OpenAPI.BASE = this.serverUrl;
|
||||
|
||||
this.#serverConnection = await new UmbServerConnection(this.serverUrl).connect();
|
||||
|
||||
this.#authContext = new UmbAuthContext(this, this.serverUrl, this.backofficePath, this.bypassAuth);
|
||||
new UmbAppContext(this, { backofficePath: this.backofficePath, serverUrl: this.serverUrl });
|
||||
|
||||
// Register Core extensions (this is specifically done here because we need these extensions to be registered before the application is initialized)
|
||||
onInit(this, umbExtensionsRegistry);
|
||||
|
||||
// Register public extensions
|
||||
await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPublicExtensions();
|
||||
|
||||
// Try to initialise the auth flow and get the runtime status
|
||||
try {
|
||||
// If the runtime level is "install" we should clear any cached tokens
|
||||
@@ -136,7 +150,6 @@ export class UmbAppElement extends UmbLitElement {
|
||||
// Instruct all requests to use the auth flow to get and use the access_token for all subsequent requests
|
||||
OpenAPI.TOKEN = () => this.#authContext!.getLatestToken();
|
||||
OpenAPI.WITH_CREDENTIALS = true;
|
||||
OpenAPI.CREDENTIALS = 'include';
|
||||
}
|
||||
|
||||
#redirect() {
|
||||
@@ -184,24 +197,7 @@ export class UmbAppElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#isAuthorizedGuard(): Guard {
|
||||
return () => {
|
||||
if (!this.#authContext) {
|
||||
throw new Error('[Fatal] AuthContext requested before it was initialized');
|
||||
}
|
||||
|
||||
if (this.#authContext.getIsAuthorized()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save location.href so we can redirect to it after login
|
||||
window.sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, location.href);
|
||||
|
||||
// Make a request to the auth server to start the auth flow
|
||||
this.#authContext.makeAuthorizationRequest();
|
||||
|
||||
// Return false to prevent the route from being rendered
|
||||
return false;
|
||||
};
|
||||
return () => this.#authController.isAuthorized() ?? false;
|
||||
}
|
||||
|
||||
#errorPage(errorMsg: string, error?: unknown) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import './components/index.js';
|
||||
const CORE_PACKAGES = [
|
||||
import('../../packages/audit-log/umbraco-package.js'),
|
||||
import('../../packages/block/umbraco-package.js'),
|
||||
import('../../packages/core/umbraco-package.js'),
|
||||
import('../../packages/data-type/umbraco-package.js'),
|
||||
import('../../packages/dictionary/umbraco-package.js'),
|
||||
import('../../packages/documents/umbraco-package.js'),
|
||||
@@ -45,7 +44,6 @@ export class UmbBackofficeElement extends UmbLitElement {
|
||||
/**
|
||||
* Backoffice extension registry.
|
||||
* This enables to register and unregister extensions via DevTools, or just via querying this element via the DOM.
|
||||
* @type {UmbExtensionsRegistry}
|
||||
*/
|
||||
public extensionRegistry = umbExtensionsRegistry;
|
||||
|
||||
@@ -53,9 +51,11 @@ export class UmbBackofficeElement extends UmbLitElement {
|
||||
super();
|
||||
|
||||
new UmbBackofficeContext(this);
|
||||
|
||||
new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
|
||||
new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry);
|
||||
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerAllExtensions();
|
||||
|
||||
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions();
|
||||
|
||||
// So far local packages are this simple to registerer, so no need for a manager to do that:
|
||||
CORE_PACKAGES.forEach(async (packageImport) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UMB_BACKOFFICE_CONTEXT } from '../backoffice.context.js';
|
||||
import type { UmbBackofficeContext } from '../backoffice.context.js';
|
||||
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { ManifestSection } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbExtensionManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
@@ -62,7 +62,11 @@ export class UmbBackofficeHeaderSectionsElement extends UmbLitElement {
|
||||
<uui-tab
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
href="${`section/${section.manifest?.meta.pathname}`}"
|
||||
label="${section.manifest?.meta.label ?? section.manifest?.name ?? ''}"></uui-tab>
|
||||
label="${ifDefined(
|
||||
section.manifest?.meta.label
|
||||
? this.localize.string(section.manifest?.meta.label)
|
||||
: section.manifest?.name,
|
||||
)}"></uui-tab>
|
||||
`,
|
||||
)}
|
||||
</uui-tab-group>
|
||||
|
||||
|
Before Width: | Height: | Size: 187 KiB |
@@ -1013,6 +1013,18 @@ export default {
|
||||
lockoutWillOccur: 'Du har været inaktiv, og du vil blive logget ud om',
|
||||
renewSession: 'Forny for at gemme dine ændringer',
|
||||
},
|
||||
login: {
|
||||
greeting0: 'Velkommen',
|
||||
greeting1: 'Velkommen',
|
||||
greeting2: 'Velkommen',
|
||||
greeting3: 'Velkommen',
|
||||
greeting4: 'Velkommen',
|
||||
greeting5: 'Velkommen',
|
||||
greeting6: 'Velkommen',
|
||||
instruction: 'Log ind på Umbraco',
|
||||
signInWith: 'Log ind med {0}',
|
||||
timeout: 'Du er blevet logget ud på grund af inaktivitet, vil du logge ind igen?',
|
||||
},
|
||||
main: {
|
||||
dashboard: 'Skrivebord',
|
||||
sections: 'Sektioner',
|
||||
|
||||
@@ -5,61 +5,66 @@ export default {
|
||||
assigndomain: 'Culture and Hostnames',
|
||||
auditTrail: 'Audit Trail',
|
||||
browse: 'Browse Node',
|
||||
changeDocType: 'Change Document Type',
|
||||
changeDataType: 'Change Data Type',
|
||||
copy: 'Copy',
|
||||
changeDocType: 'Change Document Type',
|
||||
chooseWhereToCopy: 'Choose where to copy',
|
||||
chooseWhereToImport: 'Choose where to import',
|
||||
chooseWhereToMove: 'Choose where to move',
|
||||
copy: 'Copy to',
|
||||
create: 'Create',
|
||||
export: 'Export',
|
||||
createPackage: 'Create Package',
|
||||
createblueprint: 'Create Document Blueprint',
|
||||
createGroup: 'Create group',
|
||||
createPackage: 'Create Package',
|
||||
delete: 'Delete',
|
||||
deployCompare: 'Compare',
|
||||
deployPartialRestore: 'Partial restore',
|
||||
deployQueueForTransfer: 'Queue for transfer',
|
||||
deployRestore: 'Workspace restore',
|
||||
deployTransferNow: 'Transfer now',
|
||||
deployTreeRestore: 'Tree restore',
|
||||
disable: 'Disable',
|
||||
editContent: 'Edit content',
|
||||
editSettings: 'Edit settings',
|
||||
emptyrecyclebin: 'Empty recycle bin',
|
||||
enable: 'Enable',
|
||||
export: 'Export',
|
||||
exportDocumentType: 'Export Document Type',
|
||||
folderCreate: 'Create folder',
|
||||
folderDelete: 'Delete folder',
|
||||
folderRename: 'Rename folder',
|
||||
import: 'Import',
|
||||
importdocumenttype: 'Import Document Type',
|
||||
importPackage: 'Import Package',
|
||||
infiniteEditorChooseWhereToCopy: 'Choose where to copy the selected item(s)',
|
||||
infiniteEditorChooseWhereToMove: 'Choose where to move the selected item(s)',
|
||||
liveEdit: 'Edit in Canvas',
|
||||
logout: 'Exit',
|
||||
move: 'Move',
|
||||
move: 'Move to',
|
||||
notify: 'Notifications',
|
||||
protect: 'Restrict Public Access',
|
||||
publish: 'Publish',
|
||||
unpublish: 'Unpublish',
|
||||
refreshNode: 'Reload',
|
||||
republish: 'Republish entire site',
|
||||
remove: 'Remove',
|
||||
rename: 'Rename',
|
||||
republish: 'Republish entire site',
|
||||
resendInvite: 'Resend Invitation',
|
||||
restore: 'Restore',
|
||||
chooseWhereToCopy: 'Choose where to copy',
|
||||
chooseWhereToMove: 'Choose where to move',
|
||||
chooseWhereToImport: 'Choose where to import',
|
||||
toInTheTreeStructureBelow: 'to in the tree structure below',
|
||||
infiniteEditorChooseWhereToCopy: 'Choose where to copy the selected item(s)',
|
||||
infiniteEditorChooseWhereToMove: 'Choose where to move the selected item(s)',
|
||||
wasMovedTo: 'was moved to',
|
||||
wasCopiedTo: 'was copied to',
|
||||
wasDeleted: 'was deleted',
|
||||
rights: 'Permissions',
|
||||
rollback: 'Rollback',
|
||||
sendtopublish: 'Send To Publish',
|
||||
sendToTranslate: 'Send To Translation',
|
||||
setGroup: 'Set group',
|
||||
sort: 'Sort',
|
||||
translate: 'Translate',
|
||||
update: 'Update',
|
||||
setPermissions: 'Set permissions',
|
||||
sort: 'Sort children of',
|
||||
toInTheTreeStructureBelow: 'to in the tree structure below',
|
||||
translate: 'Translate',
|
||||
trash: 'Trash',
|
||||
unlock: 'Unlock',
|
||||
createblueprint: 'Create Document Blueprint',
|
||||
resendInvite: 'Resend Invitation',
|
||||
deployQueueForTransfer: 'Queue for transfer',
|
||||
deployRestore: 'Workspace restore',
|
||||
deployTreeRestore: 'Tree restore',
|
||||
deployPartialRestore: 'Partial restore',
|
||||
deployTransferNow: 'Transfer now',
|
||||
deployCompare: 'Compare',
|
||||
unpublish: 'Unpublish',
|
||||
update: 'Update',
|
||||
wasCopiedTo: 'was copied to',
|
||||
wasDeleted: 'was deleted',
|
||||
wasMovedTo: 'was moved to',
|
||||
},
|
||||
actionCategories: {
|
||||
content: 'Content',
|
||||
@@ -745,6 +750,7 @@ export default {
|
||||
deleted: 'Deleted',
|
||||
deleting: 'Deleting...',
|
||||
design: 'Design',
|
||||
details: 'Details',
|
||||
dictionary: 'Dictionary',
|
||||
dimensions: 'Dimensions',
|
||||
discard: 'Discard',
|
||||
@@ -1010,6 +1016,18 @@ export default {
|
||||
lockoutWillOccur: "You've been idle and logout will automatically occur in",
|
||||
renewSession: 'Renew now to save your work',
|
||||
},
|
||||
login: {
|
||||
greeting0: 'Welcome',
|
||||
greeting1: 'Welcome',
|
||||
greeting2: 'Welcome',
|
||||
greeting3: 'Welcome',
|
||||
greeting4: 'Welcome',
|
||||
greeting5: 'Welcome',
|
||||
greeting6: 'Welcome',
|
||||
instruction: 'Sign in to Umbraco',
|
||||
signInWith: 'Sign in with {0}',
|
||||
timeout: 'Your session has timed out. Please sign in again below.',
|
||||
},
|
||||
main: {
|
||||
dashboard: 'Dashboard',
|
||||
sections: 'Sections',
|
||||
@@ -1800,6 +1818,7 @@ export default {
|
||||
createDate: 'User created',
|
||||
changePassword: 'Change your password',
|
||||
changePhoto: 'Change photo',
|
||||
configureMfa: 'Configure MFA',
|
||||
emailRequired: 'Required - enter an email address for this user',
|
||||
newPassword: 'New password',
|
||||
newPasswordFormatLengthTip: 'Minimum %0% character(s) to go!',
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
51
src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_blue.svg
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2_00000159465072208785227250000008040336475015305350_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256.6 344.8"
|
||||
style="enable-background:new 0 0 1256.6 344.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#283A97;}
|
||||
</style>
|
||||
<g id="Layer_1-2">
|
||||
<g>
|
||||
<path class="st0" d="M5,172.2C5.1,79.7,80.2,4.9,172.6,5s167.3,75.2,167.2,167.6c-0.1,92.4-75,167.2-167.4,167.2
|
||||
C79.9,339.7,5,264.7,5,172.2L5,172.2L5,172.2z M169,229.5c-13,0.4-26-0.8-38.7-3.5c-9.4-1.9-17.3-8.2-21.2-17
|
||||
c-3.8-8.7-5.6-22.2-5.5-40.4c0.1-9.5,0.7-19,1.8-28.4c1.1-9.2,2.2-16.8,3.2-22.8l1.1-5.9c0-0.2,0-0.3,0-0.5c0-1.7-1.2-3.1-2.8-3.4
|
||||
l-21.6-3.4H85c-1.6,0-2.9,1.1-3.3,2.6c-0.4,1.4-0.6,2.4-1.2,5.7c-1.2,6.4-2.4,12.6-3.6,21.6c-1.3,9.8-2.1,19.7-2.4,29.6
|
||||
c-0.5,6.9-0.5,13.8,0,20.7c0.5,18.3,3.7,33,9.4,43.9s15.6,18.8,29.4,23.6c13.8,4.8,33,7.2,57.7,7.1h3.1
|
||||
c24.7,0.1,43.9-2.2,57.7-7.1c13.8-4.8,23.6-12.7,29.4-23.6c5.8-11,8.9-25.6,9.4-43.9c0.5-6.9,0.5-13.8,0-20.7
|
||||
c-0.3-9.9-1.1-19.8-2.4-29.6c-1.3-9-2.4-15.1-3.6-21.6c-0.6-3.3-0.9-4.3-1.2-5.7c-0.4-1.5-1.7-2.6-3.3-2.6h-0.6l-21.6,3.4
|
||||
c-1.6,0.3-2.8,1.7-2.8,3.4c0,0.2,0,0.3,0,0.5l1.2,5.9c1.1,6,2.2,13.6,3.3,22.7c1.1,9.4,1.7,18.9,1.8,28.4
|
||||
c0.2,18.2-1.7,31.6-5.5,40.4c-3.8,8.7-11.6,15-20.9,17c-12.7,2.7-25.7,3.9-38.7,3.5L169,229.5L169,229.5z"/>
|
||||
<g>
|
||||
<path class="st0" d="M1155.2,186.1c0-33.4,9.6-56.9,48.2-56.9s48.2,23.5,48.2,56.9s-9.6,56.9-48.2,56.9
|
||||
S1155.2,219.5,1155.2,186.1z M1227.1,186.1c0-23.2-3-36.5-23.6-36.5s-23.6,13.3-23.6,36.5s3.1,36.5,23.6,36.5
|
||||
S1227.1,209.2,1227.1,186.1z"/>
|
||||
<path class="st0" d="M487.2,238.7c0.6,1,1.6,1.6,2.8,1.6h9c1.8,0,3.2-1.4,3.2-3.2l0,0V135c0-1.8-1.4-3.2-3.2-3.2h-18
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v81c-7.7,4.3-16.4,6.5-25.2,6.3c-11.5,0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v73.4c0,20.8,9.8,34.5,37.3,34.5c12.6-0.1,24.9-4.1,35.2-11.3l3.2,7.2L487.2,238.7L487.2,238.7z"/>
|
||||
<path class="st0" d="M689.8,163.7c0-20.6-10.2-34.5-35.9-34.5c-12.4,0-24.5,3.8-34.8,10.7c-4.6-6.7-13.3-10.7-27.8-10.7
|
||||
c-11.8,0.2-23.3,4.2-32.8,11.3l-3.2-7.2l0,0c-0.6-1-1.6-1.7-2.8-1.7h-9c-1.8,0-3.2,1.4-3.2,3.2v102.1c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-80.8c6.9-4.1,14.8-6.3,22.8-6.3c9.8,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-81.1c6.8-4.2,14.8-6.4,22.8-6.4c9.6,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2l0,0L689.8,163.7L689.8,163.7z"/>
|
||||
<path class="st0" d="M745.4,231.7c10.2,7.5,22.5,11.5,35.2,11.3c31.7,0,43.4-21.3,43.4-56.9s-11.7-56.9-43.4-56.9
|
||||
c-10.3,0.1-20.4,3-29.1,8.5v-32.8c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8,0-3.2,1.4-3.2,3.2v132.3c0,1.8,1.4,3.2,3.2,3.2h9
|
||||
c1.2,0,2.2-0.6,2.8-1.6l0,0L745.4,231.7L745.4,231.7z M774.9,222.3c-8.2,0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1,15.2-6.3,23.4-6.3
|
||||
c21.3,0,24.5,16.3,24.5,36.2S796.2,222.3,774.9,222.3L774.9,222.3L774.9,222.3z"/>
|
||||
<path class="st0" d="M913.9,150.5c-2.8-0.4-5.6-0.6-8.5-0.5c-9.7-0.4-19.3,1.9-27.8,6.5v80.6c0,1.8-1.4,3.2-3.2,3.2h-18
|
||||
c-1.8,0-3.2-1.4-3.2-3.2V135c0-1.8,1.4-3.2,3.2-3.2h9c1.2,0,2.2,0.6,2.8,1.6l0,0l3.2,7.2c9.9-7.5,22-11.5,34.4-11.3
|
||||
c2.8,0,5.7,0.2,8.5,0.7l0,0c1.7,0,3,2.7,3,4.4v13c0,1.8-1.4,3.2-3.2,3.2h-0.2"/>
|
||||
<path class="st0" d="M976.1,190.2c-10.7,1.3-17.2,5.4-17.2,16.7c0,8.3,3.6,16.1,16.7,16.1c8.3,0.1,16.4-2.4,23.2-7.2v-28.1
|
||||
L976.1,190.2L976.1,190.2z M1004.8,231.7C995.5,239,984,243,972.1,243c-28,0-37.3-17.4-37.3-34.8c0-23.5,15.2-33.4,39.7-35.4
|
||||
l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4c-10.1,0-20.1,1.7-29.6,4.9c-0.3,0.1-0.7,0.1-1,0c-1.8,0-3.2-1.4-3.2-3.2v-14.4
|
||||
c0-1.4,0.8-2.6,2.1-3.1l0,0c10.8-3.6,22.1-5.4,33.5-5.4c35.6,0,43.8,15.6,43.8,38.7v69.3c0,1.8-1.4,3.2-3.2,3.2h-9
|
||||
c-1.2,0-2.2-0.6-2.8-1.6l0,0L1004.8,231.7L1004.8,231.7z"/>
|
||||
<path class="st0" d="M1128.6,218c0.3,0,0.7,0,1,0c1.8,0,3.2,1.4,3.2,3.2v14.4c0,1.3-0.8,2.5-2,3l0,0c-8.9,3.2-18.4,4.8-27.9,4.6
|
||||
c-38.5,0-50.3-23-50.3-56.9s11.8-56.9,50.3-56.9c9.4-0.2,18.9,1.2,27.8,4.4l0,0c1.2,0.5,2,1.7,2,3v14.5c0,1.8-1.4,3.2-3.2,3.2
|
||||
c-0.4,0.1-0.7,0.1-1.1,0l0,0c-7.8-2.5-16-3.7-24.2-3.6c-21.1,0-27.2,14.4-27.2,35.4s6.1,35.4,27.2,35.4
|
||||
c8.2,0.1,16.4-1.1,24.2-3.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -1 +1,51 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 316"><path d="M.2 157.8C.3 70.6 71.1-.1 158.3.1S316.2 71 316.1 158.2s-70.8 157.7-157.9 157.7C70.8 315.9.1 245.1.2 157.8zm154.7 54.1c-12.3.4-24.5-.7-36.5-3.3-8.8-1.8-16.3-7.8-19.9-16-3.6-8.2-5.3-20.9-5.2-38.1.1-9 .6-17.9 1.7-26.8 1-8.7 2.1-15.8 3.1-21.5l1.1-5.6v-.5c0-1.6-1.1-2.9-2.6-3.2l-20.4-3.2h-.4c-1.5 0-2.8 1-3.1 2.5-.3 1.3-.6 2.3-1.2 5.4-1.2 6-2.2 11.8-3.4 20.4-1.3 9.3-2 18.6-2.3 27.9-.4 6.5-.4 13 0 19.6.5 17.3 3.4 31.1 8.9 41.4 5.5 10.3 14.7 17.8 27.7 22.3s31.2 6.8 54.4 6.7h2.9c23.3.1 41.4-2.1 54.4-6.7 13-4.5 22.2-12 27.7-22.3s8.4-24.1 8.9-41.4c.4-6.5.4-13 0-19.6-.3-9.3-1-18.7-2.3-27.9-1.2-8.4-2.3-14.3-3.4-20.4-.6-3.1-.8-4.1-1.2-5.4-.3-1.4-1.6-2.5-3.1-2.5h-.5l-20.4 3.2c-1.6.3-2.7 1.6-2.7 3.2v.5l1.1 5.6c1 5.6 2.1 12.8 3.1 21.5 1 8.9 1.6 17.9 1.7 26.8.2 17.1-1.6 29.8-5.2 38.1-3.6 8.2-11 14.2-19.8 16.1-12 2.5-24.2 3.6-36.5 3.3l-6.6-.1zm932.3-43.9c0-30.4 8.6-51.7 43.8-51.7s43.8 21.3 43.8 51.7-8.6 51.7-43.8 51.7-43.8-21.3-43.8-51.7zm65.3 0c0-21.1-2.7-33.1-21.5-33.1s-21.5 12-21.5 33.1 2.8 33.1 21.5 33.1c18.8 0 21.5-12 21.5-33.1zm-672.1 47.8c.5.9 1.5 1.5 2.5 1.4h8.2c1.6 0 2.9-1.3 2.9-2.9v-92.7c0-1.6-1.3-2.9-2.9-2.9h-16.3c-1.6 0-2.9 1.3-2.9 2.9v73.6c-7 3.9-14.9 5.9-22.8 5.8-10.4 0-15.6-4.5-15.6-14.6v-64.8c0-1.6-1.3-2.9-2.9-2.9h-16.4c-1.6 0-2.9 1.3-2.9 2.9v66.7c0 18.9 8.9 31.3 33.9 31.3 11.4-.1 22.6-3.7 32-10.2l2.9 6.5.3-.1zm184.1-68.1c0-18.7-9.3-31.4-32.6-31.4-11.3 0-22.3 3.4-31.6 9.8-4.1-6.1-12-9.8-25.3-9.8-10.7.2-21.1 3.8-29.7 10.2l-2.9-6.5c-.5-.9-1.5-1.5-2.5-1.4h-8.3c-1.6 0-2.9 1.3-2.9 2.9v92.8c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9v-73.5c6.2-3.8 13.4-5.8 20.7-5.8 8.9 0 14 3.3 14 12.6v66.7c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9v-73.6c6.2-3.9 13.4-5.9 20.7-5.8 8.6 0 14 3.3 14 12.6v66.7c0 1.6 1.3 2.9 2.9 2.9h16.3c1.6 0 2.9-1.3 2.9-2.9l.1-66.5zm50.4 61.7c9.3 6.9 20.5 10.5 32 10.2 28.8 0 39.4-19.3 39.4-51.7s-10.7-51.7-39.4-51.7c-9.4 0-18.5 2.7-26.4 7.7V94.2c0-1.6-1.2-2.9-2.8-3h-16.6c-1.6 0-2.9 1.3-2.9 2.9v120.3c0 1.6 1.3 2.9 2.9 2.9h8.2c1 0 2-.5 2.5-1.4l3.1-6.5zm26.8-8.5c-7.5 0-14.8-2-21.3-5.8v-54.4c6.5-3.8 13.8-5.8 21.3-5.8 19.3 0 22.3 14.8 22.3 32.9s-2.8 33.1-22.3 33.1zM868 135.7c-2.5-.3-5.1-.5-7.7-.5-8.8-.4-17.5 1.7-25.3 5.9v73.2c0 1.6-1.3 2.9-2.9 2.9h-16.3c-1.6 0-2.9-1.3-2.9-2.9v-92.7c0-1.6 1.3-2.9 2.9-2.9h8.2c1 0 2 .5 2.5 1.4l2.9 6.5c8.9-6.8 19.9-10.4 31.2-10.2 2.6 0 5.2.2 7.7.6 1.4 0 2.7 2.4 2.7 4v11.8c0 1.6-1.3 2.9-2.9 2.9h-.2m56.7 36.1c-9.8 1.2-15.6 4.9-15.6 15.2 0 7.5 3.3 14.6 15.2 14.6 7.5.1 14.9-2.2 21.1-6.5v-25.5l-20.7 2.2zm26.1 37.6c-8.5 6.7-19 10.3-29.8 10.2-25.5 0-33.9-15.8-33.9-31.6 0-21.3 13.8-30.4 36.1-32.1l22.1-1.8v-4.9c0-10.1-4.7-14-19.3-14-9.2 0-18.3 1.5-26.9 4.5h-.9c-1.6 0-2.9-1.3-2.9-2.9v-13.1c0-1.2.7-2.4 1.9-2.8 9.8-3.3 20.1-5 30.5-4.9 32.3 0 39.8 14.2 39.8 35.1V214c0 1.6-1.3 2.9-2.9 2.9h-8.2c-1 0-2-.5-2.5-1.4l-3.1-6.1zM1063 197h.9c1.6 0 2.9 1.3 2.9 2.9V213c0 1.2-.7 2.3-1.8 2.7-8.1 2.9-16.7 4.3-25.4 4.1-34.9 0-45.7-20.9-45.7-51.7s10.7-51.7 45.7-51.7c8.6-.2 17.1 1.1 25.2 4 1.1.4 1.9 1.5 1.8 2.7v13.1c0 1.6-1.3 2.9-2.9 2.9h-.9c-7.1-2.2-14.6-3.3-22-3.1-19.1 0-24.7 13.1-24.7 32.2s5.5 32.1 24.7 32.1c7.5.1 14.9-1 22-3.3" fill="#fff"/></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2_00000109717856507224707600000004572449649498936508_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256.6 344.8"
|
||||
style="enable-background:new 0 0 1256.6 344.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_1-2">
|
||||
<g>
|
||||
<path class="st0" d="M5,172.2C5.1,79.7,80.2,4.9,172.6,5s167.3,75.2,167.2,167.6c-0.1,92.4-75,167.2-167.4,167.2
|
||||
C79.9,339.7,4.9,264.7,5,172.2L5,172.2L5,172.2z M169,229.5c-13,0.4-26-0.8-38.7-3.5c-9.4-1.9-17.3-8.2-21.2-17
|
||||
c-3.8-8.7-5.6-22.2-5.5-40.4c0.1-9.5,0.7-19,1.8-28.4c1.1-9.2,2.2-16.8,3.2-22.8l1.1-5.9c0-0.2,0-0.3,0-0.5c0-1.7-1.2-3.1-2.8-3.4
|
||||
l-21.6-3.4H85c-1.6,0-2.9,1.1-3.3,2.6c-0.4,1.4-0.6,2.4-1.2,5.7c-1.2,6.4-2.4,12.6-3.6,21.6c-1.3,9.8-2.1,19.7-2.4,29.6
|
||||
c-0.5,6.9-0.5,13.8,0,20.7c0.5,18.3,3.7,33,9.4,43.9s15.6,18.8,29.4,23.6c13.8,4.8,33,7.2,57.7,7.1h3.1
|
||||
c24.7,0.1,43.9-2.2,57.7-7.1c13.8-4.8,23.6-12.7,29.4-23.6c5.8-11,8.9-25.6,9.4-43.9c0.5-6.9,0.5-13.8,0-20.7
|
||||
c-0.3-9.9-1.1-19.8-2.4-29.6c-1.3-8.9-2.4-15.1-3.6-21.6c-0.6-3.3-0.9-4.3-1.2-5.7c-0.4-1.5-1.7-2.6-3.3-2.6h-0.6l-21.6,3.4
|
||||
c-1.6,0.3-2.8,1.7-2.8,3.4c0,0.2,0,0.3,0,0.5l1.2,5.9c1.1,6,2.2,13.6,3.3,22.7c1.1,9.4,1.7,18.9,1.8,28.4
|
||||
c0.2,18.2-1.7,31.6-5.5,40.4c-3.8,8.7-11.6,15-20.9,17c-12.7,2.7-25.7,3.9-38.7,3.5L169,229.5L169,229.5z"/>
|
||||
<g>
|
||||
<path class="st0" d="M1155.2,186.1c0-33.4,9.6-56.9,48.2-56.9s48.2,23.5,48.2,56.9s-9.6,56.9-48.2,56.9
|
||||
S1155.2,219.5,1155.2,186.1z M1227.1,186.1c0-23.2-3-36.5-23.6-36.5s-23.6,13.3-23.6,36.5s3.1,36.5,23.6,36.5
|
||||
S1227.1,209.2,1227.1,186.1z"/>
|
||||
<path class="st0" d="M487.2,238.7c0.6,1,1.6,1.6,2.8,1.6h9c1.8,0,3.2-1.4,3.2-3.2l0,0V135c0-1.8-1.4-3.2-3.2-3.2h-18
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v81c-7.7,4.3-16.4,6.5-25.2,6.3c-11.5,0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v73.4c0,20.8,9.8,34.5,37.3,34.5c12.6-0.1,24.9-4.1,35.2-11.3l3.2,7.2L487.2,238.7L487.2,238.7z"/>
|
||||
<path class="st0" d="M689.8,163.7c0-20.6-10.2-34.5-35.9-34.5c-12.4,0-24.5,3.8-34.8,10.7c-4.6-6.7-13.3-10.7-27.8-10.7
|
||||
c-11.8,0.2-23.3,4.2-32.8,11.3l-3.2-7.2l0,0c-0.6-1-1.6-1.7-2.8-1.7h-9c-1.8,0-3.2,1.4-3.2,3.2v102.1c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-80.8c6.9-4.1,14.8-6.3,22.8-6.3c9.8,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-81.1c6.8-4.2,14.8-6.4,22.8-6.4c9.6,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2l0,0L689.8,163.7L689.8,163.7z"/>
|
||||
<path class="st0" d="M745.4,231.7c10.2,7.5,22.5,11.5,35.2,11.3c31.7,0,43.4-21.3,43.4-56.9s-11.7-56.9-43.4-56.9
|
||||
c-10.3,0.1-20.4,3-29.1,8.5v-32.9c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8,0-3.2,1.4-3.2,3.2v132.3c0,1.8,1.4,3.2,3.2,3.2h9
|
||||
c1.2,0,2.2-0.6,2.8-1.6l0,0L745.4,231.7L745.4,231.7z M774.9,222.3c-8.2,0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1,15.2-6.3,23.4-6.3
|
||||
c21.3,0,24.5,16.3,24.5,36.2S796.2,222.3,774.9,222.3L774.9,222.3L774.9,222.3z"/>
|
||||
<path class="st0" d="M913.9,150.5c-2.8-0.4-5.6-0.6-8.5-0.5c-9.7-0.4-19.3,1.9-27.8,6.5v80.6c0,1.8-1.4,3.2-3.2,3.2h-18
|
||||
c-1.8,0-3.2-1.4-3.2-3.2V135c0-1.8,1.4-3.2,3.2-3.2h9c1.2,0,2.2,0.6,2.8,1.6l0,0l3.2,7.2c9.9-7.5,22-11.5,34.4-11.3
|
||||
c2.8,0,5.7,0.2,8.5,0.7l0,0c1.7,0,3,2.7,3,4.4v13c0,1.8-1.4,3.2-3.2,3.2L913.9,150.5"/>
|
||||
<path class="st0" d="M976.1,190.2c-10.7,1.3-17.2,5.4-17.2,16.7c0,8.3,3.6,16.1,16.7,16.1c8.3,0.1,16.4-2.4,23.2-7.2v-28.1
|
||||
L976.1,190.2L976.1,190.2z M1004.8,231.7C995.5,239,984,243,972.1,243c-28,0-37.3-17.4-37.3-34.8c0-23.5,15.2-33.4,39.7-35.4
|
||||
l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4c-10.1,0-20.1,1.7-29.6,4.9c-0.3,0.1-0.7,0.1-1,0c-1.8,0-3.2-1.4-3.2-3.2v-14.4
|
||||
c0-1.4,0.8-2.6,2.1-3.1l0,0c10.8-3.6,22.1-5.4,33.5-5.4c35.6,0,43.8,15.6,43.8,38.7v69.3c0,1.8-1.4,3.2-3.2,3.2h-9
|
||||
c-1.2,0-2.2-0.6-2.8-1.6l0,0L1004.8,231.7L1004.8,231.7z"/>
|
||||
<path class="st0" d="M1128.6,218c0.3,0,0.7,0,1,0c1.8,0,3.2,1.4,3.2,3.2v14.4c0,1.3-0.8,2.5-2,3l0,0c-8.9,3.2-18.4,4.8-27.9,4.6
|
||||
c-38.5,0-50.3-23-50.3-56.9s11.8-56.9,50.3-56.9c9.4-0.2,18.9,1.2,27.8,4.4l0,0c1.2,0.5,2,1.7,2,3v14.5c0,1.8-1.4,3.2-3.2,3.2
|
||||
c-0.4,0.1-0.7,0.1-1.1,0l0,0c-7.8-2.5-16-3.7-24.2-3.6c-21.1,0-27.2,14.4-27.2,35.4s6.1,35.4,27.2,35.4
|
||||
c8.2,0.1,16.4-1.1,24.2-3.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -152,6 +152,7 @@ export { LogLevelModel } from './models/LogLevelModel';
|
||||
export type { LogMessagePropertyPresentationModel } from './models/LogMessagePropertyPresentationModel';
|
||||
export type { LogMessageResponseModel } from './models/LogMessageResponseModel';
|
||||
export type { LogTemplateResponseModel } from './models/LogTemplateResponseModel';
|
||||
export type { ManifestResponseModel } from './models/ManifestResponseModel';
|
||||
export type { MediaCollectionResponseModel } from './models/MediaCollectionResponseModel';
|
||||
export type { MediaConfigurationResponseModel } from './models/MediaConfigurationResponseModel';
|
||||
export type { MediaItemResponseModel } from './models/MediaItemResponseModel';
|
||||
@@ -209,7 +210,6 @@ export type { OutOfDateStatusResponseModel } from './models/OutOfDateStatusRespo
|
||||
export { OutOfDateTypeModel } from './models/OutOfDateTypeModel';
|
||||
export type { PackageConfigurationResponseModel } from './models/PackageConfigurationResponseModel';
|
||||
export type { PackageDefinitionResponseModel } from './models/PackageDefinitionResponseModel';
|
||||
export type { PackageManifestResponseModel } from './models/PackageManifestResponseModel';
|
||||
export type { PackageMigrationStatusResponseModel } from './models/PackageMigrationStatusResponseModel';
|
||||
export type { PagedAllowedDocumentTypeModel } from './models/PagedAllowedDocumentTypeModel';
|
||||
export type { PagedAllowedMediaTypeModel } from './models/PagedAllowedMediaTypeModel';
|
||||
@@ -411,6 +411,7 @@ export { IndexerResource } from './services/IndexerResource';
|
||||
export { InstallResource } from './services/InstallResource';
|
||||
export { LanguageResource } from './services/LanguageResource';
|
||||
export { LogViewerResource } from './services/LogViewerResource';
|
||||
export { ManifestResource } from './services/ManifestResource';
|
||||
export { MediaResource } from './services/MediaResource';
|
||||
export { MediaTypeResource } from './services/MediaTypeResource';
|
||||
export { MemberResource } from './services/MemberResource';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type PackageManifestResponseModel = {
|
||||
export type ManifestResponseModel = {
|
||||
name: string;
|
||||
version?: string | null;
|
||||
extensions: Array<any>;
|
||||
52
src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/ManifestResource.ts
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ManifestResponseModel } from '../models/ManifestResponseModel';
|
||||
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
|
||||
export class ManifestResource {
|
||||
|
||||
/**
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getManifestManifest(): CancelablePromise<Array<ManifestResponseModel>> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/umbraco/management/api/v1/manifest/manifest',
|
||||
errors: {
|
||||
401: `The resource is protected and requires an authentication token`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getManifestManifestPrivate(): CancelablePromise<Array<ManifestResponseModel>> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/umbraco/management/api/v1/manifest/manifest/private',
|
||||
errors: {
|
||||
401: `The resource is protected and requires an authentication token`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getManifestManifestPublic(): CancelablePromise<Array<ManifestResponseModel>> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/umbraco/management/api/v1/manifest/manifest/public',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
import type { CreatePackageRequestModel } from '../models/CreatePackageRequestModel';
|
||||
import type { PackageConfigurationResponseModel } from '../models/PackageConfigurationResponseModel';
|
||||
import type { PackageDefinitionResponseModel } from '../models/PackageDefinitionResponseModel';
|
||||
import type { PackageManifestResponseModel } from '../models/PackageManifestResponseModel';
|
||||
import type { PagedPackageDefinitionResponseModel } from '../models/PagedPackageDefinitionResponseModel';
|
||||
import type { PagedPackageMigrationStatusResponseModel } from '../models/PagedPackageMigrationStatusResponseModel';
|
||||
import type { UpdatePackageRequestModel } from '../models/UpdatePackageRequestModel';
|
||||
@@ -195,31 +194,6 @@ export class PackageResource {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getPackageManifest(): CancelablePromise<Array<PackageManifestResponseModel>> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/umbraco/management/api/v1/package/manifest',
|
||||
errors: {
|
||||
401: `The resource is protected and requires an authentication token`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getPackageManifestPublic(): CancelablePromise<Array<PackageManifestResponseModel>> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/umbraco/management/api/v1/package/manifest/public',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
|
||||
1
src/Umbraco.Web.UI.Client/src/external/base64-js/index.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { fromByteArray } from 'base64-js';
|
||||
176
src/Umbraco.Web.UI.Client/src/external/openid/LICENSE
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
122
src/Umbraco.Web.UI.Client/src/external/openid/authorization_request.ts
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DefaultCrypto } from './crypto_utils.js';
|
||||
import type { Crypto } from './crypto_utils.js';
|
||||
import { log } from './logger.js';
|
||||
import type { StringMap } from './types.js';
|
||||
|
||||
/**
|
||||
* Represents an AuthorizationRequest as JSON.
|
||||
*/
|
||||
export interface AuthorizationRequestJson {
|
||||
response_type: string;
|
||||
client_id: string;
|
||||
redirect_uri: string;
|
||||
scope: string;
|
||||
state?: string;
|
||||
extras?: StringMap;
|
||||
internal?: StringMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cryptographically random new state. Useful for CSRF protection.
|
||||
*/
|
||||
const SIZE = 10; // 10 bytes
|
||||
const newState = function (crypto: Crypto): string {
|
||||
return crypto.generateRandom(SIZE);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the AuthorizationRequest.
|
||||
* For more information look at
|
||||
* https://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
*/
|
||||
export class AuthorizationRequest {
|
||||
static RESPONSE_TYPE_TOKEN = 'token';
|
||||
static RESPONSE_TYPE_CODE = 'code';
|
||||
|
||||
// NOTE:
|
||||
// Both redirect_uri and state are actually optional.
|
||||
// However AppAuth is more opionionated, and requires you to use both.
|
||||
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
scope: string;
|
||||
responseType: string;
|
||||
state: string;
|
||||
extras?: StringMap;
|
||||
internal?: StringMap;
|
||||
/**
|
||||
* Constructs a new AuthorizationRequest.
|
||||
* Use a `undefined` value for the `state` parameter, to generate a random
|
||||
* state for CSRF protection.
|
||||
*/
|
||||
constructor(
|
||||
request: AuthorizationRequestJson,
|
||||
private crypto: Crypto = new DefaultCrypto(),
|
||||
private usePkce: boolean = true,
|
||||
) {
|
||||
this.clientId = request.client_id;
|
||||
this.redirectUri = request.redirect_uri;
|
||||
this.scope = request.scope;
|
||||
this.responseType = request.response_type || AuthorizationRequest.RESPONSE_TYPE_CODE;
|
||||
this.state = request.state || newState(crypto);
|
||||
this.extras = request.extras;
|
||||
// read internal properties if available
|
||||
this.internal = request.internal;
|
||||
}
|
||||
|
||||
setupCodeVerifier(): Promise<void> {
|
||||
if (!this.usePkce) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
const codeVerifier = this.crypto.generateRandom(128);
|
||||
const challenge: Promise<string | undefined> = this.crypto.deriveChallenge(codeVerifier).catch((error) => {
|
||||
log('Unable to generate PKCE challenge. Not using PKCE', error);
|
||||
return undefined;
|
||||
});
|
||||
return challenge.then((result) => {
|
||||
if (result) {
|
||||
// keep track of the code used.
|
||||
this.internal = this.internal || {};
|
||||
this.internal['code_verifier'] = codeVerifier;
|
||||
this.extras = this.extras || {};
|
||||
this.extras['code_challenge'] = result;
|
||||
// We always use S256. Plain is not good enough.
|
||||
this.extras['code_challenge_method'] = 'S256';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the AuthorizationRequest to a JavaScript Object.
|
||||
*/
|
||||
toJson(): Promise<AuthorizationRequestJson> {
|
||||
// Always make sure that the code verifier is setup when toJson() is called.
|
||||
return this.setupCodeVerifier().then(() => {
|
||||
return {
|
||||
response_type: this.responseType,
|
||||
client_id: this.clientId,
|
||||
redirect_uri: this.redirectUri,
|
||||
scope: this.scope,
|
||||
state: this.state,
|
||||
extras: this.extras,
|
||||
internal: this.internal,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
161
src/Umbraco.Web.UI.Client/src/external/openid/authorization_request_handler.ts
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { AuthorizationRequest } from './authorization_request.js';
|
||||
import type { AuthorizationError, AuthorizationResponse } from './authorization_response.js';
|
||||
import type { AuthorizationServiceConfiguration } from './authorization_service_configuration.js';
|
||||
import type { Crypto } from './crypto_utils.js';
|
||||
import { log } from './logger.js';
|
||||
import type { QueryStringUtils } from './query_string_utils.js';
|
||||
import type { StringMap } from './types.js';
|
||||
|
||||
/**
|
||||
* This type represents a lambda that can take an AuthorizationRequest,
|
||||
* and an AuthorizationResponse as arguments.
|
||||
*/
|
||||
export type AuthorizationListener = (
|
||||
request: AuthorizationRequest,
|
||||
response: AuthorizationResponse | null,
|
||||
error: AuthorizationError | null,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Represents a structural type holding both authorization request and response.
|
||||
*/
|
||||
export interface AuthorizationRequestResponse {
|
||||
request: AuthorizationRequest;
|
||||
response: AuthorizationResponse | null;
|
||||
error: AuthorizationError | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Service notifier.
|
||||
* This manages the communication of the AuthorizationResponse to the 3p client.
|
||||
*/
|
||||
export class AuthorizationNotifier {
|
||||
private listener: AuthorizationListener | null = null;
|
||||
|
||||
setAuthorizationListener(listener: AuthorizationListener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* The authorization complete callback.
|
||||
*/
|
||||
onAuthorizationComplete(
|
||||
request: AuthorizationRequest,
|
||||
response: AuthorizationResponse | null,
|
||||
error: AuthorizationError | null,
|
||||
): void {
|
||||
if (this.listener) {
|
||||
// complete authorization request
|
||||
this.listener(request, response, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(rahulrav@): add more built in parameters.
|
||||
/* built in parameters. */
|
||||
export const BUILT_IN_PARAMETERS = ['redirect_uri', 'client_id', 'response_type', 'state', 'scope'];
|
||||
|
||||
/**
|
||||
* Defines the interface which is capable of handling an authorization request
|
||||
* using various methods (iframe / popup / different process etc.).
|
||||
*/
|
||||
export abstract class AuthorizationRequestHandler {
|
||||
constructor(
|
||||
public utils: QueryStringUtils,
|
||||
protected crypto: Crypto,
|
||||
) {}
|
||||
|
||||
// notifier send the response back to the client.
|
||||
protected notifier: AuthorizationNotifier | null = null;
|
||||
|
||||
/**
|
||||
* A utility method to be able to build the authorization request URL.
|
||||
*/
|
||||
protected buildRequestUrl(configuration: AuthorizationServiceConfiguration, request: AuthorizationRequest) {
|
||||
// build the query string
|
||||
// coerce to any type for convenience
|
||||
const requestMap: StringMap = {
|
||||
redirect_uri: request.redirectUri,
|
||||
client_id: request.clientId,
|
||||
response_type: request.responseType,
|
||||
state: request.state,
|
||||
scope: request.scope,
|
||||
};
|
||||
|
||||
// copy over extras
|
||||
if (request.extras) {
|
||||
for (const extra in request.extras) {
|
||||
if (Object.prototype.hasOwnProperty.call(request.extras, extra)) {
|
||||
// check before inserting to requestMap
|
||||
if (BUILT_IN_PARAMETERS.indexOf(extra) < 0) {
|
||||
requestMap[extra] = request.extras[extra];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const query = this.utils.stringify(requestMap);
|
||||
const baseUrl = configuration.authorizationEndpoint;
|
||||
const url = `${baseUrl}?${query}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the authorization request if necessary & when possible.
|
||||
*/
|
||||
completeAuthorizationRequestIfPossible(): Promise<void> {
|
||||
// call complete authorization if possible to see there might
|
||||
// be a response that needs to be delivered.
|
||||
log(`Checking to see if there is an authorization response to be delivered.`);
|
||||
if (!this.notifier) {
|
||||
log(`Notifier is not present on AuthorizationRequest handler.
|
||||
No delivery of result will be possible`);
|
||||
}
|
||||
return this.completeAuthorizationRequest().then((result) => {
|
||||
if (!result) {
|
||||
log(`No result is available yet.`);
|
||||
}
|
||||
if (result && this.notifier) {
|
||||
this.notifier.onAuthorizationComplete(result.request, result.response, result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default Authorization Service notifier.
|
||||
*/
|
||||
setAuthorizationNotifier(notifier: AuthorizationNotifier): AuthorizationRequestHandler {
|
||||
this.notifier = notifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an authorization request.
|
||||
*/
|
||||
abstract performAuthorizationRequest(
|
||||
configuration: AuthorizationServiceConfiguration,
|
||||
request: AuthorizationRequest,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Checks if an authorization flow can be completed, and completes it.
|
||||
* The handler returns a `Promise<AuthorizationRequestResponse>` if ready, or a `Promise<null>`
|
||||
* if not ready.
|
||||
*/
|
||||
protected abstract completeAuthorizationRequest(): Promise<AuthorizationRequestResponse | null>;
|
||||
}
|
||||
79
src/Umbraco.Web.UI.Client/src/external/openid/authorization_response.ts
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the AuthorizationResponse as a JSON object.
|
||||
*/
|
||||
export interface AuthorizationResponseJson {
|
||||
code: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the AuthorizationError as a JSON object.
|
||||
*/
|
||||
export interface AuthorizationErrorJson {
|
||||
error: string;
|
||||
error_description?: string;
|
||||
error_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the Authorization Response type.
|
||||
* For more information look at
|
||||
* https://tools.ietf.org/html/rfc6749#section-4.1.2
|
||||
*/
|
||||
export class AuthorizationResponse {
|
||||
code: string;
|
||||
state: string;
|
||||
|
||||
constructor(response: AuthorizationResponseJson) {
|
||||
this.code = response.code;
|
||||
this.state = response.state;
|
||||
}
|
||||
|
||||
toJson(): AuthorizationResponseJson {
|
||||
return { code: this.code, state: this.state };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the Authorization error response.
|
||||
* For more information look at:
|
||||
* https://tools.ietf.org/html/rfc6749#section-4.1.2.1
|
||||
*/
|
||||
export class AuthorizationError {
|
||||
error: string;
|
||||
errorDescription?: string;
|
||||
errorUri?: string;
|
||||
state?: string;
|
||||
|
||||
constructor(error: AuthorizationErrorJson) {
|
||||
this.error = error.error;
|
||||
this.errorDescription = error.error_description;
|
||||
this.errorUri = error.error_uri;
|
||||
this.state = error.state;
|
||||
}
|
||||
|
||||
toJson(): AuthorizationErrorJson {
|
||||
return {
|
||||
error: this.error,
|
||||
error_description: this.errorDescription,
|
||||
error_uri: this.errorUri,
|
||||
state: this.state,
|
||||
};
|
||||
}
|
||||
}
|
||||
81
src/Umbraco.Web.UI.Client/src/external/openid/authorization_service_configuration.ts
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Requestor } from './xhr.js';
|
||||
import { FetchRequestor } from './xhr.js';
|
||||
|
||||
/**
|
||||
* Represents AuthorizationServiceConfiguration as a JSON object.
|
||||
*/
|
||||
export interface AuthorizationServiceConfigurationJson {
|
||||
authorization_endpoint: string;
|
||||
token_endpoint: string;
|
||||
revocation_endpoint: string;
|
||||
end_session_endpoint?: string;
|
||||
userinfo_endpoint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard base path for well-known resources on domains.
|
||||
* See https://tools.ietf.org/html/rfc5785 for more information.
|
||||
*/
|
||||
const WELL_KNOWN_PATH = '.well-known';
|
||||
|
||||
/**
|
||||
* The standard resource under the well known path at which an OpenID Connect
|
||||
* discovery document can be found under an issuer's base URI.
|
||||
*/
|
||||
const OPENID_CONFIGURATION = 'openid-configuration';
|
||||
|
||||
/**
|
||||
* Configuration details required to interact with an authorization service.
|
||||
*
|
||||
* More information at https://openid.net/specs/openid-connect-discovery-1_0-17.html
|
||||
*/
|
||||
export class AuthorizationServiceConfiguration {
|
||||
authorizationEndpoint: string;
|
||||
tokenEndpoint: string;
|
||||
revocationEndpoint: string;
|
||||
userInfoEndpoint?: string;
|
||||
endSessionEndpoint?: string;
|
||||
|
||||
constructor(request: AuthorizationServiceConfigurationJson) {
|
||||
this.authorizationEndpoint = request.authorization_endpoint;
|
||||
this.tokenEndpoint = request.token_endpoint;
|
||||
this.revocationEndpoint = request.revocation_endpoint;
|
||||
this.userInfoEndpoint = request.userinfo_endpoint;
|
||||
this.endSessionEndpoint = request.end_session_endpoint;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
authorization_endpoint: this.authorizationEndpoint,
|
||||
token_endpoint: this.tokenEndpoint,
|
||||
revocation_endpoint: this.revocationEndpoint,
|
||||
end_session_endpoint: this.endSessionEndpoint,
|
||||
userinfo_endpoint: this.userInfoEndpoint,
|
||||
};
|
||||
}
|
||||
|
||||
static fetchFromIssuer(openIdIssuerUrl: string, requestor?: Requestor): Promise<AuthorizationServiceConfiguration> {
|
||||
const fullUrl = `${openIdIssuerUrl}/${WELL_KNOWN_PATH}/${OPENID_CONFIGURATION}`;
|
||||
|
||||
const requestorToUse = requestor || new FetchRequestor();
|
||||
|
||||
return requestorToUse
|
||||
.xhr<AuthorizationServiceConfigurationJson>({ url: fullUrl, dataType: 'json', method: 'GET' })
|
||||
.then((json) => new AuthorizationServiceConfiguration(json));
|
||||
}
|
||||
}
|
||||
98
src/Umbraco.Web.UI.Client/src/external/openid/crypto_utils.ts
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as base64 from '@umbraco-cms/backoffice/external/base64-js';
|
||||
|
||||
import { AppAuthError } from './errors.js';
|
||||
|
||||
const HAS_CRYPTO = typeof window !== 'undefined' && !!(window.crypto as any);
|
||||
const HAS_SUBTLE_CRYPTO = HAS_CRYPTO && !!(window.crypto.subtle as any);
|
||||
const CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
export function bufferToString(buffer: Uint8Array) {
|
||||
const state = [];
|
||||
for (let i = 0; i < buffer.byteLength; i += 1) {
|
||||
const index = buffer[i] % CHARSET.length;
|
||||
state.push(CHARSET[index]);
|
||||
}
|
||||
return state.join('');
|
||||
}
|
||||
|
||||
export function urlSafe(buffer: Uint8Array): string {
|
||||
const encoded = base64.fromByteArray(new Uint8Array(buffer));
|
||||
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
// adapted from source: http://stackoverflow.com/a/11058858
|
||||
// this is used in place of TextEncode as the api is not yet
|
||||
// well supported: https://caniuse.com/#search=TextEncoder
|
||||
export function textEncodeLite(str: string) {
|
||||
const buf = new ArrayBuffer(str.length);
|
||||
const bufView = new Uint8Array(buf);
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return bufView;
|
||||
}
|
||||
|
||||
export interface Crypto {
|
||||
/**
|
||||
* Generate a random string
|
||||
*/
|
||||
generateRandom(size: number): string;
|
||||
/**
|
||||
* Compute the SHA256 of a given code.
|
||||
* This is useful when using PKCE.
|
||||
*/
|
||||
deriveChallenge(code: string): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of the `Crypto` interface.
|
||||
* This uses the capabilities of the browser.
|
||||
*/
|
||||
export class DefaultCrypto implements Crypto {
|
||||
generateRandom(size: number) {
|
||||
const buffer = new Uint8Array(size);
|
||||
if (HAS_CRYPTO) {
|
||||
window.crypto.getRandomValues(buffer);
|
||||
} else {
|
||||
// fall back to Math.random() if nothing else is available
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
buffer[i] = (Math.random() * CHARSET.length) | 0;
|
||||
}
|
||||
}
|
||||
return bufferToString(buffer);
|
||||
}
|
||||
|
||||
deriveChallenge(code: string): Promise<string> {
|
||||
if (code.length < 43 || code.length > 128) {
|
||||
return Promise.reject(new AppAuthError('Invalid code length.'));
|
||||
}
|
||||
if (!HAS_SUBTLE_CRYPTO) {
|
||||
return Promise.reject(new AppAuthError('window.crypto.subtle is unavailable.'));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.subtle.digest('SHA-256', textEncodeLite(code)).then(
|
||||
(buffer) => {
|
||||
return resolve(urlSafe(new Uint8Array(buffer)));
|
||||
},
|
||||
(error) => reject(error),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
24
src/Umbraco.Web.UI.Client/src/external/openid/errors.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the AppAuthError type.
|
||||
*/
|
||||
export class AppAuthError {
|
||||
constructor(
|
||||
public message: string,
|
||||
public extras?: any,
|
||||
) {}
|
||||
}
|
||||
21
src/Umbraco.Web.UI.Client/src/external/openid/flags.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* Global flags that control the behavior of App Auth JS. */
|
||||
|
||||
/* Logging turned on ? */
|
||||
export const IS_LOG = true;
|
||||
|
||||
/* Profiling turned on ? */
|
||||
export const IS_PROFILE = false;
|
||||
@@ -1,18 +1,17 @@
|
||||
export {
|
||||
BaseTokenRequestHandler,
|
||||
BasicQueryStringUtils,
|
||||
FetchRequestor,
|
||||
LocalStorageBackend,
|
||||
RedirectRequestHandler,
|
||||
RevokeTokenRequest,
|
||||
} from '@openid/appauth';
|
||||
export { AuthorizationRequest } from '@openid/appauth/built/authorization_request';
|
||||
export { AuthorizationNotifier } from '@openid/appauth/built/authorization_request_handler';
|
||||
export { AuthorizationServiceConfiguration } from '@openid/appauth/built/authorization_service_configuration';
|
||||
export {
|
||||
GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
GRANT_TYPE_REFRESH_TOKEN,
|
||||
TokenRequest,
|
||||
} from '@openid/appauth/built/token_request';
|
||||
export { TokenResponse } from '@openid/appauth/built/token_response';
|
||||
export type { LocationLike, StringMap } from '@openid/appauth/built/types';
|
||||
export * from './authorization_request.js';
|
||||
export * from './authorization_request_handler.js';
|
||||
export * from './authorization_response.js';
|
||||
export * from './authorization_service_configuration.js';
|
||||
export * from './crypto_utils.js';
|
||||
export * from './errors.js';
|
||||
export * from './flags.js';
|
||||
export * from './logger.js';
|
||||
export * from './query_string_utils.js';
|
||||
export * from './redirect_based_handler.js';
|
||||
export * from './revoke_token_request.js';
|
||||
export * from './storage.js';
|
||||
export * from './token_request.js';
|
||||
export * from './token_request_handler.js';
|
||||
export * from './token_response.js';
|
||||
export type * from './types.js';
|
||||
export * from './xhr.js';
|
||||
|
||||
71
src/Umbraco.Web.UI.Client/src/external/openid/logger.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IS_LOG, IS_PROFILE } from './flags.js';
|
||||
|
||||
export function log(message: string, ...args: any[]) {
|
||||
if (IS_LOG) {
|
||||
const length = args ? args.length : 0;
|
||||
if (length > 0) {
|
||||
console.log(message, ...args);
|
||||
} else {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if native support for profiling is available.
|
||||
const NATIVE_PROFILE_SUPPORT = typeof window !== 'undefined' && !!window.performance && !!console.profile;
|
||||
|
||||
/**
|
||||
* A decorator that can profile a function.
|
||||
*/
|
||||
export function profile(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (IS_PROFILE) {
|
||||
return performProfile(target, propertyKey, descriptor);
|
||||
} else {
|
||||
// return as-is
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
function performProfile(target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
||||
const originalCallable = descriptor.value;
|
||||
// name must exist
|
||||
let name = originalCallable.name;
|
||||
if (!name) {
|
||||
name = 'anonymous function';
|
||||
}
|
||||
if (NATIVE_PROFILE_SUPPORT) {
|
||||
descriptor.value = function (args: any[]) {
|
||||
console.profile(name);
|
||||
const startTime = window.performance.now();
|
||||
const result = originalCallable.call(this || window, ...args);
|
||||
const duration = window.performance.now() - startTime;
|
||||
console.log(`${name} took ${duration} ms`);
|
||||
console.profileEnd();
|
||||
return result;
|
||||
};
|
||||
} else {
|
||||
descriptor.value = function (args: any[]) {
|
||||
log(`Profile start ${name}`);
|
||||
const start = Date.now();
|
||||
const result = originalCallable.call(this || window, ...args);
|
||||
const duration = Date.now() - start;
|
||||
log(`Profile end ${name} took ${duration} ms.`);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return descriptor;
|
||||
}
|
||||
64
src/Umbraco.Web.UI.Client/src/external/openid/query_string_utils.ts
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { LocationLike, StringMap } from './types.js';
|
||||
|
||||
/**
|
||||
* Query String Utilities.
|
||||
*/
|
||||
export interface QueryStringUtils {
|
||||
stringify(input: StringMap): string;
|
||||
parse(query: LocationLike, useHash?: boolean): StringMap;
|
||||
parseQueryString(query: string): StringMap;
|
||||
}
|
||||
|
||||
export class BasicQueryStringUtils implements QueryStringUtils {
|
||||
parse(input: LocationLike, useHash?: boolean) {
|
||||
if (useHash) {
|
||||
return this.parseQueryString(input.hash);
|
||||
} else {
|
||||
return this.parseQueryString(input.search);
|
||||
}
|
||||
}
|
||||
|
||||
parseQueryString(query: string): StringMap {
|
||||
const result: StringMap = {};
|
||||
// if anything starts with ?, # or & remove it
|
||||
query = query.trim().replace(/^(\?|#|&)/, '');
|
||||
const params = query.split('&');
|
||||
for (let i = 0; i < params.length; i += 1) {
|
||||
const param = params[i]; // looks something like a=b
|
||||
const parts = param.split('=');
|
||||
if (parts.length >= 2) {
|
||||
const key = decodeURIComponent(parts.shift()!);
|
||||
const value = parts.length > 0 ? parts.join('=') : null;
|
||||
if (value) {
|
||||
result[key] = decodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
stringify(input: StringMap) {
|
||||
const encoded: string[] = [];
|
||||
for (const key in input) {
|
||||
if (Object.prototype.hasOwnProperty.call(input, key) && input[key]) {
|
||||
encoded.push(`${encodeURIComponent(key)}=${encodeURIComponent(input[key])}`);
|
||||
}
|
||||
}
|
||||
return encoded.join('&');
|
||||
}
|
||||
}
|
||||
146
src/Umbraco.Web.UI.Client/src/external/openid/redirect_based_handler.ts
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthorizationRequest } from './authorization_request.js';
|
||||
import type { AuthorizationRequestResponse } from './authorization_request_handler.js';
|
||||
import { AuthorizationRequestHandler } from './authorization_request_handler.js';
|
||||
import { AuthorizationError, AuthorizationResponse } from './authorization_response.js';
|
||||
import type { AuthorizationServiceConfiguration } from './authorization_service_configuration.js';
|
||||
import type { Crypto } from './crypto_utils.js';
|
||||
import { DefaultCrypto } from './crypto_utils.js';
|
||||
import { log } from './logger.js';
|
||||
import { BasicQueryStringUtils } from './query_string_utils.js';
|
||||
import type { StorageBackend } from './storage.js';
|
||||
import { LocalStorageBackend } from './storage.js';
|
||||
import type { LocationLike } from './types.js';
|
||||
|
||||
/** key for authorization request. */
|
||||
const authorizationRequestKey = (handle: string) => {
|
||||
return `${handle}_appauth_authorization_request`;
|
||||
};
|
||||
|
||||
/** key for authorization service configuration */
|
||||
const authorizationServiceConfigurationKey = (handle: string) => {
|
||||
return `${handle}_appauth_authorization_service_configuration`;
|
||||
};
|
||||
|
||||
/** key in local storage which represents the current authorization request. */
|
||||
const AUTHORIZATION_REQUEST_HANDLE_KEY = 'appauth_current_authorization_request';
|
||||
|
||||
/**
|
||||
* Represents an AuthorizationRequestHandler which uses a standard
|
||||
* redirect based code flow.
|
||||
*/
|
||||
export class RedirectRequestHandler extends AuthorizationRequestHandler {
|
||||
constructor(
|
||||
// use the provided storage backend
|
||||
// or initialize local storage with the default storage backend which
|
||||
// uses window.localStorage
|
||||
public storageBackend: StorageBackend = new LocalStorageBackend(),
|
||||
utils = new BasicQueryStringUtils(),
|
||||
public locationLike: LocationLike = window.location,
|
||||
crypto: Crypto = new DefaultCrypto(),
|
||||
) {
|
||||
super(utils, crypto);
|
||||
}
|
||||
|
||||
performAuthorizationRequest(configuration: AuthorizationServiceConfiguration, request: AuthorizationRequest) {
|
||||
const handle = this.crypto.generateRandom(10);
|
||||
|
||||
// before you make request, persist all request related data in local storage.
|
||||
const persisted = Promise.all([
|
||||
this.storageBackend.setItem(AUTHORIZATION_REQUEST_HANDLE_KEY, handle),
|
||||
// Calling toJson() adds in the code & challenge when possible
|
||||
request
|
||||
.toJson()
|
||||
.then((result) => this.storageBackend.setItem(authorizationRequestKey(handle), JSON.stringify(result))),
|
||||
this.storageBackend.setItem(authorizationServiceConfigurationKey(handle), JSON.stringify(configuration.toJson())),
|
||||
]);
|
||||
|
||||
persisted.then(() => {
|
||||
// make the redirect request
|
||||
const url = this.buildRequestUrl(configuration, request);
|
||||
log('Making a request to ', request, url);
|
||||
this.locationLike.assign(url);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to introspect the contents of storage backend and completes the
|
||||
* request.
|
||||
*/
|
||||
protected completeAuthorizationRequest(): Promise<AuthorizationRequestResponse | null> {
|
||||
// TODO(rahulrav@): handle authorization errors.
|
||||
return this.storageBackend.getItem(AUTHORIZATION_REQUEST_HANDLE_KEY).then((handle) => {
|
||||
if (handle) {
|
||||
// we have a pending request.
|
||||
// fetch authorization request, and check state
|
||||
return (
|
||||
this.storageBackend
|
||||
.getItem(authorizationRequestKey(handle))
|
||||
// requires a corresponding instance of result
|
||||
// TODO(rahulrav@): check for inconsitent state here
|
||||
.then((result) => JSON.parse(result!))
|
||||
.then((json) => new AuthorizationRequest(json))
|
||||
.then((request) => {
|
||||
// check redirect_uri and state
|
||||
const currentUri = `${this.locationLike.origin}${this.locationLike.pathname}`;
|
||||
const queryParams = this.utils.parse(this.locationLike, true /* use hash */);
|
||||
const state: string | undefined = queryParams['state'];
|
||||
const code: string | undefined = queryParams['code'];
|
||||
const error: string | undefined = queryParams['error'];
|
||||
log('Potential authorization request ', currentUri, queryParams, state, code, error);
|
||||
const shouldNotify = state === request.state;
|
||||
let authorizationResponse: AuthorizationResponse | null = null;
|
||||
let authorizationError: AuthorizationError | null = null;
|
||||
if (shouldNotify) {
|
||||
if (error) {
|
||||
// get additional optional info.
|
||||
const errorUri = queryParams['error_uri'];
|
||||
const errorDescription = queryParams['error_description'];
|
||||
authorizationError = new AuthorizationError({
|
||||
error: error,
|
||||
error_description: errorDescription,
|
||||
error_uri: errorUri,
|
||||
state: state,
|
||||
});
|
||||
} else {
|
||||
authorizationResponse = new AuthorizationResponse({ code: code, state: state });
|
||||
}
|
||||
// cleanup state
|
||||
return Promise.all([
|
||||
this.storageBackend.removeItem(AUTHORIZATION_REQUEST_HANDLE_KEY),
|
||||
this.storageBackend.removeItem(authorizationRequestKey(handle)),
|
||||
this.storageBackend.removeItem(authorizationServiceConfigurationKey(handle)),
|
||||
]).then(() => {
|
||||
log('Delivering authorization response');
|
||||
return {
|
||||
request: request,
|
||||
response: authorizationResponse,
|
||||
error: authorizationError,
|
||||
} as AuthorizationRequestResponse;
|
||||
});
|
||||
} else {
|
||||
log('Mismatched request (state and request_uri) dont match.');
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
73
src/Umbraco.Web.UI.Client/src/external/openid/revoke_token_request.ts
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
import type { StringMap } from './types.js';
|
||||
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported token types
|
||||
*/
|
||||
export type TokenTypeHint = 'refresh_token' | 'access_token';
|
||||
|
||||
/**
|
||||
* Represents the Token Request as JSON.
|
||||
*/
|
||||
export interface RevokeTokenRequestJson {
|
||||
token: string;
|
||||
token_type_hint?: TokenTypeHint;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a revoke token request.
|
||||
* For more information look at:
|
||||
* https://tools.ietf.org/html/rfc7009#section-2.1
|
||||
*/
|
||||
export class RevokeTokenRequest {
|
||||
token: string;
|
||||
tokenTypeHint: TokenTypeHint | undefined;
|
||||
clientId: string | undefined;
|
||||
clientSecret: string | undefined;
|
||||
|
||||
constructor(request: RevokeTokenRequestJson) {
|
||||
this.token = request.token;
|
||||
this.tokenTypeHint = request.token_type_hint;
|
||||
this.clientId = request.client_id;
|
||||
this.clientSecret = request.client_secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a TokenRequest to a JavaScript object.
|
||||
*/
|
||||
toJson(): RevokeTokenRequestJson {
|
||||
const json: RevokeTokenRequestJson = { token: this.token };
|
||||
if (this.tokenTypeHint) {
|
||||
json['token_type_hint'] = this.tokenTypeHint;
|
||||
}
|
||||
if (this.clientId) {
|
||||
json['client_id'] = this.clientId;
|
||||
}
|
||||
if (this.clientSecret) {
|
||||
json['client_secret'] = this.clientSecret;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
toStringMap(): StringMap {
|
||||
const json = this.toJson();
|
||||
// :(
|
||||
return json as any;
|
||||
}
|
||||
}
|
||||
101
src/Umbraco.Web.UI.Client/src/external/openid/storage.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A subset of the `Storage` interface which we need for the backends to work.
|
||||
*
|
||||
* Essentially removes the indexable properties and readonly properties from
|
||||
* `Storage` in lib.dom.d.ts. This is so that a custom type can extend it for
|
||||
* testing.
|
||||
*/
|
||||
export interface UnderlyingStorage {
|
||||
readonly length: number;
|
||||
clear(): void;
|
||||
getItem(key: string): string | null;
|
||||
removeItem(key: string): void;
|
||||
setItem(key: string, data: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous storage APIs. All methods return a `Promise`.
|
||||
* All methods take the `DOMString`
|
||||
* IDL type (as it is the lowest common denominator).
|
||||
*/
|
||||
export abstract class StorageBackend {
|
||||
/**
|
||||
* When passed a key `name`, will return that key's value.
|
||||
*/
|
||||
public abstract getItem(name: string): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* When passed a key `name`, will remove that key from the storage.
|
||||
*/
|
||||
public abstract removeItem(name: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* When invoked, will empty all keys out of the storage.
|
||||
*/
|
||||
public abstract clear(): Promise<void>;
|
||||
|
||||
/**
|
||||
* The setItem() method of the `StorageBackend` interface,
|
||||
* when passed a key name and value, will add that key to the storage,
|
||||
* or update that key's value if it already exists.
|
||||
*/
|
||||
public abstract setItem(name: string, value: string): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `StorageBackend` backed by `localstorage`.
|
||||
*/
|
||||
export class LocalStorageBackend extends StorageBackend {
|
||||
private storage: UnderlyingStorage;
|
||||
constructor(storage?: UnderlyingStorage) {
|
||||
super();
|
||||
this.storage = storage || window.localStorage;
|
||||
}
|
||||
|
||||
public getItem(name: string): Promise<string | null> {
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
const value = this.storage.getItem(name);
|
||||
if (value) {
|
||||
resolve(value);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public removeItem(name: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.storage.removeItem(name);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public clear(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.storage.clear();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public setItem(name: string, value: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.storage.setItem(name, value);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
99
src/Umbraco.Web.UI.Client/src/external/openid/token_request.ts
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/* eslint-disable local-rules/exported-string-constant-naming */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { StringMap } from './types.js';
|
||||
|
||||
export const GRANT_TYPE_AUTHORIZATION_CODE = 'authorization_code';
|
||||
export const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token';
|
||||
|
||||
/**
|
||||
* Represents the Token Request as JSON.
|
||||
*/
|
||||
export interface TokenRequestJson {
|
||||
grant_type: string;
|
||||
code?: string;
|
||||
refresh_token?: string;
|
||||
redirect_uri: string;
|
||||
client_id: string;
|
||||
extras?: StringMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an Access Token request.
|
||||
* For more information look at:
|
||||
* https://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
*/
|
||||
export class TokenRequest {
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
grantType: string;
|
||||
code: string | undefined;
|
||||
refreshToken: string | undefined;
|
||||
extras: StringMap | undefined;
|
||||
|
||||
constructor(request: TokenRequestJson) {
|
||||
this.clientId = request.client_id;
|
||||
this.redirectUri = request.redirect_uri;
|
||||
this.grantType = request.grant_type;
|
||||
this.code = request.code;
|
||||
this.refreshToken = request.refresh_token;
|
||||
this.extras = request.extras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a TokenRequest to a JavaScript object.
|
||||
*/
|
||||
toJson(): TokenRequestJson {
|
||||
return {
|
||||
grant_type: this.grantType,
|
||||
code: this.code,
|
||||
refresh_token: this.refreshToken,
|
||||
redirect_uri: this.redirectUri,
|
||||
client_id: this.clientId,
|
||||
extras: this.extras,
|
||||
};
|
||||
}
|
||||
|
||||
toStringMap(): StringMap {
|
||||
const map: StringMap = {
|
||||
grant_type: this.grantType,
|
||||
client_id: this.clientId,
|
||||
redirect_uri: this.redirectUri,
|
||||
};
|
||||
|
||||
if (this.code) {
|
||||
map['code'] = this.code;
|
||||
}
|
||||
|
||||
if (this.refreshToken) {
|
||||
map['refresh_token'] = this.refreshToken;
|
||||
}
|
||||
|
||||
// copy over extras
|
||||
if (this.extras) {
|
||||
for (const extra in this.extras) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(this.extras, extra) &&
|
||||
!Object.prototype.hasOwnProperty.call(map, extra)
|
||||
) {
|
||||
// check before inserting to requestMap
|
||||
map[extra] = this.extras[extra];
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
87
src/Umbraco.Web.UI.Client/src/external/openid/token_request_handler.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { AuthorizationServiceConfiguration } from './authorization_service_configuration.js';
|
||||
import { AppAuthError } from './errors.js';
|
||||
import { BasicQueryStringUtils } from './query_string_utils.js';
|
||||
import type { QueryStringUtils } from './query_string_utils.js';
|
||||
import type { RevokeTokenRequest } from './revoke_token_request.js';
|
||||
import type { TokenRequest } from './token_request.js';
|
||||
import { TokenError, TokenResponse } from './token_response.js';
|
||||
import type { TokenErrorJson, TokenResponseJson } from './token_response.js';
|
||||
import { FetchRequestor, type Requestor } from './xhr.js';
|
||||
|
||||
/**
|
||||
* Represents an interface which can make a token request.
|
||||
*/
|
||||
export interface TokenRequestHandler {
|
||||
/**
|
||||
* Performs the token request, given the service configuration.
|
||||
*/
|
||||
performTokenRequest(configuration: AuthorizationServiceConfiguration, request: TokenRequest): Promise<TokenResponse>;
|
||||
|
||||
performRevokeTokenRequest(
|
||||
configuration: AuthorizationServiceConfiguration,
|
||||
request: RevokeTokenRequest,
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default token request handler.
|
||||
*/
|
||||
export class BaseTokenRequestHandler implements TokenRequestHandler {
|
||||
constructor(
|
||||
public readonly requestor: Requestor = new FetchRequestor(),
|
||||
public readonly utils: QueryStringUtils = new BasicQueryStringUtils(),
|
||||
) {}
|
||||
|
||||
private isTokenResponse(response: TokenResponseJson | TokenErrorJson): response is TokenResponseJson {
|
||||
return (response as TokenErrorJson).error === undefined;
|
||||
}
|
||||
|
||||
performRevokeTokenRequest(
|
||||
configuration: AuthorizationServiceConfiguration,
|
||||
request: RevokeTokenRequest,
|
||||
): Promise<boolean> {
|
||||
const revokeTokenResponse = this.requestor.xhr<boolean>({
|
||||
url: configuration.revocationEndpoint,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
data: this.utils.stringify(request.toStringMap()),
|
||||
});
|
||||
|
||||
return revokeTokenResponse.then((response) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
performTokenRequest(configuration: AuthorizationServiceConfiguration, request: TokenRequest): Promise<TokenResponse> {
|
||||
const tokenResponse = this.requestor.xhr<TokenResponseJson | TokenErrorJson>({
|
||||
url: configuration.tokenEndpoint,
|
||||
method: 'POST',
|
||||
dataType: 'json', // adding implicit dataType
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
data: this.utils.stringify(request.toStringMap()),
|
||||
});
|
||||
|
||||
return tokenResponse.then((response) => {
|
||||
if (this.isTokenResponse(response)) {
|
||||
return new TokenResponse(response);
|
||||
} else {
|
||||
return Promise.reject<TokenResponse>(new AppAuthError(response.error, new TokenError(response)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
137
src/Umbraco.Web.UI.Client/src/external/openid/token_response.ts
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the access token types.
|
||||
* For more information see:
|
||||
* https://tools.ietf.org/html/rfc6749#section-7.1
|
||||
*/
|
||||
export type TokenType = 'bearer' | 'mac';
|
||||
|
||||
/**
|
||||
* Represents the TokenResponse as a JSON Object.
|
||||
*/
|
||||
export interface TokenResponseJson {
|
||||
access_token: string;
|
||||
token_type?: TokenType /* treating token type as optional, as its going to be inferred. */;
|
||||
expires_in?: string /* lifetime in seconds. */;
|
||||
refresh_token?: string;
|
||||
scope?: string;
|
||||
id_token?: string /* https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse */;
|
||||
issued_at?: number /* when was it issued ? */;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the possible error codes from the token endpoint.
|
||||
* For more information look at:
|
||||
* https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
*/
|
||||
export type ErrorType =
|
||||
| 'invalid_request'
|
||||
| 'invalid_client'
|
||||
| 'invalid_grant'
|
||||
| 'unauthorized_client'
|
||||
| 'unsupported_grant_type'
|
||||
| 'invalid_scope';
|
||||
|
||||
/**
|
||||
* Represents the TokenError as a JSON Object.
|
||||
*/
|
||||
export interface TokenErrorJson {
|
||||
error: ErrorType;
|
||||
error_description?: string;
|
||||
error_uri?: string;
|
||||
}
|
||||
|
||||
// constants
|
||||
const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 mins in seconds
|
||||
|
||||
/**
|
||||
* Returns the instant of time in seconds.
|
||||
*/
|
||||
export const nowInSeconds = () => Math.round(new Date().getTime() / 1000);
|
||||
|
||||
/**
|
||||
* Represents the Token Response type.
|
||||
* For more information look at:
|
||||
* https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
*/
|
||||
export class TokenResponse {
|
||||
accessToken: string;
|
||||
tokenType: TokenType;
|
||||
expiresIn: number | undefined;
|
||||
refreshToken: string | undefined;
|
||||
scope: string | undefined;
|
||||
idToken: string | undefined;
|
||||
issuedAt: number;
|
||||
|
||||
constructor(response: TokenResponseJson) {
|
||||
this.accessToken = response.access_token;
|
||||
this.tokenType = response.token_type || 'bearer';
|
||||
if (response.expires_in) {
|
||||
this.expiresIn = parseInt(response.expires_in, 10);
|
||||
}
|
||||
this.refreshToken = response.refresh_token;
|
||||
this.scope = response.scope;
|
||||
this.idToken = response.id_token;
|
||||
this.issuedAt = response.issued_at || nowInSeconds();
|
||||
}
|
||||
|
||||
toJson(): TokenResponseJson {
|
||||
return {
|
||||
access_token: this.accessToken,
|
||||
id_token: this.idToken,
|
||||
refresh_token: this.refreshToken,
|
||||
scope: this.scope,
|
||||
token_type: this.tokenType,
|
||||
issued_at: this.issuedAt,
|
||||
expires_in: this.expiresIn?.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
isValid(buffer: number = AUTH_EXPIRY_BUFFER): boolean {
|
||||
if (this.expiresIn) {
|
||||
const now = nowInSeconds();
|
||||
return now < this.issuedAt + this.expiresIn + buffer;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the Token Error type.
|
||||
* For more information look at:
|
||||
* https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
*/
|
||||
export class TokenError {
|
||||
error: ErrorType;
|
||||
errorDescription: string | undefined;
|
||||
errorUri: string | undefined;
|
||||
|
||||
constructor(tokenError: TokenErrorJson) {
|
||||
this.error = tokenError.error;
|
||||
this.errorDescription = tokenError.error_description;
|
||||
this.errorUri = tokenError.error_uri;
|
||||
}
|
||||
|
||||
toJson(): TokenErrorJson {
|
||||
return {
|
||||
error: this.error,
|
||||
error_description: this.errorDescription,
|
||||
error_uri: this.errorUri,
|
||||
};
|
||||
}
|
||||
}
|
||||
38
src/Umbraco.Web.UI.Client/src/external/openid/types.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface StringMap {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a window.location like object.
|
||||
*/
|
||||
export interface LocationLike {
|
||||
hash: string;
|
||||
host: string;
|
||||
origin: string;
|
||||
hostname: string;
|
||||
pathname: string;
|
||||
port: string;
|
||||
protocol: string;
|
||||
search: string;
|
||||
assign(url: string): void;
|
||||
}
|
||||
|
||||
export interface XhrRequestInit extends RequestInit {
|
||||
url: string;
|
||||
dataType?: string;
|
||||
data?: any;
|
||||
}
|
||||
78
src/Umbraco.Web.UI.Client/src/external/openid/xhr.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable local-rules/umb-class-prefix */
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AppAuthError } from './errors.js';
|
||||
import type { XhrRequestInit } from './types.js';
|
||||
|
||||
/**
|
||||
* An class that abstracts away the ability to make an XMLHttpRequest.
|
||||
*/
|
||||
export abstract class Requestor {
|
||||
abstract xhr<T>(settings: unknown): Promise<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses fetch API to make Ajax requests
|
||||
*/
|
||||
export class FetchRequestor extends Requestor {
|
||||
xhr<T>(settings: XhrRequestInit): Promise<T> {
|
||||
if (!settings.url) {
|
||||
return Promise.reject(new AppAuthError('A URL must be provided.'));
|
||||
}
|
||||
const url: URL = new URL(<string>settings.url);
|
||||
const requestInit: RequestInit = {};
|
||||
requestInit.method = settings.method;
|
||||
requestInit.mode = 'cors';
|
||||
|
||||
if (settings.data) {
|
||||
if (settings.method && settings.method.toUpperCase() === 'POST') {
|
||||
requestInit.body = <string>settings.data;
|
||||
} else {
|
||||
const searchParams = new URLSearchParams(settings.data);
|
||||
searchParams.forEach((value, key) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Set the request headers
|
||||
requestInit.headers = {};
|
||||
if (settings.headers) {
|
||||
requestInit.headers = settings.headers;
|
||||
}
|
||||
|
||||
const isJsonDataType = settings.dataType && settings.dataType.toLowerCase() === 'json';
|
||||
|
||||
// Set 'Accept' header value for json requests (Taken from
|
||||
// https://github.com/jquery/jquery/blob/e0d941156900a6bff7c098c8ea7290528e468cf8/src/ajax.js#L644
|
||||
// )
|
||||
if (isJsonDataType) {
|
||||
(requestInit.headers as any).Accept = 'application/json, text/javascript, */*; q=0.01';
|
||||
}
|
||||
|
||||
return fetch(url.toString(), requestInit).then((response) => {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (isJsonDataType || (contentType && contentType.indexOf('application/json') !== -1)) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.text();
|
||||
}
|
||||
} else {
|
||||
return Promise.reject(new AppAuthError(response.status.toString(), response.statusText));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,5 @@ export {
|
||||
switchMap,
|
||||
filter,
|
||||
startWith,
|
||||
skip,
|
||||
} from 'rxjs';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { ManifestBase } from '../types/index.js';
|
||||
import { isManifestBaseType } from '../type-guards/index.js';
|
||||
import {
|
||||
PackageResource,
|
||||
OpenAPI,
|
||||
type PackageManifestResponseModel,
|
||||
} from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { OpenAPI, ManifestResource, type ManifestResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -26,7 +22,19 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase {
|
||||
* @remark Users must have the BACKOFFICE_ACCESS permission to access this method.
|
||||
*/
|
||||
public async registerAllExtensions() {
|
||||
const { data: packages } = await tryExecuteAndNotify(this, PackageResource.getPackageManifest());
|
||||
const { data: packages } = await tryExecuteAndNotify(this, ManifestResource.getManifestManifest());
|
||||
if (packages) {
|
||||
await this.#loadServerPackages(packages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all private extensions from the server.
|
||||
* This is used to register all private extensions that are available to the user.
|
||||
* @remark Users must have the BACKOFFICE_ACCESS permission to access this method.
|
||||
*/
|
||||
public async registerPrivateExtensions() {
|
||||
const { data: packages } = await tryExecuteAndNotify(this, ManifestResource.getManifestManifestPrivate());
|
||||
if (packages) {
|
||||
await this.#loadServerPackages(packages);
|
||||
}
|
||||
@@ -38,13 +46,13 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase {
|
||||
* @remark Any user can access this method without any permissions.
|
||||
*/
|
||||
public async registerPublicExtensions() {
|
||||
const { data: packages } = await tryExecuteAndNotify(this, PackageResource.getPackageManifestPublic());
|
||||
const { data: packages } = await tryExecuteAndNotify(this, ManifestResource.getManifestManifestPublic());
|
||||
if (packages) {
|
||||
await this.#loadServerPackages(packages);
|
||||
}
|
||||
}
|
||||
|
||||
async #loadServerPackages(packages: PackageManifestResponseModel[]) {
|
||||
async #loadServerPackages(packages: ManifestResponseModel[]) {
|
||||
const extensions: ManifestBase[] = [];
|
||||
|
||||
packages.forEach((p) => {
|
||||
|
||||
@@ -6,4 +6,4 @@ export * from './load-manifest-api.function.js';
|
||||
export * from './load-manifest-element.function.js';
|
||||
export * from './load-manifest-plain-css.function.js';
|
||||
export * from './load-manifest-plain-js.function.js';
|
||||
export * from './types.js';
|
||||
export type * from './types.js';
|
||||
|
||||
@@ -2,7 +2,7 @@ export * from './condition/index.js';
|
||||
export * from './controller/index.js';
|
||||
export * from './functions/index.js';
|
||||
export * from './initializers/index.js';
|
||||
export * from './models/index.js';
|
||||
export type * from './models/index.js';
|
||||
export * from './registry/extension.registry.js';
|
||||
export * from './type-guards/index.js';
|
||||
export * from './types/index.js';
|
||||
export type * from './types/index.js';
|
||||
|
||||
@@ -274,6 +274,22 @@ describe('UmbLocalizeController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('string', () => {
|
||||
it('should replace words prefixed with a # with translated value', async () => {
|
||||
const str = '#close';
|
||||
const str2 = '#logout #close';
|
||||
const str3 = '#logout #missing_translation_key #close';
|
||||
expect(controller.string(str)).to.equal('Close');
|
||||
expect(controller.string(str2)).to.equal('Log out Close');
|
||||
expect(controller.string(str3)).to.equal('Log out #missing_translation_key Close');
|
||||
});
|
||||
|
||||
it('should return the word with a # if the word is not found', async () => {
|
||||
const str = '#missing_translation_key';
|
||||
expect(controller.string(str)).to.equal('#missing_translation_key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('host element', () => {
|
||||
let element: UmbLocalizeControllerHostElement;
|
||||
|
||||
|
||||
@@ -159,4 +159,21 @@ export class UmbLocalizationController<LocalizationSetType extends UmbLocalizati
|
||||
relativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {
|
||||
return new Intl.RelativeTimeFormat(this.lang(), options).format(value, unit);
|
||||
}
|
||||
|
||||
string(text: string): string {
|
||||
// find all words starting with #
|
||||
const regex = /#\w+/g;
|
||||
|
||||
const localizedText = text.replace(regex, (match: string) => {
|
||||
const key = match.slice(1);
|
||||
// TODO: find solution to pass dynamic string to term
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const localized = this.term(key);
|
||||
// we didn't find a localized string, so we return the original string with the #
|
||||
return localized === key ? match : localized;
|
||||
});
|
||||
|
||||
return localizedText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {
|
||||
|
||||
switch (import.meta.env.VITE_UMBRACO_EXTENSION_MOCKS) {
|
||||
case 'on':
|
||||
handlers.push(manifestsHandlers.manifestDevelopmentHandler);
|
||||
handlers.push(...manifestsHandlers.manifestDevelopmentHandlers);
|
||||
break;
|
||||
|
||||
default:
|
||||
handlers.push(manifestsHandlers.manifestEmptyHandler);
|
||||
handlers.push(...manifestsHandlers.manifestEmptyHandlers);
|
||||
}
|
||||
|
||||
export { handlers };
|
||||
|
||||
@@ -20,7 +20,7 @@ import { handlers as configHandlers } from './handlers/config.handlers.js';
|
||||
export const handlers = [
|
||||
serverHandlers.serverRunningHandler,
|
||||
serverHandlers.serverInformationHandler,
|
||||
manifestsHandlers.manifestEmptyHandler,
|
||||
...manifestsHandlers.manifestEmptyHandlers,
|
||||
...auditLogHandlers,
|
||||
...installHandlers,
|
||||
...upgradeHandlers,
|
||||
|
||||
@@ -3,107 +3,159 @@ const { rest } = window.MockServiceWorker;
|
||||
import type { PackageManifestResponse } from '../../packages/packages/types.js';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const manifestDevelopmentHandler = rest.get(umbracoPath('/package/manifest'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([
|
||||
{
|
||||
name: 'My Package Name',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'bundle',
|
||||
alias: 'My.Package.Bundle',
|
||||
name: 'My Package Bundle',
|
||||
js: '/App_Plugins/custom-bundle-package/index.js',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Named Package',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'My.Section.Custom',
|
||||
name: 'Custom Section',
|
||||
js: '/App_Plugins/section.js',
|
||||
elementName: 'my-section-custom',
|
||||
weight: 1,
|
||||
meta: {
|
||||
label: 'Custom',
|
||||
pathname: 'my-custom',
|
||||
export const manifestDevelopmentHandlers = [
|
||||
rest.get(umbracoPath('/manifest/manifest/private'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([
|
||||
{
|
||||
name: 'My Package Name',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'bundle',
|
||||
alias: 'My.Package.Bundle',
|
||||
name: 'My Package Bundle',
|
||||
js: '/App_Plugins/custom-bundle-package/index.js',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'My.PropertyEditorUI.Custom',
|
||||
name: 'My Custom Property Editor UI',
|
||||
js: '/App_Plugins/property-editor.js',
|
||||
elementName: 'my-property-editor-ui-custom',
|
||||
meta: {
|
||||
label: 'My Custom Property',
|
||||
icon: 'document',
|
||||
group: 'Common',
|
||||
propertyEditorSchema: 'Umbraco.TextBox',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Named Package',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'My.Section.Custom',
|
||||
name: 'Custom Section',
|
||||
js: '/App_Plugins/section.js',
|
||||
elementName: 'my-section-custom',
|
||||
weight: 1,
|
||||
meta: {
|
||||
label: 'Custom',
|
||||
pathname: 'my-custom',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with an entry point',
|
||||
extensions: [
|
||||
{
|
||||
type: 'entryPoint',
|
||||
name: 'My Custom Entry Point',
|
||||
alias: 'My.Entrypoint.Custom',
|
||||
js: '/App_Plugins/custom-entrypoint.js',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with a view',
|
||||
extensions: [
|
||||
{
|
||||
type: 'packageView',
|
||||
alias: 'My.PackageView.Custom',
|
||||
name: 'My Custom Package View',
|
||||
js: '/App_Plugins/package-view.js',
|
||||
meta: {
|
||||
packageName: 'Package with a view',
|
||||
{
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'My.PropertyEditorUI.Custom',
|
||||
name: 'My Custom Property Editor UI',
|
||||
js: '/App_Plugins/property-editor.js',
|
||||
elementName: 'my-property-editor-ui-custom',
|
||||
meta: {
|
||||
label: 'My Custom Property',
|
||||
icon: 'document',
|
||||
group: 'Common',
|
||||
propertyEditorSchema: 'Umbraco.TextBox',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.Google',
|
||||
name: 'My Custom Google MFA Provider',
|
||||
forProviderName: 'Google Authenticator',
|
||||
},
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.SMS',
|
||||
name: 'My Custom SMS MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with an entry point',
|
||||
extensions: [
|
||||
{
|
||||
type: 'entryPoint',
|
||||
name: 'My Custom Entry Point',
|
||||
alias: 'My.Entrypoint.Custom',
|
||||
js: '/App_Plugins/custom-entrypoint.js',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.Google',
|
||||
name: 'My Custom Google MFA Provider',
|
||||
forProviderName: 'Google Authenticator',
|
||||
},
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.SMS',
|
||||
name: 'My Custom SMS MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with a view',
|
||||
extensions: [
|
||||
{
|
||||
type: 'packageView',
|
||||
alias: 'My.PackageView.Custom',
|
||||
name: 'My Custom Package View',
|
||||
js: '/App_Plugins/package-view.js',
|
||||
meta: {
|
||||
packageName: 'Package with a view',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom',
|
||||
name: 'My Custom MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
);
|
||||
}),
|
||||
rest.get(umbracoPath('/manifest/manifest/public'), (_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([
|
||||
{
|
||||
name: 'My Auth Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'My.AuthProvider.Google',
|
||||
name: 'My Custom Auth Provider',
|
||||
forProviderName: 'Umbraco.Google',
|
||||
meta: {
|
||||
label: 'Sign in with Google',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'My.AuthProvider.Github',
|
||||
name: 'My Github Auth Provider',
|
||||
forProviderName: 'Umbraco.Github',
|
||||
meta: {
|
||||
label: 'GitHub',
|
||||
defaultView: {
|
||||
look: 'primary',
|
||||
icon: 'icon-github',
|
||||
color: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
export const manifestEmptyHandler = rest.get(umbracoPath('/package/manifest'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([]),
|
||||
);
|
||||
});
|
||||
export const manifestEmptyHandlers = [
|
||||
rest.get(umbracoPath('/manifest/manifest/private'), (_req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json<PackageManifestResponse>([]));
|
||||
}),
|
||||
rest.get(umbracoPath('/manifest/manifest/public'), (_req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json<PackageManifestResponse>([]));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -21,7 +21,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
name: 'Save Block Grid Area Type Workspace Action',
|
||||
api: UmbSubmitWorkspaceAction,
|
||||
meta: {
|
||||
label: 'Submit',
|
||||
label: '#general_submit',
|
||||
look: 'primary',
|
||||
color: 'positive',
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ export const workspaceViews: Array<ManifestWorkspaceView> = [
|
||||
js: () => import('./settings.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Settings',
|
||||
label: '#general_settings',
|
||||
pathname: 'settings',
|
||||
icon: 'icon-settings',
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ export const workspaceViews: Array<ManifestWorkspaceView> = [
|
||||
js: () => import('./block-grid-type-workspace-view-settings.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Settings',
|
||||
label: '#general_settings',
|
||||
pathname: 'settings',
|
||||
icon: 'icon-settings',
|
||||
},
|
||||
@@ -27,7 +27,7 @@ export const workspaceViews: Array<ManifestWorkspaceView> = [
|
||||
js: () => import('./block-grid-type-workspace-view-areas.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Areas',
|
||||
label: '#blockEditor_areas',
|
||||
pathname: 'areas',
|
||||
icon: 'icon-grid',
|
||||
},
|
||||
@@ -45,7 +45,7 @@ export const workspaceViews: Array<ManifestWorkspaceView> = [
|
||||
js: () => import('./block-grid-type-workspace-view-advanced.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Advanced',
|
||||
label: '#blockEditor_tabAreas',
|
||||
pathname: 'advanced',
|
||||
icon: 'icon-wrench',
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ export const workspaceViews: Array<ManifestWorkspaceView> = [
|
||||
js: () => import('./block-list-type-workspace-view.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Settings',
|
||||
label: '#blockEditor_tabBlockSettings',
|
||||
pathname: 'settings',
|
||||
icon: 'icon-settings',
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ export const workspaceViews: Array<ManifestWorkspaceView> = [
|
||||
js: () => import('./block-rte-type-workspace-view.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Settings',
|
||||
label: '#general_settings',
|
||||
pathname: 'settings',
|
||||
icon: 'icon-settings',
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ export const manifests: Array<ManifestWorkspaceActions> = [
|
||||
name: 'Save Block Type Workspace Action',
|
||||
api: UmbSubmitWorkspaceAction,
|
||||
meta: {
|
||||
label: 'Submit',
|
||||
label: '#general_submit',
|
||||
look: 'primary',
|
||||
color: 'positive',
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
name: 'Save Block Type Workspace Action',
|
||||
api: UmbSubmitWorkspaceAction,
|
||||
meta: {
|
||||
label: 'Submit',
|
||||
label: '#general_submit',
|
||||
look: 'primary',
|
||||
color: 'positive',
|
||||
},
|
||||
@@ -38,7 +38,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
js: () => import('./views/edit/block-workspace-view-edit.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Content',
|
||||
label: '#general_content',
|
||||
pathname: 'content',
|
||||
icon: 'icon-document',
|
||||
blockElementManagerName: 'content',
|
||||
@@ -63,7 +63,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
js: () => import('./views/edit/block-workspace-view-edit.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Settings',
|
||||
label: '#general_settings',
|
||||
pathname: 'settings',
|
||||
icon: 'icon-settings',
|
||||
blockElementManagerName: 'settings',
|
||||
|
||||
@@ -89,6 +89,7 @@ export class UmbAuthFlow {
|
||||
// state
|
||||
readonly #configuration: AuthorizationServiceConfiguration;
|
||||
readonly #redirectUri: string;
|
||||
readonly #postLogoutRedirectUri: string;
|
||||
readonly #clientId: string;
|
||||
readonly #scope: string;
|
||||
|
||||
@@ -99,10 +100,12 @@ export class UmbAuthFlow {
|
||||
constructor(
|
||||
openIdConnectUrl: string,
|
||||
redirectUri: string,
|
||||
postLogoutRedirectUri: string,
|
||||
clientId = 'umbraco-back-office',
|
||||
scope = 'offline_access',
|
||||
) {
|
||||
this.#redirectUri = redirectUri;
|
||||
this.#postLogoutRedirectUri = postLogoutRedirectUri;
|
||||
this.#clientId = clientId;
|
||||
this.#scope = scope;
|
||||
|
||||
@@ -187,15 +190,23 @@ export class UmbAuthFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will make an authorization request to the server.
|
||||
* Make an authorization request to the server using the specified identity provider.
|
||||
* This method will redirect the user to the authorization endpoint of the server.
|
||||
*
|
||||
* @param username The username to use for the authorization request. It will be provided to the OpenID server as a hint.
|
||||
* @param identityProvider The identity provider to use for the authorization request.
|
||||
* @param usernameHint (Optional) The username to use for the authorization request. It will be provided to the OpenID server as a hint.
|
||||
*/
|
||||
makeAuthorizationRequest(username?: string): void {
|
||||
makeAuthorizationRequest(identityProvider: string, usernameHint?: string): void {
|
||||
const extras: StringMap = { prompt: 'consent', access_type: 'offline' };
|
||||
|
||||
if (username) {
|
||||
extras['login_hint'] = username;
|
||||
// If the identity provider is not 'Umbraco', we will add it to the extras.
|
||||
if (identityProvider !== 'Umbraco') {
|
||||
extras['identity_provider'] = identityProvider;
|
||||
}
|
||||
|
||||
// If there is a username hint, we will add it to the extras.
|
||||
if (usernameHint) {
|
||||
extras['login_hint'] = usernameHint;
|
||||
}
|
||||
|
||||
// create a request
|
||||
@@ -275,7 +286,7 @@ export class UmbAuthFlow {
|
||||
// which will redirect the user back to the client
|
||||
// and the client will then try and log in again (if the user is not logged in)
|
||||
// which will redirect the user to the login page
|
||||
location.href = `${this.#configuration.endSessionEndpoint}?post_logout_redirect_uri=${this.#redirectUri}`;
|
||||
location.href = `${this.#configuration.endSessionEndpoint}?post_logout_redirect_uri=${this.#postLogoutRedirectUri}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { umbExtensionsRegistry } from '../extension-registry/index.js';
|
||||
import { UmbAuthFlow } from './auth-flow.js';
|
||||
import { UMB_AUTH_CONTEXT } from './auth.context.token.js';
|
||||
import type { UmbOpenApiConfiguration } from './models/openApiConfiguration.js';
|
||||
@@ -5,11 +6,15 @@ import { OpenAPI } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { ReplaySubject, filter, switchMap } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
|
||||
export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
#isAuthorized = new UmbBooleanState<boolean>(false);
|
||||
readonly isAuthorized = this.#isAuthorized.asObservable();
|
||||
|
||||
#isInitialized = new ReplaySubject<boolean>(1);
|
||||
readonly isInitialized = this.#isInitialized.asObservable().pipe(filter((isInitialized) => isInitialized));
|
||||
|
||||
#isBypassed = false;
|
||||
#serverUrl;
|
||||
#backofficePath;
|
||||
@@ -21,14 +26,16 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
this.#serverUrl = serverUrl;
|
||||
this.#backofficePath = backofficePath;
|
||||
|
||||
this.#authFlow = new UmbAuthFlow(serverUrl, this.#getRedirectUrl());
|
||||
this.#authFlow = new UmbAuthFlow(serverUrl, this.getRedirectUrl(), this.getPostLogoutRedirectUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the login flow.
|
||||
* @param identityProvider The provider to use for login. Default is 'Umbraco'.
|
||||
* @param usernameHint The username hint to use for login.
|
||||
*/
|
||||
makeAuthorizationRequest() {
|
||||
return this.#authFlow.makeAuthorizationRequest();
|
||||
makeAuthorizationRequest(identityProvider = 'Umbraco', usernameHint?: string) {
|
||||
return this.#authFlow.makeAuthorizationRequest(identityProvider, usernameHint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +95,16 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
return this.#authFlow.clearTokenStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the case where the user has timed out, i.e. the token has expired.
|
||||
* This will clear the token storage and set the user as unauthorized.
|
||||
* @memberof UmbAuthContext
|
||||
*/
|
||||
timeOut() {
|
||||
this.clearTokenStorage();
|
||||
this.#isAuthorized.setValue(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the user out by removing any tokens from the browser.
|
||||
* @memberof UmbAuthContext
|
||||
@@ -141,7 +158,19 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
};
|
||||
}
|
||||
|
||||
#getRedirectUrl() {
|
||||
setInitialized() {
|
||||
this.#isInitialized.next(true);
|
||||
}
|
||||
|
||||
getAuthProviders() {
|
||||
return this.isInitialized.pipe(switchMap(() => umbExtensionsRegistry.byType('authProvider')));
|
||||
}
|
||||
|
||||
getRedirectUrl() {
|
||||
return `${window.location.origin}${this.#backofficePath}`;
|
||||
}
|
||||
|
||||
getPostLogoutRedirectUrl() {
|
||||
return `${window.location.origin}${this.#backofficePath.endsWith('/') ? this.#backofficePath : this.#backofficePath + '/'}logout`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { ManifestAuthProvider } from '../../extension-registry/models/index.js';
|
||||
import type { UmbAuthProviderDefaultProps } from '../types.js';
|
||||
import { UmbLitElement } from '../../lit-element/lit-element.element.js';
|
||||
import { UmbTextStyles } from '../../style/index.js';
|
||||
import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@customElement('umb-auth-provider-default')
|
||||
export class UmbAuthProviderDefaultElement extends UmbLitElement implements UmbAuthProviderDefaultProps {
|
||||
@property({ attribute: false })
|
||||
manifest!: ManifestAuthProvider;
|
||||
|
||||
@property({ attribute: false })
|
||||
onSubmit!: (providerName: string, loginHint?: string) => void;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.setAttribute('part', 'auth-provider-default');
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button
|
||||
type="button"
|
||||
@click=${() => this.onSubmit(this.manifest.forProviderName)}
|
||||
id="auth-provider-button"
|
||||
.label=${this.manifest.meta?.label ?? this.manifest.forProviderName}
|
||||
.look=${this.manifest.meta?.defaultView?.look ?? 'outline'}
|
||||
.color=${this.manifest.meta?.defaultView?.color ?? 'default'}>
|
||||
${this.manifest.meta?.defaultView?.icon
|
||||
? html`<uui-icon .name=${this.manifest.meta?.defaultView?.icon}></uui-icon>`
|
||||
: nothing}
|
||||
${this.manifest.meta?.label ?? this.manifest.forProviderName}
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#auth-provider-button {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-auth-provider-default': UmbAuthProviderDefaultElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './auth-provider-default.element.js';
|
||||
@@ -1,3 +1,9 @@
|
||||
import './components/index.js';
|
||||
|
||||
export * from './auth.context.js';
|
||||
export * from './auth.context.token.js';
|
||||
export * from './modals/index.js';
|
||||
export * from './models/openApiConfiguration.js';
|
||||
export * from './components/index.js';
|
||||
|
||||
export type * from './types.js';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { ManifestTypes } from '../extension-registry/models/index.js';
|
||||
import { manifests as modalManifests } from './modals/manifests.js';
|
||||
import { manifests as providerManifests } from './providers/manifests.js';
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [...modalManifests, ...providerManifests];
|
||||
@@ -0,0 +1 @@
|
||||
export * from './umb-app-auth-modal.token.js';
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestModal> = [
|
||||
{
|
||||
type: 'modal',
|
||||
alias: 'Umb.Modal.AppAuth',
|
||||
name: 'Umb App Auth Modal',
|
||||
js: () => import('./umb-app-auth-modal.element.js'),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,92 @@
|
||||
import { UmbModalBaseElement } from '../../modal/index.js';
|
||||
import type { UmbModalAppAuthConfig, UmbModalAppAuthValue } from './umb-app-auth-modal.token.js';
|
||||
import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
|
||||
@customElement('umb-app-auth-modal')
|
||||
export class UmbAppAuthModalElement extends UmbModalBaseElement<UmbModalAppAuthConfig, UmbModalAppAuthValue> {
|
||||
get props() {
|
||||
return {
|
||||
userLoginState: this.data?.userLoginState ?? 'loggingIn',
|
||||
onSubmit: this.onSubmit,
|
||||
};
|
||||
}
|
||||
|
||||
get headline() {
|
||||
return this.data?.userLoginState === 'timedOut'
|
||||
? this.localize.term('login_instruction')
|
||||
: this.localize.term(
|
||||
[
|
||||
'login_greeting0',
|
||||
'login_greeting1',
|
||||
'login_greeting2',
|
||||
'login_greeting3',
|
||||
'login_greeting4',
|
||||
'login_greeting5',
|
||||
'login_greeting6',
|
||||
][new Date().getDay()],
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-body-layout id="login-layout">
|
||||
<h1 id="greeting" slot="header">${this.headline}</h1>
|
||||
${this.data?.userLoginState === 'timedOut'
|
||||
? html`<p style="margin-top:0">${this.localize.term('login_timeout')}</p>`
|
||||
: ''}
|
||||
<umb-extension-slot
|
||||
id="providers"
|
||||
type="authProvider"
|
||||
default-element="umb-auth-provider-default"
|
||||
.props=${this.props}></umb-extension-slot>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
private onSubmit = (providerName: string) => {
|
||||
this.value = { providerName };
|
||||
this._submitModal();
|
||||
};
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
--umb-body-layout-color-background: #fff;
|
||||
}
|
||||
|
||||
#login-layout {
|
||||
width: 380px;
|
||||
max-width: 80vw;
|
||||
min-height: 327px;
|
||||
}
|
||||
|
||||
#greeting {
|
||||
width: 100%;
|
||||
color: var(--umb-login-header-color, var(--uui-color-interactive));
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: var(--umb-login-header-font-size, 2rem);
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#providers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-5);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbAppAuthModalElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-app-auth-modal': UmbAppAuthModalElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { UmbModalToken } from '../../modal/token/index.js';
|
||||
import type { UmbUserLoginState } from '../types.js';
|
||||
|
||||
export type UmbModalAppAuthConfig = {
|
||||
userLoginState: UmbUserLoginState;
|
||||
};
|
||||
|
||||
export type UmbModalAppAuthValue = {
|
||||
/**
|
||||
* The name of the provider that the user has selected to authenticate with.
|
||||
* @required
|
||||
*/
|
||||
providerName?: string;
|
||||
|
||||
/**
|
||||
* The login hint that the user has provided to the provider.
|
||||
* @optional
|
||||
*/
|
||||
loginHint?: string;
|
||||
};
|
||||
|
||||
export const UMB_MODAL_APP_AUTH = new UmbModalToken<UmbModalAppAuthConfig, UmbModalAppAuthValue>('Umb.Modal.AppAuth', {
|
||||
modal: {
|
||||
type: 'dialog',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ManifestAuthProvider } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestAuthProvider> = [
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'Umb.AuthProviders.Umbraco',
|
||||
name: 'Umbraco login provider',
|
||||
forProviderName: 'Umbraco',
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Sign in with Umbraco',
|
||||
defaultView: {
|
||||
icon: 'icon-umbraco',
|
||||
look: 'primary',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
26
src/Umbraco.Web.UI.Client/src/packages/core/auth/types.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { ManifestAuthProvider } from '../extension-registry/index.js';
|
||||
|
||||
/**
|
||||
* User login state that can be used to determine the current state of the user.
|
||||
* @example 'loggedIn'
|
||||
*/
|
||||
export type UmbUserLoginState = 'loggingIn' | 'loggedOut' | 'timedOut';
|
||||
|
||||
export interface UmbAuthProviderDefaultProps {
|
||||
/**
|
||||
* The manifest for the registered provider.
|
||||
*/
|
||||
manifest?: ManifestAuthProvider;
|
||||
|
||||
/**
|
||||
* The current user login state.
|
||||
*/
|
||||
userLoginState?: UmbUserLoginState;
|
||||
|
||||
/**
|
||||
* Callback that is called when the user selects a provider.
|
||||
* @param providerName The name of the provider that the user selected.
|
||||
* @param loginHint The login hint to use for login if available.
|
||||
*/
|
||||
onSubmit: (providerName: string, loginHint?: string) => void;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import type { ManifestCollectionView } from '../extension-registry/models/index.js';
|
||||
import { umbExtensionsRegistry } from '../extension-registry/index.js';
|
||||
import { UmbCollectionViewManager } from './collection-view.manager.js';
|
||||
import { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@customElement('test-my-controller-host')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, repeat, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, repeat, customElement, state, nothing, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbModalManagerContext, UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UmbModalElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -13,6 +13,9 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
@state()
|
||||
_modals: Array<UmbModalContext> = [];
|
||||
|
||||
@property({ reflect: true, attribute: 'fill-background' })
|
||||
fillBackground = false;
|
||||
|
||||
private _modalManager?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
@@ -35,6 +38,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
/** We cannot render the umb-modal element directly in the uui-modal-container because it wont get recognized by UUI.
|
||||
* We therefore have a helper class which creates the uui-modal element and returns it. */
|
||||
#createModalElements(modals: Array<UmbModalContext>) {
|
||||
this.removeAttribute('fill-background');
|
||||
const oldValue = this._modals;
|
||||
this._modals = modals;
|
||||
|
||||
@@ -61,6 +65,15 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
modal.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modal.key));
|
||||
|
||||
this._modalElementMap.set(modal.key, modalElement);
|
||||
|
||||
// If any of the modals are fillBackground, set the fillBackground property to true
|
||||
if (modal.backdropBackground) {
|
||||
this.fillBackground = true;
|
||||
this.shadowRoot
|
||||
?.getElementById('container')
|
||||
?.style.setProperty('--backdrop-background', modal.backdropBackground);
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
@@ -78,13 +91,13 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-modal-container>
|
||||
<uui-modal-container id="container">
|
||||
${this._modals.length > 0
|
||||
? repeat(
|
||||
this._modals,
|
||||
(modal) => modal.key,
|
||||
(modal) => this.#renderModal(modal.key),
|
||||
)
|
||||
)
|
||||
: ''}
|
||||
</uui-modal-container>
|
||||
`;
|
||||
@@ -97,6 +110,10 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
:host([fill-background]) #container::after {
|
||||
background: var(--backdrop-background);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ export class UmbBodyLayoutElement extends LitElement {
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
background-color: var(--uui-color-background);
|
||||
background-color: var(--umb-body-layout-color-background, var(--uui-color-background));
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -11,7 +11,7 @@ export const contentTypeDesignEditorManifest: UmbBackofficeManifestKind = {
|
||||
element: () => import('./content-type-design-editor.element.js'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Design',
|
||||
label: '#general_design',
|
||||
pathname: 'design',
|
||||
icon: 'icon-document-dashed-line',
|
||||
},
|
||||
|
||||
@@ -11,11 +11,11 @@ export const manifest: UmbBackofficeManifestKind = {
|
||||
type: 'entityAction',
|
||||
kind: 'delete',
|
||||
api: () => import('./delete.action.js'),
|
||||
weight: 900,
|
||||
weight: 1100,
|
||||
forEntityTypes: [],
|
||||
meta: {
|
||||
icon: 'icon-trash',
|
||||
label: 'Delete...',
|
||||
label: '#actions_delete',
|
||||
itemRepositoryAlias: '',
|
||||
detailRepositoryAlias: '',
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = {
|
||||
forEntityTypes: [],
|
||||
meta: {
|
||||
icon: 'icon-documents',
|
||||
label: 'Duplicate to...',
|
||||
label: '#actions_copy',
|
||||
itemRepositoryAlias: '',
|
||||
duplicateRepositoryAlias: '',
|
||||
pickerModal: '',
|
||||
|
||||
@@ -2,4 +2,3 @@ export * from './duplicate/duplicate.action.js';
|
||||
export * from './delete/delete.action.js';
|
||||
export * from './move/move.action.js';
|
||||
export * from './sort-children-of/sort-children-of.action.js';
|
||||
export * from './trash/trash.action.js';
|
||||
|
||||
@@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = {
|
||||
forEntityTypes: [],
|
||||
meta: {
|
||||
icon: 'icon-enter',
|
||||
label: 'Move to (TBD)...',
|
||||
label: '#actions_move',
|
||||
itemRepositoryAlias: '',
|
||||
moveRepositoryAlias: '',
|
||||
pickerModal: '',
|
||||
|
||||
@@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = {
|
||||
forEntityTypes: [],
|
||||
meta: {
|
||||
icon: 'icon-height',
|
||||
label: 'Sort Children...',
|
||||
label: '#actions_sort',
|
||||
itemRepositoryAlias: '',
|
||||
sortRepositoryAlias: '',
|
||||
},
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { UmbEntityActionBase } from '../../entity-action-base.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
//import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export class UmbTrashEntityAction extends UmbEntityActionBase<any> {
|
||||
constructor(host: UmbControllerHost, args: any) {
|
||||
super(host, args);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
console.log(`execute trash for: ${this.args.unique}`);
|
||||
/*
|
||||
if (!this.unique) throw new Error('Unique is not available');
|
||||
if (!this.repository) return;
|
||||
|
||||
const { data } = await this.repository.requestItems([this.unique]);
|
||||
|
||||
if (data) {
|
||||
const item = data[0];
|
||||
|
||||
await umbConfirmModal(this._host, {
|
||||
headline: `Trash ${item.name}`,
|
||||
content: 'Are you sure you want to move this item to the recycle bin?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Trash',
|
||||
});
|
||||
|
||||
this.repository?.trash(this.unique);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbTrashEntityAction;
|
||||