Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,35 @@ Enabling this feature makes these connections susceptible to man-in-the-middle a

</Warning>

### Enable hardened mode

<table>
<colgroup>
<col style={{width: '38%'}} />
<col style={{width: '61%'}} />
</colgroup>
<tbody>
<tr>
<td><p>Configure Mattermost to run in a hardened mode that makes user experience trade-offs in the interest of security.</p><ul><li><strong>true</strong>: Hardened mode is enabled.</li><li><strong>false</strong>: <strong>(Default)</strong> Hardened mode is disabled.</li></ul></td>
<td><ul><li>System Config path: <strong>Environment &gt; Web Server</strong></li><li><code>config.json</code> setting: <code>ServiceSettings</code> &gt; <code>EnableHardenedMode</code> &gt; <code>false</code></li><li>Environment variable: <code>MM_SERVICESETTINGS_ENABLEHARDENEDMODE</code></li></ul></td>
</tr>
</tbody>
</table>

<Note>

This setting was called `ServiceSettings.ExperimentalEnableHardenedMode` until Mattermost v11.11. On first start after upgrading, the server automatically migrates the old key to `ServiceSettings.EnableHardenedMode` and removes it from `config.json`.

Changes made when hardened mode is enabled:

- Failed login returns a generic error message instead of a specific message for username and password.
- If [multi-factor authentication (MFA)](/administration-guide/onboard/multi-factor-authentication) is enabled, the route to check if a user has MFA enabled always returns true. This causes the MFA input screen to appear even if the user does not have MFA enabled. The user may enter any value to pass the screen. Note that hardened mode does not affect user experience when MFA is enforced.
- Password reset does not inform the user that they can not reset their SSO account through Mattermost and instead claims to have sent the password reset email.
- Mattermost sanitizes all 500 errors before returned to the client. Use the supplied `request_id` to match user-facing errors with the server logs.
- Standard users authenticated via username and password can't use post props reserved for integrations, such as `override_username` or `override_icon_url`.

</Note>

### Managed resource paths

<table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,6 @@ Specify the maximum frequency, in seconds, which the batching job checks for new
</tbody>
</table>

### Enable account deactivation

**True**: Ability for users to deactivate their own account from **Settings \> Advanced \> Deactivate Account**. If a user deactivates their own account, they will get an email notification confirming they were deactivated. Available only when authentication is set to use email/password. Not available when authentication uses SAML or AD/LDAP.

**False**: Ability for users to deactivate their own account is disabled.

<table style={{width: '98%'}}>
<colgroup>
<col style={{width: '97%'}} />
</colgroup>
<tbody>
<tr>
<td>This feature's <code>config.json</code> setting is <code>"EnableUserDeactivation": false</code> with options <code>true</code> and <code>false</code>.</td>
</tr>
</tbody>
</table>

### Enable automatic replies

**True**: Users can enable Automatic Replies in **Settings \> Notifications**. Users set a custom message that will be automatically sent in response to Direct Messages.
Expand Down Expand Up @@ -154,31 +137,6 @@ This setting determines whether team leave/join system messages are posted in th
</tbody>
</table>

### Enable hardened mode

**True**: Enables a hardened mode for Mattermost that makes user experience trade-offs in the interest of security.

**False**: Disables hardened mode.

Changes made when hardened mode is enabled:

- Failed login returns a generic error message instead of a specific message for username and password.
- If [multi-factor authentication (MFA)](/administration-guide/onboard/multi-factor-authentication) is enabled, the route to check if a user has MFA enabled always returns true. This causes the MFA input screen to appear even if the user does not have MFA enabled. The user may enter any value to pass the screen. Note that hardened mode does not affect user experience when MFA is enforced.
- Password reset does not inform the user that they can not reset their SSO account through Mattermost and instead claims to have sent the password reset email.
- Mattermost sanitizes all 500 errors before returned to the client. Use the supplied `request_id` to match user facing errors with the server logs.
- Standard users authenticated via username and password can't use post props reserved for integrations, such as `override_username` or `override_icon_url`.

<table>
<colgroup>
<col style={{width: '100%'}} />
</colgroup>
<tbody>
<tr>
<td>This feature's <code>config.json</code> setting is <code>"ExperimentalEnableHardenedMode": false</code> with options <code>true</code> and <code>false</code>.</td>
</tr>
</tbody>
</table>

### Enable Mobile Watermark

Available on [Enterprise Advanced](https://mattermost.com/pricing/) from Mattermost v11.7 onward.
Expand Down Expand Up @@ -261,21 +219,6 @@ If the team URL of the primary team is `https://example.mattermost.com/myteam/`,
</tbody>
</table>

### User status away timeout

This setting defines the number of seconds after which the user's status indicator changes to "Away", when they are away from Mattermost.

<table style={{width: '82%'}}>
<colgroup>
<col style={{width: '82%'}} />
</colgroup>
<tbody>
<tr>
<td>This feature's <code>config.json</code> setting is <code>"UserStatusAwayTimeout": 300</code> with numerical input.</td>
</tr>
</tbody>
</table>

### Disable data refetching on browser refocus

This setting disables re-fetching of channel and channel members on browser focus.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,21 @@ Access the following configuration settings in the System Console by going to **
</tbody>
</table>

### User status away timeout

<table>
<colgroup>
<col style={{width: '53%'}} />
<col style={{width: '46%'}} />
</colgroup>
<tbody>
<tr>
<td><p>The number of seconds of inactivity after which a user's status indicator changes to <strong>Away</strong>.</p><p>Numerical input in seconds. Default is <strong>300</strong> (5 minutes).</p></td>
<td><ul><li>System Config path: <strong>Site Configuration &gt; Users and Teams</strong></li><li><code>config.json</code> setting: <code>TeamSettings</code> &gt; <code>UserStatusAwayTimeout</code> &gt; <code>300</code></li><li>Environment variable: <code>MM_TEAMSETTINGS_USERSTATUSAWAYTIMEOUT</code></li></ul></td>
</tr>
</tbody>
</table>

### Enable custom user groups

<table>
Expand Down Expand Up @@ -1003,6 +1018,21 @@ Access the following configuration settings in the System Console by going to **
</tbody>
</table>

### Enable account deactivation

<table>
<colgroup>
<col style={{width: '53%'}} />
<col style={{width: '46%'}} />
</colgroup>
<tbody>
<tr>
<td><ul><li><strong>true</strong>: Users can deactivate their own account from <strong>Settings &gt; Advanced &gt; Deactivate Account</strong>. When a user deactivates their own account, they receive an email notification confirming the deactivation.</li><li><strong>false</strong>: <strong>(Default)</strong> Users can't deactivate their own account.</li></ul><p>Available only when authentication is set to use email and password. Not available when authentication uses SAML or AD/LDAP.</p></td>
<td><ul><li>System Config path: <strong>Site Configuration &gt; Users and Teams</strong></li><li><code>config.json</code> setting: <code>TeamSettings</code> &gt; <code>EnableUserDeactivation</code> &gt; <code>false</code></li><li>Environment variable: <code>MM_TEAMSETTINGS_ENABLEUSERDEACTIVATION</code></li></ul></td>
</tr>
</tbody>
</table>

### Channel category sorting

From Mattermost v11.8, channel category sorting is enabled by default. When enabled, channel admins can choose a default sidebar category when creating or editing a channel. Channel admins can select an existing category, type a new category name, or clear the default category from channel settings. Members who join the channel see it under that category in their sidebar. When disabled, the default category selector is hidden.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ This option isn't something you can set using the mobile app.

## Deactivate account

You can deactivate your account if you access Mattermost using an email address and password, and when your system admin has [enabled your ability to do so](/administration-guide/configure/experimental-configuration-settings#enable-account-deactivation). Deactivating your account removes your ability to access Mattermost, and disables all email and mobile notifications.
You can deactivate your account if you access Mattermost using an email address and password, and when your system admin has [enabled your ability to do so](/administration-guide/configure/site-configuration-settings#enable-account-deactivation). Deactivating your account removes your ability to access Mattermost, and disables all email and mobile notifications.

<Important>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"EnableAPITeamDeletion": true,
"EnableAPITriggerAdminNotifications": false,
"EnableAPIUserDeletion": false,
"ExperimentalEnableHardenedMode": false,
"EnableHardenedMode": false,
"ExperimentalStrictCSRFEnforcement": false,
"EnableEmailInvitations": true,
"DisableBotsWhenOwnerIsDeactivated": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"EnableAPITeamDeletion": true,
"EnableAPITriggerAdminNotifications": false,
"EnableAPIUserDeletion": false,
"ExperimentalEnableHardenedMode": false,
"EnableHardenedMode": false,
"ExperimentalStrictCSRFEnforcement": false,
"EnableEmailInvitations": true,
"DisableBotsWhenOwnerIsDeactivated": true,
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/playwright/lib/src/server/default_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
EnableAPIPostDeletion: false,
EnableDesktopLandingPage: true,
MinimumDesktopAppVersion: '',
ExperimentalEnableHardenedMode: false,
EnableHardenedMode: false,
ExperimentalStrictCSRFEnforcement: false,
EnableEmailInvitations: false,
DisableBotsWhenOwnerIsDeactivated: true,
Expand Down Expand Up @@ -815,7 +815,7 @@
PropertyFieldRank: true,
TeamMembershipAccessControl: true,
MmBlocksEnabled: true,
ClusterGracefulDrain: true,

Check warning on line 818 in e2e-tests/playwright/lib/src/server/default_config.ts

View workflow job for this annotation

GitHub Actions / check

File has too many lines (909). Maximum allowed is 800
ChannelBookmarks: true,
EnableConcurrentReact: false,
EnableMFIPluginSignaturePublicKey: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {expect, test} from '@mattermost/playwright-lib';

test.describe('draft channel switch', () => {
/**
* @objective Verify a typed draft on one channel persists, restores after
* switching away and back, and posts only to the origin channel.
*/
test('typed draft stays on the origin channel after switching away and back', {tag: '@messaging'}, async ({pw}) => {
const {team, user} = await pw.initSetup();
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'off-topic');
await channelsPage.toBeVisible();

const originDraft = `origin-draft-${pw.random.id()}`;
const destinationMessage = `town-square-${pw.random.id()}`;

// # Type a draft in Off-Topic and do not send it
await channelsPage.centerView.postCreate.writeMessage(originDraft);

// # Switch to Town Square via the sidebar
await channelsPage.sidebarLeft.goToItem('town-square');
await channelsPage.centerView.header.toHaveTitle('Town Square');

// * Destination composer must not inherit the origin draft
expect(await channelsPage.centerView.postCreate.getInputValue()).toBe('');

// * Origin draft was persisted: the channel pencil is in the DOM
// (often CSS-hidden until hover) and the Drafts sidebar link appears
await expect(channelsPage.sidebarLeft.item('off-topic').getByTestId('draftIcon')).toHaveCount(1);
await channelsPage.sidebarLeft.draftsVisible();

// # Send a different message from Town Square
await channelsPage.centerView.postCreate.writeMessage(destinationMessage);
await channelsPage.centerView.postCreate.sendMessage();

// * Town Square shows the destination message
await channelsPage.centerView.waitUntilLastPostContains(destinationMessage);

// # Return to Off-Topic
await channelsPage.sidebarLeft.goToItem('off-topic');
await channelsPage.centerView.header.toHaveTitle('Off-Topic');

// * Origin draft is still in the composer
expect(await channelsPage.centerView.postCreate.getInputValue()).toBe(originDraft);

// # Send the restored draft
await channelsPage.centerView.postCreate.sendMessage();

// * Off-Topic shows the origin draft message
await channelsPage.centerView.waitUntilLastPostContains(originDraft);

// # Return to Town Square
await channelsPage.sidebarLeft.goToItem('town-square');
await channelsPage.centerView.header.toHaveTitle('Town Square');

// * Origin draft did not post to Town Square
await expect(channelsPage.centerView.container).not.toContainText(originDraft);
});

/**
* @objective Verify Ctrl/Cmd+K restores the destination draft and routes
* messages to the selected channel with concurrent React enabled.
*/
test(
'quick switcher keeps drafts and messages scoped to their channels with concurrent React',
{tag: '@messaging'},
async ({pw}) => {
await pw.ensureFeatureFlag('EnableConcurrentReact', true);

const {team, user} = await pw.initSetup();
const {channelsPage, page} = await pw.testBrowser.login(user);

await channelsPage.goto(team.name, 'off-topic');
await channelsPage.toBeVisible();

const originDraft = `quick-switch-origin-${pw.random.id()}`;
const destinationMessage = `quick-switch-destination-${pw.random.id()}`;

// # Leave a draft in Off-Topic
await channelsPage.centerView.postCreate.writeMessage(originDraft);

// # Switch to Town Square using Ctrl/Cmd+K
await page.keyboard.press('ControlOrMeta+K');
await expect(channelsPage.findChannelsModal.input).toBeVisible();
await channelsPage.findChannelsModal.input.fill('town');
await channelsPage.findChannelsModal.selectChannel('town-square');
await channelsPage.centerView.header.toHaveTitle('Town Square');

// * Town Square did not inherit the Off-Topic draft
expect(await channelsPage.centerView.postCreate.getInputValue()).toBe('');

// # Send a destination-owned message
await channelsPage.centerView.postCreate.writeMessage(destinationMessage);
await channelsPage.centerView.postCreate.sendMessage();
await channelsPage.centerView.waitUntilLastPostContains(destinationMessage);

// # Return to Off-Topic using Ctrl/Cmd+K
await page.keyboard.press('ControlOrMeta+K');
await expect(channelsPage.findChannelsModal.input).toBeVisible();
await channelsPage.findChannelsModal.input.fill('off');
await channelsPage.findChannelsModal.selectChannel('off-topic');
await channelsPage.centerView.header.toHaveTitle('Off-Topic');

// * The origin draft was restored and the destination message was not misrouted
expect(await channelsPage.centerView.postCreate.getInputValue()).toBe(originDraft);
await expect(channelsPage.centerView.container).not.toContainText(destinationMessage);
},
);

/**
* @objective Verify sending /msg to an existing DM clears the origin
* channel draft instead of leaving it behind for later restoration.
*
* @precondition
* The DM channel already exists so the redirect uses the fast path with no
* createDirectChannel round trip.
*/
test(
'sending /msg to an existing DM clears the origin draft instead of restoring it',
{tag: '@slash_commands'},
async ({pw}) => {
const {adminClient, userClient, team, user} = await pw.initSetup();
const [target] = await adminClient.createUsers(team.id, 1, 'draft-msg');

await userClient.createDirectChannel([user.id, target.id]);

const {channelsPage, page} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'off-topic');
await channelsPage.toBeVisible();

// Trailing space dismisses the @mention autocomplete.
await channelsPage.centerView.postCreate.writeMessage(`/msg @${target.username} `);
await channelsPage.centerView.postCreate.sendMessage();

await channelsPage.centerView.header.toHaveTitle(target.username);
await expect(page).toHaveURL(new RegExp(`/${team.name}/messages/@${target.username}`));

// * Destination composer is empty — it did not adopt the /msg text
expect(await channelsPage.centerView.postCreate.getInputValue()).toBe('');

// # Return to Off-Topic
await channelsPage.sidebarLeft.goToItem('off-topic');
await channelsPage.centerView.header.toHaveTitle('Off-Topic');

// * Origin draft was cleared by the submit, not left behind as /msg
expect(await channelsPage.centerView.postCreate.getInputValue()).toBe('');
await expect(channelsPage.sidebarLeft.item('off-topic').getByTestId('draftIcon')).toHaveCount(0);
},
);

/**
* @objective Verify a message typed after a settled /msg redirect to an
* existing DM posts to the DM, not the origin channel.
*
* @precondition
* The DM channel already exists so the redirect uses the fast path with no
* createDirectChannel round trip.
*/
test(
'posts a later message to the DM rather than the origin channel after /msg',
{tag: '@slash_commands'},
async ({pw}) => {
const {adminClient, userClient, team, user} = await pw.initSetup();
const [target] = await adminClient.createUsers(team.id, 1, 'stale-dm');

const dmChannel = await userClient.createDirectChannel([user.id, target.id]);
await userClient.createPost({
channel_id: dmChannel.id,
message: 'seeding the existing DM',
} as Parameters<typeof userClient.createPost>[0]);

const {channelsPage, page} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'off-topic');
await channelsPage.toBeVisible();

// Trailing space dismisses the @mention autocomplete.
await channelsPage.centerView.postCreate.writeMessage(`/msg @${target.username} `);
await channelsPage.centerView.postCreate.sendMessage();

await channelsPage.centerView.header.toHaveTitle(target.username);
await expect(page).toHaveURL(new RegExp(`/${team.name}/messages/@${target.username}`));

const message = `stale-draft-${pw.random.id()}`;
await channelsPage.centerView.postCreate.writeMessage(message);
await channelsPage.centerView.postCreate.sendMessage();

// * Follow-up message appears in the DM
await channelsPage.centerView.waitUntilLastPostContains(message);

// # Return to Off-Topic
await channelsPage.sidebarLeft.goToItem('off-topic');
await channelsPage.centerView.header.toHaveTitle('Off-Topic');

// * Follow-up message did not post to the origin channel
await expect(channelsPage.centerView.container).not.toContainText(message);
},
);
});
Loading
Loading