From 22e993f44c129c30b73a426089c08f08db8c348b Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Wed, 8 Jan 2025 10:27:59 +0100
Subject: [PATCH] V15: Change password should not be shown when local login is
disabled (#17900)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: include the option whether a login provider has disabled local login
* feat: include information whether change password or two factor is allowed
* generate new OpenApi.json
* generate new ts client
* feat: request the server configuration and include in the UmbAppContext
* fix: for login screen, check that a provider did not disable the local login
* fix: use UmbAppContext to fetch the version check period
* chore: remove unused method
* revert current user configuration changes
* generate new ts client
* fix: add condition for "allow password change" on the change password button
* fix: create new IsDefaultKind condition to separate change password logic
* fix: update "allow change password" and "allow mfa" conditions to take the user configuration into consideration
* chore: export consts
* remove falsely named attribute that happens to not be useful anyway
* chore: revamp logic for early return to make it more readable
* convert `isInitialized` to a Promise as it is only being resolved once anyway, which then additionally saves a call to `this.observe` in the conditions
---------
Co-authored-by: Niels Lyngsø
---
.../Server/ConfigurationServerController.cs | 16 +++++-
.../Factories/UserPresentationFactory.cs | 4 ++
src/Umbraco.Cms.Api.Management/OpenApi.json | 22 ++++---
.../ServerConfigurationResponseModel.cs | 2 +
.../User/UserConfigurationResponseModel.cs | 4 ++
.../apps/app/app-context-config.interface.ts | 8 +++
.../src/apps/app/app.context.ts | 6 ++
.../src/apps/app/app.element.ts | 6 +-
.../src/apps/app/server-connection.ts | 23 +++++++-
.../src/external/backend-api/src/sdk.gen.ts | 5 +-
.../src/external/backend-api/src/types.gen.ts | 7 ++-
.../src/mocks/handlers/server.handlers.ts | 1 +
.../auth/modals/umb-app-auth-modal.element.ts | 57 ++++++++++++++++---
.../sysinfo/repository/sysinfo.repository.ts | 21 ++-----
.../src/packages/sysinfo/types.ts | 6 +-
.../entity-action/manifests.ts | 3 +
.../modals/external-login-modal.element.ts | 1 -
.../user/current-user/profile/manifests.ts | 5 ++
...-allow-change-password-action.condition.ts | 33 +++++++----
.../user-allow-mfa-action.condition.ts | 21 +++++--
.../user/user/conditions/constants.ts | 1 +
.../conditions/is-default-kind/constants.ts | 1 +
.../conditions/is-default-kind/manifests.ts | 10 ++++
.../user-is-default-kind.condition.ts | 15 +++++
.../user/user/conditions/manifests.ts | 2 +
.../config/user-config.repository.ts | 15 ++++-
.../utils/all-umb-consts/index.ts | 2 +-
27 files changed, 228 insertions(+), 69 deletions(-)
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/constants.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/manifests.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/user-is-default-kind.condition.ts
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Server/ConfigurationServerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Server/ConfigurationServerController.cs
index 048ed55206..05c35f32cb 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Server/ConfigurationServerController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Server/ConfigurationServerController.cs
@@ -1,8 +1,10 @@
using Asp.Versioning;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Api.Management.ViewModels.Server;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
@@ -14,6 +16,7 @@ public class ConfigurationServerController : ServerControllerBase
{
private readonly SecuritySettings _securitySettings;
private readonly GlobalSettings _globalSettings;
+ private readonly IBackOfficeExternalLoginProviders _externalLoginProviders;
[Obsolete("Use the constructor that accepts all arguments. Will be removed in V16.")]
public ConfigurationServerController(IOptions securitySettings)
@@ -21,13 +24,21 @@ public class ConfigurationServerController : ServerControllerBase
{
}
- [ActivatorUtilitiesConstructor]
+ [Obsolete("Use the constructor that accepts all arguments. Will be removed in V16.")]
public ConfigurationServerController(IOptions securitySettings, IOptions globalSettings)
+ : this(securitySettings, globalSettings, StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ [ActivatorUtilitiesConstructor]
+ public ConfigurationServerController(IOptions securitySettings, IOptions globalSettings, IBackOfficeExternalLoginProviders externalLoginProviders)
{
_securitySettings = securitySettings.Value;
_globalSettings = globalSettings.Value;
+ _externalLoginProviders = externalLoginProviders;
}
+ [AllowAnonymous]
[HttpGet("configuration")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ServerConfigurationResponseModel), StatusCodes.Status200OK)]
@@ -36,7 +47,8 @@ public class ConfigurationServerController : ServerControllerBase
var responseModel = new ServerConfigurationResponseModel
{
AllowPasswordReset = _securitySettings.AllowPasswordReset,
- VersionCheckPeriod = _globalSettings.VersionCheckPeriod
+ VersionCheckPeriod = _globalSettings.VersionCheckPeriod,
+ AllowLocalLogin = _externalLoginProviders.HasDenyLocalLogin() is false,
};
return Task.FromResult(Ok(responseModel));
diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
index 7ba1229416..0b7867a812 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
@@ -155,6 +155,10 @@ public class UserPresentationFactory : IUserPresentationFactory
CanInviteUsers = _emailSender.CanSendRequiredEmail() && _externalLoginProviders.HasDenyLocalLogin() is false,
UsernameIsEmail = _securitySettings.UsernameIsEmail,
PasswordConfiguration = _passwordConfigurationPresentationFactory.CreatePasswordConfigurationResponseModel(),
+
+ // You should not be able to change any password or set 2fa if any providers has deny local login set.
+ AllowChangePassword = _externalLoginProviders.HasDenyLocalLogin() is false,
+ AllowTwoFactor = _externalLoginProviders.HasDenyLocalLogin() is false,
});
public async Task CreateUpdateModelAsync(Guid existingUserKey, UpdateUserRequestModel updateModel)
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 7042802f04..0da041cea4 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -25276,16 +25276,8 @@
}
}
}
- },
- "401": {
- "description": "The resource is protected and requires an authentication token"
}
- },
- "security": [
- {
- "Backoffice User": [ ]
- }
- ]
+ }
}
},
"/umbraco/management/api/v1/server/information": {
@@ -43250,6 +43242,7 @@
},
"ServerConfigurationResponseModel": {
"required": [
+ "allowLocalLogin",
"allowPasswordReset",
"versionCheckPeriod"
],
@@ -43261,6 +43254,9 @@
"versionCheckPeriod": {
"type": "integer",
"format": "int32"
+ },
+ "allowLocalLogin": {
+ "type": "boolean"
}
},
"additionalProperties": false
@@ -45379,6 +45375,8 @@
},
"UserConfigurationResponseModel": {
"required": [
+ "allowChangePassword",
+ "allowTwoFactor",
"canInviteUsers",
"passwordConfiguration",
"usernameIsEmail"
@@ -45397,6 +45395,12 @@
"$ref": "#/components/schemas/PasswordConfigurationResponseModel"
}
]
+ },
+ "allowChangePassword": {
+ "type": "boolean"
+ },
+ "allowTwoFactor": {
+ "type": "boolean"
}
},
"additionalProperties": false
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Server/ServerConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Server/ServerConfigurationResponseModel.cs
index a424a24798..9e8ef9fe7e 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/Server/ServerConfigurationResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Server/ServerConfigurationResponseModel.cs
@@ -5,4 +5,6 @@ public class ServerConfigurationResponseModel
public bool AllowPasswordReset { get; set; }
public int VersionCheckPeriod { get; set; }
+
+ public bool AllowLocalLogin { get; set; }
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs
index a64cb4273c..7f64bd08e3 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/User/UserConfigurationResponseModel.cs
@@ -9,4 +9,8 @@ public class UserConfigurationResponseModel
public bool UsernameIsEmail { get; set; }
public required PasswordConfigurationResponseModel PasswordConfiguration { get; set; }
+
+ public bool AllowChangePassword { get; set; }
+
+ public bool AllowTwoFactor { get; set; }
}
diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app-context-config.interface.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app-context-config.interface.ts
index ede076ea25..ede2d780f1 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/app/app-context-config.interface.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/app/app-context-config.interface.ts
@@ -1,3 +1,5 @@
+import type { UmbServerConnection } from './server-connection.js';
+
/**
* Configuration interface for the Umbraco App Context
* @interface UmbAppContextConfig
@@ -16,4 +18,10 @@ export interface UmbAppContextConfig {
* @memberof UmbAppContextConfig
*/
backofficePath: string;
+
+ /**
+ * Configuration for the server connection.
+ * @memberof UmbAppContextConfig
+ */
+ serverConnection: UmbServerConnection;
}
diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app.context.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app.context.ts
index 39a248c8f5..857064431b 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/app/app.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/app/app.context.ts
@@ -6,11 +6,13 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export class UmbAppContext extends UmbContextBase {
#serverUrl: string;
#backofficePath: string;
+ #serverConnection;
constructor(host: UmbControllerHost, config: UmbAppContextConfig) {
super(host, UMB_APP_CONTEXT);
this.#serverUrl = config.serverUrl;
this.#backofficePath = config.backofficePath;
+ this.#serverConnection = config.serverConnection;
}
getBackofficePath() {
@@ -20,6 +22,10 @@ export class UmbAppContext extends UmbContextBase {
getServerUrl() {
return this.#serverUrl;
}
+
+ getServerConnection() {
+ return this.#serverConnection;
+ }
}
export const UMB_APP_CONTEXT = new UmbContextToken('UmbAppContext');
diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts
index 3be7df953e..e77efa374b 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts
@@ -163,7 +163,11 @@ export class UmbAppElement extends UmbLitElement {
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 });
+ new UmbAppContext(this, {
+ backofficePath: this.backofficePath,
+ serverUrl: this.serverUrl,
+ serverConnection: this.#serverConnection,
+ });
// Register Core extensions (this is specifically done here because we need these extensions to be registered before the application is initialized)
onInit(this, umbExtensionsRegistry);
diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/server-connection.ts b/src/Umbraco.Web.UI.Client/src/apps/app/server-connection.ts
index 6f30829184..4365b643f0 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/app/server-connection.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/app/server-connection.ts
@@ -1,5 +1,5 @@
import { RuntimeLevelModel, ServerService } from '@umbraco-cms/backoffice/external/backend-api';
-import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
+import { UmbBooleanState, UmbNumberState } from '@umbraco-cms/backoffice/observable-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
export class UmbServerConnection {
@@ -9,6 +9,15 @@ export class UmbServerConnection {
#isConnected = new UmbBooleanState(false);
isConnected = this.#isConnected.asObservable();
+ #versionCheckPeriod = new UmbNumberState(undefined);
+ versionCheckPeriod = this.#versionCheckPeriod.asObservable();
+
+ #allowLocalLogin = new UmbBooleanState(false);
+ allowLocalLogin = this.#allowLocalLogin.asObservable();
+
+ #allowPasswordReset = new UmbBooleanState(false);
+ allowPasswordReset = this.#allowPasswordReset.asObservable();
+
constructor(serverUrl: string) {
this.#url = serverUrl;
}
@@ -19,6 +28,7 @@ export class UmbServerConnection {
*/
async connect() {
await this.#setStatus();
+ await this.#setServerConfiguration();
return this;
}
@@ -59,4 +69,15 @@ export class UmbServerConnection {
this.#isConnected.setValue(true);
this.#status = data?.serverStatus ?? RuntimeLevelModel.UNKNOWN;
}
+
+ async #setServerConfiguration() {
+ const { data, error } = await tryExecute(ServerService.getServerConfiguration());
+ if (error) {
+ throw error;
+ }
+
+ this.#versionCheckPeriod.setValue(data?.versionCheckPeriod);
+ this.#allowLocalLogin.setValue(data?.allowLocalLogin ?? false);
+ this.#allowPasswordReset.setValue(data?.allowPasswordReset ?? false);
+ }
}
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
index c3f1d924dc..40b4e3a592 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
@@ -6685,10 +6685,7 @@ export class ServerService {
public static getServerConfiguration(): CancelablePromise {
return __request(OpenAPI, {
method: 'GET',
- url: '/umbraco/management/api/v1/server/configuration',
- errors: {
- 401: 'The resource is protected and requires an authentication token'
- }
+ url: '/umbraco/management/api/v1/server/configuration'
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts
index 476aa305f9..6ededd6264 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts
@@ -1027,8 +1027,8 @@ export type HealthCheckWithResultPresentationModel = {
export enum HealthStatusModel {
HEALTHY = 'Healthy',
UNHEALTHY = 'Unhealthy',
- CORRUPT = 'Corrupt',
- REBUILDING = 'Rebuilding'
+ REBUILDING = 'Rebuilding',
+ CORRUPT = 'Corrupt'
}
export type HealthStatusResponseModel = {
@@ -2204,6 +2204,7 @@ export type ServerConfigurationItemResponseModel = {
export type ServerConfigurationResponseModel = {
allowPasswordReset: boolean;
versionCheckPeriod: number;
+ allowLocalLogin: boolean;
};
export type ServerInformationResponseModel = {
@@ -2681,6 +2682,8 @@ export type UserConfigurationResponseModel = {
canInviteUsers: boolean;
usernameIsEmail: boolean;
passwordConfiguration: (PasswordConfigurationResponseModel);
+ allowChangePassword: boolean;
+ allowTwoFactor: boolean;
};
export type UserDataModel = {
diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/server.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/server.handlers.ts
index 668b2cb718..b635eb3b90 100644
--- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/server.handlers.ts
+++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/server.handlers.ts
@@ -49,6 +49,7 @@ export const serverInformationHandlers = [
ctx.json({
allowPasswordReset: true,
versionCheckPeriod: 7, // days
+ allowLocalLogin: true,
}),
);
}),
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts
index 2082e50550..a92627d95d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts
@@ -4,7 +4,7 @@ import { UmbTextStyles } from '../../style/text-style.style.js';
import { UMB_AUTH_CONTEXT } from '../auth.context.token.js';
import type { UmbAuthProviderDefaultProps } from '../types.js';
import type { UmbModalAppAuthConfig, UmbModalAppAuthValue } from './umb-app-auth-modal.token.js';
-import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
@customElement('umb-app-auth-modal')
@@ -15,6 +15,12 @@ export class UmbAppAuthModalElement extends UmbModalBaseElement {
this._serverUrl = context.getServerUrl();
this.style.setProperty(
'--image',
`url('${this._serverUrl}/umbraco/management/api/v1/security/back-office/graphics/login-background') no-repeat center center/cover`,
);
+
+ const serverConnection = context.getServerConnection();
+
+ this.observe(serverConnection.allowLocalLogin, (allowLocalLogin) => {
+ this._allowLocalLogin = allowLocalLogin;
+ });
+
+ this.observe(serverConnection.isConnected, (isConnected) => {
+ this._loading = !isConnected;
+ });
});
}
@@ -93,17 +107,36 @@ export class UmbAppAuthModalElement extends UmbModalBaseElement${this.localize.term('login_timeout')}
`
: ''}
-
+ ${when(
+ this._loading,
+ () => html`
+
+
+
+ `,
+ () =>
+ html` `,
+ )}
`;
}
+ #filterProvider = (provider: ManifestAuthProvider) => {
+ if (this._allowLocalLogin) {
+ return true;
+ }
+
+ // Do not show any Umbraco auth provider if local login is disabled
+ return provider.forProviderName.toLowerCase() !== 'umbraco';
+ };
+
private onSubmit = async (providerOrManifest: string | ManifestAuthProvider, loginHint?: string) => {
try {
const authContext = await this.getContext(UMB_AUTH_CONTEXT);
@@ -231,6 +264,12 @@ export class UmbAppAuthModalElement extends UmbModalBaseElement {
// Check if we are allowed to check again
- const versionCheckPeriod = await this.#getVersionCheckPeriod();
+ const appContext = await this.getContext(UMB_APP_CONTEXT);
+ const versionCheckPeriod = await this.observe(appContext.getServerConnection().versionCheckPeriod).asPromise();
if (versionCheckPeriod <= 0) {
return null;
@@ -77,14 +72,6 @@ export class UmbSysinfoRepository extends UmbRepositoryBase {
return null;
}
- async #getVersionCheckPeriod(): Promise {
- if (!this.#serverConfiguration) {
- this.#serverConfiguration = await this.requestServerConfiguration();
- }
-
- return this.#serverConfiguration?.versionCheckPeriod ?? 7;
- }
-
#getStoredServerUpgradeCheck(lastCheck: Date): UmbServerUpgradeCheck | null {
const storedCheck = localStorage.getItem('umb:serverUpgradeCheck');
if (storedCheck) {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/sysinfo/types.ts b/src/Umbraco.Web.UI.Client/src/packages/sysinfo/types.ts
index d4a6519664..7babfafeb4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/sysinfo/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/sysinfo/types.ts
@@ -1,7 +1,3 @@
-import type {
- ServerConfigurationResponseModel,
- UpgradeCheckResponseModel,
-} from '@umbraco-cms/backoffice/external/backend-api';
+import type { UpgradeCheckResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
-export type UmbServerConfiguration = ServerConfigurationResponseModel;
export type UmbServerUpgradeCheck = UpgradeCheckResponseModel & { expires: string };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/change-password/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/entity-action/manifests.ts
index 01148ac695..dce4333405 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/change-password/entity-action/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/entity-action/manifests.ts
@@ -14,6 +14,9 @@ export const manifests: Array = [
label: '#user_changePassword',
},
conditions: [
+ {
+ alias: 'Umb.Condition.User.IsDefaultKind',
+ },
{
alias: 'Umb.Condition.User.AllowChangePassword',
},
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.element.ts
index c5f2d1c0a2..a6c7cf07c9 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.element.ts
@@ -144,7 +144,6 @@ export class UmbCurrentUserExternalLoginModalElement extends UmbLitElement {
this.#onProviderEnable(item)}>
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts
index 0d088f3dd0..3893b85d47 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/manifests.ts
@@ -39,5 +39,10 @@ export const manifests: Array = [
label: '#general_changePassword',
icon: 'lock',
},
+ conditions: [
+ {
+ alias: 'Umb.Condition.User.AllowChangePassword',
+ },
+ ],
},
];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-change-password/user-allow-change-password-action.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-change-password/user-allow-change-password-action.condition.ts
index d82ad989ae..6d2176da44 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-change-password/user-allow-change-password-action.condition.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-change-password/user-allow-change-password-action.condition.ts
@@ -1,14 +1,27 @@
-import { UmbUserKind } from '../../utils/index.js';
-import { UmbUserActionConditionBase } from '../user-allow-action-base.condition.js';
+import UmbUserConfigRepository from '../../repository/config/user-config.repository.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';
+import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
-export class UmbUserAllowChangePasswordActionCondition extends UmbUserActionConditionBase {
- async _onUserDataChange() {
- // don't allow the current user to delete themselves
- if (this.userKind === UmbUserKind.DEFAULT) {
- this.permitted = true;
- } else {
- this.permitted = false;
- }
+export class UmbUserAllowChangePasswordActionCondition extends UmbConditionBase {
+ #configRepository = new UmbUserConfigRepository(this._host);
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ constructor(host: UmbControllerHost, args: any) {
+ super(host, args);
+ this.#init();
+ }
+
+ async #init() {
+ await this.#configRepository.initialized;
+
+ this.observe(
+ this.#configRepository.part('allowChangePassword'),
+ (isAllowed) => {
+ this.permitted = isAllowed;
+ },
+ '_userAllowChangePasswordActionCondition',
+ );
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-mfa/user-allow-mfa-action.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-mfa/user-allow-mfa-action.condition.ts
index 88bd9f4274..b1d37a1e24 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-mfa/user-allow-mfa-action.condition.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/allow-mfa/user-allow-mfa-action.condition.ts
@@ -1,16 +1,29 @@
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbConditionBase, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
+import { UmbUserConfigRepository } from '../../repository/config/index.js';
export class UmbUserAllowMfaActionCondition extends UmbConditionBase {
+ #configRepository = new UmbUserConfigRepository(this._host);
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(host: UmbControllerHost, args: any) {
super(host, args);
+ this.#init();
+ }
+
+ async #init() {
+ await this.#configRepository.initialized;
- // Check if there are any MFA providers available
this.observe(
- umbExtensionsRegistry.byType('mfaLoginProvider'),
- (exts) => (this.permitted = exts.length > 0),
- '_userAllowMfaActionConditionProviders',
+ observeMultiple([
+ this.#configRepository.part('allowTwoFactor'),
+ umbExtensionsRegistry.byType('mfaLoginProvider'),
+ ]),
+ ([allowTwoFactor, exts]) => {
+ this.permitted = allowTwoFactor && exts.length > 0;
+ },
+ '_userAllowMfaActionCondition',
);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/constants.ts
index 72e5bc2cc7..518d532df2 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/constants.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/constants.ts
@@ -5,3 +5,4 @@ export * from './allow-enable/constants.js';
export * from './allow-external-login/constants.js';
export * from './allow-mfa/constants.js';
export * from './allow-unlock/constants.js';
+export * from './is-default-kind/constants.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/constants.ts
new file mode 100644
index 0000000000..71364d3ae7
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/constants.ts
@@ -0,0 +1 @@
+export const UMB_USER_IS_DEFAULT_KIND_CONDITION_ALIAS = 'Umb.Condition.User.IsDefaultKind';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/manifests.ts
new file mode 100644
index 0000000000..1c25a7658e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/manifests.ts
@@ -0,0 +1,10 @@
+import { UMB_USER_IS_DEFAULT_KIND_CONDITION_ALIAS } from './constants.js';
+
+export const manifests: Array = [
+ {
+ type: 'condition',
+ name: 'User Is Default Kind Condition',
+ alias: UMB_USER_IS_DEFAULT_KIND_CONDITION_ALIAS,
+ api: () => import('./user-is-default-kind.condition.js'),
+ },
+];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/user-is-default-kind.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/user-is-default-kind.condition.ts
new file mode 100644
index 0000000000..086e72c181
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/is-default-kind/user-is-default-kind.condition.ts
@@ -0,0 +1,15 @@
+import { UmbUserKind } from '../../utils/index.js';
+import { UmbUserActionConditionBase } from '../user-allow-action-base.condition.js';
+
+export class UmbUserIsDefaultKindCondition extends UmbUserActionConditionBase {
+ async _onUserDataChange() {
+ // don't allow the current user to delete themselves
+ if (this.userKind === UmbUserKind.DEFAULT) {
+ this.permitted = true;
+ } else {
+ this.permitted = false;
+ }
+ }
+}
+
+export { UmbUserIsDefaultKindCondition as api };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts
index 32277cea4e..3e4f1ee52a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts
@@ -5,6 +5,7 @@ import { manifests as userAllowEnableActionManifests } from './allow-enable/mani
import { manifests as userAllowExternalLoginActionManifests } from './allow-external-login/manifests.js';
import { manifests as userAllowMfaActionManifests } from './allow-mfa/manifests.js';
import { manifests as userAllowUnlockActionManifests } from './allow-unlock/manifests.js';
+import { manifests as userIsDefaultKindManifests } from './is-default-kind/manifests.js';
export const manifests: Array = [
...userAllowChangePasswordActionManifests,
@@ -14,4 +15,5 @@ export const manifests: Array = [
...userAllowExternalLoginActionManifests,
...userAllowMfaActionManifests,
...userAllowUnlockActionManifests,
+ ...userIsDefaultKindManifests,
];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/config/user-config.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/config/user-config.repository.ts
index 082ab74f2d..ac3b5b95eb 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/config/user-config.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/config/user-config.repository.ts
@@ -7,14 +7,23 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { Observable } from '@umbraco-cms/backoffice/observable-api';
export class UmbUserConfigRepository extends UmbRepositoryBase implements UmbApi {
+ /**
+ * Promise that resolves when the repository has been initialized, i.e. when the user configuration has been fetched from the server.
+ * @memberof UmbUserConfigRepository
+ */
+ initialized: Promise;
+
#dataStore?: typeof UMB_USER_CONFIG_STORE_CONTEXT.TYPE;
#dataSource = new UmbUserConfigServerDataSource(this);
constructor(host: UmbControllerHost) {
super(host);
- this.consumeContext(UMB_USER_CONFIG_STORE_CONTEXT, (store) => {
- this.#dataStore = store;
- this.#init();
+ this.initialized = new Promise((resolve) => {
+ this.consumeContext(UMB_USER_CONFIG_STORE_CONTEXT, async (store) => {
+ this.#dataStore = store;
+ await this.#init();
+ resolve();
+ });
});
}
diff --git a/src/Umbraco.Web.UI.Client/utils/all-umb-consts/index.ts b/src/Umbraco.Web.UI.Client/utils/all-umb-consts/index.ts
index ab031de6e6..168782f0df 100644
--- a/src/Umbraco.Web.UI.Client/utils/all-umb-consts/index.ts
+++ b/src/Umbraco.Web.UI.Client/utils/all-umb-consts/index.ts
@@ -400,7 +400,7 @@ export const foundConsts = [{
},
{
path: '@umbraco-cms/backoffice/user',
- consts: ["UMB_CREATE_USER_CLIENT_CREDENTIAL_MODAL","UMB_CREATE_USER_CLIENT_CREDENTIAL_MODAL_ALIAS","UMB_USER_CLIENT_CREDENTIAL_REPOSITORY_ALIAS","UMB_USER_COLLECTION_ALIAS","UMB_USER_COLLECTION_REPOSITORY_ALIAS","UMB_USER_COLLECTION_CONTEXT","UMB_COLLECTION_VIEW_USER_TABLE","UMB_COLLECTION_VIEW_USER_GRID","UMB_USER_ALLOW_CHANGE_PASSWORD_CONDITION_ALIAS","UMB_USER_ALLOW_DELETE_CONDITION_ALIAS","UMB_USER_ALLOW_DISABLE_CONDITION_ALIAS","UMB_USER_ALLOW_ENABLE_CONDITION_ALIAS","UMB_USER_ALLOW_EXTERNAL_LOGIN_CONDITION_ALIAS","UMB_USER_ALLOW_MFA_CONDITION_ALIAS","UMB_USER_ALLOW_UNLOCK_CONDITION_ALIAS","UMB_CREATE_USER_MODAL","UMB_CREATE_USER_SUCCESS_MODAL","UMB_CREATE_USER_MODAL_ALIAS","UMB_USER_ENTITY_TYPE","UMB_USER_ROOT_ENTITY_TYPE","UMB_INVITE_USER_MODAL","UMB_RESEND_INVITE_TO_USER_MODAL","UMB_INVITE_USER_REPOSITORY_ALIAS","UMB_USER_MFA_MODAL","UMB_USER_PICKER_MODAL","UMB_USER_WORKSPACE_PATH","UMB_USER_ROOT_WORKSPACE_PATH","UMB_USER_AVATAR_REPOSITORY_ALIAS","UMB_CHANGE_USER_PASSWORD_REPOSITORY_ALIAS","UMB_USER_CONFIG_REPOSITORY_ALIAS","UMB_USER_CONFIG_STORE_ALIAS","UMB_USER_CONFIG_STORE_CONTEXT","UMB_USER_DETAIL_REPOSITORY_ALIAS","UMB_USER_DETAIL_STORE_ALIAS","UMB_USER_DETAIL_STORE_CONTEXT","UMB_DISABLE_USER_REPOSITORY_ALIAS","UMB_ENABLE_USER_REPOSITORY_ALIAS","UMB_USER_ITEM_REPOSITORY_ALIAS","UMB_USER_ITEM_STORE_ALIAS","UMB_USER_ITEM_STORE_CONTEXT","UMB_NEW_USER_PASSWORD_REPOSITORY_ALIAS","UMB_UNLOCK_USER_REPOSITORY_ALIAS","UMB_USER_WORKSPACE_ALIAS","UMB_USER_WORKSPACE_CONTEXT","UMB_USER_ROOT_WORKSPACE_ALIAS"]
+ consts: ["UMB_CREATE_USER_CLIENT_CREDENTIAL_MODAL","UMB_CREATE_USER_CLIENT_CREDENTIAL_MODAL_ALIAS","UMB_USER_CLIENT_CREDENTIAL_REPOSITORY_ALIAS","UMB_USER_COLLECTION_ALIAS","UMB_USER_COLLECTION_REPOSITORY_ALIAS","UMB_USER_COLLECTION_CONTEXT","UMB_COLLECTION_VIEW_USER_TABLE","UMB_COLLECTION_VIEW_USER_GRID","UMB_USER_ALLOW_CHANGE_PASSWORD_CONDITION_ALIAS","UMB_USER_ALLOW_DELETE_CONDITION_ALIAS","UMB_USER_ALLOW_DISABLE_CONDITION_ALIAS","UMB_USER_ALLOW_ENABLE_CONDITION_ALIAS","UMB_USER_ALLOW_EXTERNAL_LOGIN_CONDITION_ALIAS","UMB_USER_ALLOW_MFA_CONDITION_ALIAS","UMB_USER_ALLOW_UNLOCK_CONDITION_ALIAS","UMB_USER_IS_DEFAULT_KIND_CONDITION_ALIAS","UMB_CREATE_USER_MODAL","UMB_CREATE_USER_SUCCESS_MODAL","UMB_CREATE_USER_MODAL_ALIAS","UMB_USER_ENTITY_TYPE","UMB_USER_ROOT_ENTITY_TYPE","UMB_INVITE_USER_MODAL","UMB_RESEND_INVITE_TO_USER_MODAL","UMB_INVITE_USER_REPOSITORY_ALIAS","UMB_USER_MFA_MODAL","UMB_USER_PICKER_MODAL","UMB_USER_WORKSPACE_PATH","UMB_USER_ROOT_WORKSPACE_PATH","UMB_USER_AVATAR_REPOSITORY_ALIAS","UMB_CHANGE_USER_PASSWORD_REPOSITORY_ALIAS","UMB_USER_CONFIG_REPOSITORY_ALIAS","UMB_USER_CONFIG_STORE_ALIAS","UMB_USER_CONFIG_STORE_CONTEXT","UMB_USER_DETAIL_REPOSITORY_ALIAS","UMB_USER_DETAIL_STORE_ALIAS","UMB_USER_DETAIL_STORE_CONTEXT","UMB_DISABLE_USER_REPOSITORY_ALIAS","UMB_ENABLE_USER_REPOSITORY_ALIAS","UMB_USER_ITEM_REPOSITORY_ALIAS","UMB_USER_ITEM_STORE_ALIAS","UMB_USER_ITEM_STORE_CONTEXT","UMB_NEW_USER_PASSWORD_REPOSITORY_ALIAS","UMB_UNLOCK_USER_REPOSITORY_ALIAS","UMB_USER_WORKSPACE_ALIAS","UMB_USER_WORKSPACE_CONTEXT","UMB_USER_ROOT_WORKSPACE_ALIAS"]
},
{
path: '@umbraco-cms/backoffice/utils',