V13: Allow external login custom views to see where they are being shown (#15251)

* fix: make sure isTimedOut state is forwarded to the login screen

* fix: make umb-custom-view work without attributeChangedCallback

* style subheadline

* add support for userViewState on external login custom views

* fix: subheadline should be a span

* check for isTimedOut on internal login screen

* reformat code

* add args to external login provider custom view

* send args to custom view

* set state to "logout" when clicking the logout button

* force user to new login page /umbraco/login?logout=true if logged out
This commit is contained in:
Jacob Overgaard
2023-11-20 08:52:20 +01:00
committed by GitHub
parent 77288caeed
commit 8447123ef3
10 changed files with 93 additions and 43 deletions

View File

@@ -113,6 +113,7 @@
<umb-editors ng-show="infiniteMode"></umb-editors>
<umb-login ng-if="login.show"
is-timed-out="login.isTimedOut"
on-login="hideLoginScreen()">
</umb-login>
</umb-backoffice-icon-registry>

View File

@@ -156,7 +156,9 @@ angular.module('umbraco.services')
lastServerTimeoutSet = null;
currentUser = null;
openLoginDialog(isLogout === undefined ? true : !isLogout);
if (!isLogout) {
openLoginDialog(true);
}
}
// Register a handler for when an item is added to the retry queue
@@ -231,15 +233,11 @@ angular.module('umbraco.services')
return authResource.performLogout()
.then(function (data) {
userAuthExpired();
userAuthExpired(true);
if (data && data.signOutRedirectUrl) {
$window.location.replace(data.signOutRedirectUrl);
}
else {
//done!
return null;
}
const signOutRedirectUrl = data && data.signOutRedirectUrl ? data.signOutRedirectUrl : 'login?logout=true';
$window.location.replace(signOutRedirectUrl);
});
},

View File

@@ -83,7 +83,7 @@ function MainController($scope, $location, appState, treeService, notificationsS
evts.push(eventsService.on("app.notAuthenticated", function (evt, data) {
$scope.authenticated = null;
$scope.user = null;
const isTimedOut = data && data.isTimedOut ? true : false;
const isTimedOut = !!(data && data.isTimedOut);
$scope.showLoginScreen(isTimedOut);

View File

@@ -1,5 +1,5 @@
window.app.config(function ($routeProvider) {
/**
* This determines if the route can continue depending on authentication and initialization requirements
* @param {boolean} authRequired If true, it checks if the user is authenticated and will resolve successfully
@@ -82,9 +82,6 @@ window.app.config(function ($routeProvider) {
return {
isLoggedOut: function ($q, $location, userService) {
return userService.logout().then(function () {
// we have to redirect here instead of the routes redirectTo
// https://github.com/angular/angular.js/commit/7f4b356c2bebb87f0c26b57a20415b004b20bcd1
$location.path("/login/false");
//success so continue
return $q.when(true);
}, function() {
@@ -117,9 +114,9 @@ window.app.config(function ($routeProvider) {
template: "<div ng-include='templateUrl'></div>",
//This controller will execute for this route, then we can execute some code in order to set the template Url
controller: function ($scope, $route, $routeParams, $location, sectionService) {
//We are going to check the currently loaded sections for the user and if the section we are navigating
//to has a custom route path we'll use that
//to has a custom route path we'll use that
sectionService.getSectionsForUser().then(function(sections) {
//find the one we're requesting
var found = _.find(sections, function(s) {
@@ -199,7 +196,7 @@ window.app.config(function ($routeProvider) {
})
.otherwise({ redirectTo: '/login' });
}).config(function ($locationProvider) {
$locationProvider.html5Mode(false); //turn html5 mode off
$locationProvider.hashPrefix('');
});

View File

@@ -36,7 +36,17 @@
<div class="flex flex-column flx-gap-sm">
<div ng-repeat="login in vm.externalLoginProviders">
<umb-custom-view ng-if="login.customView" custom-view="{{ ::login.customView }}"></umb-custom-view>
<umb-custom-view
ng-if="login.customView"
ng-attr-custom-view="{{ ::login.customView }}"
ng-attr-args='{{"{" +
"\"providerName\": \"" + login.authType + "\"," +
"\"displayName\": \"" + login.caption + "\"," +
"\"externalLoginUrl\": \"" + vm.externalLinkLoginFormAction + "\"," +
"\"userViewState\": \"loggedIn\"" +
"}"
| safe_html }}'>
</umb-custom-view>
<div ng-if="!login.customView">
<form action="{{ ::vm.externalLinkLoginFormAction }}" id="oauthloginform-{{ ::login.authType }}" method="POST"

View File

@@ -10,9 +10,9 @@
return-url=""
ng-on-umb-login-success="vm.onLoginSuccess($event)"
>
<p ng-if="vm.isTimedOut" slot="subheadline">
<localize key="login_timeout">Log in below</localize>.
</p>
<span ng-if="vm.isTimedOut" slot="subheadline">
<umb-localize key="login_timeout">Log in below</umb-localize>.
</span>
<umb-external-login-provider
ng-repeat="login in vm.externalLoginProviders"
slot="external"
@@ -23,6 +23,7 @@
external-login-url="{{ ::vm.externalLoginFormAction }}"
ng-attr-button-look="{{ ::login.options.buttonLook }}"
ng-attr-button-color="{{ ::login.options.buttonColor }}"
ng-attr-user-view-state="{{ ::vm.isTimedOut ? 'timedOut' : 'loggedOut' }}"
></umb-external-login-provider>
</umb-auth>
</div>

View File

@@ -6,10 +6,13 @@ import { until } from 'lit/directives/until.js';
import { loadCustomView, renderCustomView } from '../utils/load-custom-view.function.js';
import { umbLocalizationContext } from '../external/localization/localization-context.js';
type UserViewState = 'loggingIn' | 'loggedIn' | 'loggedOut' | 'timedOut';
type ExternalLoginCustomViewElement = HTMLElement & {
displayName: string;
providerName: string;
externalLoginUrl: string;
displayName?: string;
providerName?: string;
externalLoginUrl?: string;
userViewState?: UserViewState;
};
/**
@@ -50,6 +53,17 @@ export class UmbExternalLoginProviderElement extends LitElement {
@property({attribute: 'provider-name'})
providerName = '';
/**
* Gets or sets the view state of the user. This indicates in which state the user is in the login process,
* which can be used to determine where the external-login-provider is being shown.
*
* @attr user-view-state
* @example loggingIn
* @default loggingIn
*/
@property({attribute: 'user-view-state'})
userViewState: UserViewState = 'loggingIn';
/**
* Gets or sets the url to the external login provider.
*
@@ -68,7 +82,6 @@ export class UmbExternalLoginProviderElement extends LitElement {
return this.#externalLoginUrl;
}
/**
* Gets or sets the icon to display next to the provider name.
* This should be the name of an icon in the Umbraco Backoffice icon set.
@@ -104,6 +117,17 @@ export class UmbExternalLoginProviderElement extends LitElement {
#externalLoginUrl = '';
constructor() {
super();
const searchParams = new URLSearchParams(window.location.search);
const isLogout = searchParams.get('logout') === 'true';
if (isLogout) {
this.userViewState = 'loggedOut';
}
}
protected render() {
return this.customView
? until(this.renderCustomView(), html`
@@ -146,6 +170,7 @@ export class UmbExternalLoginProviderElement extends LitElement {
customView.displayName = this.displayName;
customView.providerName = this.providerName;
customView.externalLoginUrl = this.externalLoginUrl;
customView.userViewState = this.userViewState;
}
return renderCustomView(customView);

View File

@@ -95,10 +95,12 @@ export default class UmbLoginPageElement extends LitElement {
render() {
return html`
<h1 id="greeting">
<umb-localize key="general_welcome">Welcome</umb-localize>
</h1>
<slot name="subheadline"></slot>
<header id="header">
<h1 id="greeting">
<umb-localize key="general_welcome">Welcome</umb-localize>
</h1>
<slot name="subheadline"></slot>
</header>
${this.disableLocalLogin
? nothing
: html`
@@ -158,6 +160,18 @@ export default class UmbLoginPageElement extends LitElement {
flex-direction: column;
}
#header {
text-align: center;
display: flex;
flex-direction: column;
gap: var(--uui-size-space-5);
}
#header span {
color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
font-size: 14px;
}
#greeting {
color: var(--uui-color-interactive);
text-align: center;

View File

@@ -6,30 +6,32 @@ import {loadCustomView, renderCustomView} from "../utils/load-custom-view.functi
@customElement('umb-custom-view')
export default class UmbCustomViewElement extends LitElement {
@property({ attribute: 'custom-view' })
customView?: string;
set customView (value: string) {
this.#customView = value;
this.#loadView();
}
@property({ attribute: 'args' })
args?: any;
@property({ type: Object, attribute: 'args'})
set args (value: any) {
this.#args = value;
this.#loadView();
}
@state()
protected component: any = null;
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
super.attributeChangedCallback(name, _old, value);
if (name === 'custom-view') {
this.#loadView();
}
}
#args?: any;
#customView?: string;
async #loadView() {
if (!this.customView || !this.customView.endsWith('.js') && !this.customView.endsWith('.html')) {
if (!this.#customView || !this.#customView.endsWith('.js') && !this.#customView.endsWith('.html')) {
return;
}
const customView = await loadCustomView(this.customView);
const customView = await loadCustomView(this.#customView);
if (this.args) {
Object.entries(this.args).forEach(([key, value]) => {
if (this.#args) {
Object.entries(this.#args).forEach(([key, value]) => {
(customView as any)[key] = value;
});
}

View File

@@ -8,7 +8,7 @@ template.innerHTML = `
<div>
<uui-button id="button" look="primary" label="My custom button">
<uui-icon name="favorite"></uui-icon>
My Custom button (<span id="providerName"></span>)
My Custom button (<span id="providerName"></span> / <span id="userViewState"></span>)
</uui-button>
</div>
`;
@@ -16,6 +16,7 @@ template.innerHTML = `
export class MyCustomView extends HTMLElement {
providerName = '';
displayName = '';
userViewState = '';
constructor() {
super();
this.attachShadow({ mode: 'open' });
@@ -29,6 +30,7 @@ export class MyCustomView extends HTMLElement {
connectedCallback() {
console.log('My custom view connected');
this.shadowRoot.getElementById('providerName').innerText = this.providerName;
this.shadowRoot.getElementById('userViewState').innerText = this.userViewState;
}
}