diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 897654701bb07..24c22cc6fc6dc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1899,8 +1899,6 @@ importers:
projects/packages/account-protection: {}
- projects/packages/account-protection: {}
-
projects/packages/admin-ui: {}
projects/packages/assets:
diff --git a/projects/js-packages/api/changelog/add-jetpack-account-protection-security-settings b/projects/js-packages/api/changelog/add-jetpack-account-protection-security-settings
new file mode 100644
index 0000000000000..778ccde6854ed
--- /dev/null
+++ b/projects/js-packages/api/changelog/add-jetpack-account-protection-security-settings
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Adds Account Protection requests
diff --git a/projects/js-packages/api/index.jsx b/projects/js-packages/api/index.jsx
index fb948bf9eace4..4aab566ac3604 100644
--- a/projects/js-packages/api/index.jsx
+++ b/projects/js-packages/api/index.jsx
@@ -515,6 +515,16 @@ function JetpackRestApiClient( root, nonce ) {
getRequest( `${ wpcomOriginApiUrl }jetpack/v4/search/stats`, getParams )
.then( checkStatus )
.then( parseJsonResponse ),
+ fetchAccountProtectionSettings: () =>
+ getRequest( `${ apiRoot }jetpack/v4/account-protection`, getParams )
+ .then( checkStatus )
+ .then( parseJsonResponse ),
+ updateAccountProtectionSettings: newSettings =>
+ postRequest( `${ apiRoot }jetpack/v4/account-protection`, postParams, {
+ body: JSON.stringify( newSettings ),
+ } )
+ .then( checkStatus )
+ .then( parseJsonResponse ),
fetchWafSettings: () =>
getRequest( `${ apiRoot }jetpack/v4/waf`, getParams )
.then( checkStatus )
diff --git a/projects/packages/account-protection/changelog/add-jetpack-account-protection-security-settings b/projects/packages/account-protection/changelog/add-jetpack-account-protection-security-settings
new file mode 100644
index 0000000000000..af516388c3c6c
--- /dev/null
+++ b/projects/packages/account-protection/changelog/add-jetpack-account-protection-security-settings
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Adds handling for module activation and deactivation
diff --git a/projects/packages/account-protection/composer.json b/projects/packages/account-protection/composer.json
index 8d9bc408b7b38..6fe92cb6eb03b 100644
--- a/projects/packages/account-protection/composer.json
+++ b/projects/packages/account-protection/composer.json
@@ -4,7 +4,9 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
- "php": ">=7.2"
+ "php": ">=7.2",
+ "automattic/jetpack-connection": "@dev",
+ "automattic/jetpack-status": "@dev"
},
"require-dev": {
"yoast/phpunit-polyfills": "^1.1.1",
diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php
index 0dd56070f3289..745900f5d11a2 100644
--- a/projects/packages/account-protection/src/class-account-protection.php
+++ b/projects/packages/account-protection/src/class-account-protection.php
@@ -1,16 +1,97 @@
modules = $modules ?? new Modules();
+ }
+
+ /**
+ * Initializes the configurations needed for the account protection module.
+ */
+ public function init() {
+ // Account protection activation/deactivation hooks
+ add_action( 'jetpack_activate_module_' . self::ACCOUNT_PROTECTION_MODULE_NAME, array( $this, 'on_account_protection_activation' ) );
+ add_action( 'jetpack_deactivate_module_' . self::ACCOUNT_PROTECTION_MODULE_NAME, array( $this, 'on_account_protection_deactivation' ) );
+
+ // Register REST routes
+ add_action( 'rest_api_init', array( new REST_Controller(), 'register_rest_routes' ) );
+ }
+
+ /**
+ * Activate the account protection on module activation.
+ */
+ public function on_account_protection_activation() {
+ // Account protection activated
+ }
+
+ /**
+ * Deactivate the account protection on module activation.
+ */
+ public function on_account_protection_deactivation() {
+ // Account protection deactivated
+ }
+
+ /**
+ * Determines if the account protection module is enabled on the site.
+ *
+ * @return bool
+ */
+ public function is_enabled() {
+ return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME );
+ }
+
+ /**
+ * Enables the account protection module.
+ *
+ * @return bool
+ */
+ public function enable() {
+ // Return true if already enabled.
+ if ( $this->is_enabled() ) {
+ return true;
+ }
+ return $this->modules->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false );
+ }
+
+ /**
+ * Disables the account protection module.
+ *
+ * @return bool
+ */
+ public function disable() {
+ // Return true if already disabled.
+ if ( ! $this->is_enabled() ) {
+ return true;
+ }
+ return $this->modules->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME );
+ }
}
diff --git a/projects/packages/account-protection/src/class-rest-controller.php b/projects/packages/account-protection/src/class-rest-controller.php
new file mode 100644
index 0000000000000..762fb90570c30
--- /dev/null
+++ b/projects/packages/account-protection/src/class-rest-controller.php
@@ -0,0 +1,106 @@
+routes_registered ) {
+ return;
+ }
+
+ register_rest_route(
+ 'jetpack/v4',
+ '/account-protection',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_settings' ),
+ 'permission_callback' => array( $this, 'permissions_callback' ),
+ )
+ );
+
+ register_rest_route(
+ 'jetpack/v4',
+ '/account-protection',
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'update_settings' ),
+ 'permission_callback' => array( $this, 'permissions_callback' ),
+ )
+ );
+
+ $this->routes_registered = true;
+ }
+
+ /**
+ * Account Protection Settings Endpoint
+ *
+ * @return WP_REST_Response
+ */
+ public function get_settings() {
+ return rest_ensure_response(
+ array(
+ Account_Protection::STRICT_MODE_OPTION_NAME => get_option( Account_Protection::STRICT_MODE_OPTION_NAME ),
+ )
+ );
+ }
+
+ /**
+ * Update Account Protection Settings Endpoint
+ *
+ * @param WP_REST_Request $request The API request.
+ *
+ * @return WP_REST_Response|WP_Error
+ */
+ public function update_settings( $request ) {
+ // Strict Mode
+ if ( isset( $request[ Account_Protection::STRICT_MODE_OPTION_NAME ] ) ) {
+ update_option( Account_Protection::STRICT_MODE_OPTION_NAME, $request[ Account_Protection::STRICT_MODE_OPTION_NAME ] ? '1' : '' );
+ }
+
+ return $this->get_settings();
+ }
+
+ /**
+ * Account Protection Endpoint Permissions Callback
+ *
+ * @return bool|WP_Error True if user can view the Jetpack admin page.
+ */
+ public function permissions_callback() {
+ if ( current_user_can( 'manage_options' ) ) {
+ return true;
+ }
+
+ return new WP_Error(
+ 'invalid_user_permission_manage_options',
+ REST_Connector::get_user_permissions_error_msg(),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+}
diff --git a/projects/packages/account-protection/tests/php/bootstrap.php b/projects/packages/account-protection/tests/php/bootstrap.php
index f8dc1879de499..76d1ee3de35c7 100644
--- a/projects/packages/account-protection/tests/php/bootstrap.php
+++ b/projects/packages/account-protection/tests/php/bootstrap.php
@@ -2,7 +2,7 @@
/**
* Bootstrap.
*
- * @package automattic/
+ * @package automattic/jetpack-account-protection
*/
/**
diff --git a/projects/plugins/jetpack/_inc/client/components/data/query-account-protection-settings/index.jsx b/projects/plugins/jetpack/_inc/client/components/data/query-account-protection-settings/index.jsx
new file mode 100644
index 0000000000000..d86ec79e0917b
--- /dev/null
+++ b/projects/plugins/jetpack/_inc/client/components/data/query-account-protection-settings/index.jsx
@@ -0,0 +1,44 @@
+import PropTypes from 'prop-types';
+import { Component } from 'react';
+import { connect } from 'react-redux';
+import {
+ fetchAccountProtectionSettings,
+ isFetchingAccountProtectionSettings,
+} from 'state/account-protection';
+import { isOfflineMode } from 'state/connection';
+
+class QueryAccountProtectionSettings extends Component {
+ static propTypes = {
+ isFetchingAccountProtectionSettings: PropTypes.bool,
+ isOfflineMode: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ isFetchingAccountProtectionSettings: false,
+ isOfflineMode: false,
+ };
+
+ componentDidMount() {
+ if ( ! this.props.isFetchingAccountProtectionSettings && ! this.props.isOfflineMode ) {
+ this.props.fetchAccountProtectionSettings();
+ }
+ }
+
+ render() {
+ return null;
+ }
+}
+
+export default connect(
+ state => {
+ return {
+ isFetchingAccountProtectionSettings: isFetchingAccountProtectionSettings( state ),
+ isOfflineMode: isOfflineMode( state ),
+ };
+ },
+ dispatch => {
+ return {
+ fetchAccountProtectionSettings: () => dispatch( fetchAccountProtectionSettings() ),
+ };
+ }
+)( QueryAccountProtectionSettings );
diff --git a/projects/plugins/jetpack/_inc/client/lib/plans/constants.js b/projects/plugins/jetpack/_inc/client/lib/plans/constants.js
index 0a486259173e5..12a0743eb48cc 100644
--- a/projects/plugins/jetpack/_inc/client/lib/plans/constants.js
+++ b/projects/plugins/jetpack/_inc/client/lib/plans/constants.js
@@ -417,6 +417,7 @@ export const FEATURE_POST_BY_EMAIL = 'post-by-email-jetpack';
export const FEATURE_JETPACK_SOCIAL = 'social-jetpack';
export const FEATURE_JETPACK_BLAZE = 'blaze-jetpack';
export const FEATURE_JETPACK_EARN = 'earn-jetpack';
+export const FEATURE_JETPACK_ACCOUNT_PROTECTION = 'account-protection-jetpack';
// Upsells
export const JETPACK_FEATURE_PRODUCT_UPSELL_MAP = {
@@ -439,6 +440,7 @@ export const JETPACK_FEATURE_PRODUCT_UPSELL_MAP = {
[ FEATURE_VIDEOPRESS ]: PLAN_JETPACK_VIDEOPRESS,
[ FEATURE_NEWSLETTER_JETPACK ]: PLAN_JETPACK_CREATOR_YEARLY,
[ FEATURE_WORDADS_JETPACK ]: PLAN_JETPACK_SECURITY_T1_YEARLY,
+ [ FEATURE_JETPACK_ACCOUNT_PROTECTION ]: PLAN_JETPACK_FREE,
};
/**
diff --git a/projects/plugins/jetpack/_inc/client/security/account-protection.jsx b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx
new file mode 100644
index 0000000000000..7334eb9e3ca7d
--- /dev/null
+++ b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx
@@ -0,0 +1,191 @@
+import { ToggleControl } from '@automattic/jetpack-components';
+import { ExternalLink } from '@wordpress/components';
+import { createInterpolateElement } from '@wordpress/element';
+import { __, _x } from '@wordpress/i18n';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { FormFieldset } from 'components/forms';
+import { createNotice, removeNotice } from 'components/global-notices/state/notices/actions';
+import { withModuleSettingsFormHelpers } from 'components/module-settings/with-module-settings-form-helpers';
+import { ModuleToggle } from 'components/module-toggle';
+import SettingsCard from 'components/settings-card';
+import SettingsGroup from 'components/settings-group';
+import QueryAccountProtectionSettings from '../components/data/query-account-protection-settings';
+import InfoPopover from '../components/info-popover';
+import { FEATURE_JETPACK_ACCOUNT_PROTECTION } from '../lib/plans/constants';
+import { updateAccountProtectionSettings } from '../state/account-protection/actions';
+import {
+ getAccountProtectionSettings,
+ isFetchingAccountProtectionSettings,
+ isUpdatingAccountProtectionSettings,
+} from '../state/account-protection/reducer';
+
+const AccountProtection = class extends Component {
+ /**
+ * Get options for initial state.
+ *
+ * @return {object}
+ */
+ state = {
+ strictMode: this.props.settings?.strictMode,
+ };
+
+ /**
+ * Keep the form values in sync with updates to the settings prop.
+ *
+ * @param {object} prevProps - Next render props.
+ */
+ componentDidUpdate = prevProps => {
+ // Sync the form values with the settings prop.
+ if ( this.props.settings !== prevProps.settings ) {
+ this.setState( {
+ ...this.state,
+ strictMode: this.props.settings?.strictMode,
+ } );
+ }
+ };
+
+ /**
+ * Handle settings updates.
+ *
+ * @return {void}
+ */
+ onSubmit = () => {
+ this.props.removeNotice( 'module-setting-update' );
+ this.props.removeNotice( 'module-setting-update-success' );
+
+ this.props.createNotice( 'is-info', __( 'Updating settingsā¦', 'jetpack' ), {
+ id: 'module-setting-update',
+ } );
+ this.props
+ .updateAccountProtectionSettings( this.state )
+ .then( () => {
+ this.props.removeNotice( 'module-setting-update' );
+ this.props.createNotice( 'is-success', __( 'Updated Settings.', 'jetpack' ), {
+ id: 'module-setting-update-success',
+ } );
+ } )
+ .catch( () => {
+ this.props.removeNotice( 'module-setting-update' );
+ this.props.createNotice( 'is-error', __( 'Error updating settings.', 'jetpack' ), {
+ id: 'module-setting-update',
+ } );
+ } );
+ };
+
+ /**
+ * Toggle strict mode.
+ */
+ toggleStrictMode = () => {
+ const state = {
+ ...this.state,
+ strictMode: ! this.state.strictMode,
+ };
+
+ this.setState( state, this.onSubmit );
+ };
+
+ render() {
+ const isAccountProtectionActive = this.props.getOptionValue( 'account-protection' ),
+ unavailableInOfflineMode = this.props.isUnavailableInOfflineMode( 'account-protection' );
+ const baseInputDisabledCase =
+ ! isAccountProtectionActive ||
+ unavailableInOfflineMode ||
+ this.props.isFetchingAccountProtectionSettings ||
+ this.props.isSavingAnyOption( [ 'account-protection' ] );
+
+ return (
+
+ { isAccountProtectionActive && }
+
+
+
+ { __(
+ 'Protect your site with enhanced password detection and profile management security.',
+ 'jetpack'
+ ) }
+
+
+ { isAccountProtectionActive && (
+
+
+
+
+ { __( 'Require strong passwords', 'jetpack' ) }
+
+
+ { createInterpolateElement(
+ __(
+ 'Allow Jetpack to enforce strict password rules. Learn more
Privacy Information',
+ 'jetpack'
+ ),
+ {
+ ExternalLink: , // TODO: Update this redirect URL
+ hr:
,
+ }
+ ) }
+
+
+ }
+ />
+
+
+ ) }
+
+
+ );
+ }
+};
+
+export default connect(
+ state => {
+ return {
+ isFetchingSettings: isFetchingAccountProtectionSettings( state ),
+ isUpdatingAccountProtectionSettings: isUpdatingAccountProtectionSettings( state ),
+ settings: getAccountProtectionSettings( state ),
+ };
+ },
+ dispatch => {
+ return {
+ updateAccountProtectionSettings: newSettings =>
+ dispatch( updateAccountProtectionSettings( newSettings ) ),
+ createNotice: ( type, message, props ) => dispatch( createNotice( type, message, props ) ),
+ removeNotice: notice => dispatch( removeNotice( notice ) ),
+ };
+ }
+)( withModuleSettingsFormHelpers( AccountProtection ) );
diff --git a/projects/plugins/jetpack/_inc/client/security/allowList.jsx b/projects/plugins/jetpack/_inc/client/security/allowList.jsx
index e102a89cd8918..8f9d8621477ab 100644
--- a/projects/plugins/jetpack/_inc/client/security/allowList.jsx
+++ b/projects/plugins/jetpack/_inc/client/security/allowList.jsx
@@ -155,7 +155,7 @@ const AllowList = class extends Component {
label={
{ __(
- "Prevent Jetpack's security features from blocking specific IP addresses",
+ "Prevent Jetpack's security features from blocking specific IP addresses.",
'jetpack'
) }
diff --git a/projects/plugins/jetpack/_inc/client/security/index.jsx b/projects/plugins/jetpack/_inc/client/security/index.jsx
index f6e2c9369fc53..d4677461de9ea 100644
--- a/projects/plugins/jetpack/_inc/client/security/index.jsx
+++ b/projects/plugins/jetpack/_inc/client/security/index.jsx
@@ -12,6 +12,7 @@ import { isModuleFound } from 'state/search';
import { getSettings } from 'state/settings';
import { siteHasFeature } from 'state/site';
import { isPluginActive, isPluginInstalled } from 'state/site/plugins';
+import AccountProtection from './account-protection';
import AllowList from './allowList';
import Antispam from './antispam';
import BackupsScan from './backups-scan';
@@ -91,6 +92,8 @@ export class Security extends Component {
);
+ const foundAccountProtection = this.props.isModuleFound( 'account-protection' );
+
return (
@@ -112,6 +115,7 @@ export class Security extends Component {
>
) }
+ { foundAccountProtection &&
}
{ foundWaf &&
}
{ foundProtect &&
}
{ ( foundWaf || foundProtect ) &&
}
diff --git a/projects/plugins/jetpack/_inc/client/security/style.scss b/projects/plugins/jetpack/_inc/client/security/style.scss
index 9a4608ee3bf57..385e7feaa710f 100644
--- a/projects/plugins/jetpack/_inc/client/security/style.scss
+++ b/projects/plugins/jetpack/_inc/client/security/style.scss
@@ -56,7 +56,9 @@
}
&__share-data-popover {
- margin-left: 8px;
+ display: flex;
+ align-items: center;
+ margin-left: 4px;
}
&__upgrade-popover {
@@ -189,4 +191,24 @@
.jp-form-settings-group p {
margin-bottom: 0.5rem;
+}
+
+.account-protection__settings {
+ &__toggle-setting {
+ flex-wrap: wrap;
+ display: flex;
+ margin-bottom: 24px;
+
+ &__label {
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ &__strict-mode-popover {
+ display: flex;
+ align-items: center;
+ margin-left: 4px;
+ }
+
}
\ No newline at end of file
diff --git a/projects/plugins/jetpack/_inc/client/state/account-protection/actions.js b/projects/plugins/jetpack/_inc/client/state/account-protection/actions.js
new file mode 100644
index 0000000000000..feee531d78a38
--- /dev/null
+++ b/projects/plugins/jetpack/_inc/client/state/account-protection/actions.js
@@ -0,0 +1,66 @@
+import restApi from '@automattic/jetpack-api';
+import {
+ ACCOUNT_PROTECTION_SETTINGS_FETCH,
+ ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE,
+ ACCOUNT_PROTECTION_SETTINGS_FETCH_FAIL,
+ ACCOUNT_PROTECTION_SETTINGS_UPDATE,
+ ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS,
+ ACCOUNT_PROTECTION_SETTINGS_UPDATE_FAIL,
+} from 'state/action-types';
+
+export const fetchAccountProtectionSettings = () => {
+ return dispatch => {
+ dispatch( {
+ type: ACCOUNT_PROTECTION_SETTINGS_FETCH,
+ } );
+ return restApi
+ .fetchAccountProtectionSettings()
+ .then( settings => {
+ dispatch( {
+ type: ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE,
+ settings,
+ } );
+ return settings;
+ } )
+ .catch( error => {
+ dispatch( {
+ type: ACCOUNT_PROTECTION_SETTINGS_FETCH_FAIL,
+ error: error,
+ } );
+ } );
+ };
+};
+
+/**
+ * Update Account Protection Settings
+ *
+ * @param {object} newSettings - The new settings to be saved.
+ * @param {boolean} newSettings.strictMode - Whether strict mode is enabled.
+ * @return {Function} - The action.
+ */
+export const updateAccountProtectionSettings = newSettings => {
+ return dispatch => {
+ dispatch( {
+ type: ACCOUNT_PROTECTION_SETTINGS_UPDATE,
+ } );
+ return restApi
+ .updateAccountProtectionSettings( {
+ jetpack_account_protection_strict_mode: newSettings.strictMode,
+ } )
+ .then( settings => {
+ dispatch( {
+ type: ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS,
+ settings,
+ } );
+ return settings;
+ } )
+ .catch( error => {
+ dispatch( {
+ type: ACCOUNT_PROTECTION_SETTINGS_UPDATE_FAIL,
+ error: error,
+ } );
+
+ throw error;
+ } );
+ };
+};
diff --git a/projects/plugins/jetpack/_inc/client/state/account-protection/index.js b/projects/plugins/jetpack/_inc/client/state/account-protection/index.js
new file mode 100644
index 0000000000000..5e3164b4c9f72
--- /dev/null
+++ b/projects/plugins/jetpack/_inc/client/state/account-protection/index.js
@@ -0,0 +1,2 @@
+export * from './reducer';
+export * from './actions';
diff --git a/projects/plugins/jetpack/_inc/client/state/account-protection/reducer.js b/projects/plugins/jetpack/_inc/client/state/account-protection/reducer.js
new file mode 100644
index 0000000000000..cb42d7bccc486
--- /dev/null
+++ b/projects/plugins/jetpack/_inc/client/state/account-protection/reducer.js
@@ -0,0 +1,87 @@
+import { assign, get } from 'lodash';
+import { combineReducers } from 'redux';
+import {
+ ACCOUNT_PROTECTION_SETTINGS_FETCH,
+ ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE,
+ ACCOUNT_PROTECTION_SETTINGS_FETCH_FAIL,
+ ACCOUNT_PROTECTION_SETTINGS_UPDATE,
+ ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS,
+ ACCOUNT_PROTECTION_SETTINGS_UPDATE_FAIL,
+} from 'state/action-types';
+
+export const data = ( state = {}, action ) => {
+ switch ( action.type ) {
+ case ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE:
+ case ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS:
+ return assign( {}, state, {
+ strictMode: Boolean( action.settings?.jetpack_account_protection_strict_mode ),
+ } );
+ default:
+ return state;
+ }
+};
+
+export const initialRequestsState = {
+ isFetchingAccountProtectionSettings: false,
+ isUpdatingAccountProtectionSettings: false,
+};
+
+export const requests = ( state = initialRequestsState, action ) => {
+ switch ( action.type ) {
+ case ACCOUNT_PROTECTION_SETTINGS_FETCH:
+ return assign( {}, state, {
+ isFetchingAccountProtectionSettings: true,
+ } );
+ case ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE:
+ case ACCOUNT_PROTECTION_SETTINGS_FETCH_FAIL:
+ return assign( {}, state, {
+ isFetchingAccountProtectionSettings: false,
+ } );
+ case ACCOUNT_PROTECTION_SETTINGS_UPDATE:
+ return assign( {}, state, {
+ isUpdatingAccountProtectionSettings: true,
+ } );
+ case ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS:
+ case ACCOUNT_PROTECTION_SETTINGS_UPDATE_FAIL:
+ return assign( {}, state, {
+ isUpdatingAccountProtectionSettings: false,
+ } );
+ default:
+ return state;
+ }
+};
+
+export const reducer = combineReducers( {
+ data,
+ requests,
+} );
+
+/**
+ * Returns true if currently requesting the account protection settings. Otherwise false.
+ *
+ * @param {object} state - Global state tree
+ * @return {boolean} Whether the account protection settings are being requested
+ */
+export function isFetchingAccountProtectionSettings( state ) {
+ return !! state.jetpack.accountProtection.requests.isFetchingAccountProtectionSettings;
+}
+
+/**
+ * Returns true if currently updating the account protection settings. Otherwise false.
+ *
+ * @param {object} state - Global state tree
+ * @return {boolean} Whether the account protection settings are being requested
+ */
+export function isUpdatingAccountProtectionSettings( state ) {
+ return !! state.jetpack.accountProtection.requests.isUpdatingAccountProtectionSettings;
+}
+
+/**
+ * Returns the account protection's settings.
+ *
+ * @param {object} state - Global state tree
+ * @return {string} File path to bootstrap.php
+ */
+export function getAccountProtectionSettings( state ) {
+ return get( state.jetpack.accountProtection, [ 'data' ], {} );
+}
diff --git a/projects/plugins/jetpack/_inc/client/state/action-types.js b/projects/plugins/jetpack/_inc/client/state/action-types.js
index 633c8476061a7..67bf7de0e487a 100644
--- a/projects/plugins/jetpack/_inc/client/state/action-types.js
+++ b/projects/plugins/jetpack/_inc/client/state/action-types.js
@@ -249,6 +249,15 @@ export const JETPACK_LICENSING_GET_USER_LICENSES_FAILURE =
export const JETPACK_CONNECTION_HAS_SEEN_WC_CONNECTION_MODAL =
'JETPACK_CONNECTION_HAS_SEEN_WC_CONNECTION_MODAL';
+export const ACCOUNT_PROTECTION_SETTINGS_FETCH = 'ACCOUNT_PROTECTION_SETTINGS_FETCH';
+export const ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE =
+ 'ACCOUNT_PROTECTION_SETTINGS_FETCH_RECEIVE';
+export const ACCOUNT_PROTECTION_SETTINGS_FETCH_FAIL = 'ACCOUNT_PROTECTION_SETTINGS_FETCH_FAIL';
+export const ACCOUNT_PROTECTION_SETTINGS_UPDATE = 'ACCOUNT_PROTECTION_SETTINGS_UPDATE';
+export const ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS =
+ 'ACCOUNT_PROTECTION_SETTINGS_UPDATE_SUCCESS';
+export const ACCOUNT_PROTECTION_SETTINGS_UPDATE_FAIL = 'ACCOUNT_PROTECTION_SETTINGS_UPDATE_FAIL';
+
export const WAF_SETTINGS_FETCH = 'WAF_SETTINGS_FETCH';
export const WAF_SETTINGS_FETCH_RECEIVE = 'WAF_SETTINGS_FETCH_RECEIVE';
export const WAF_SETTINGS_FETCH_FAIL = 'WAF_SETTINGS_FETCH_FAIL';
diff --git a/projects/plugins/jetpack/_inc/client/state/reducer.js b/projects/plugins/jetpack/_inc/client/state/reducer.js
index c50656c1c4a47..80e2be96f3be8 100644
--- a/projects/plugins/jetpack/_inc/client/state/reducer.js
+++ b/projects/plugins/jetpack/_inc/client/state/reducer.js
@@ -1,5 +1,6 @@
import { combineReducers } from 'redux';
import { globalNotices } from 'components/global-notices/state/notices/reducer';
+import { reducer as accountProtection } from 'state/account-protection/reducer';
import { dashboard } from 'state/at-a-glance/reducer';
import { reducer as connection } from 'state/connection/reducer';
import { reducer as devCard } from 'state/dev-version/reducer';
@@ -48,6 +49,7 @@ const jetpackReducer = combineReducers( {
disconnectSurvey,
trackingSettings,
licensing,
+ accountProtection,
waf,
introOffers,
} );
diff --git a/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
index fb20269ef3fa5..4e4ac0f3f6460 100644
--- a/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
+++ b/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
@@ -2335,7 +2335,14 @@ public static function get_updateable_data_list( $selector = '' ) {
'validate_callback' => __CLASS__ . '::validate_posint',
'jp_group' => 'settings',
),
-
+ // Account Protection.
+ 'jetpack_account_protection_strict_mode' => array(
+ 'description' => esc_html__( 'Strict mode - Require strong passwords.', 'jetpack' ),
+ 'type' => 'boolean',
+ 'default' => 0,
+ 'validate_callback' => __CLASS__ . '::validate_boolean',
+ 'jp_group' => 'account-protection',
+ ),
// WAF.
'jetpack_waf_automatic_rules' => array(
'description' => esc_html__( 'Enable automatic rules - Protect your site against untrusted traffic sources with automatic security rules.', 'jetpack' ),
diff --git a/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings b/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings
new file mode 100644
index 0000000000000..4c36bca9e49ec
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Adds the Account Protection module toggle
diff --git a/projects/plugins/jetpack/composer.json b/projects/plugins/jetpack/composer.json
index 0e3b15ff516f7..ffc560a01d2c3 100644
--- a/projects/plugins/jetpack/composer.json
+++ b/projects/plugins/jetpack/composer.json
@@ -12,6 +12,7 @@
"ext-json": "*",
"ext-openssl": "*",
"automattic/jetpack-a8c-mc-stats": "@dev",
+ "automattic/jetpack-account-protection": "@dev",
"automattic/jetpack-admin-ui": "@dev",
"automattic/jetpack-assets": "@dev",
"automattic/jetpack-autoloader": "@dev",
diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock
index 10393b0ef209c..4e68dd26f60c7 100644
--- a/projects/plugins/jetpack/composer.lock
+++ b/projects/plugins/jetpack/composer.lock
@@ -59,6 +59,78 @@
"relative": true
}
},
+ {
+ "name": "automattic/jetpack-account-protection",
+ "version": "dev-trunk",
+ "dist": {
+ "type": "path",
+ "url": "../../packages/account-protection",
+ "reference": "badc1036552f26a900a69608df22284e603981ed"
+ },
+ "require": {
+ "automattic/jetpack-connection": "@dev",
+ "automattic/jetpack-status": "@dev",
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "automattic/jetpack-changelogger": "@dev",
+ "automattic/wordbless": "dev-master",
+ "yoast/phpunit-polyfills": "^1.1.1"
+ },
+ "suggest": {
+ "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
+ },
+ "type": "jetpack-library",
+ "extra": {
+ "autotagger": true,
+ "branch-alias": {
+ "dev-trunk": "0.1.x-dev"
+ },
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-account-protection/compare/v${old}...v${new}"
+ },
+ "mirror-repo": "Automattic/jetpack-account-protection",
+ "textdomain": "jetpack-account-protection",
+ "version-constants": {
+ "::PACKAGE_VERSION": "src/class-account-protection.php"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "build-development": [
+ "echo 'Add your build step to composer.json, please!'"
+ ],
+ "build-production": [
+ "echo 'Add your build step to composer.json, please!'"
+ ],
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "post-install-cmd": [
+ "WorDBless\\Composer\\InstallDropin::copy"
+ ],
+ "post-update-cmd": [
+ "WorDBless\\Composer\\InstallDropin::copy"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-php \"$COVERAGE_DIR/php.cov\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ]
+ },
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "Account protection",
+ "transport-options": {
+ "relative": true
+ }
+ },
{
"name": "automattic/jetpack-admin-ui",
"version": "dev-trunk",
@@ -6021,6 +6093,7 @@
"minimum-stability": "dev",
"stability-flags": {
"automattic/jetpack-a8c-mc-stats": 20,
+ "automattic/jetpack-account-protection": 20,
"automattic/jetpack-admin-ui": 20,
"automattic/jetpack-assets": 20,
"automattic/jetpack-autoloader": 20,
diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
index 7277ce875df59..1a797bea3d27e 100644
--- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
+++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
@@ -127,6 +127,7 @@
'jetpack_subscriptions_login_navigation_enabled' => '(bool) Whether the Subscriber Login block navigation placement is enabled',
'jetpack_subscriptions_subscribe_navigation_enabled' => '(Bool) Whether the Subscribe block navigation placement is enabled',
'wpcom_ai_site_prompt' => '(string) User input in the AI site prompt',
+ 'jetpack_account_protection_strict_mode' => '(bool) Whether to enforce strict password requirements',
'jetpack_waf_automatic_rules' => '(bool) Whether the WAF should enforce automatic firewall rules',
'jetpack_waf_ip_allow_list' => '(string) List of IP addresses to always allow',
'jetpack_waf_ip_allow_list_enabled' => '(bool) Whether the IP allow list is enabled',
@@ -490,6 +491,7 @@ function ( $newsletter_category ) {
'jetpack_comment_form_color_scheme' => (string) get_option( 'jetpack_comment_form_color_scheme' ),
'in_site_migration_flow' => (string) get_option( 'in_site_migration_flow', '' ),
'migration_source_site_domain' => (string) get_option( 'migration_source_site_domain' ),
+ 'jetpack_account_protection_strict_mode' => (bool) get_option( 'jetpack_account_protection_strict_mode' ),
'jetpack_waf_automatic_rules' => (bool) get_option( 'jetpack_waf_automatic_rules' ),
'jetpack_waf_ip_allow_list' => (string) get_option( 'jetpack_waf_ip_allow_list' ),
'jetpack_waf_ip_allow_list_enabled' => (bool) get_option( 'jetpack_waf_ip_allow_list_enabled' ),
diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php
new file mode 100644
index 0000000000000..c552efec4cc41
--- /dev/null
+++ b/projects/plugins/jetpack/modules/account-protection.php
@@ -0,0 +1,18 @@
+init();
diff --git a/projects/plugins/jetpack/modules/waf.php b/projects/plugins/jetpack/modules/waf.php
index 1d5a5984f4bab..0df3856fb1948 100644
--- a/projects/plugins/jetpack/modules/waf.php
+++ b/projects/plugins/jetpack/modules/waf.php
@@ -1,7 +1,7 @@