@@ -3469,6 +3469,19 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
key: 'LdapSettings.MaximumLoginAttempts',
|
||||
label: defineMessage({id: 'admin.ldap.maximumLoginAttemptsTitle', defaultMessage: 'Maximum Login Attempts:'}),
|
||||
help_text: defineMessage({id: 'admin.ldap.maximumLoginAttemptsDesc', defaultMessage: 'The maximum number of login attempts before the Mattermost account is locked. You can unlock the account in system console on the users page. Setting this value lower than your LDAP maximum login attempts ensures that the users won\'t be locked out of your LDAP server because of failed login attempts in Mattermost.'}),
|
||||
isDisabled: it.any(
|
||||
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.LDAP)),
|
||||
it.all(
|
||||
it.stateIsFalse('LdapSettings.Enable'),
|
||||
it.stateIsFalse('LdapSettings.EnableSync'),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'LdapSettings.LdapServer',
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {resetFailedAttempts} from 'mattermost-redux/actions/users';
|
||||
|
||||
import ConfirmModalRedux from 'components/confirm_modal_redux';
|
||||
|
||||
type Props = {
|
||||
user: UserProfile;
|
||||
onError: (error: ServerError) => void;
|
||||
onSuccess: () => void;
|
||||
onExited: () => void;
|
||||
}
|
||||
|
||||
export default function ConfirmResetFailedAttemptsModal({user, onSuccess, onError, onExited}: Props) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
async function confirm() {
|
||||
const {error} = await dispatch(resetFailedAttempts(user.id));
|
||||
if (error) {
|
||||
onError(error);
|
||||
}
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id='confirm_reset_failed_attempts_modal.title'
|
||||
defaultMessage='Reset failed login attempts for {username} and unlock account'
|
||||
values={{
|
||||
username: user.username,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id='confirm_reset_failed_attempts_modal.desc'
|
||||
defaultMessage="You're about to reset the failed login attempts for {username} and unlock their account. Are you sure you want to continue?"
|
||||
values={{
|
||||
username: user.username,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const createGroupMembershipsButton = (
|
||||
<FormattedMessage
|
||||
id='confirm_reset_failed_attempts_modal.create'
|
||||
defaultMessage='Yes'
|
||||
/>
|
||||
);
|
||||
|
||||
const cancelGroupMembershipsButton = (
|
||||
<FormattedMessage
|
||||
id='confirm_reset_failed_attempts_modal.cancel'
|
||||
defaultMessage='No'
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfirmModalRedux
|
||||
title={title}
|
||||
message={message}
|
||||
confirmButtonClass='btn btn-danger'
|
||||
cancelButtonText={cancelGroupMembershipsButton}
|
||||
confirmButtonText={createGroupMembershipsButton}
|
||||
onConfirm={confirm}
|
||||
onExited={onExited}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import ConfirmManageUserSettingsModal from './confirm_manage_user_settings_modal';
|
||||
import ConfirmResetFailedAttemptsModal from './confirm_reset_failed_attempts_modal';
|
||||
import CreateGroupSyncablesMembershipsModal from './create_group_syncables_membership_modal';
|
||||
import DeactivateMemberModal from './deactivate_member_modal';
|
||||
import DemoteToGuestModal from './demote_to_guest_modal';
|
||||
@@ -303,6 +304,24 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
|
||||
);
|
||||
}, [user, updateUser, onError]);
|
||||
|
||||
const handleResetAttemptsClick = useCallback(() => {
|
||||
function onResetAttemptsSuccess() {
|
||||
updateUser({failed_attempts: 0});
|
||||
}
|
||||
|
||||
dispatch(
|
||||
openModal({
|
||||
modalId: ModalIdentifiers.CONFIRM_RESET_FAILED_ATTEMPTS_MODAL,
|
||||
dialogType: ConfirmResetFailedAttemptsModal,
|
||||
dialogProps: {
|
||||
user,
|
||||
onError,
|
||||
onSuccess: onResetAttemptsSuccess,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}, [user, updateUser, onError]);
|
||||
|
||||
const disableActivationToggle = user.auth_service === Constants.LDAP_SERVICE;
|
||||
|
||||
const getManagedByLDAPText = (managedByLDAP: boolean) => {
|
||||
@@ -314,6 +333,18 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
|
||||
} : {};
|
||||
};
|
||||
|
||||
const showResetFailedAttempts = useCallback(() => {
|
||||
if (user.failed_attempts === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.auth_service !== Constants.LDAP_SERVICE && user.auth_service !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<Menu.Container
|
||||
menuButton={{
|
||||
@@ -414,6 +445,18 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
|
||||
onClick={handleResetPasswordClick}
|
||||
/>
|
||||
}
|
||||
{showResetFailedAttempts() && (
|
||||
<Menu.Item
|
||||
id={`${menuItemIdPrefix}-resetAttempts`}
|
||||
labels={
|
||||
<FormattedMessage
|
||||
id='admin.system_users.list.actions.menu.resetAttempts'
|
||||
defaultMessage='Reset login attempts'
|
||||
/>
|
||||
}
|
||||
onClick={handleResetAttemptsClick}
|
||||
/>
|
||||
)}
|
||||
{user.mfa_active && config.ServiceSettings?.EnableMultifactorAuthentication &&
|
||||
<Menu.Item
|
||||
id={`${menuItemIdPrefix}-removeMFA`}
|
||||
|
||||
@@ -1335,6 +1335,8 @@
|
||||
"admin.ldap.loginNameDesc": "The placeholder text that appears in the login field on the login page. Defaults to \"AD/LDAP Username\".",
|
||||
"admin.ldap.loginNameEx": "E.g.: \"AD/LDAP Username\"",
|
||||
"admin.ldap.loginNameTitle": "Login Field Name:",
|
||||
"admin.ldap.maximumLoginAttemptsDesc": "The maximum number of login attempts before the Mattermost account is locked. You can unlock the account in system console on the users page. Setting this value lower than your LDAP maximum login attempts ensures that the users won't be locked out of your LDAP server because of failed login attempts in Mattermost.",
|
||||
"admin.ldap.maximumLoginAttemptsTitle": "Maximum Login Attempts:",
|
||||
"admin.ldap.maxPageSizeEx": "E.g.: \"2000\"",
|
||||
"admin.ldap.maxPageSizeHelpText": "The maximum number of users the Mattermost server will request from the AD/LDAP server at one time. 0 is unlimited.",
|
||||
"admin.ldap.maxPageSizeTitle": "Maximum Page Size:",
|
||||
@@ -2629,6 +2631,7 @@
|
||||
"admin.system_users.list.actions.menu.promoteToMember": "Promote to member",
|
||||
"admin.system_users.list.actions.menu.removeMFA": "Remove MFA",
|
||||
"admin.system_users.list.actions.menu.removeSessions": "Remove sessions",
|
||||
"admin.system_users.list.actions.menu.resetAttempts": "Reset login attempts",
|
||||
"admin.system_users.list.actions.menu.resetPassword": "Reset password",
|
||||
"admin.system_users.list.actions.menu.resyncUserViaLdapGroups": "Re-sync user via LDAP groups",
|
||||
"admin.system_users.list.actions.menu.switchToEmailPassword": "Switch to Email/Password",
|
||||
@@ -3484,6 +3487,10 @@
|
||||
"commercial_support.download_support_packet": "Download Support Packet",
|
||||
"commercial_support.title": "Commercial Support",
|
||||
"confirm_modal.cancel": "Cancel",
|
||||
"confirm_reset_failed_attempts_modal.cancel": "No",
|
||||
"confirm_reset_failed_attempts_modal.create": "Yes",
|
||||
"confirm_reset_failed_attempts_modal.desc": "You're about to reset the failed login attempts for {username} and unlock their account. Are you sure you want to continue?",
|
||||
"confirm_reset_failed_attempts_modal.title": "Reset failed login attempts for {username} and unlock account",
|
||||
"confirm_switch_to_yearly_modal.confirm": "Confirm",
|
||||
"confirm_switch_to_yearly_modal.contact_sales": "Contact Sales",
|
||||
"confirm_switch_to_yearly_modal.subtitle": "Changing to the annual plan is irreversible. Are you sure you want to switch from monthly to the annual plan?",
|
||||
|
||||
@@ -1070,6 +1070,24 @@ export function updateUserPassword(userId: string, currentPassword: string, newP
|
||||
};
|
||||
}
|
||||
|
||||
export function resetFailedAttempts(userId: string): ActionFuncAsync<true> {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
await Client4.resetFailedAttempts(userId);
|
||||
} catch (error) {
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
const profile = getState().entities.users.profiles[userId];
|
||||
if (profile) {
|
||||
dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, failed_attempts: 0}});
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function updateUserActive(userId: string, active: boolean): ActionFuncAsync<true> {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
|
||||
@@ -467,6 +467,7 @@ export const ModalIdentifiers = {
|
||||
SECURE_CONNECTION_ACCEPT_INVITE: 'secure_connection_accept_invite',
|
||||
SHARED_CHANNEL_REMOTE_INVITE: 'shared_channel_remote_invite',
|
||||
SHARED_CHANNEL_REMOTE_UNINVITE: 'shared_channel_remote_uninvite',
|
||||
CONFIRM_RESET_FAILED_ATTEMPTS_MODAL: 'confirm_reset_failed_attempts_modal',
|
||||
USER_PROPERTY_FIELD_DELETE: 'user_property_field_delete',
|
||||
};
|
||||
|
||||
|
||||
@@ -674,6 +674,13 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
resetFailedAttempts = (userId: string) => {
|
||||
return this.doFetch<StatusOK>(
|
||||
`${this.getUserRoute(userId)}/reset_failed_attempts`,
|
||||
{method: 'post'},
|
||||
);
|
||||
};
|
||||
|
||||
getKnownUsers = () => {
|
||||
return this.doFetch<Array<UserProfile['id']>>(
|
||||
`${this.getUsersRoute()}/known`,
|
||||
|
||||
@@ -709,6 +709,7 @@ export type LdapSettings = {
|
||||
LoginButtonColor: string;
|
||||
LoginButtonBorderColor: string;
|
||||
LoginButtonTextColor: string;
|
||||
MaximumLoginAttempts: number;
|
||||
};
|
||||
|
||||
export type ComplianceSettings = {
|
||||
|
||||
@@ -60,6 +60,7 @@ export type UserProfile = {
|
||||
terms_of_service_create_at: number;
|
||||
remote_id?: string;
|
||||
status?: string;
|
||||
failed_attempts?: number;
|
||||
custom_profile_attributes?: Record<string, string>;
|
||||
};
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user