Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cc7e4f4
Protect Dashboard: Add a feature-flagged module with a blank wp-build…
enejb Oct 5, 2026
b57245a
Protect Dashboard: Take over the Protect page and switch from the Jet…
enejb Oct 5, 2026
f734ccf
Protect Dashboard: Name the module Protect and reuse the Protect plug…
enejb Oct 5, 2026
dd09c11
Protect Dashboard: Pass Page its required children
enejb Oct 5, 2026
0b32196
Protect Dashboard: Add tabbed layout and section framework
enejb Oct 5, 2026
6cd5960
Protect Dashboard: Update lockfile for @wordpress/build peer resolution
enejb Oct 6, 2026
5786579
Protect Dashboard: Address review feedback for the foundation
enejb Oct 6, 2026
535893b
Protect Dashboard: Render card titles as headings
enejb Oct 6, 2026
771a390
Protect Dashboard: Keep the _doing_it_wrong version tag replaceable
enejb Oct 6, 2026
9f9babd
Protect Dashboard: Add the Login protection section
enejb Oct 6, 2026
5c74b1e
Protect Dashboard: Address review feedback for the Login protection s…
enejb Oct 6, 2026
32d7400
My Jetpack: Point Protect's manage link at the dashboard module
kraftbj Oct 6, 2026
8477ec8
Protect Dashboard: Test the flag gate and the menu takeover
kraftbj Oct 6, 2026
fb487b8
Protect Dashboard: Capture render output with output buffering
kraftbj Oct 6, 2026
f334b5e
Protect Dashboard: Keep IP list drafts and let a failed settings load…
kraftbj Oct 6, 2026
7a79959
Protect Dashboard: Build ProtectCard on the shared Card component
kraftbj Oct 6, 2026
823915c
Protect Dashboard: Add tests for the settings hook, IP list field and…
kraftbj Oct 6, 2026
fab647e
Merge remote-tracking branch 'origin/add/protect-dashboard-module' in…
kraftbj Oct 6, 2026
3cc966f
Protect Dashboard: Move section and threat tests into the module suite
kraftbj Oct 6, 2026
b8fc772
Merge remote-tracking branch 'origin/trunk' into macau/module-trunk-sync
kraftbj Oct 6, 2026
97bf7e5
Merge remote-tracking branch 'origin/add/protect-dashboard-module' in…
kraftbj Oct 6, 2026
9173d11
Merge remote-tracking branch 'origin/add/protect-dashboard-foundation…
kraftbj Oct 6, 2026
5af1232
Merge remote-tracking branch 'origin/trunk' into add/protect-dashboar…
kraftbj Oct 6, 2026
98ff7ae
My Jetpack: Read Protect's Jetpack delivery from a filter
kraftbj Oct 6, 2026
c50d3ec
Merge remote-tracking branch 'origin/add/protect-dashboard-module' in…
kraftbj Oct 6, 2026
39b77b9
Merge remote-tracking branch 'origin/add/protect-dashboard-foundation…
kraftbj Oct 6, 2026
e0ee4f8
Protect Dashboard: Type the Login protection section's state
kraftbj Oct 6, 2026
e056518
Merge remote-tracking branch 'origin/trunk' into add/protect-dashboar…
kraftbj Oct 8, 2026
4b15be7
Protect Dashboard: Move the Login protection section into the protect…
kraftbj Oct 8, 2026
a46dd54
Merge branch 'trunk' into add/protect-dashboard-login-protection
kraftbj Oct 9, 2026
1d0b785
Protect Dashboard: Link the Login protection card to Settings with Ta…
kraftbj Oct 9, 2026
d6a99bb
Protect Dashboard: Add tests for the Login protection section
kraftbj Oct 9, 2026
f4eca9e
Merge remote-tracking branch 'origin/trunk' into add/protect-dashboar…
kraftbj Oct 12, 2026
410997b
Merge remote-tracking branch 'origin/trunk' into add/protect-dashboar…
kraftbj Oct 13, 2026
d2a5728
Protect Dashboard: Report WordPress.com login accurately when it can'…
kraftbj Oct 13, 2026
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
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add the Login protection section to the Protect dashboard, covering brute force protection, account protection and WordPress.com login.
1 change: 1 addition & 0 deletions projects/packages/protect/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"php": ">=7.4",
"automattic/jetpack-admin-ui": "@dev",
"automattic/jetpack-connection": "@dev",
"automattic/jetpack-ip": "@dev",
"automattic/jetpack-protect-models": "@dev",
"automattic/jetpack-protect-status": "@dev",
"automattic/jetpack-status": "@dev",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import LoginProtectionCard from './login-protection-card';
import LoginProtectionSettingsCard from './login-protection-settings-card';
import type { ProtectSection } from '../types';
import type { LoginProtectionState } from './types';

const section: ProtectSection = { key: 'loginProtection' };
const section: ProtectSection< LoginProtectionState | undefined > = {
key: 'loginProtection',
OverviewCard: LoginProtectionCard,
SettingsCard: LoginProtectionSettingsCard,
};

export default section;
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { __, sprintf } from '@wordpress/i18n';
import { lock } from '@wordpress/icons';
import { Badge, Stack, Text } from '@wordpress/ui';
import { CardRow, ProtectCard, Stat } from '../../components/card';
import TabLink from '../../components/tab-link';
import isModuleActive from '../../data/is-module-active';
import type { LoginProtectionContext, ModuleState } from './types';
import type { CardStatus } from '../../components/card';

/**
* One way of protecting logins, with whether it's on.
*
* @param props - Component props.
* @param props.name - The method's name.
* @param props.description - What it does.
* @param props.state - Its module's live state.
* @return The row.
*/
function Method( {
name,
description,
state,
}: {
name: string;
description: string;
state: ModuleState;
} ) {
let badge = <Badge intent="draft">{ __( 'Off', 'jetpack-protect-pkg' ) }</Badge>;
if ( ! state.available ) {
badge = <Badge intent="none">{ __( 'Unavailable', 'jetpack-protect-pkg' ) }</Badge>;
} else if ( state.active ) {
badge = <Badge intent="stable">{ __( 'On', 'jetpack-protect-pkg' ) }</Badge>;
}

return (
<CardRow>
<Stack direction="row" gap="md" align="center" justify="space-between">
<Stack direction="column" gap="xs">
<Text variant="body-lg">{ name }</Text>
<Text variant="body-md" className="jp-protect-card__muted">
{ description }
</Text>
</Stack>
{ badge }
</Stack>
</CardRow>
);
}

/**
* The card badge's intent: neutral when no method is available.
*
* @param on - How many methods are on.
* @param available - How many methods are available.
* @return The intent.
*/
function getStatusIntent( on: number, available: number ): CardStatus[ 'intent' ] {
if ( available === 0 ) {
return 'none';
}
return on === available ? 'stable' : 'medium';
}

/**
* The Login protection card: brute force protection, account protection and WordPress.com login.
*
* @param props - The dashboard context.
* @param props.state - This section's state from PHP.
* @param props.settings - Jetpack settings, for live module state.
* @param props.openTab - Switches dashboard tabs, for the link to Settings.
* @return The card, or nothing without the section's state.
*/
export default function LoginProtectionCard( {
state: login,
settings,
openTab,
}: LoginProtectionContext ) {
if ( ! login ) {
return null;
}

const live = ( module: string, initial: ModuleState ): ModuleState => ( {
available: initial.available,
active: isModuleActive( settings.settings, module, initial.active ),
} );
const bruteForce = live( 'protect', login.bruteForce );
const accountProtection = live( 'account-protection', login.accountProtection );
// The module can stay active after the owner disconnects, but WordPress.com login doesn't run then.
const sso = live( 'sso', { ...login.sso, available: login.sso.available && login.ssoUsable } );

const methods = [ bruteForce, accountProtection, sso ];
const on = methods.filter( method => method.available && method.active ).length;
const available = methods.filter( method => method.available ).length;

return (
<ProtectCard
icon={ lock }
title={ __( 'Login protection', 'jetpack-protect-pkg' ) }
status={ {
label: sprintf(
/* translators: %1$d is how many login protections are on, %2$d how many there are. */
__( '%1$d of %2$d on', 'jetpack-protect-pkg' ),
on,
available
),
intent: getStatusIntent( on, available ),
} }
>
{ bruteForce.active && (
<CardRow>
<Stat
label={ __( 'All-time blocked login attempts', 'jetpack-protect-pkg' ) }
value={ login.blockedCount }
/>
</CardRow>
) }
<Method
name={ __( 'Brute force protection', 'jetpack-protect-pkg' ) }
description={ __(
'Blocks IP addresses that keep failing to log in.',
'jetpack-protect-pkg'
) }
state={ bruteForce }
/>
<Method
name={ __( 'Account protection', 'jetpack-protect-pkg' ) }
description={ __(
'Asks users with a weak or leaked password to verify their identity and choose a new one.',
'jetpack-protect-pkg'
) }
state={ accountProtection }
/>
<Method
name={ __( 'WordPress.com login', 'jetpack-protect-pkg' ) }
description={ __(
'Lets users log in with their WordPress.com account, with optional two-step authentication.',
'jetpack-protect-pkg'
) }
state={ sso }
/>
<CardRow>
<TabLink tab="settings" onOpen={ openTab }>
{ __( 'Configure login protection', 'jetpack-protect-pkg' ) }
</TabLink>
</CardRow>
</ProtectCard>
);
}
Loading
Loading