From 3f56ee70659d1bf8af8b9d259826c2a3483c521f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 10:10:26 -0800 Subject: [PATCH 01/61] Add Account Protection toggle to Jetpack security settings --- pnpm-lock.yaml | 2 + .../client/components/settings-card/index.jsx | 19 +++++++ .../_inc/client/lib/plans/constants.js | 2 + .../client/security/account-protection.jsx | 50 +++++++++++++++++++ .../jetpack/_inc/client/security/index.jsx | 4 ++ .../jetpack/modules/account-protection.php | 14 ++++++ 6 files changed, 91 insertions(+) create mode 100644 projects/plugins/jetpack/_inc/client/security/account-protection.jsx create mode 100644 projects/plugins/jetpack/modules/account-protection.php diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 381294a9066fb..2dc54f62fb891 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1765,6 +1765,8 @@ importers: specifier: 4.9.1 version: 4.9.1(webpack@5.94.0) + projects/packages/account-protection: {} + projects/packages/admin-ui: {} projects/packages/assets: diff --git a/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx b/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx index 8cb3d077921bd..6dd2932eb7285 100644 --- a/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx +++ b/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx @@ -24,6 +24,7 @@ import { getJetpackProductUpsellByFeature, FEATURE_JETPACK_BLAZE, FEATURE_JETPACK_EARN, + FEATURE_JETPACK_ACCOUNT_PROTECTION, } from 'lib/plans/constants'; import ProStatus from 'pro-status'; import { @@ -455,6 +456,24 @@ export const SettingsCard = inprops => { rna /> ); + case FEATURE_JETPACK_ACCOUNT_PROTECTION: + if ( props.hasConnectedOwner ) { + return ''; + } + + return ( + + ); default: return ''; } 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..39b074365c852 --- /dev/null +++ b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx @@ -0,0 +1,50 @@ +import { getRedirectUrl } from '@automattic/jetpack-components'; +import { __, _x } from '@wordpress/i18n'; +import React, { Component } from 'react'; +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 { FEATURE_JETPACK_ACCOUNT_PROTECTION } from '../lib/plans/constants'; + +const AccountProtectionComponent = class extends Component { + render() { + const isAccountProtectionActive = this.props.getOptionValue( 'account-protection' ), + unavailableInOfflineMode = this.props.isUnavailableInOfflineMode( 'account-protection' ); + return ( + + + + + { __( 'Require strong passwords', 'jetpack' ) } + + + + + ); + } +}; + +export const AccountProtection = withModuleSettingsFormHelpers( AccountProtectionComponent ); diff --git a/projects/plugins/jetpack/_inc/client/security/index.jsx b/projects/plugins/jetpack/_inc/client/security/index.jsx index f6e2c9369fc53..ff1ec0efad4f2 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/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php new file mode 100644 index 0000000000000..87bd3c9925d1d --- /dev/null +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -0,0 +1,14 @@ + Date: Thu, 9 Jan 2025 11:43:59 -0800 Subject: [PATCH 02/61] Import package and run activation/deactivation on module toggle --- .../src/class-account-protection.php | 66 ++++++++- projects/plugins/jetpack/composer.json | 1 + projects/plugins/jetpack/composer.lock | 139 +++++++++++++----- .../jetpack/modules/account-protection.php | 5 + 4 files changed, 174 insertions(+), 37 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index c895dc25a0216..29623cfd361f1 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -1,16 +1,76 @@ is_active( 'account-protection' ); + } + + /** + * Enables the account protection module. + * + * @return bool + */ + public static function enable() { + // Return true if already enabled. + if ( self::is_enabled() ) { + return true; + } + return ( new Modules() )->activate( 'account-protection', false, false ); + } + + /** + * Disables the account protection module. + * + * @return bool + */ + public static function disable() { + // Return true if already disabled. + if ( ! self::is_enabled() ) { + return true; + } + return ( new Modules() )->deactivate( 'account-protection' ); + } } diff --git a/projects/plugins/jetpack/composer.json b/projects/plugins/jetpack/composer.json index bd613184927c5..ee311a946c40e 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 f823169790931..2c890a56ac012 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7b6c53f88fcb9c7098d80137fd6d13c1", + "content-hash": "cbb88a4e4e1b0088ff12393af82e5cdc", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -59,6 +59,76 @@ "relative": true } }, + { + "name": "automattic/jetpack-account-protection", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/account-protection", + "reference": "c22829e6a80ff9f5cd10e4b4eece3d405f69e8f9" + }, + "require": { + "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", @@ -3320,16 +3390,16 @@ "packages-dev": [ { "name": "antecedent/patchwork", - "version": "2.2.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/antecedent/patchwork.git", - "reference": "b07d4fb37c3c723c8755122160c089e077d5de65" + "reference": "1bf183a3e1bd094f231a2128b9ecc5363c269245" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/b07d4fb37c3c723c8755122160c089e077d5de65", - "reference": "b07d4fb37c3c723c8755122160c089e077d5de65", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/1bf183a3e1bd094f231a2128b9ecc5363c269245", + "reference": "1bf183a3e1bd094f231a2128b9ecc5363c269245", "shasum": "" }, "require": { @@ -3362,9 +3432,9 @@ ], "support": { "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.2.0" + "source": "https://github.com/antecedent/patchwork/tree/2.2.1" }, - "time": "2024-09-27T16:59:55+00:00" + "time": "2024-12-11T10:19:54+00:00" }, { "name": "automattic/jetpack-changelogger", @@ -3686,16 +3756,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -3738,9 +3808,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -5300,16 +5370,16 @@ }, { "name": "symfony/console", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -5373,7 +5443,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -5389,7 +5459,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5410,12 +5480,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -5722,8 +5792,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5861,12 +5931,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -6059,16 +6129,16 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "e9c8413de4c8ae03d2923a44f17d0d7dad1b96be" + "reference": "0b31ce834facf03b8b44b6587e65b3cf1d7cfb94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/e9c8413de4c8ae03d2923a44f17d0d7dad1b96be", - "reference": "e9c8413de4c8ae03d2923a44f17d0d7dad1b96be", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/0b31ce834facf03b8b44b6587e65b3cf1d7cfb94", + "reference": "0b31ce834facf03b8b44b6587e65b3cf1d7cfb94", "shasum": "" }, "require": { @@ -6118,13 +6188,14 @@ "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2024-09-06T22:03:10+00:00" + "time": "2025-01-08T16:58:34+00:00" } ], "aliases": [], "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/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index 87bd3c9925d1d..76849893538ea 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -12,3 +12,8 @@ * * @package automattic/jetpack */ + +use Automattic\Jetpack\Account_Protection\Account_Protection; + +$account_protection = new Account_Protection(); +$account_protection->init(); From c83c60454e2ae906f03265ad927bceb036f47c38 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 11:44:55 -0800 Subject: [PATCH 03/61] changelog --- .../add-jetpack-account-protection-security-settings | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings 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..04ebecc39c6e1 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Adds a Account Protection module toggle From ab4f99aa37d7f7d4fe937f7e5c8091fcbd7f44c7 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 13:44:11 -0800 Subject: [PATCH 04/61] Add Protect Settings page and hook up Account Protection toggle --- projects/plugins/protect/composer.json | 3 +- projects/plugins/protect/composer.lock | 127 ++++++++++++++---- .../protect/src/class-jetpack-protect.php | 2 + .../protect/src/class-rest-controller.php | 65 +++++++++ projects/plugins/protect/src/js/api.ts | 12 ++ .../src/js/components/admin-page/index.jsx | 4 + projects/plugins/protect/src/js/constants.js | 1 + .../use-account-protection-query.ts | 17 +++ .../use-toggle-account-protection-mutation.ts | 40 ++++++ projects/plugins/protect/src/js/index.tsx | 2 + .../protect/src/js/routes/settings/index.jsx | 99 ++++++++++++++ .../src/js/routes/settings/styles.module.scss | 53 ++++++++ .../plugins/protect/src/js/types/global.d.ts | 1 + 13 files changed, 397 insertions(+), 29 deletions(-) create mode 100644 projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts create mode 100644 projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts create mode 100644 projects/plugins/protect/src/js/routes/settings/index.jsx create mode 100644 projects/plugins/protect/src/js/routes/settings/styles.module.scss diff --git a/projects/plugins/protect/composer.json b/projects/plugins/protect/composer.json index 6819583bc0ea3..ab10678d3898e 100644 --- a/projects/plugins/protect/composer.json +++ b/projects/plugins/protect/composer.json @@ -17,7 +17,8 @@ "automattic/jetpack-plans": "@dev", "automattic/jetpack-waf": "@dev", "automattic/jetpack-status": "@dev", - "automattic/jetpack-protect-status": "@dev" + "automattic/jetpack-protect-status": "@dev", + "automattic/jetpack-account-protection": "@dev" }, "require-dev": { "yoast/phpunit-polyfills": "^1.1.1", diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index 920a59e6563a4..74d116ee76a0c 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3645f8b938b9d5c8f1dc3b736ea3abaa", + "content-hash": "0cdb2fec4c2556c8d7b6d23371401a0a", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -59,6 +59,76 @@ "relative": true } }, + { + "name": "automattic/jetpack-account-protection", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/account-protection", + "reference": "c22829e6a80ff9f5cd10e4b4eece3d405f69e8f9" + }, + "require": { + "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", @@ -2260,16 +2330,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -2312,9 +2382,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -4058,16 +4128,16 @@ }, { "name": "symfony/console", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -4131,7 +4201,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -4147,7 +4217,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4168,12 +4238,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4480,8 +4550,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4619,12 +4689,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4817,16 +4887,16 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "e9c8413de4c8ae03d2923a44f17d0d7dad1b96be" + "reference": "0b31ce834facf03b8b44b6587e65b3cf1d7cfb94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/e9c8413de4c8ae03d2923a44f17d0d7dad1b96be", - "reference": "e9c8413de4c8ae03d2923a44f17d0d7dad1b96be", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/0b31ce834facf03b8b44b6587e65b3cf1d7cfb94", + "reference": "0b31ce834facf03b8b44b6587e65b3cf1d7cfb94", "shasum": "" }, "require": { @@ -4876,12 +4946,13 @@ "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2024-09-06T22:03:10+00:00" + "time": "2025-01-08T16:58:34+00:00" } ], "aliases": [], "minimum-stability": "dev", "stability-flags": { + "automattic/jetpack-account-protection": 20, "automattic/jetpack-admin-ui": 20, "automattic/jetpack-assets": 20, "automattic/jetpack-autoloader": 20, diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 293ccdaeb3ce7..b2cbf268a2a2b 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -9,6 +9,7 @@ exit; } +use Automattic\Jetpack\Account_Protection\Account_Protection; use Automattic\Jetpack\Admin_UI\Admin_Menu; use Automattic\Jetpack\Assets; use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; @@ -226,6 +227,7 @@ public function initial_state() { 'jetpackScan' => My_Jetpack_Products::get_product( 'scan' ), 'hasPlan' => Plan::has_required_plan(), 'onboardingProgress' => Onboarding::get_current_user_progress(), + 'accountProtection' => Account_Protection::is_enabled(), 'waf' => array( 'wafSupported' => Waf_Runner::is_supported_environment(), 'currentIp' => IP_Utils::get_ip(), diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php index 0aa752ddfd6d1..32d85f5e8ad97 100644 --- a/projects/plugins/protect/src/class-rest-controller.php +++ b/projects/plugins/protect/src/class-rest-controller.php @@ -9,6 +9,7 @@ namespace Automattic\Jetpack\Protect; +use Automattic\Jetpack\Account_Protection\Account_Protection; use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication; use Automattic\Jetpack\IP\Utils as IP_Utils; use Automattic\Jetpack\Protect_Status\REST_Controller as Protect_Status_REST_Controller; @@ -117,6 +118,30 @@ public static function register_rest_endpoints() { ) ); + register_rest_route( + 'jetpack-protect/v1', + 'toggle-account-protection', + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::api_toggle_account_protection', + 'permission_callback' => function () { + return current_user_can( 'manage_options' ); + }, + ) + ); + + register_rest_route( + 'jetpack-protect/v1', + 'account-protection', + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::api_get_account_protection', + 'permission_callback' => function () { + return current_user_can( 'manage_options' ); + }, + ) + ); + register_rest_route( 'jetpack-protect/v1', 'toggle-waf', @@ -340,6 +365,46 @@ public static function api_scan() { return new WP_REST_Response( 'Scan enqueued.' ); } + /** + * Toggles the Account Protection module on or off for the API endpoint + * + * @return WP_REST_Response|WP_Error + */ + public static function api_toggle_account_protection() { + if ( Account_Protection::is_enabled() ) { + $disabled = Account_Protection::disable(); + if ( ! $disabled ) { + return new WP_Error( + 'account_protection_disable_failed', + __( 'An error occurred disabling account protection.', 'jetpack-protect' ), + array( 'status' => 500 ) + ); + } + + return rest_ensure_response( true ); + } + + $enabled = Account_Protection::enable(); + if ( ! $enabled ) { + return new WP_Error( + 'account_protection_enable_failed', + __( 'An error occurred enabling account protection.', 'jetpack-protect' ), + array( 'status' => 500 ) + ); + } + + return rest_ensure_response( true ); + } + + /** + * Get Account Protection data for the API endpoint + * + * @return WP_Rest_Response + */ + public static function api_get_account_protection() { + return new WP_REST_Response( Account_Protection::is_enabled() ); + } + /** * Toggles the WAF module on or off for the API endpoint * diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index 2b98a6164bf8b..b2570e892fc61 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -3,6 +3,18 @@ import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; const API = { + toggleAccountProtection: () => + apiFetch( { + method: 'POST', + path: 'jetpack-protect/v1/toggle-account-protection', + } ), + + getAccountProtection: () => + apiFetch( { + path: 'jetpack-protect/v1/account-protection', + method: 'GET', + } ), + getWaf: (): Promise< WafStatus > => apiFetch( { path: 'jetpack-protect/v1/waf', diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index 4579831b5f0a5..95e34eb79daa8 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -69,6 +69,10 @@ const AdminPage = ( { children } ) => { } /> + { __( 'Settings', 'jetpack-protect' ) } } + /> { children } diff --git a/projects/plugins/protect/src/js/constants.js b/projects/plugins/protect/src/js/constants.js index 5ec94bdccafc9..f643493fd37bd 100644 --- a/projects/plugins/protect/src/js/constants.js +++ b/projects/plugins/protect/src/js/constants.js @@ -31,3 +31,4 @@ export const QUERY_ONBOARDING_PROGRESS_KEY = 'onboarding progress'; export const QUERY_PRODUCT_DATA_KEY = 'product data'; export const QUERY_SCAN_STATUS_KEY = 'scan status'; export const QUERY_WAF_KEY = 'waf'; +export const QUERY_ACCOUNT_PROTECTION_KEY = 'account protection'; diff --git a/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts new file mode 100644 index 0000000000000..cc9ac9cbf202c --- /dev/null +++ b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts @@ -0,0 +1,17 @@ +import { useQuery, UseQueryResult } from '@tanstack/react-query'; +import camelize from 'camelize'; +import API from '../../api'; +import { QUERY_ACCOUNT_PROTECTION_KEY } from '../../constants'; + +/** + * WAF Query Hook + * + * @return {UseQueryResult} useQuery result. + */ +export default function useAccountProtectionQuery(): UseQueryResult< boolean > { + return useQuery( { + queryKey: [ QUERY_ACCOUNT_PROTECTION_KEY ], + queryFn: API.getAccountProtection, + initialData: camelize( window?.jetpackProtectInitialState?.accountProtection ), + } ); +} diff --git a/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts b/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts new file mode 100644 index 0000000000000..10fa874cb1ebb --- /dev/null +++ b/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts @@ -0,0 +1,40 @@ +import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; +import { __ } from '@wordpress/i18n'; +import API from '../../api'; +import { QUERY_ACCOUNT_PROTECTION_KEY } from '../../constants'; +import useNotices from '../../hooks/use-notices'; + +/** + * Toggle Account Protection Mutatation + * + * @return {UseMutationResult} useMutation result. + */ +export default function useToggleAccountProtectMutation(): UseMutationResult { + const queryClient = useQueryClient(); + const { showSuccessNotice, showSavingNotice, showErrorNotice } = useNotices(); + + return useMutation( { + mutationFn: API.toggleAccountProtection, + onMutate: () => { + showSavingNotice(); + // Get the current account protection settings. + const initialValue = queryClient.getQueryData( [ QUERY_ACCOUNT_PROTECTION_KEY ] ); + + // Optimistically update settings. + queryClient.setQueryData( [ QUERY_ACCOUNT_PROTECTION_KEY ], ( status: boolean ) => ! status ); + + return { initialValue }; + }, + onSuccess: () => { + showSuccessNotice( __( 'Changes saved.', 'jetpack-protect' ) ); + }, + onError: () => { + showErrorNotice( + __( 'An error occurred toggling the account protection module.', 'jetpack-protect' ) + ); + }, + onSettled: () => { + queryClient.invalidateQueries( { queryKey: [ QUERY_ACCOUNT_PROTECTION_KEY ] } ); + }, + } ); +} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..3ffe20e853986 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -13,6 +13,7 @@ import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; import ScanHistoryRoute from './routes/scan/history'; +import SettingsRoute from './routes/settings'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -56,6 +57,7 @@ function render() { + } /> } /> } /> { + const { hasPlan } = usePlan(); + const toggleAccountProtectionMutation = useToggleAccountProtectionMutation(); + const { data: isAccountProtectionEnabled } = useAccountProtectionQuery(); + + // Track view for Protect Account Protection page. + useAnalyticsTracks( { + pageViewEventName: 'protect_account_protection', + pageViewEventProperties: { + has_plan: hasPlan, + }, + } ); + + /** + * Toggle Account Protection Module + * + * Flips the switch on the Account Protection module, and then refreshes the data. + */ + const toggleAccountProtection = useCallback( async () => { + toggleAccountProtectionMutation.mutate(); + }, [ toggleAccountProtectionMutation ] ); + + const accountProtectionSettings = ( +
+
+ +
+
+ + { __( 'Require strong passwords', 'jetpack-protect' ) } + + + { createInterpolateElement( + __( + 'When enabled, users can only set passwords that meet strong security standards, helping protect their accounts and your site.', + 'jetpack-protect' + ), + { + link: , // TODO: Update this redirect URL + } + ) } + + { isAccountProtectionEnabled && ( + + + { createInterpolateElement( + __( + 'Jetpack recommends activating this setting. Please be mindful of the risks.', + 'jetpack-protect' + ), + { + link: , // TODO: Update this redirect URL + } + ) } + + ) } +
+
+ ); + + /** + * Render + */ + return ( + + + + +
{ accountProtectionSettings }
+ +
+
+
+ ); +}; + +export default SettingsPage; diff --git a/projects/plugins/protect/src/js/routes/settings/styles.module.scss b/projects/plugins/protect/src/js/routes/settings/styles.module.scss new file mode 100644 index 0000000000000..3b43e636ffd96 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/settings/styles.module.scss @@ -0,0 +1,53 @@ +.container { + width: 100%; + max-width: calc( 744px + ( var( --spacing-base ) * 6 ) ); // 744px + 48px (desired inner width + horizontal padding) +} + +.toggle-section { + display: flex; + + &:not(:first-child) { + margin-top: calc( var( --spacing-base ) * 7 ); // 56px + } + + &__control { + padding-top: calc( var( --spacing-base ) / 2 ); // 4px + margin-right: calc( var( --spacing-base ) * 2 ); // 16px + + @media ( min-width: 600px ) { + margin-right: calc( var( --spacing-base ) * 5 ); // 48px + } + } + + &__content { + width: 100%; + } + + &__description { + a { + color: inherit; + + &:hover { + color: var( --jp-black ); + } + } + } + + &__warning { + color: var( --jp-red-50 ); + + a { + color: var( --jp-red-50 ); + + &:hover { + color: var( --jp-red-70 ) + } + } + + svg { + fill: var( --jp-red-50 ); + margin-bottom: calc( -1 * var( --spacing-base ) * 3/4 ); // -6px + margin-right: calc( var( --spacing-base ) / 4 ); // 2px + } + } +} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/types/global.d.ts b/projects/plugins/protect/src/js/types/global.d.ts index 826b133869a7a..2f01fad2c7a54 100644 --- a/projects/plugins/protect/src/js/types/global.d.ts +++ b/projects/plugins/protect/src/js/types/global.d.ts @@ -29,6 +29,7 @@ declare global { jetpackScan: ProductData; hasPlan: boolean; onboardingProgress: string[]; + accountProtection: boolean; waf: WafStatus; }; } From c2e8f1eb13c2f33f67a34ef55c905befd7b31666 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 13:45:25 -0800 Subject: [PATCH 05/61] changelog --- .../protect/changelog/add-protect-account-protection-settings | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/protect/changelog/add-protect-account-protection-settings diff --git a/projects/plugins/protect/changelog/add-protect-account-protection-settings b/projects/plugins/protect/changelog/add-protect-account-protection-settings new file mode 100644 index 0000000000000..0383c19735e86 --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-account-protection-settings @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds the Account Protection module toggle From b64fdaf43b2318dc121e47577a01ad21afbd147d Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 13:48:06 -0800 Subject: [PATCH 06/61] Update changelog --- .../changelog/add-jetpack-account-protection-security-settings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings b/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings index 04ebecc39c6e1..4c36bca9e49ec 100644 --- a/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings +++ b/projects/plugins/jetpack/changelog/add-jetpack-account-protection-security-settings @@ -1,4 +1,4 @@ Significance: minor Type: enhancement -Adds a Account Protection module toggle +Adds the Account Protection module toggle From 3f685364189e17df934ba1420e093d4c7cfb9587 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 14:58:26 -0800 Subject: [PATCH 07/61] Register modules on plugin activation --- projects/plugins/protect/src/class-jetpack-protect.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index b2cbf268a2a2b..9d84cc27525d3 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -59,6 +59,7 @@ class Jetpack_Protect { ); const JETPACK_WAF_MODULE_SLUG = 'waf'; const JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG = 'protect'; + const JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG = 'account-protection'; const JETPACK_PROTECT_ACTIVATION_OPTION = JETPACK_PROTECT_SLUG . '_activated'; /** @@ -277,12 +278,13 @@ public static function do_plugin_activation_activities() { } /** - * Activates the waf and brute force protection modules and disables the activation option + * Activates the waf, brute force protection and account protection modules and disables the activation option */ public static function activate_modules() { delete_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ); ( new Modules() )->activate( self::JETPACK_WAF_MODULE_SLUG, false, false ); ( new Modules() )->activate( self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, false, false ); + ( new Modules() )->activate( self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG, false, false ); } /** @@ -340,7 +342,7 @@ public function admin_bar( $wp_admin_bar ) { * @return array */ public function protect_filter_available_modules( $modules ) { - return array_merge( array( self::JETPACK_WAF_MODULE_SLUG, self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG ), $modules ); + return array_merge( array( self::JETPACK_WAF_MODULE_SLUG, self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG ), $modules ); } /** From 3628b02dce3c81b2801244dfa6d03e1783ec7bae Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 15:08:00 -0800 Subject: [PATCH 08/61] Ensure package is initialized on plugin activation --- projects/plugins/protect/src/class-jetpack-protect.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 9d84cc27525d3..e2a1cfd74d38b 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -114,6 +114,9 @@ function () { // Web application firewall package. $config->ensure( 'waf' ); + + // Account protection package. + $config->ensure( 'account_protection' ); }, 1 ); @@ -135,6 +138,7 @@ public function init() { REST_Controller::init(); My_Jetpack_Initializer::init(); Site_Health::init(); + Account_Protection::init(); // Sets up JITMS. JITM::configure(); From 3f90fe40747ccdcaccc8294fbac5163ebd3ec768 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 9 Jan 2025 15:09:17 -0800 Subject: [PATCH 09/61] Make account protection class init static --- .../account-protection/src/class-account-protection.php | 2 +- projects/plugins/jetpack/modules/account-protection.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 29623cfd361f1..c623faa14781a 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -19,7 +19,7 @@ class Account_Protection { /** * Initializes the configurations needed for the account protection module. */ - public function init() { + public static function init() { // Account protection activation/deactivation hooks add_action( 'jetpack_activate_module_account-protection', __CLASS__ . '::on_account_protection_activation' ); add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index 76849893538ea..891d023f7fe37 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -15,5 +15,4 @@ use Automattic\Jetpack\Account_Protection\Account_Protection; -$account_protection = new Account_Protection(); -$account_protection->init(); +Account_Protection::init(); From 3bfbcb3de075174da0771e746c1d03537e46bbed Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 13 Jan 2025 14:55:00 -0800 Subject: [PATCH 10/61] Add auth hooks, redirect and a custom login action template --- .../src/assets/jetpack-logo.php | 21 ++ .../src/class-account-protection.php | 203 +++++++++++++++++- .../src/css/password-detection.css | 50 +++++ .../templates/password-detection-template.php | 22 ++ 4 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 projects/packages/account-protection/src/assets/jetpack-logo.php create mode 100644 projects/packages/account-protection/src/css/password-detection.css create mode 100644 projects/packages/account-protection/src/templates/password-detection-template.php diff --git a/projects/packages/account-protection/src/assets/jetpack-logo.php b/projects/packages/account-protection/src/assets/jetpack-logo.php new file mode 100644 index 0000000000000..b91e3c5c216f5 --- /dev/null +++ b/projects/packages/account-protection/src/assets/jetpack-logo.php @@ -0,0 +1,21 @@ + + "Jetpack Logo" + + + + + + + + + diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index c623faa14781a..8a4b9ce49c20b 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -23,20 +23,217 @@ public static function init() { // Account protection activation/deactivation hooks add_action( 'jetpack_activate_module_account-protection', __CLASS__ . '::on_account_protection_activation' ); add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); + + if ( self::is_enabled() ) { + // Validate password post successful login + add_action( 'wp_authenticate_user', __CLASS__ . '::custom_post_login_password_check', 10, 2 ); + + // Add custom validation flow for users with unsafe passwords + add_action( + 'login_form_password-detection', + function () { + // Restrict access for logged-out users + if ( ! is_user_logged_in() ) { + wp_redirect( wp_login_url() ); + exit; + } + + $current_user = wp_get_current_user(); + // TODO: Are we confident we always have a user here? How to handle it otherwise... + $password_status = get_user_meta( $current_user->ID, 'jetpack_account_protection_password_status', true ); + + // Restrict access for logged in users with secure or unevaluated passwords to the admin + if ( ! $password_status || 'safe' === $password_status ) { + wp_redirect( admin_url() ); + exit; + } + + if ( isset( $_POST['reset'] ) ) { + $site_url = home_url(); + $parsed_url = parse_url( $site_url ); + $domain_name = $parsed_url['host']; + $username = $current_user->user_login; + $email = $current_user->user_email; + $masked_email = self::mask_email_address( $email ); + + $key = get_password_reset_key( $current_user ); + $locale = get_user_locale( $current_user ); + $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $current_user->user_login ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; + + // Send reset email to user - only initially and on resend, not refresh + self::send_custom_reset_email( $domain_name, $username, $email, $password_reset_link ); + + $header_title = 'Secure Your Account'; + $page_title = "Let's secure your account"; + $content = ' +

Your current password was found in a public leak, which means your account might be at risk.

+

Don\'t worry - To keep your account safe, we\'ve sent a verification email to a ' . $masked_email . '. After that, we\'ll guide you through updating your password.

+

Please check your inbox and click the link to verify it\'s you. Didn\'t get the email? Resend email

'; + + } elseif ( isset( $_POST['proceed'] ) ) { + wp_redirect( admin_url() ); + exit; + } else { + $header_title = 'Stay Secure'; + $page_title = 'Take action to stay secure'; + $content = ' +

Your current password was found in a public leak, which means your account might be at risk.

+

It is highly recommended that you update your password.

+
+
+ +
+
+ +
+
+

Learn more about the risks of using weak passwords and how to protect your account.

'; + } + + // Include the template + include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; + exit; + } + ); + + // Ensure jetpack_account_protection_password_status user meta is removed on password change + // TODO: Is there potentially another hook for storing old password after reset? + // Or we can store old passwords by hash and use wp_check_password to validate + add_action( + 'after_password_reset', + function ( $user, $new_pass ) { + delete_user_meta( $user->ID, 'jetpack_account_protection_password_status' ); + + // Add old password hash to user meta + // $password_hash_history = get_user_meta( $user->ID, 'jetpack_account_protection_password_hash_history', true ) ?: []; + // $password_hash_history[] = $user->user_pass; + // update_user_meta( $user->ID, 'jetpack_account_protection_password_hash_history', $password_hash_history ); + }, + 10, + 2 + ); + + // Ensure jetpack_account_protection_password_status user meta is removed when user updates password via profile updates + add_action( + 'profile_update', + function ( $user_id, $old_user_data ) { + // Profile updates should include validation, but we should reset user meta to be safe + if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { + // TODO: Only if the password is actually updated + delete_user_meta( $user_id, 'jetpack_account_protection_password_status' ); + + // // Add old password hash to user meta + // $password_hash_history = get_user_meta( $user_id, 'jetpack_account_protection_password_hash_history', true ) ?: []; + // // Add the current password hash to the array + // $password_hash_history[] = $old_user_data->user_pass; + // update_user_meta( $user_id, 'jetpack_account_protection_password_hash_history', $password_hash_history ); + } + }, + 10, + 2 + ); + + // TODO: Action/cron for clearing out old password hashes? + + } + } + + /** + * Custom login validation. + */ + public static function custom_post_login_password_check( $user, $password ) { + if ( ! self::custom_password_check( $password ) ) { + error_log( 'Password check failed' ); + // TODO: Are there any potential issues with using this, eg if somehow the pass is updated before this is corrected? + update_user_meta( $user->ID, 'jetpack_account_protection_password_status', 'unsafe' ); + + // Log the user in but customize the redirect + add_filter( 'login_redirect', __CLASS__ . '::custom_login_redirect', 10, 3 ); + } else { + update_user_meta( $user->ID, 'user', 'safe' ); + } + + return $user; + } + + /** + * Custom post login password check. + */ + public static function custom_password_check( $password ) { + // TESTING BGN + // TODO: This belongs in the set/update validation + // current test wont work here because we wouldn't reach this part because core validation would error out the login process... + // This would work somewhere... + // error_log( var_export( wp_check_password( 'wordpress', $old_hash ), true ) ); + + // $user = wp_get_current_user(); + // $user_id = $user->ID; + + // // Retrieve old hashes + // $old_hashes = get_user_meta( $user_id, 'old_password_hashes', true ) ?: []; + + // // Check against each old hash + // foreach ( $old_hashes as $old_hash ) { + // if ( wp_check_password( $password, $old_hash ) ) { + // error_log( var_export( 'Password found in old hashes ' . $password, true ) ); + // } + // } + // Once we have the reset_link we can test this... + // TESTING STOP + + // TODO: The validation here is less extension then when setting a password, for example, no need to include a historic check + return $password ? false : true; + } + + public static function custom_login_redirect() { + return home_url( '/wp-login.php?action=password-detection' ); + } + + public static function send_custom_reset_email( $domain_name, $username, $email, $password_reset_link ) { + error_log( 'Site: ' . $domain_name ); + error_log( 'Username: ' . $username ); + error_log( 'Sending custom reset email to ' . $email ); + error_log( 'Password reset link: ' . $password_reset_link ); + } + + /** + * Mask an email address like d*****@g*****.com. + * + * @param string $email The email address to mask. + * @return string The masked email address. + */ + public static function mask_email_address( $email ) { + $parts = explode( '@', $email ); + $name = $parts[0]; + $domain = $parts[1]; + + // Mask the name part (first letter + asterisks) + $masked_name = substr( $name, 0, 1 ) . str_repeat( '*', strlen( $name ) - 1 ); + + // Mask the domain part (first letter + asterisks + domain extension) + $domain_parts = explode( '.', $domain ); + $masked_domain = substr( $domain_parts[0], 0, 1 ) . str_repeat( '*', strlen( $domain_parts[0] ) - 1 ) . '.' . $domain_parts[1]; + + return $masked_name . '@' . $masked_domain; } /** * Activate the account protection on module activation. */ public static function on_account_protection_activation() { - // Account protection activated } /** - * Deactivate the account protection on module activation. + * Deactivate the account protection on module deactivation. */ public static function on_account_protection_deactivation() { - // Account protection deactivated + // Remove user meta on deactivation + $users = get_users(); + foreach ( $users as $user ) { + delete_user_meta( $user->ID, 'jetpack_account_protection_password_status' ); + // TODO: Do we want to clear password hash history only on deactivation? + // TODO: Ensure this happens on plugin deactivation as well? + } } /** diff --git a/projects/packages/account-protection/src/css/password-detection.css b/projects/packages/account-protection/src/css/password-detection.css new file mode 100644 index 0000000000000..00d9203ea32c8 --- /dev/null +++ b/projects/packages/account-protection/src/css/password-detection.css @@ -0,0 +1,50 @@ +body { + background: #f0f0f1; + min-width: 0; + color: #3c434a; + font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 13px; + line-height: 1.4; +} + +.custom { + background: #fff; + width: 420px; + margin: 124px auto; + padding: 26px 24px; + font-weight: 400; + overflow: hidden; + background: #fff; + border: 1px solid #c3c4c7; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); +} + +.custom-title { + font-size: 24px; + font-weight: 500; +} + +.actions { + display: flex; + flex-direction: column; + gap: 8px; +} + +.action { + height: 36px; + cursor: pointer; + width: 100%; +} + +.action-reset { + margin-top: 10px; + background-color: #0000EE; + border: 1px solid #0000EE; + color: #fff; + } + +.action-proceed { + background-color: #fff; + border: 1px solid #0000EE; + color: #0000EE; +} \ No newline at end of file diff --git a/projects/packages/account-protection/src/templates/password-detection-template.php b/projects/packages/account-protection/src/templates/password-detection-template.php new file mode 100644 index 0000000000000..748663cb8460c --- /dev/null +++ b/projects/packages/account-protection/src/templates/password-detection-template.php @@ -0,0 +1,22 @@ + + + + + + + + <?php echo 'Jetpack - ' . esc_html( $header_title ); ?> + + + +
+ +

+ +
+ +'; \ No newline at end of file From 289dbdbdbd5d95e5a5dff44b9f246558b47bcf08 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 14 Jan 2025 09:01:52 -0800 Subject: [PATCH 11/61] Reorg, add Password_Detection class --- .../src/class-account-protection.php | 334 +++++++++--------- .../src/class-password-detection.php | 15 + 2 files changed, 183 insertions(+), 166 deletions(-) create mode 100644 projects/packages/account-protection/src/class-password-detection.php diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 8a4b9ce49c20b..1bfb3b36b019a 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -25,175 +25,198 @@ public static function init() { add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); if ( self::is_enabled() ) { - // Validate password post successful login - add_action( 'wp_authenticate_user', __CLASS__ . '::custom_post_login_password_check', 10, 2 ); + // Validate password after successful login + add_action( 'wp_authenticate_user', __CLASS__ . '::login_form_password_detection', 10, 2 ); - // Add custom validation flow for users with unsafe passwords + // Add password detection flow for users with unsafe passwords add_action( 'login_form_password-detection', - function () { - // Restrict access for logged-out users - if ( ! is_user_logged_in() ) { - wp_redirect( wp_login_url() ); - exit; - } - - $current_user = wp_get_current_user(); - // TODO: Are we confident we always have a user here? How to handle it otherwise... - $password_status = get_user_meta( $current_user->ID, 'jetpack_account_protection_password_status', true ); - - // Restrict access for logged in users with secure or unevaluated passwords to the admin - if ( ! $password_status || 'safe' === $password_status ) { - wp_redirect( admin_url() ); - exit; - } - - if ( isset( $_POST['reset'] ) ) { - $site_url = home_url(); - $parsed_url = parse_url( $site_url ); - $domain_name = $parsed_url['host']; - $username = $current_user->user_login; - $email = $current_user->user_email; - $masked_email = self::mask_email_address( $email ); - - $key = get_password_reset_key( $current_user ); - $locale = get_user_locale( $current_user ); - $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $current_user->user_login ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; - - // Send reset email to user - only initially and on resend, not refresh - self::send_custom_reset_email( $domain_name, $username, $email, $password_reset_link ); - - $header_title = 'Secure Your Account'; - $page_title = "Let's secure your account"; - $content = ' -

Your current password was found in a public leak, which means your account might be at risk.

-

Don\'t worry - To keep your account safe, we\'ve sent a verification email to a ' . $masked_email . '. After that, we\'ll guide you through updating your password.

-

Please check your inbox and click the link to verify it\'s you. Didn\'t get the email? Resend email

'; - - } elseif ( isset( $_POST['proceed'] ) ) { - wp_redirect( admin_url() ); - exit; - } else { - $header_title = 'Stay Secure'; - $page_title = 'Take action to stay secure'; - $content = ' -

Your current password was found in a public leak, which means your account might be at risk.

-

It is highly recommended that you update your password.

-
-
- -
-
- -
-
-

Learn more about the risks of using weak passwords and how to protect your account.

'; - } - - // Include the template - include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; - exit; - } + __CLASS__ . '::render_password_detection_page', + 10, + 2 ); - // Ensure jetpack_account_protection_password_status user meta is removed on password change - // TODO: Is there potentially another hook for storing old password after reset? - // Or we can store old passwords by hash and use wp_check_password to validate + // Ensure jetpack_account_protection_password_status usermeta is removed on password change add_action( 'after_password_reset', function ( $user, $new_pass ) { delete_user_meta( $user->ID, 'jetpack_account_protection_password_status' ); - - // Add old password hash to user meta - // $password_hash_history = get_user_meta( $user->ID, 'jetpack_account_protection_password_hash_history', true ) ?: []; - // $password_hash_history[] = $user->user_pass; - // update_user_meta( $user->ID, 'jetpack_account_protection_password_hash_history', $password_hash_history ); }, 10, 2 ); - // Ensure jetpack_account_protection_password_status user meta is removed when user updates password via profile updates + // Ensure jetpack_account_protection_password_status usermeta is removed when user updates password via profile updates add_action( 'profile_update', function ( $user_id, $old_user_data ) { // Profile updates should include validation, but we should reset user meta to be safe if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { // TODO: Only if the password is actually updated - delete_user_meta( $user_id, 'jetpack_account_protection_password_status' ); - - // // Add old password hash to user meta - // $password_hash_history = get_user_meta( $user_id, 'jetpack_account_protection_password_hash_history', true ) ?: []; - // // Add the current password hash to the array - // $password_hash_history[] = $old_user_data->user_pass; - // update_user_meta( $user_id, 'jetpack_account_protection_password_hash_history', $password_hash_history ); + self::remove_password_detection_usermeta( $user_id ); } }, 10, 2 ); + } + } - // TODO: Action/cron for clearing out old password hashes? + /** + * Activate the account protection on module activation. + */ + public static function on_account_protection_activation() { + } + /** + * Deactivate the account protection on module deactivation. + */ + public static function on_account_protection_deactivation() { + // Remove user meta on deactivation + $users = get_users(); + foreach ( $users as $user ) { + self::remove_password_detection_usermeta( $user->ID ); + // TODO: Remove usermeta on plugin deactivation as well } } /** - * Custom login validation. + * Determines if the account protection module is enabled on the site. + * + * @return bool + */ + public static function is_enabled() { + return ( new Modules() )->is_active( 'account-protection' ); + } + + /** + * Enables the account protection module. + * + * @return bool + */ + public static function enable() { + // Return true if already enabled. + if ( self::is_enabled() ) { + return true; + } + return ( new Modules() )->activate( 'account-protection', false, false ); + } + + /** + * Disables the account protection module. + * + * @return bool + */ + public static function disable() { + // Return true if already disabled. + if ( ! self::is_enabled() ) { + return true; + } + return ( new Modules() )->deactivate( 'account-protection' ); + } + + /** + * Check if the password is safe after login. + * + * @param WP_User $user The user object. + * @param string $password The password. + * @return WP_User The user object. */ - public static function custom_post_login_password_check( $user, $password ) { - if ( ! self::custom_password_check( $password ) ) { - error_log( 'Password check failed' ); - // TODO: Are there any potential issues with using this, eg if somehow the pass is updated before this is corrected? + public static function login_form_password_detection( $user, $password ) { + if ( ! self::validate_password( $password ) ) { + // TODO: Ensure this usermeta is always up to date update_user_meta( $user->ID, 'jetpack_account_protection_password_status', 'unsafe' ); - // Log the user in but customize the redirect - add_filter( 'login_redirect', __CLASS__ . '::custom_login_redirect', 10, 3 ); + // Redirect to the password detection page + add_filter( 'login_redirect', __CLASS__ . '::password_detection_redirect', 10, 3 ); } else { - update_user_meta( $user->ID, 'user', 'safe' ); + update_user_meta( $user->ID, 'jetpack_account_protection_password_status', 'safe' ); } return $user; } /** - * Custom post login password check. + * Render password detection page. + * + * This page is shown to users with unsafe passwords after login. + * + * @return void */ - public static function custom_password_check( $password ) { - // TESTING BGN - // TODO: This belongs in the set/update validation - // current test wont work here because we wouldn't reach this part because core validation would error out the login process... - // This would work somewhere... - // error_log( var_export( wp_check_password( 'wordpress', $old_hash ), true ) ); - - // $user = wp_get_current_user(); - // $user_id = $user->ID; - - // // Retrieve old hashes - // $old_hashes = get_user_meta( $user_id, 'old_password_hashes', true ) ?: []; - - // // Check against each old hash - // foreach ( $old_hashes as $old_hash ) { - // if ( wp_check_password( $password, $old_hash ) ) { - // error_log( var_export( 'Password found in old hashes ' . $password, true ) ); - // } - // } - // Once we have the reset_link we can test this... - // TESTING STOP - - // TODO: The validation here is less extension then when setting a password, for example, no need to include a historic check - return $password ? false : true; + public static function render_password_detection_page() { + // Restrict direct access to logged in users + if ( ! is_user_logged_in() ) { + wp_redirect( wp_login_url() ); + exit; + } + + $current_user = wp_get_current_user(); + $user_password_status = get_user_meta( $current_user->ID, 'jetpack_account_protection_password_status', true ); + + // Restrict direct access to users with unsafe passwords + if ( ! $user_password_status || 'safe' === $user_password_status ) { + wp_redirect( admin_url() ); + exit; + } + + if ( isset( $_POST['reset'] ) ) { + $email = $current_user->user_email; + + // Send reset email to the user - only initially and on resend, not refresh + $email_sent = self::send_password_reset_email( $current_user, $email ); + if ( ! $email_sent ) { + // TODO: Handle email sending errors + } + + $header_title = 'Secure Your Account'; + $page_title = "Let's secure your account"; + $content = ' +

Your current password was found in a public leak, which means your account might be at risk.

+

Don\'t worry - To keep your account safe, we\'ve sent a verification email to a ' . self::mask_email_address( $email ) . '. After that, we\'ll guide you through updating your password.

+

Please check your inbox and click the link to verify it\'s you. Didn\'t get the email? Resend email

'; + + } elseif ( isset( $_POST['proceed'] ) ) { + wp_redirect( admin_url() ); + exit; + } else { + $header_title = 'Stay Secure'; + $page_title = 'Take action to stay secure'; + $content = ' +

Your current password was found in a public leak, which means your account might be at risk.

+

It is highly recommended that you update your password.

+
+
+ +
+
+ +
+
+

Learn more about the risks of using weak passwords and how to protect your account.

'; + } + + include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; + exit; } - public static function custom_login_redirect() { - return home_url( '/wp-login.php?action=password-detection' ); + /** + * Password validation. + * + * @param string $password The password to validate. + * @return bool True if the password is valid, false otherwise. + */ + public static function validate_password( $password ) { + // TODO: Update to use custom password validation method when available. + return $password ? false : true; } - public static function send_custom_reset_email( $domain_name, $username, $email, $password_reset_link ) { - error_log( 'Site: ' . $domain_name ); - error_log( 'Username: ' . $username ); - error_log( 'Sending custom reset email to ' . $email ); - error_log( 'Password reset link: ' . $password_reset_link ); + /** + * Redirect to the password detection page. + * + * @return string The URL to redirect to. + */ + public static function password_detection_redirect() { + return home_url( '/wp-login.php?action=password-detection' ); } /** @@ -218,56 +241,35 @@ public static function mask_email_address( $email ) { } /** - * Activate the account protection on module activation. - */ - public static function on_account_protection_activation() { - } - - /** - * Deactivate the account protection on module deactivation. - */ - public static function on_account_protection_deactivation() { - // Remove user meta on deactivation - $users = get_users(); - foreach ( $users as $user ) { - delete_user_meta( $user->ID, 'jetpack_account_protection_password_status' ); - // TODO: Do we want to clear password hash history only on deactivation? - // TODO: Ensure this happens on plugin deactivation as well? - } - } - - /** - * Determines if the account protection module is enabled on the site. + * Send password reset email. * - * @return bool + * @param WP_User $user The user object. + * @param string $email The user email. + * @return bool True if the email was sent successfully, false otherwise. */ - public static function is_enabled() { - return ( new Modules() )->is_active( 'account-protection' ); + public static function send_password_reset_email( $user, $email ) { + $site_url = home_url(); + $parsed_url = parse_url( $site_url ); + $domain_name = $parsed_url['host']; + $username = $user->user_login; + + $key = get_password_reset_key( $user ); + $locale = get_user_locale( $user ); + $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $username ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; + + // TODO: Update to use custom email method when available, passing $domain_name, $email, $username, and $password_reset_link + return true; } /** - * Enables the account protection module. + * Remove the password detection usermeta. * - * @return bool + * @param int $user_id The user ID. */ - public static function enable() { - // Return true if already enabled. - if ( self::is_enabled() ) { - return true; - } - return ( new Modules() )->activate( 'account-protection', false, false ); + public static function remove_password_detection_usermeta( $user_id ) { + delete_user_meta( $user_id, 'jetpack_account_protection_password_status' ); } - /** - * Disables the account protection module. - * - * @return bool - */ - public static function disable() { - // Return true if already disabled. - if ( ! self::is_enabled() ) { - return true; - } - return ( new Modules() )->deactivate( 'account-protection' ); - } + // TODO: Move password detection methods to a dedicated class + // TODO: Add killswitch define and is support env checks here } diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php new file mode 100644 index 0000000000000..93050157d831e --- /dev/null +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -0,0 +1,15 @@ + Date: Tue, 14 Jan 2025 12:50:43 -0800 Subject: [PATCH 12/61] Remove user cxn req and banner --- .../client/components/settings-card/index.jsx | 19 ------------------- .../jetpack/modules/account-protection.php | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx b/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx index 6dd2932eb7285..8cb3d077921bd 100644 --- a/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx +++ b/projects/plugins/jetpack/_inc/client/components/settings-card/index.jsx @@ -24,7 +24,6 @@ import { getJetpackProductUpsellByFeature, FEATURE_JETPACK_BLAZE, FEATURE_JETPACK_EARN, - FEATURE_JETPACK_ACCOUNT_PROTECTION, } from 'lib/plans/constants'; import ProStatus from 'pro-status'; import { @@ -456,24 +455,6 @@ export const SettingsCard = inprops => { rna /> ); - case FEATURE_JETPACK_ACCOUNT_PROTECTION: - if ( props.hasConnectedOwner ) { - return ''; - } - - return ( - - ); default: return ''; } diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index 891d023f7fe37..4814ac0eca89b 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -5,7 +5,7 @@ * Sort Order: 4 * First Introduced: 14.3 * Requires Connection: Yes - * Requires User Connection: Yes + * Requires User Connection: No * Auto Activate: Yes * Module Tags: Account Protection * Feature: Security From 5a1af0b530159883ee67c31c8bfe6cc9cbfb5d8c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 14 Jan 2025 12:59:34 -0800 Subject: [PATCH 13/61] Do not enabled module by default --- projects/plugins/jetpack/modules/account-protection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index 4814ac0eca89b..b84d338782098 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -6,7 +6,7 @@ * First Introduced: 14.3 * Requires Connection: Yes * Requires User Connection: No - * Auto Activate: Yes + * Auto Activate: No * Module Tags: Account Protection * Feature: Security * From 3b35efe33d914f06f2596a5c4bfd26e339380a50 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 15 Jan 2025 06:23:55 -0800 Subject: [PATCH 14/61] Add strict mode option and settings toggle --- projects/js-packages/api/index.jsx | 10 ++ .../src/class-account-protection.php | 7 +- .../src/class-rest-controller.php | 100 ++++++++++++ .../index.jsx | 44 +++++ .../client/security/account-protection.jsx | 151 +++++++++++++++++- .../_inc/client/security/allowList.jsx | 2 +- .../jetpack/_inc/client/security/index.jsx | 2 +- .../jetpack/_inc/client/security/style.scss | 24 ++- .../state/account-protection/actions.js | 66 ++++++++ .../client/state/account-protection/index.js | 2 + .../state/account-protection/reducer.js | 87 ++++++++++ .../jetpack/_inc/client/state/action-types.js | 9 ++ .../jetpack/_inc/client/state/reducer.js | 2 + .../lib/class.core-rest-api-endpoints.php | 9 +- ....wpcom-json-api-site-settings-endpoint.php | 2 + projects/plugins/jetpack/modules/waf.php | 2 +- 16 files changed, 508 insertions(+), 11 deletions(-) create mode 100644 projects/packages/account-protection/src/class-rest-controller.php create mode 100644 projects/plugins/jetpack/_inc/client/components/data/query-account-protection-settings/index.jsx create mode 100644 projects/plugins/jetpack/_inc/client/state/account-protection/actions.js create mode 100644 projects/plugins/jetpack/_inc/client/state/account-protection/index.js create mode 100644 projects/plugins/jetpack/_inc/client/state/account-protection/reducer.js diff --git a/projects/js-packages/api/index.jsx b/projects/js-packages/api/index.jsx index 8233d0ba8a616..6f6cdffe0b325 100644 --- a/projects/js-packages/api/index.jsx +++ b/projects/js-packages/api/index.jsx @@ -510,6 +510,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/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index c623faa14781a..f809bbe567657 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -14,7 +14,9 @@ */ class Account_Protection { - const PACKAGE_VERSION = '1.0.0-alpha'; + const PACKAGE_VERSION = '1.0.0-alpha'; + const ACCOUNT_PROTECTION_MODULE_NAME = 'account-protection'; + const STRICT_MODE_OPTION_NAME = 'jetpack_account_protection_strict_mode'; /** * Initializes the configurations needed for the account protection module. @@ -23,6 +25,9 @@ public static function init() { // Account protection activation/deactivation hooks add_action( 'jetpack_activate_module_account-protection', __CLASS__ . '::on_account_protection_activation' ); add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); + + // Register REST routes + add_action( 'rest_api_init', array( new REST_Controller(), 'register_rest_routes' ) ); } /** 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..60e6f71f6cc74 --- /dev/null +++ b/projects/packages/account-protection/src/class-rest-controller.php @@ -0,0 +1,100 @@ + WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_settings', + 'permission_callback' => __CLASS__ . '::permissions_callback', + ) + ); + + register_rest_route( + 'jetpack/v4', + '/account-protection', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::update_settings', + 'permission_callback' => __CLASS__ . '::permissions_callback', + ) + ); + + $routes_registered = true; + } + + /** + * Account Protection Settings Endpoint + * + * @return WP_REST_Response + */ + public static 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 static 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 self::get_settings(); + } + + /** + * Account Protection Endpoint Permissions Callback + * + * @return bool|WP_Error True if user can view the Jetpack admin page. + */ + public static 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/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/security/account-protection.jsx b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx index 39b074365c852..6b5e9126b7493 100644 --- a/projects/plugins/jetpack/_inc/client/security/account-protection.jsx +++ b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx @@ -1,16 +1,99 @@ -import { getRedirectUrl } from '@automattic/jetpack-components'; +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 ); + }; -const AccountProtectionComponent = class extends Component { 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 && } - { __( 'Require strong passwords', 'jetpack' ) } + { __( + 'Protect your site with enhanced password detection and profile management security.', + 'jetpack' + ) } + { isAccountProtectionActive && ( + +
+ + + { __( 'Reqiure strong passwords', 'jetpack' ) } + + + { createInterpolateElement( + __( + 'Allow Jetpack to enforce strict password rules. Learn more
Privacy Information', + 'jetpack' + ), + { + ExternalLink: , // TODO: Update this redirect URL + hr:
, + } + ) } +
+
+ } + /> +
+ + ) } ); } }; -export const AccountProtection = withModuleSettingsFormHelpers( AccountProtectionComponent ); +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 ff1ec0efad4f2..d4677461de9ea 100644 --- a/projects/plugins/jetpack/_inc/client/security/index.jsx +++ b/projects/plugins/jetpack/_inc/client/security/index.jsx @@ -12,7 +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 AccountProtection from './account-protection'; import AllowList from './allowList'; import Antispam from './antispam'; import BackupsScan from './backups-scan'; 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 c4785d4a2ced5..7da1fbb07cf1d 100644 --- a/projects/plugins/jetpack/_inc/client/state/action-types.js +++ b/projects/plugins/jetpack/_inc/client/state/action-types.js @@ -245,6 +245,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 5ff156b807a49..14e85f0fb5289 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'; @@ -46,6 +47,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 e56d427c0c5ea..9697a1d79d423 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 @@ -2358,7 +2358,14 @@ public static function get_updateable_data_list( $selector = '' ) { 'validate_callback' => __CLASS__ . '::validate_posint', 'jp_group' => 'custom-content-types', ), - + // 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/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 9852478a7c53d..4b931dadb330f 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/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 @@ Date: Wed, 15 Jan 2025 06:29:22 -0800 Subject: [PATCH 15/61] changelog --- .../add-jetpack-account-protection-security-settings | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/api/changelog/add-jetpack-account-protection-security-settings 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 From b62811b31700435ab2a4f47d0ea30e4d943d1c73 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 15 Jan 2025 08:50:04 -0800 Subject: [PATCH 16/61] Add strict mode toggle --- .../protect/src/js/routes/settings/index.jsx | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/projects/plugins/protect/src/js/routes/settings/index.jsx b/projects/plugins/protect/src/js/routes/settings/index.jsx index d8172573fb0ea..edbed69dc3bda 100644 --- a/projects/plugins/protect/src/js/routes/settings/index.jsx +++ b/projects/plugins/protect/src/js/routes/settings/index.jsx @@ -49,7 +49,7 @@ const SettingsPage = () => {
- { __( 'Require strong passwords', 'jetpack-protect' ) } + { __( 'Account protection', 'jetpack-protect' ) } { createInterpolateElement( @@ -62,20 +62,46 @@ const SettingsPage = () => { } ) } - { isAccountProtectionEnabled && ( - - - { createInterpolateElement( - __( - 'Jetpack recommends activating this setting. Please be mindful of the risks.', - 'jetpack-protect' - ), - { - link: , // TODO: Update this redirect URL - } - ) } - - ) } +
+ + ); + + const strictModeSettings = ( +
+
+ +
+
); @@ -88,7 +114,10 @@ const SettingsPage = () => { -
{ accountProtectionSettings }
+
+ { accountProtectionSettings } + { isAccountProtectionEnabled && strictModeSettings } +
From 28f5820cfbbc299d473991d41209566821b98bd9 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 15 Jan 2025 11:10:11 -0800 Subject: [PATCH 17/61] Add strict mode toggle and endpoints --- .../src/class-account-protection.php | 13 ++++ .../src/class-rest-controller.php | 8 +-- .../components/breve/features/events.ts | 4 +- .../protect/src/class-jetpack-protect.php | 5 +- projects/plugins/protect/src/js/api.ts | 19 ++++-- .../use-account-protection-mutation.ts | 57 ++++++++++++++++++ .../use-account-protection-query.ts | 5 +- ...ggle-account-protection-module-mutation.ts | 45 ++++++++++++++ .../use-toggle-account-protection-mutation.ts | 40 ------------- .../use-account-protection-data/index.jsx | 59 +++++++++++++++++++ .../protect/src/js/routes/settings/index.jsx | 31 ++++------ .../src/js/types/account-protection.ts | 12 ++++ 12 files changed, 225 insertions(+), 73 deletions(-) create mode 100644 projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts create mode 100644 projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-module-mutation.ts delete mode 100644 projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts create mode 100644 projects/plugins/protect/src/js/hooks/use-account-protection-data/index.jsx create mode 100644 projects/plugins/protect/src/js/types/account-protection.ts diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index f809bbe567657..6ef5c215d4fdd 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -78,4 +78,17 @@ public static function disable() { } return ( new Modules() )->deactivate( 'account-protection' ); } + + /** + * Get the account protection settings. + * + * @return array + */ + public static function get_settings() { + $settings = array( + self::STRICT_MODE_OPTION_NAME => get_option( self::STRICT_MODE_OPTION_NAME, false ), + ); + + return $settings; + } } diff --git a/projects/packages/account-protection/src/class-rest-controller.php b/projects/packages/account-protection/src/class-rest-controller.php index 60e6f71f6cc74..d8af3451bc861 100644 --- a/projects/packages/account-protection/src/class-rest-controller.php +++ b/projects/packages/account-protection/src/class-rest-controller.php @@ -58,11 +58,9 @@ public static function register_rest_routes() { * @return WP_REST_Response */ public static function get_settings() { - return rest_ensure_response( - array( - Account_Protection::STRICT_MODE_OPTION_NAME => get_option( Account_Protection::STRICT_MODE_OPTION_NAME ), - ) - ); + $settings = Account_Protection::get_settings(); + + return rest_ensure_response( $settings ); } /** diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index 58075d8857569..eb5eb0442d2e4 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -97,7 +97,7 @@ async function handleMouseEnter( e: MouseEvent ) { target: el, virtual: virtual, } as Anchor ); - }, 500 ); + }, 500 ) as unknown as number; } function handleMouseLeave() { @@ -106,7 +106,7 @@ function handleMouseLeave() { highlightTimeout = setTimeout( () => { ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( false ); - }, 100 ); + }, 100 ) as unknown as number; } export default function registerEvents( clientId: string ) { diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index e2a1cfd74d38b..8c0ec4f1b5b99 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -232,7 +232,10 @@ public function initial_state() { 'jetpackScan' => My_Jetpack_Products::get_product( 'scan' ), 'hasPlan' => Plan::has_required_plan(), 'onboardingProgress' => Onboarding::get_current_user_progress(), - 'accountProtection' => Account_Protection::is_enabled(), + 'accountProtection' => array( + 'isEnabled' => Account_Protection::is_enabled(), + 'settings' => Account_Protection::get_settings(), + ), 'waf' => array( 'wafSupported' => Waf_Runner::is_supported_environment(), 'currentIp' => IP_Utils::get_ip(), diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts index b2570e892fc61..186ac89c2c513 100644 --- a/projects/plugins/protect/src/js/api.ts +++ b/projects/plugins/protect/src/js/api.ts @@ -1,19 +1,28 @@ -import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan'; +import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan'; import apiFetch from '@wordpress/api-fetch'; import camelize from 'camelize'; +import { AccountProtectionStatus } from './types/account-protection'; +import { WafStatus } from './types/waf'; const API = { + getAccountProtection: (): Promise< AccountProtectionStatus > => + apiFetch( { + path: 'jetpack-protect/v1/account-protection', + method: 'GET', + } ), + toggleAccountProtection: () => apiFetch( { method: 'POST', path: 'jetpack-protect/v1/toggle-account-protection', } ), - getAccountProtection: () => + updateAccountProtection: data => apiFetch( { - path: 'jetpack-protect/v1/account-protection', - method: 'GET', - } ), + method: 'POST', + path: 'jetpack/v4/account-protection', + data, + } ).then( camelize ), getWaf: (): Promise< WafStatus > => apiFetch( { diff --git a/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts new file mode 100644 index 0000000000000..abd7749a797cd --- /dev/null +++ b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts @@ -0,0 +1,57 @@ +import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; +import { __ } from '@wordpress/i18n'; +import camelize from 'camelize'; +import API from '../../api'; +import { QUERY_ACCOUNT_PROTECTION_KEY } from '../../constants'; +import useNotices from '../../hooks/use-notices'; +import { AccountProtectionStatus } from '../../types/account-protection'; + +/** + * Account Protection Mutatation Hook + * + * @return {UseMutationResult} useMutation result. + */ +export default function useAccountProtectionMutation(): UseMutationResult< + unknown, + { [ key: string ]: unknown }, + unknown, + { initialValue: AccountProtectionStatus } +> { + const queryClient = useQueryClient(); + const { showSuccessNotice, showSavingNotice, showErrorNotice } = useNotices(); + + return useMutation( { + mutationFn: API.updateAccountProtection, + onMutate: settings => { + showSavingNotice(); + + // Get the current Account Protection settings. + const initialValue = queryClient.getQueryData( [ + QUERY_ACCOUNT_PROTECTION_KEY, + ] ) as AccountProtectionStatus; + + // Optimistically update the Account Protection settings. + queryClient.setQueryData( + [ QUERY_ACCOUNT_PROTECTION_KEY ], + ( accountProtectionStatus: AccountProtectionStatus ) => ( { + ...accountProtectionStatus, + settings: { + ...accountProtectionStatus.settings, + ...camelize( settings ), + }, + } ) + ); + + return { initialValue }; + }, + onSuccess: () => { + showSuccessNotice( __( 'Changes saved.', 'jetpack-protect' ) ); + }, + onError: ( error, variables, context ) => { + // Reset the WAF config to its previous state. + queryClient.setQueryData( [ QUERY_ACCOUNT_PROTECTION_KEY ], context.initialValue ); + + showErrorNotice( __( 'Error saving changes.', 'jetpack-protect' ) ); + }, + } ); +} diff --git a/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts index cc9ac9cbf202c..01dd3354432a9 100644 --- a/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts +++ b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-query.ts @@ -2,13 +2,14 @@ import { useQuery, UseQueryResult } from '@tanstack/react-query'; import camelize from 'camelize'; import API from '../../api'; import { QUERY_ACCOUNT_PROTECTION_KEY } from '../../constants'; +import { AccountProtectionStatus } from '../../types/account-protection'; /** - * WAF Query Hook + * Account Protection Query Hook * * @return {UseQueryResult} useQuery result. */ -export default function useAccountProtectionQuery(): UseQueryResult< boolean > { +export default function useAccountProtectionQuery(): UseQueryResult< AccountProtectionStatus > { return useQuery( { queryKey: [ QUERY_ACCOUNT_PROTECTION_KEY ], queryFn: API.getAccountProtection, diff --git a/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-module-mutation.ts b/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-module-mutation.ts new file mode 100644 index 0000000000000..2f8ca342902ea --- /dev/null +++ b/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-module-mutation.ts @@ -0,0 +1,45 @@ +import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; +import { __ } from '@wordpress/i18n'; +import API from '../../api'; +import { QUERY_ACCOUNT_PROTECTION_KEY } from '../../constants'; +import useNotices from '../../hooks/use-notices'; +import { AccountProtectionStatus } from '../../types/account-protection'; + +/** + * Toggle Account Protection Mutatation + * + * @return {UseMutationResult} useMutation result. + */ +export default function useToggleAccountProtectionMutation(): UseMutationResult { + const queryClient = useQueryClient(); + const { showSavingNotice, showSuccessNotice, showErrorNotice } = useNotices(); + + return useMutation( { + mutationFn: API.toggleAccountProtection, + onMutate: () => { + showSavingNotice(); + + // Get the current Account Protection settings. + const initialValue = queryClient.getQueryData( [ + QUERY_ACCOUNT_PROTECTION_KEY, + ] ) as AccountProtectionStatus; + + // Optimistically update the Account Protection settings. + queryClient.setQueryData( + [ QUERY_ACCOUNT_PROTECTION_KEY ], + ( accountProtectionStatus: AccountProtectionStatus ) => ( { + ...accountProtectionStatus, + isEnabled: ! initialValue.isEnabled, + } ) + ); + + return { initialValue }; + }, + onSuccess: () => { + showSuccessNotice( __( 'Changes saved.', 'jetpack-protect' ) ); + }, + onError: () => { + showErrorNotice( __( 'Error savings changes.', 'jetpack-protect' ) ); + }, + } ); +} diff --git a/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts b/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts deleted file mode 100644 index 10fa874cb1ebb..0000000000000 --- a/projects/plugins/protect/src/js/data/account-protection/use-toggle-account-protection-mutation.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; -import { __ } from '@wordpress/i18n'; -import API from '../../api'; -import { QUERY_ACCOUNT_PROTECTION_KEY } from '../../constants'; -import useNotices from '../../hooks/use-notices'; - -/** - * Toggle Account Protection Mutatation - * - * @return {UseMutationResult} useMutation result. - */ -export default function useToggleAccountProtectMutation(): UseMutationResult { - const queryClient = useQueryClient(); - const { showSuccessNotice, showSavingNotice, showErrorNotice } = useNotices(); - - return useMutation( { - mutationFn: API.toggleAccountProtection, - onMutate: () => { - showSavingNotice(); - // Get the current account protection settings. - const initialValue = queryClient.getQueryData( [ QUERY_ACCOUNT_PROTECTION_KEY ] ); - - // Optimistically update settings. - queryClient.setQueryData( [ QUERY_ACCOUNT_PROTECTION_KEY ], ( status: boolean ) => ! status ); - - return { initialValue }; - }, - onSuccess: () => { - showSuccessNotice( __( 'Changes saved.', 'jetpack-protect' ) ); - }, - onError: () => { - showErrorNotice( - __( 'An error occurred toggling the account protection module.', 'jetpack-protect' ) - ); - }, - onSettled: () => { - queryClient.invalidateQueries( { queryKey: [ QUERY_ACCOUNT_PROTECTION_KEY ] } ); - }, - } ); -} diff --git a/projects/plugins/protect/src/js/hooks/use-account-protection-data/index.jsx b/projects/plugins/protect/src/js/hooks/use-account-protection-data/index.jsx new file mode 100644 index 0000000000000..90e473c270bc6 --- /dev/null +++ b/projects/plugins/protect/src/js/hooks/use-account-protection-data/index.jsx @@ -0,0 +1,59 @@ +import { useCallback } from 'react'; +import useAccountProtectionMutation from '../../data/account-protection/use-account-protection-mutation'; +import useAccountProtectionQuery from '../../data/account-protection/use-account-protection-query'; +import useToggleAccountProtectionMutation from '../../data/account-protection/use-toggle-account-protection-module-mutation'; +import useAnalyticsTracks from '../use-analytics-tracks'; + +/** + * Use Account Protection Data Hook + * + * @return {object} Account Protection data and methods for interacting with it. + */ +const useAccountProtectionData = () => { + const { recordEvent } = useAnalyticsTracks(); + const { data: accountProtection } = useAccountProtectionQuery(); + const accountProtectionMutation = useAccountProtectionMutation(); + const toggleAccountProtectionMutation = useToggleAccountProtectionMutation(); + + /** + * Toggle Account Protection Module + * + * Flips the switch on the Account Protection module, and then refreshes the data. + */ + const toggleAccountProtection = useCallback( async () => { + toggleAccountProtectionMutation.mutate(); + }, [ toggleAccountProtectionMutation ] ); + + /** + * Toggle Strict Mode + * + * Flips the switch on the strict mode option, and then refreshes the data. + */ + const toggleStrictMode = useCallback( async () => { + const value = ! accountProtection.settings.jetpackAccountProtectionStrictMode; + const mutationObj = { jetpack_account_protection_strict_mode: value }; + if ( ! value ) { + mutationObj.jetpack_account_protection_strict_mode = false; + } + await accountProtectionMutation.mutateAsync( mutationObj ); + recordEvent( + mutationObj + ? 'jetpack_account_protection_strict_mode_enabled' + : 'jetpack_account_protection_strict_mode_disabled' + ); + }, [ + recordEvent, + accountProtection.settings.jetpackAccountProtectionStrictMode, + accountProtectionMutation, + ] ); + + return { + ...accountProtection, + isUpdating: accountProtectionMutation.isPending, + isToggling: toggleAccountProtectionMutation.isPending, + toggleAccountProtection, + toggleStrictMode, + }; +}; + +export default useAccountProtectionData; diff --git a/projects/plugins/protect/src/js/routes/settings/index.jsx b/projects/plugins/protect/src/js/routes/settings/index.jsx index edbed69dc3bda..3fe6b735f7d3e 100644 --- a/projects/plugins/protect/src/js/routes/settings/index.jsx +++ b/projects/plugins/protect/src/js/routes/settings/index.jsx @@ -8,18 +8,22 @@ import { import { createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; -import { useCallback } from 'react'; import AdminPage from '../../components/admin-page'; -import useAccountProtectionQuery from '../../data/account-protection/use-account-protection-query'; -import useToggleAccountProtectionMutation from '../../data/account-protection/use-toggle-account-protection-mutation'; +import useAccountProtectionData from '../../hooks/use-account-protection-data'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; import styles from './styles.module.scss'; const SettingsPage = () => { const { hasPlan } = usePlan(); - const toggleAccountProtectionMutation = useToggleAccountProtectionMutation(); - const { data: isAccountProtectionEnabled } = useAccountProtectionQuery(); + const { + settings: { jetpackAccountProtectionStrictMode: strictMode }, + isEnabled: isAccountProtectionEnabled, + toggleAccountProtection, + toggleStrictMode, + isToggling, + isUpdating, + } = useAccountProtectionData(); // Track view for Protect Account Protection page. useAnalyticsTracks( { @@ -29,22 +33,13 @@ const SettingsPage = () => { }, } ); - /** - * Toggle Account Protection Module - * - * Flips the switch on the Account Protection module, and then refreshes the data. - */ - const toggleAccountProtection = useCallback( async () => { - toggleAccountProtectionMutation.mutate(); - }, [ toggleAccountProtectionMutation ] ); - const accountProtectionSettings = (
@@ -70,9 +65,9 @@ const SettingsPage = () => {
diff --git a/projects/plugins/protect/src/js/types/account-protection.ts b/projects/plugins/protect/src/js/types/account-protection.ts new file mode 100644 index 0000000000000..37d557638982b --- /dev/null +++ b/projects/plugins/protect/src/js/types/account-protection.ts @@ -0,0 +1,12 @@ +export type AccountProtectionStatus = { + /** Whether the "account-protection" module is enabled. */ + isEnabled: boolean; + + /** The current Account Protetion settings. */ + settings: AccountProtectionSettings; +}; + +export type AccountProtectionSettings = { + /** Whether the user has enabled strict mode. */ + jetpackAccountProtectionStrictMode: boolean; +}; From b72e93a95209873de638ff21084ceb778b47f7f3 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 15 Jan 2025 12:36:24 -0800 Subject: [PATCH 18/61] Reorg and add kill switch and is supported check --- .../src/class-account-protection.php | 200 +++++------------- .../src/class-password-detection.php | 132 ++++++++++++ .../src/class-password-reset-email.php | 57 +++++ 3 files changed, 238 insertions(+), 151 deletions(-) create mode 100644 projects/packages/account-protection/src/class-password-reset-email.php diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 63187c7724381..863c5c5e0aff5 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -26,39 +26,43 @@ public static function init() { add_action( 'jetpack_activate_module_account-protection', __CLASS__ . '::on_account_protection_activation' ); add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); + // Do not run in unsupported environments + add_action( 'jetpack_get_available_modules', __CLASS__ . '::remove_module_on_unsupported_environments' ); + add_action( 'jetpack_get_available_standalone_modules', __CLASS__ . '::remove_standalone_module_on_unsupported_environments' ); + // Register REST routes add_action( 'rest_api_init', array( new REST_Controller(), 'register_rest_routes' ) ); if ( self::is_enabled() ) { // Validate password after successful login - add_action( 'wp_authenticate_user', __CLASS__ . '::login_form_password_detection', 10, 2 ); + add_action( 'wp_authenticate_user', array( 'Automattic\Jetpack\Account_Protection\Password_Detection', 'login_form_password_detection' ), 10, 2 ); // Add password detection flow for users with unsafe passwords add_action( 'login_form_password-detection', - __CLASS__ . '::render_password_detection_page', + array( 'Automattic\Jetpack\Account_Protection\Password_Detection', 'render_password_detection_page' ), 10, 2 ); - // Ensure jetpack_account_protection_password_status usermeta is removed on password change + // Remove password detection usermeta on password reset add_action( 'after_password_reset', function ( $user, $new_pass ) { - delete_user_meta( $user->ID, 'jetpack_account_protection_password_status' ); + Password_Detection::remove_password_detection_usermeta( $user->ID ); }, 10, 2 ); - // Ensure jetpack_account_protection_password_status usermeta is removed when user updates password via profile updates + // Remove password detection usermeta on profile password update add_action( 'profile_update', function ( $user_id, $old_user_data ) { // Profile updates should include validation, but we should reset user meta to be safe if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - // TODO: Only if the password is actually updated - self::remove_password_detection_usermeta( $user_id ); + // TODO: Ensure this only happens if the password is actually updated + Password_Detection::remove_password_detection_usermeta( $user_id ); } }, 10, @@ -80,8 +84,8 @@ public static function on_account_protection_deactivation() { // Remove user meta on deactivation $users = get_users(); foreach ( $users as $user ) { - self::remove_password_detection_usermeta( $user->ID ); - // TODO: Remove usermeta on plugin deactivation as well + Password_Detection::remove_password_detection_usermeta( $user->ID ); + // TODO: Remove usermeta on plugin deactivation } } @@ -121,173 +125,67 @@ public static function disable() { } /** - * Get the account protection settings. - * - * @return array - */ - public static function get_settings() { - $settings = array( - self::STRICT_MODE_OPTION_NAME => get_option( self::STRICT_MODE_OPTION_NAME, false ), - ); - - return $settings; - } - - /** - * Check if the password is safe after login. + * Determines if Account Protection is supported in the current environment. * - * @param WP_User $user The user object. - * @param string $password The password. - * @return WP_User The user object. + * @return bool */ - public static function login_form_password_detection( $user, $password ) { - if ( ! self::validate_password( $password ) ) { - // TODO: Ensure this usermeta is always up to date - update_user_meta( $user->ID, 'jetpack_account_protection_password_status', 'unsafe' ); - - // Redirect to the password detection page - add_filter( 'login_redirect', __CLASS__ . '::password_detection_redirect', 10, 3 ); - } else { - update_user_meta( $user->ID, 'jetpack_account_protection_password_status', 'safe' ); + public static function is_supported_environment() { + // Do not run when killswitch is enabled + if ( defined( 'DISABLE_JETPACK_ACCOUNT_PROTECTION' ) && DISABLE_JETPACK_ACCOUNT_PROTECTION ) { + return false; } - return $user; + return true; } /** - * Render password detection page. + * Disables the Account Protection module when on an unsupported platform in Jetpack. * - * This page is shown to users with unsafe passwords after login. + * @param array $modules Filterable value for `jetpack_get_available_modules`. * - * @return void + * @return array Array of module slugs. */ - public static function render_password_detection_page() { - // Restrict direct access to logged in users - if ( ! is_user_logged_in() ) { - wp_redirect( wp_login_url() ); - exit; - } - - $current_user = wp_get_current_user(); - $user_password_status = get_user_meta( $current_user->ID, 'jetpack_account_protection_password_status', true ); - - // Restrict direct access to users with unsafe passwords - if ( ! $user_password_status || 'safe' === $user_password_status ) { - wp_redirect( admin_url() ); - exit; + public static function remove_module_on_unsupported_environments( $modules ) { + if ( ! self::is_supported_environment() ) { + // Account protection should never be available on unsupported platforms. + unset( $modules['account-protection'] ); } - if ( isset( $_POST['reset'] ) ) { - $email = $current_user->user_email; - - // Send reset email to the user - only initially and on resend, not refresh - $email_sent = self::send_password_reset_email( $current_user, $email ); - if ( ! $email_sent ) { - // TODO: Handle email sending errors - } - - $header_title = 'Secure Your Account'; - $page_title = "Let's secure your account"; - $content = ' -

Your current password was found in a public leak, which means your account might be at risk.

-

Don\'t worry - To keep your account safe, we\'ve sent a verification email to a ' . self::mask_email_address( $email ) . '. After that, we\'ll guide you through updating your password.

-

Please check your inbox and click the link to verify it\'s you. Didn\'t get the email? Resend email

'; - - } elseif ( isset( $_POST['proceed'] ) ) { - wp_redirect( admin_url() ); - exit; - } else { - $header_title = 'Stay Secure'; - $page_title = 'Take action to stay secure'; - $content = ' -

Your current password was found in a public leak, which means your account might be at risk.

-

It is highly recommended that you update your password.

-
-
- -
-
- -
-
-

Learn more about the risks of using weak passwords and how to protect your account.

'; - } - - include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; - exit; - } - - /** - * Password validation. - * - * @param string $password The password to validate. - * @return bool True if the password is valid, false otherwise. - */ - public static function validate_password( $password ) { - // TODO: Update to use custom password validation method when available. - return $password ? false : true; + return $modules; } /** - * Redirect to the password detection page. + * Disables the Account Protection module when on an unsupported platform in a standalone plugin. * - * @return string The URL to redirect to. - */ - public static function password_detection_redirect() { - return home_url( '/wp-login.php?action=password-detection' ); - } - - /** - * Mask an email address like d*****@g*****.com. + * @param array $modules Filterable value for `jetpack_get_available_standalone_modules`. * - * @param string $email The email address to mask. - * @return string The masked email address. + * @return array Array of module slugs. */ - public static function mask_email_address( $email ) { - $parts = explode( '@', $email ); - $name = $parts[0]; - $domain = $parts[1]; - - // Mask the name part (first letter + asterisks) - $masked_name = substr( $name, 0, 1 ) . str_repeat( '*', strlen( $name ) - 1 ); + public static function remove_standalone_module_on_unsupported_environments( $modules ) { + if ( ! self::is_supported_environment() ) { + // Account Protection should never be available on unsupported platforms. + $modules = array_filter( + $modules, + function ( $module ) { + return $module !== 'account-protection'; + } + ); - // Mask the domain part (first letter + asterisks + domain extension) - $domain_parts = explode( '.', $domain ); - $masked_domain = substr( $domain_parts[0], 0, 1 ) . str_repeat( '*', strlen( $domain_parts[0] ) - 1 ) . '.' . $domain_parts[1]; + } - return $masked_name . '@' . $masked_domain; + return $modules; } /** - * Send password reset email. + * Get the account protection settings. * - * @param WP_User $user The user object. - * @param string $email The user email. - * @return bool True if the email was sent successfully, false otherwise. + * @return array */ - public static function send_password_reset_email( $user, $email ) { - $site_url = home_url(); - $parsed_url = parse_url( $site_url ); - $domain_name = $parsed_url['host']; - $username = $user->user_login; - - $key = get_password_reset_key( $user ); - $locale = get_user_locale( $user ); - $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $username ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; - - // TODO: Update to use custom email method when available, passing $domain_name, $email, $username, and $password_reset_link - return true; - } + public static function get_settings() { + $settings = array( + self::STRICT_MODE_OPTION_NAME => get_option( self::STRICT_MODE_OPTION_NAME, false ), + ); - /** - * Remove the password detection usermeta. - * - * @param int $user_id The user ID. - */ - public static function remove_password_detection_usermeta( $user_id ) { - delete_user_meta( $user_id, 'jetpack_account_protection_password_status' ); + return $settings; } - - // TODO: Move password detection methods to a dedicated class - // TODO: Add killswitch define and is support env checks here } diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 93050157d831e..dda8a0551a7e4 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -12,4 +12,136 @@ * Class Password_Detection */ class Password_Detection { + + const PASSWORD_DETECTION_USER_META_KEY = 'jetpack_account_protection_password_status'; + + /** + * Redirect to the password detection page. + * + * @return string The URL to redirect to. + */ + public static function password_detection_redirect() { + return home_url( '/wp-login.php?action=password-detection' ); + } + + /** + * Check if the password is safe after login. + * + * @param WP_User $user The user object. + * @param string $password The password. + * @return WP_User The user object. + */ + public static function login_form_password_detection( $user, $password ) { + if ( ! self::validate_password( $password ) ) { + // TODO: Ensure this usermeta is always up to date + self::add_password_detection_usermeta( $user->ID, 'unsafe' ); + + // Redirect to the password detection page + add_filter( 'login_redirect', __CLASS__ . '::password_detection_redirect', 10, 3 ); + } else { + self::add_password_detection_usermeta( $user->ID, 'safe' ); + + } + + return $user; + } + + /** + * Render password detection page. + * + * This page is shown to users with unsafe passwords after login. + * + * @return void + */ + public static function render_password_detection_page() { + // Restrict direct access to logged in users + if ( ! is_user_logged_in() ) { + wp_redirect( wp_login_url() ); + exit; + } + + $current_user = wp_get_current_user(); + $user_password_status = self::get_password_detection_usermeta( $current_user->ID ); + + // Restrict direct access to users with unsafe passwords + if ( ! $user_password_status || 'safe' === $user_password_status ) { + wp_redirect( admin_url() ); + exit; + } + + if ( isset( $_POST['reset'] ) ) { + $email = $current_user->user_email; + + // Send reset email to the user - only initially and on resend, not refresh + $email_sent = Password_Reset_Email::send( $current_user, $email ); + if ( ! $email_sent ) { + // TODO: Handle email sending errors + } + + $header_title = 'Secure Your Account'; + $page_title = "Let's secure your account"; + $content = ' +

Your current password was found in a public leak, which means your account might be at risk.

+

Don\'t worry - To keep your account safe, we\'ve sent a verification email to a ' . Password_Reset_Email::mask_email_address( $email ) . '. After that, we\'ll guide you through updating your password.

+

Please check your inbox and click the link to verify it\'s you. Didn\'t get the email? Resend email

'; + + } elseif ( isset( $_POST['proceed'] ) ) { + wp_redirect( admin_url() ); + exit; + } else { + $header_title = 'Stay Secure'; + $page_title = 'Take action to stay secure'; + $content = ' +

Your current password was found in a public leak, which means your account might be at risk.

+

It is highly recommended that you update your password.

+
+
+ +
+
+ +
+
+

Learn more about the risks of using weak passwords and how to protect your account.

'; + } + + include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; + exit; + } + + /** + * Password validation. + * + * @param string $password The password to validate. + * @return bool True if the password is valid, false otherwise. + */ + public static function validate_password( $password ) { + // TODO: Update to use custom password validation method(s) when available. + return $password ? false : true; + } + + /** + * Add the password detection usermeta. + * + * @param int $user_id The user ID. + */ + public static function add_password_detection_usermeta( $user_id, $setting ) { + update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); + } + + /** + * Remove the password detection usermeta. + * + * @param int $user_id The user ID. + */ + public static function remove_password_detection_usermeta( $user_id ) { + delete_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY ); + } + + /** + * Get the password detection usermeta. + */ + public static function get_password_detection_usermeta( $user_id ) { + return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); + } } diff --git a/projects/packages/account-protection/src/class-password-reset-email.php b/projects/packages/account-protection/src/class-password-reset-email.php new file mode 100644 index 0000000000000..e395394a82da3 --- /dev/null +++ b/projects/packages/account-protection/src/class-password-reset-email.php @@ -0,0 +1,57 @@ +user_login; + + $key = get_password_reset_key( $user ); + $locale = get_user_locale( $user ); + $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $username ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; + + // TODO: Update to use custom email method when available, passing $domain_name, $email, $username, and $password_reset_link + return true; + } +} From 7fad7f98f727bdee66fa4d7f1aaee227bb368196 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 15 Jan 2025 13:59:31 -0800 Subject: [PATCH 19/61] Add testing infrastructure --- .../account-protection/tests/.phpcs.dir.xml | 4 ---- .../tests/action-test-coverage.sh | 9 ++++++++ .../tests/php/integration/bootstrap.php | 16 ++++++++++++++ .../tests/php/integration/phpunit.xml.dist | 21 +++++++++++++++++++ .../tests/php/{ => unit}/bootstrap.php | 2 +- .../tests/php/unit/phpunit.xml.dist | 21 +++++++++++++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) delete mode 100644 projects/packages/account-protection/tests/.phpcs.dir.xml create mode 100755 projects/packages/account-protection/tests/action-test-coverage.sh create mode 100644 projects/packages/account-protection/tests/php/integration/bootstrap.php create mode 100644 projects/packages/account-protection/tests/php/integration/phpunit.xml.dist rename projects/packages/account-protection/tests/php/{ => unit}/bootstrap.php (64%) create mode 100644 projects/packages/account-protection/tests/php/unit/phpunit.xml.dist diff --git a/projects/packages/account-protection/tests/.phpcs.dir.xml b/projects/packages/account-protection/tests/.phpcs.dir.xml deleted file mode 100644 index 46951fe77b37e..0000000000000 --- a/projects/packages/account-protection/tests/.phpcs.dir.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/projects/packages/account-protection/tests/action-test-coverage.sh b/projects/packages/account-protection/tests/action-test-coverage.sh new file mode 100755 index 0000000000000..8a7a1e9de6565 --- /dev/null +++ b/projects/packages/account-protection/tests/action-test-coverage.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -veo pipefail + +EXIT=0 +php -dpcov.directory=. ./vendor/bin/phpunit --coverage-php "$COVERAGE_DIR/integration/php.cov" --configuration tests/php/integration/phpunit.xml.dist || EXIT=1 +php -dpcov.directory=. ./vendor/bin/phpunit --coverage-php "$COVERAGE_DIR/unit/php.cov" --configuration tests/php/unit/phpunit.xml.dist || EXIT=1 + +exit $EXIT diff --git a/projects/packages/account-protection/tests/php/integration/bootstrap.php b/projects/packages/account-protection/tests/php/integration/bootstrap.php new file mode 100644 index 0000000000000..4c1205f352a1d --- /dev/null +++ b/projects/packages/account-protection/tests/php/integration/bootstrap.php @@ -0,0 +1,16 @@ + + + + + + + ../../../src + + + + + . + + + diff --git a/projects/packages/account-protection/tests/php/bootstrap.php b/projects/packages/account-protection/tests/php/unit/bootstrap.php similarity index 64% rename from projects/packages/account-protection/tests/php/bootstrap.php rename to projects/packages/account-protection/tests/php/unit/bootstrap.php index 46763b04a2cdb..e16bad0ecf0bf 100644 --- a/projects/packages/account-protection/tests/php/bootstrap.php +++ b/projects/packages/account-protection/tests/php/unit/bootstrap.php @@ -8,4 +8,4 @@ /** * Include the composer autoloader. */ -require_once __DIR__ . '/../../vendor/autoload.php'; +require_once __DIR__ . '/../../../vendor/autoload.php'; diff --git a/projects/packages/account-protection/tests/php/unit/phpunit.xml.dist b/projects/packages/account-protection/tests/php/unit/phpunit.xml.dist new file mode 100644 index 0000000000000..e901dd1acf030 --- /dev/null +++ b/projects/packages/account-protection/tests/php/unit/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + + + ../../../src + + + + + . + + + From 39a28d5998299ec063e45e57cdfabcd7141a3410 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 16 Jan 2025 12:54:51 -0800 Subject: [PATCH 20/61] Add email handlings, resend AJAX action, and attempt limitations --- .../src/class-account-protection.php | 22 +++- .../src/class-password-detection.php | 116 ++++++++++++------ .../src/css/password-detection.css | 9 +- .../src/js/resend-password-reset.js | 71 +++++++++++ .../templates/password-detection-template.php | 43 +++++-- 5 files changed, 207 insertions(+), 54 deletions(-) create mode 100644 projects/packages/account-protection/src/js/resend-password-reset.js diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 863c5c5e0aff5..a20cd51388aa5 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -45,10 +45,13 @@ public static function init() { 2 ); + // Register AJAX resend password reset email action + add_action( 'wp_ajax_resend_password_reset', array( 'Automattic\Jetpack\Account_Protection\Password_Detection', 'ajax_resend_password_reset_email' ) ); + // Remove password detection usermeta on password reset add_action( 'after_password_reset', - function ( $user, $new_pass ) { + function ( $user ) { Password_Detection::remove_password_detection_usermeta( $user->ID ); }, 10, @@ -58,16 +61,23 @@ function ( $user, $new_pass ) { // Remove password detection usermeta on profile password update add_action( 'profile_update', - function ( $user_id, $old_user_data ) { - // Profile updates should include validation, but we should reset user meta to be safe - if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - // TODO: Ensure this only happens if the password is actually updated - Password_Detection::remove_password_detection_usermeta( $user_id ); + function ( $user_id ) { + // TODO: Ensure nonce verfication works as expected + if ( + ! empty( $_POST['_wpnonce'] ) && + wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) + ) { + // Profile updates should include validation, but we should reset user meta to be safe + if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { + // TODO: Ensure this only happens if the password is actually updated + Password_Detection::remove_password_detection_usermeta( $user_id ); + } } }, 10, 2 ); + } } diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index dda8a0551a7e4..9b1a55968ce38 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -1,5 +1,4 @@ ID ) { + wp_safe_redirect( wp_login_url() ); exit; } - $current_user = wp_get_current_user(); - $user_password_status = self::get_password_detection_usermeta( $current_user->ID ); - // Restrict direct access to users with unsafe passwords + $user_password_status = self::get_password_detection_usermeta( $current_user->ID ); if ( ! $user_password_status || 'safe' === $user_password_status ) { - wp_redirect( admin_url() ); + wp_safe_redirect( admin_url() ); exit; } + add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_password_detection_styles' ); + + // Use a transient to track email sent status + $transient_key = 'password_reset_email_sent_' . $current_user->ID; + $email_sent_flag = get_transient( $transient_key ); + + // TODO: Add nonce verification if ( isset( $_POST['reset'] ) ) { + $reset = true; $email = $current_user->user_email; - // Send reset email to the user - only initially and on resend, not refresh - $email_sent = Password_Reset_Email::send( $current_user, $email ); - if ( ! $email_sent ) { - // TODO: Handle email sending errors + // Send reset email + if ( ! $email_sent_flag ) { + $email_sent = Password_Reset_Email::send( $current_user, $email ); + if ( $email_sent ) { + // Set transient to mark the email as sent + set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); + } + // TODO: Handle email sending failure } - $header_title = 'Secure Your Account'; - $page_title = "Let's secure your account"; - $content = ' -

Your current password was found in a public leak, which means your account might be at risk.

-

Don\'t worry - To keep your account safe, we\'ve sent a verification email to a ' . Password_Reset_Email::mask_email_address( $email ) . '. After that, we\'ll guide you through updating your password.

-

Please check your inbox and click the link to verify it\'s you. Didn\'t get the email? Resend email

'; - + add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_resend_password_reset_scripts' ); + // TODO: Add nonce verification } elseif ( isset( $_POST['proceed'] ) ) { - wp_redirect( admin_url() ); + wp_safe_redirect( admin_url() ); exit; - } else { - $header_title = 'Stay Secure'; - $page_title = 'Take action to stay secure'; - $content = ' -

Your current password was found in a public leak, which means your account might be at risk.

-

It is highly recommended that you update your password.

-
-
- -
-
- -
-
-

Learn more about the risks of using weak passwords and how to protect your account.

'; } include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; exit; } + /** + * Enqueue the resend password reset email scripts. + */ + public static function enqueue_resend_password_reset_scripts() { + wp_enqueue_script( 'resend-password-reset', plugin_dir_url( __FILE__ ) . 'js/resend-password-reset.js', array( 'jquery' ), Account_Protection::PACKAGE_VERSION, true ); + + // Pass AJAX URL and nonce to the script + wp_localize_script( + 'resend-password-reset', + 'ajaxObject', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'resend_password_reset_nonce' ), + ) + ); + } + + /** + * Enqueue the password detection page styles. + */ + public static function enqueue_password_detection_styles() { + wp_enqueue_style( + 'password-detection-styles', + plugin_dir_url( __FILE__ ) . 'css/password-detection.css', + array(), + Account_Protection::PACKAGE_VERSION + ); + } + + /** + * Run AJAX request to resend password reset email. + */ + public static function ajax_resend_password_reset_email() { + // Verify the nonce for security + check_ajax_referer( 'resend_password_reset_nonce', 'security' ); + + // Check if the user is logged in + if ( ! is_user_logged_in() ) { + wp_send_json_error( array( 'message' => 'User not authenticated' ) ); + } + + $current_user = wp_get_current_user(); + $email = $current_user->user_email; + + // Resend the email + $email_sent = Password_Reset_Email::send( $current_user, $email ); + if ( $email_sent ) { + wp_send_json_success( array( 'message' => 'Resend successful.' ) ); + } else { + wp_send_json_error( array( 'message' => 'Resend failed. ' ) ); + } + } + /** * Password validation. * @@ -123,7 +166,8 @@ public static function validate_password( $password ) { /** * Add the password detection usermeta. * - * @param int $user_id The user ID. + * @param int $user_id The user ID. + * @param string $setting The password detection setting. */ public static function add_password_detection_usermeta( $user_id, $setting ) { update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); @@ -140,6 +184,8 @@ public static function remove_password_detection_usermeta( $user_id ) { /** * Get the password detection usermeta. + * + * @param int $user_id The user ID. */ public static function get_password_detection_usermeta( $user_id ) { return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); diff --git a/projects/packages/account-protection/src/css/password-detection.css b/projects/packages/account-protection/src/css/password-detection.css index 00d9203ea32c8..d1ec425dd5da3 100644 --- a/projects/packages/account-protection/src/css/password-detection.css +++ b/projects/packages/account-protection/src/css/password-detection.css @@ -1,5 +1,5 @@ -body { - background: #f0f0f1; +.password-detection-wrapper { + background-color: #f0f0f1; min-width: 0; color: #3c434a; font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; @@ -7,19 +7,18 @@ body { line-height: 1.4; } -.custom { +.password-detection { background: #fff; width: 420px; margin: 124px auto; padding: 26px 24px; font-weight: 400; overflow: hidden; - background: #fff; border: 1px solid #c3c4c7; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); } -.custom-title { +.password-detection-title { font-size: 24px; font-weight: 500; } diff --git a/projects/packages/account-protection/src/js/resend-password-reset.js b/projects/packages/account-protection/src/js/resend-password-reset.js new file mode 100644 index 0000000000000..2e0cdf8a4ab0a --- /dev/null +++ b/projects/packages/account-protection/src/js/resend-password-reset.js @@ -0,0 +1,71 @@ +/* global jQuery, ajaxObject */ +( function ( $ ) { + $( document ).ready( function () { + const attemptLimit = 3; + let attempts = 0; + + $( '#resend-password-reset' ).on( 'click', function ( e ) { + e.preventDefault(); // Prevent the default action + + const message = $( '#resend-password-reset-message' ); + const button = $( this ); + + // Store the original text of the message + const originalMessageText = message.text(); + + // Update message and hide button while resending + message.text( 'Resending email...' ); + button.hide(); + + attempts++; + + // Perform the AJAX request + $.ajax( { + url: ajaxObject.ajax_url, + type: 'POST', + data: { + action: 'resend_password_reset', + security: ajaxObject.nonce, + }, + success: function ( response ) { + if ( response.success ) { + // Show success message + message.text( response.data.message ).show(); + + // Hide the status message and show the button after 5 seconds + setTimeout( function () { + let messageText = originalMessageText; + if ( attempts < attemptLimit ) { + button.show(); + } else { + messageText += 'Please try again later.'; + } + message.text( messageText ).show(); + }, 5000 ); + } else { + // Show error message + let messageText = 'An error occurred. '; + if ( attempts < attemptLimit ) { + button.text( 'Please try again' ).show(); + } else { + messageText += 'Please contact support.'; // TODO: Add support redirect + } + + message.text( messageText ).show(); + } + }, + error: function () { + // Show error message + let messageText = 'An error occurred. '; + if ( attempts < attemptLimit ) { + button.text( 'Please try again' ).show(); + } else { + messageText += 'Please contact support.'; // TODO: Add support redirect + } + + message.text( messageText ).show(); + }, + } ); + } ); + } ); +} )( jQuery ); diff --git a/projects/packages/account-protection/src/templates/password-detection-template.php b/projects/packages/account-protection/src/templates/password-detection-template.php index 748663cb8460c..ec3651384bf67 100644 --- a/projects/packages/account-protection/src/templates/password-detection-template.php +++ b/projects/packages/account-protection/src/templates/password-detection-template.php @@ -1,7 +1,16 @@ @@ -9,14 +18,32 @@ - <?php echo 'Jetpack - ' . esc_html( $header_title ); ?> - + <?php echo 'Jetpack - ' . $reset ? 'Stay Secure' : 'Secure Your Account'; ?> + - -
+ +
-

- +

+ +

+

Don't worry - To keep your account safe, we've sent a verification email to a user_email ) ); ?>. After that, we'll guide you through updating your password.

+

Please check your inbox and click the link to verify it's you.

+

Didn't get the email? Resend email

+ +

+

It is highly recommended that you update your password.

+
+
+ +
+
+ +
+
+

Learn more about the risks of using weak passwords and how to protect your account.

+
+ -'; \ No newline at end of file + From da85a185dda50d59b77cb1b0f906a8a54a7c0a85 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 16 Jan 2025 19:13:22 -0800 Subject: [PATCH 21/61] Add nonces, checks and template error handling --- .../src/class-account-protection.php | 2 - .../src/class-password-detection.php | 118 +++++++++++++++--- .../src/class-password-reset-email.php | 21 ++-- .../templates/password-detection-template.php | 26 +++- 4 files changed, 130 insertions(+), 37 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index a20cd51388aa5..213c17cd0839a 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -62,14 +62,12 @@ function ( $user ) { add_action( 'profile_update', function ( $user_id ) { - // TODO: Ensure nonce verfication works as expected if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) ) { // Profile updates should include validation, but we should reset user meta to be safe if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - // TODO: Ensure this only happens if the password is actually updated Password_Detection::remove_password_detection_usermeta( $user_id ); } } diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 9b1a55968ce38..acadc69749f1f 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -67,35 +67,55 @@ public static function render_password_detection_page() { exit; } - add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_password_detection_styles' ); - // Use a transient to track email sent status $transient_key = 'password_reset_email_sent_' . $current_user->ID; $email_sent_flag = get_transient( $transient_key ); - // TODO: Add nonce verification - if ( isset( $_POST['reset'] ) ) { + // Initialize template variables + $reset = false; + $context = 'Your current password was found in a public leak, which means your account might be at risk.'; + $error = ''; + + add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_password_detection_styles' ); + + // Handle reset_password_action form submission + if ( isset( $_POST['reset-password'] ) ) { $reset = true; - $email = $current_user->user_email; - - // Send reset email - if ( ! $email_sent_flag ) { - $email_sent = Password_Reset_Email::send( $current_user, $email ); - if ( $email_sent ) { - // Set transient to mark the email as sent - set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); + + // Verify nonce + if ( isset( $_POST['_wpnonce_reset_password'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_reset_password'] ) ), 'reset_password_action' ) ) { + // Send password reset email + if ( ! $email_sent_flag ) { + $email_sent = Password_Reset_Email::send( $current_user ); + if ( $email_sent ) { + // Set transient to mark the email as sent + set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); + } else { + $error = 'email_send_error'; + } } - // TODO: Handle email sending failure + + add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_resend_password_reset_scripts' ); + } else { + $error = 'reset_passowrd_nonce_verification_error'; } - add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_resend_password_reset_scripts' ); - // TODO: Add nonce verification + // Handle proceed_action form submission } elseif ( isset( $_POST['proceed'] ) ) { - wp_safe_redirect( admin_url() ); - exit; + $reset = true; + + // Verify nonce + if ( isset( $_POST['_wpnonce_proceed'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_proceed'] ) ), 'proceed_action' ) ) { + wp_safe_redirect( admin_url() ); + exit; + } else { + $error = 'proceed_nonce_verification_error'; + } } - include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; + // TODO: Remove this once we decide which template approach is best + // include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; + self::render_password_detection_template( $reset, $context, $error, Password_Reset_Email::mask_email_address( $current_user->user_email ) ); exit; } @@ -190,4 +210,66 @@ public static function remove_password_detection_usermeta( $user_id ) { public static function get_password_detection_usermeta( $user_id ) { return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); } + + /** + * Render template for password detection page. + * + * @param bool $reset Whether the user is resetting their password. + * @param string $context The context for the password detection page. + * @param string $error The error message to display. + * @param string $masked_email The masked email address. + */ + public static function render_password_detection_template( $reset, $context, $error, $masked_email ) { + ?> + + + + + + <?php echo 'Jetpack - ' . $reset ? 'Stay Secure' : 'Secure Your Account'; ?> + + + +
+ +

+ +

+ + +

We've encountered an issue verifying your request to proceed without updating your password.

+ +

While attempting to send a verification email to , an error occurred.

+ + +

Don't worry - To keep your account safe, we've sent a verification email to . After that, we'll guide you through updating your password.

+ + +

Click here to be redirected to your admin dashboard.

+ +

Please check your inbox and click the link to verify it's you. Alternatively, you can update your password from your account profile.

+

Didn't get the email? Resend email

+ + +

+

It is highly recommended that you update your password.

+
+
+ + +
+
+ + +
+
+

Learn more about the risks of using weak passwords and how to protect your account.

+ +
+ + + + user_login; + public static function send( $user ) { + // $site_url = home_url(); + // $parsed_url = wp_parse_url( $site_url ); + // $domain_name = $parsed_url['host']; + // $username = $user->user_login; + // $email = $user->user_email; - $key = get_password_reset_key( $user ); - $locale = get_user_locale( $user ); - $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $username ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; + // $key = get_password_reset_key( $user ); + // $locale = get_user_locale( $user ); + // $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $username ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; // TODO: Update to use custom email method when available, passing $domain_name, $email, $username, and $password_reset_link - return true; + return $user ? true : false; } } diff --git a/projects/packages/account-protection/src/templates/password-detection-template.php b/projects/packages/account-protection/src/templates/password-detection-template.php index ec3651384bf67..a853c08623b7f 100644 --- a/projects/packages/account-protection/src/templates/password-detection-template.php +++ b/projects/packages/account-protection/src/templates/password-detection-template.php @@ -9,8 +9,8 @@ use Automattic\Jetpack\Account_Protection\Password_Reset_Email; -$reset = isset( $reset ) ? $reset : false; -$context = isset( $context ) ? $context : 'Your current password was found in a public leak, which means your account might be at risk.'; +$masked_email = Password_Reset_Email::mask_email_address( $email ); + ?> @@ -27,17 +27,31 @@

-

Don't worry - To keep your account safe, we've sent a verification email to a user_email ) ); ?>. After that, we'll guide you through updating your password.

-

Please check your inbox and click the link to verify it's you.

-

Didn't get the email? Resend email

+ + +

We've encountered an issue verifying your request to proceed without updating your password.

+ +

While attempting to send a verification email to , an error occurred.

+ + +

Don't worry - To keep your account safe, we've sent a verification email to . After that, we'll guide you through updating your password.

+ + +

Click here to be redirected to your admin dashboard.

+ +

Please check your inbox and click the link to verify it's you. Alternatively, you can update your password from your account profile.

+

Didn't get the email? Resend email

+

It is highly recommended that you update your password.

- + +
+
From 6a43ec0e23ae78c09a537f997fc3803ab86b230a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 16 Jan 2025 19:31:09 -0800 Subject: [PATCH 22/61] Use method over template to avoid lint errors --- .../src/class-password-detection.php | 112 ++++++++++-------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index acadc69749f1f..bcc01faacb4b4 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -113,7 +113,7 @@ public static function render_password_detection_page() { } } - // TODO: Remove this once we decide which template approach is best + // TODO: Remove this once we decide which template approach is best - ensure the functionality is 1:1 // include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; self::render_password_detection_template( $reset, $context, $error, Password_Reset_Email::mask_email_address( $current_user->user_email ) ); exit; @@ -220,56 +220,64 @@ public static function get_password_detection_usermeta( $user_id ) { * @param string $masked_email The masked email address. */ public static function render_password_detection_template( $reset, $context, $error, $masked_email ) { - ?> - - - - - - <?php echo 'Jetpack - ' . $reset ? 'Stay Secure' : 'Secure Your Account'; ?> - - - -
- -

- -

- - -

We've encountered an issue verifying your request to proceed without updating your password.

- -

While attempting to send a verification email to , an error occurred.

- - -

Don't worry - To keep your account safe, we've sent a verification email to . After that, we'll guide you through updating your password.

- - -

Click here to be redirected to your admin dashboard.

- -

Please check your inbox and click the link to verify it's you. Alternatively, you can update your password from your account profile.

-

Didn't get the email? Resend email

- - -

-

It is highly recommended that you update your password.

-
-
- - -
-
- - -
-
-

Learn more about the risks of using weak passwords and how to protect your account.

- -
- - - - '; + echo ''; + echo ''; + echo ''; + echo ''; + echo '' . esc_html( $reset ? 'Jetpack - Stay Secure' : 'Jetpack - Secure Your Account' ) . ''; + wp_head(); + echo ''; + echo ''; + echo '
'; + + // Include Jetpack logo + require plugin_dir_path( __FILE__ ) . '/assets/jetpack-logo.php'; + + echo '

' . esc_html( $reset ? 'Take action to stay secure' : "Let's secure your account" ) . '

'; + + if ( $reset ) { + echo '

' . esc_html( $context ) . '

'; + if ( $error ) { + if ( 'proceed_nonce_verification_error' === $error ) { + echo '

We\'ve encountered an issue verifying your request to proceed without updating your password.

'; + } else { + echo '

'; + echo 'reset_password_nonce_verification_error' === $error + ? "We've encountered an issue verifying your request to create a new password. " + : ''; + echo 'While attempting to send a verification email to ' . esc_html( $masked_email ) . ', an error occurred.'; + echo '

'; + } + } else { + echo '

Don\'t worry - To keep your account safe, we\'ve sent a verification email to ' . esc_html( $masked_email ) . '. After that, we\'ll guide you through updating your password.

'; + } + echo '

Please check your inbox and click the link to verify it\'s you. Alternatively, you can update your password from your account profile.

'; + echo '

'; + echo 'Didn\'t get the email? '; + echo 'Resend email'; + echo '

'; + } else { + echo '

' . esc_html( $context ) . '

'; + echo '

It is highly recommended that you update your password.

'; + echo '
'; + echo '
'; + wp_nonce_field( 'reset_password_action', '_wpnonce_reset_password' ); + echo ''; + echo '
'; + echo '
'; + wp_nonce_field( 'proceed_action', '_wpnonce_proceed' ); + echo ''; + echo '
'; + echo '
'; + echo '

Learn more about the risks of using weak passwords and how to protect your account.

'; + } + + echo '
'; + wp_footer(); + echo ''; + echo ''; } } From 755705697e083ae33ec5e6342b4b6fa561c51bdd Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 09:04:06 -0800 Subject: [PATCH 23/61] Improve render_password_detection_template, update SVG file ext --- .../{jetpack-logo.php => jetpack-logo.svg} | 0 .../src/class-password-detection.php | 114 +++++++++--------- .../templates/password-detection-template.php | 2 +- 3 files changed, 57 insertions(+), 59 deletions(-) rename projects/packages/account-protection/src/assets/{jetpack-logo.php => jetpack-logo.svg} (100%) diff --git a/projects/packages/account-protection/src/assets/jetpack-logo.php b/projects/packages/account-protection/src/assets/jetpack-logo.svg similarity index 100% rename from projects/packages/account-protection/src/assets/jetpack-logo.php rename to projects/packages/account-protection/src/assets/jetpack-logo.svg diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index bcc01faacb4b4..91db2d23ae9d0 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -221,63 +221,61 @@ public static function get_password_detection_usermeta( $user_id ) { */ public static function render_password_detection_template( $reset, $context, $error, $masked_email ) { defined( 'ABSPATH' ) || exit; - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo '' . esc_html( $reset ? 'Jetpack - Stay Secure' : 'Jetpack - Secure Your Account' ) . ''; - wp_head(); - echo ''; - echo ''; - echo '
'; - - // Include Jetpack logo - require plugin_dir_path( __FILE__ ) . '/assets/jetpack-logo.php'; - - echo '

' . esc_html( $reset ? 'Take action to stay secure' : "Let's secure your account" ) . '

'; - - if ( $reset ) { - echo '

' . esc_html( $context ) . '

'; - if ( $error ) { - if ( 'proceed_nonce_verification_error' === $error ) { - echo '

We\'ve encountered an issue verifying your request to proceed without updating your password.

'; - } else { - echo '

'; - echo 'reset_password_nonce_verification_error' === $error - ? "We've encountered an issue verifying your request to create a new password. " - : ''; - echo 'While attempting to send a verification email to ' . esc_html( $masked_email ) . ', an error occurred.'; - echo '

'; - } - } else { - echo '

Don\'t worry - To keep your account safe, we\'ve sent a verification email to ' . esc_html( $masked_email ) . '. After that, we\'ll guide you through updating your password.

'; - } - echo '

Please check your inbox and click the link to verify it\'s you. Alternatively, you can update your password from your account profile.

'; - echo '

'; - echo 'Didn\'t get the email? '; - echo 'Resend email'; - echo '

'; - } else { - echo '

' . esc_html( $context ) . '

'; - echo '

It is highly recommended that you update your password.

'; - echo '
'; - echo '
'; - wp_nonce_field( 'reset_password_action', '_wpnonce_reset_password' ); - echo ''; - echo '
'; - echo '
'; - wp_nonce_field( 'proceed_action', '_wpnonce_proceed' ); - echo ''; - echo '
'; - echo '
'; - echo '

Learn more about the risks of using weak passwords and how to protect your account.

'; - } - - echo '
'; - wp_footer(); - echo ''; - echo ''; + ?> + + + + + + <?php echo esc_html( $reset ? 'Jetpack - Stay Secure' : 'Jetpack - Secure Your Account' ); ?> + + + +
+ +

+ +

+ + +

We've encountered an issue verifying your request to proceed without updating your password.

+ +

+ + While attempting to send a verification email to , an error occurred. +

+ + +

Don't worry - To keep your account safe, we've sent a verification email to . After that, we'll guide you through updating your password.

+ +

Please check your inbox and click the link to verify it's you. Alternatively, you can update your password from your account profile.

+

+ Didn't get the email? + Resend email +

+ +

+

It is highly recommended that you update your password.

+
+
+ + +
+
+ + +
+
+

Learn more about the risks of using weak passwords and how to protect your account.

+ +
+ + + +
- +

From 16845a7239e3cd50493f70598a64535b8e1f17d6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 09:11:45 -0800 Subject: [PATCH 24/61] Remove template file and include --- .../src/class-password-detection.php | 2 - .../templates/password-detection-template.php | 63 ------------------- 2 files changed, 65 deletions(-) delete mode 100644 projects/packages/account-protection/src/templates/password-detection-template.php diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 91db2d23ae9d0..9cc0f7c39136f 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -113,8 +113,6 @@ public static function render_password_detection_page() { } } - // TODO: Remove this once we decide which template approach is best - ensure the functionality is 1:1 - // include plugin_dir_path( __FILE__ ) . 'templates/password-detection-template.php'; self::render_password_detection_template( $reset, $context, $error, Password_Reset_Email::mask_email_address( $current_user->user_email ) ); exit; } diff --git a/projects/packages/account-protection/src/templates/password-detection-template.php b/projects/packages/account-protection/src/templates/password-detection-template.php deleted file mode 100644 index d8ffb833391ec..0000000000000 --- a/projects/packages/account-protection/src/templates/password-detection-template.php +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - <?php echo 'Jetpack - ' . $reset ? 'Stay Secure' : 'Secure Your Account'; ?> - - - -
- -

- -

- - -

We've encountered an issue verifying your request to proceed without updating your password.

- -

While attempting to send a verification email to , an error occurred.

- - -

Don't worry - To keep your account safe, we've sent a verification email to . After that, we'll guide you through updating your password.

- - -

Click here to be redirected to your admin dashboard.

- -

Please check your inbox and click the link to verify it's you. Alternatively, you can update your password from your account profile.

-

Didn't get the email? Resend email

- - -

-

It is highly recommended that you update your password.

-
-
- - -
-
- - -
-
-

Learn more about the risks of using weak passwords and how to protect your account.

- -
- - - From 5f959f1ebb0287cce2af4b2cd1e9b13c7256e99c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 11:46:17 -0800 Subject: [PATCH 25/61] Prep for validation endpoints --- .../src/class-password-detection.php | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 9cc0f7c39136f..61dac3b16959f 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -7,6 +7,9 @@ namespace Automattic\Jetpack\Account_Protection; +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\Connection\Manager as Connection_Manager; + /** * Class Password_Detection */ @@ -31,6 +34,16 @@ public static function password_detection_redirect() { * @return WP_User The user object. */ public static function login_form_password_detection( $user, $password ) { + // Check if the user is already a WP_Error object + if ( is_wp_error( $user ) ) { + return $user; + } + + // Ensure the password is correct for this user + if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) { + return $user; + } + if ( ! self::validate_password( $password ) ) { // TODO: Ensure this usermeta is always up to date self::add_password_detection_usermeta( $user->ID, 'unsafe' ); @@ -177,10 +190,57 @@ public static function ajax_resend_password_reset_email() { * @return bool True if the password is valid, false otherwise. */ public static function validate_password( $password ) { - // TODO: Update to use custom password validation method(s) when available. + // TODO: Uncomment out once endpoint is live + // Check compromised and common passwords + // $weak_password = self::check_weak_passwords( $password ); + return $password ? false : true; } + /** + * Check if the password is in the list of common/compromised passwords. + * + * @param string $password The password to check. + * @return bool|WP_Error True if the password is in the list of common/compromised passwords, false otherwise. + */ + public static function check_weak_passwords( $password ) { + $api_url = '/jetpack-protect-weak-password'; + + $is_connected = ( new Connection_Manager() )->is_connected(); + + if ( ! $is_connected ) { + return new \WP_Error( 'site_not_connected' ); + } + + // Hash pass with sha1, and pass first 5 characters to the API + $hashed_password = sha1( $password ); + $password_prefix = substr( $hashed_password, 0, 5 ); + + $response = Client::wpcom_json_api_request_as_blog( + $api_url . '/' . $password_prefix, + '2', + array( 'method' => 'GET' ), + null, + 'wpcom' + ); + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) { + return new \WP_Error( 'failed_fetching_weak_passwords', 'Failed to fetch weak passwords from the server', array( 'status' => $response_code ) ); + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + // Check if the password is in the list of common/compromised passwords + $password_suffix = substr( $hashed_password, 5 ); + if ( in_array( $password_suffix, $body['compromised'] ?? array(), true ) ) { + return true; + } + + return false; + } + /** * Add the password detection usermeta. * From a4ba9593d71fe7dc1784e5a4d051b220cb547665 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 14:16:33 -0800 Subject: [PATCH 26/61] Update classes to be dynamic --- .../src/class-account-protection.php | 50 +++++++++---------- .../src/class-password-detection.php | 44 ++++++++-------- .../src/class-password-reset-email.php | 4 +- .../src/class-rest-controller.php | 22 ++++---- .../jetpack/modules/account-protection.php | 2 +- .../protect/src/class-jetpack-protect.php | 7 +-- .../protect/src/class-rest-controller.php | 9 ++-- 7 files changed, 70 insertions(+), 68 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 213c17cd0839a..42145fa3ede97 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -21,38 +21,38 @@ class Account_Protection { /** * Initializes the configurations needed for the account protection module. */ - public static function init() { + public function init() { // Account protection activation/deactivation hooks - add_action( 'jetpack_activate_module_account-protection', __CLASS__ . '::on_account_protection_activation' ); - add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); + add_action( 'jetpack_activate_module_account-protection', array( $this, 'on_account_protection_activation' ) ); + add_action( 'jetpack_deactivate_module_account-protection', array( $this, 'on_account_protection_deactivation' ) ); // Do not run in unsupported environments - add_action( 'jetpack_get_available_modules', __CLASS__ . '::remove_module_on_unsupported_environments' ); - add_action( 'jetpack_get_available_standalone_modules', __CLASS__ . '::remove_standalone_module_on_unsupported_environments' ); + add_action( 'jetpack_get_available_modules', array( $this, 'remove_module_on_unsupported_environments' ) ); + add_action( 'jetpack_get_available_standalone_modules', array( $this, 'remove_standalone_module_on_unsupported_environments' ) ); // Register REST routes add_action( 'rest_api_init', array( new REST_Controller(), 'register_rest_routes' ) ); - if ( self::is_enabled() ) { + if ( $this->is_enabled() ) { // Validate password after successful login - add_action( 'wp_authenticate_user', array( 'Automattic\Jetpack\Account_Protection\Password_Detection', 'login_form_password_detection' ), 10, 2 ); + add_action( 'wp_authenticate_user', array( new Password_Detection(), 'login_form_password_detection' ), 10, 2 ); // Add password detection flow for users with unsafe passwords add_action( 'login_form_password-detection', - array( 'Automattic\Jetpack\Account_Protection\Password_Detection', 'render_password_detection_page' ), + array( new Password_Detection(), 'render_password_detection_page' ), 10, 2 ); // Register AJAX resend password reset email action - add_action( 'wp_ajax_resend_password_reset', array( 'Automattic\Jetpack\Account_Protection\Password_Detection', 'ajax_resend_password_reset_email' ) ); + add_action( 'wp_ajax_resend_password_reset', array( new Password_Detection(), 'ajax_resend_password_reset_email' ) ); // Remove password detection usermeta on password reset add_action( 'after_password_reset', function ( $user ) { - Password_Detection::remove_password_detection_usermeta( $user->ID ); + ( new Password_Detection() )->remove_password_detection_usermeta( $user->ID ); }, 10, 2 @@ -68,7 +68,7 @@ function ( $user_id ) { ) { // Profile updates should include validation, but we should reset user meta to be safe if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - Password_Detection::remove_password_detection_usermeta( $user_id ); + ( new Password_Detection() )->remove_password_detection_usermeta( $user_id ); } } }, @@ -82,17 +82,17 @@ function ( $user_id ) { /** * Activate the account protection on module activation. */ - public static function on_account_protection_activation() { + public function on_account_protection_activation() { } /** * Deactivate the account protection on module deactivation. */ - public static function on_account_protection_deactivation() { + public function on_account_protection_deactivation() { // Remove user meta on deactivation $users = get_users(); foreach ( $users as $user ) { - Password_Detection::remove_password_detection_usermeta( $user->ID ); + ( new Password_Detection() )->remove_password_detection_usermeta( $user->ID ); // TODO: Remove usermeta on plugin deactivation } } @@ -102,7 +102,7 @@ public static function on_account_protection_deactivation() { * * @return bool */ - public static function is_enabled() { + public function is_enabled() { return ( new Modules() )->is_active( 'account-protection' ); } @@ -111,9 +111,9 @@ public static function is_enabled() { * * @return bool */ - public static function enable() { + public function enable() { // Return true if already enabled. - if ( self::is_enabled() ) { + if ( $this->is_enabled() ) { return true; } return ( new Modules() )->activate( 'account-protection', false, false ); @@ -124,9 +124,9 @@ public static function enable() { * * @return bool */ - public static function disable() { + public function disable() { // Return true if already disabled. - if ( ! self::is_enabled() ) { + if ( ! $this->is_enabled() ) { return true; } return ( new Modules() )->deactivate( 'account-protection' ); @@ -137,7 +137,7 @@ public static function disable() { * * @return bool */ - public static function is_supported_environment() { + public function is_supported_environment() { // Do not run when killswitch is enabled if ( defined( 'DISABLE_JETPACK_ACCOUNT_PROTECTION' ) && DISABLE_JETPACK_ACCOUNT_PROTECTION ) { return false; @@ -153,8 +153,8 @@ public static function is_supported_environment() { * * @return array Array of module slugs. */ - public static function remove_module_on_unsupported_environments( $modules ) { - if ( ! self::is_supported_environment() ) { + public function remove_module_on_unsupported_environments( $modules ) { + if ( ! $this->is_supported_environment() ) { // Account protection should never be available on unsupported platforms. unset( $modules['account-protection'] ); } @@ -169,8 +169,8 @@ public static function remove_module_on_unsupported_environments( $modules ) { * * @return array Array of module slugs. */ - public static function remove_standalone_module_on_unsupported_environments( $modules ) { - if ( ! self::is_supported_environment() ) { + public function remove_standalone_module_on_unsupported_environments( $modules ) { + if ( ! $this->is_supported_environment() ) { // Account Protection should never be available on unsupported platforms. $modules = array_filter( $modules, @@ -189,7 +189,7 @@ function ( $module ) { * * @return array */ - public static function get_settings() { + public function get_settings() { $settings = array( self::STRICT_MODE_OPTION_NAME => get_option( self::STRICT_MODE_OPTION_NAME, false ), ); diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 61dac3b16959f..0dcc178e1d2ba 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -22,7 +22,7 @@ class Password_Detection { * * @return string The URL to redirect to. */ - public static function password_detection_redirect() { + public function password_detection_redirect() { return home_url( '/wp-login.php?action=password-detection' ); } @@ -33,7 +33,7 @@ public static function password_detection_redirect() { * @param string $password The password. * @return WP_User The user object. */ - public static function login_form_password_detection( $user, $password ) { + public function login_form_password_detection( $user, $password ) { // Check if the user is already a WP_Error object if ( is_wp_error( $user ) ) { return $user; @@ -44,14 +44,14 @@ public static function login_form_password_detection( $user, $password ) { return $user; } - if ( ! self::validate_password( $password ) ) { + if ( ! $this->validate_password( $password ) ) { // TODO: Ensure this usermeta is always up to date - self::add_password_detection_usermeta( $user->ID, 'unsafe' ); + $this->add_password_detection_usermeta( $user->ID, 'unsafe' ); // Redirect to the password detection page - add_filter( 'login_redirect', __CLASS__ . '::password_detection_redirect', 10, 3 ); + add_filter( 'login_redirect', array( $this, 'password_detection_redirect' ), 10, 3 ); } else { - self::add_password_detection_usermeta( $user->ID, 'safe' ); + $this->add_password_detection_usermeta( $user->ID, 'safe' ); } @@ -65,7 +65,7 @@ public static function login_form_password_detection( $user, $password ) { * * @return void */ - public static function render_password_detection_page() { + public function render_password_detection_page() { // Restrict direct access to logged in users $current_user = wp_get_current_user(); if ( 0 === $current_user->ID ) { @@ -74,7 +74,7 @@ public static function render_password_detection_page() { } // Restrict direct access to users with unsafe passwords - $user_password_status = self::get_password_detection_usermeta( $current_user->ID ); + $user_password_status = $this->get_password_detection_usermeta( $current_user->ID ); if ( ! $user_password_status || 'safe' === $user_password_status ) { wp_safe_redirect( admin_url() ); exit; @@ -89,7 +89,7 @@ public static function render_password_detection_page() { $context = 'Your current password was found in a public leak, which means your account might be at risk.'; $error = ''; - add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_password_detection_styles' ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_password_detection_styles' ) ); // Handle reset_password_action form submission if ( isset( $_POST['reset-password'] ) ) { @@ -99,7 +99,7 @@ public static function render_password_detection_page() { if ( isset( $_POST['_wpnonce_reset_password'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_reset_password'] ) ), 'reset_password_action' ) ) { // Send password reset email if ( ! $email_sent_flag ) { - $email_sent = Password_Reset_Email::send( $current_user ); + $email_sent = ( new Password_Reset_Email() )->send( $current_user ); if ( $email_sent ) { // Set transient to mark the email as sent set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); @@ -108,7 +108,7 @@ public static function render_password_detection_page() { } } - add_action( 'wp_enqueue_scripts', __CLASS__ . '::enqueue_resend_password_reset_scripts' ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_resend_password_reset_scripts' ) ); } else { $error = 'reset_passowrd_nonce_verification_error'; } @@ -126,14 +126,14 @@ public static function render_password_detection_page() { } } - self::render_password_detection_template( $reset, $context, $error, Password_Reset_Email::mask_email_address( $current_user->user_email ) ); + $this->render_password_detection_template( $reset, $context, $error, ( new Password_Reset_Email() )->mask_email_address( $current_user->user_email ) ); exit; } /** * Enqueue the resend password reset email scripts. */ - public static function enqueue_resend_password_reset_scripts() { + public function enqueue_resend_password_reset_scripts() { wp_enqueue_script( 'resend-password-reset', plugin_dir_url( __FILE__ ) . 'js/resend-password-reset.js', array( 'jquery' ), Account_Protection::PACKAGE_VERSION, true ); // Pass AJAX URL and nonce to the script @@ -150,7 +150,7 @@ public static function enqueue_resend_password_reset_scripts() { /** * Enqueue the password detection page styles. */ - public static function enqueue_password_detection_styles() { + public function enqueue_password_detection_styles() { wp_enqueue_style( 'password-detection-styles', plugin_dir_url( __FILE__ ) . 'css/password-detection.css', @@ -162,7 +162,7 @@ public static function enqueue_password_detection_styles() { /** * Run AJAX request to resend password reset email. */ - public static function ajax_resend_password_reset_email() { + public function ajax_resend_password_reset_email() { // Verify the nonce for security check_ajax_referer( 'resend_password_reset_nonce', 'security' ); @@ -175,7 +175,7 @@ public static function ajax_resend_password_reset_email() { $email = $current_user->user_email; // Resend the email - $email_sent = Password_Reset_Email::send( $current_user, $email ); + $email_sent = ( new Password_Reset_Email() )->send( $current_user, $email ); if ( $email_sent ) { wp_send_json_success( array( 'message' => 'Resend successful.' ) ); } else { @@ -189,7 +189,7 @@ public static function ajax_resend_password_reset_email() { * @param string $password The password to validate. * @return bool True if the password is valid, false otherwise. */ - public static function validate_password( $password ) { + public function validate_password( $password ) { // TODO: Uncomment out once endpoint is live // Check compromised and common passwords // $weak_password = self::check_weak_passwords( $password ); @@ -203,7 +203,7 @@ public static function validate_password( $password ) { * @param string $password The password to check. * @return bool|WP_Error True if the password is in the list of common/compromised passwords, false otherwise. */ - public static function check_weak_passwords( $password ) { + public function check_weak_passwords( $password ) { $api_url = '/jetpack-protect-weak-password'; $is_connected = ( new Connection_Manager() )->is_connected(); @@ -247,7 +247,7 @@ public static function check_weak_passwords( $password ) { * @param int $user_id The user ID. * @param string $setting The password detection setting. */ - public static function add_password_detection_usermeta( $user_id, $setting ) { + public function add_password_detection_usermeta( $user_id, $setting ) { update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); } @@ -256,7 +256,7 @@ public static function add_password_detection_usermeta( $user_id, $setting ) { * * @param int $user_id The user ID. */ - public static function remove_password_detection_usermeta( $user_id ) { + public function remove_password_detection_usermeta( $user_id ) { delete_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY ); } @@ -265,7 +265,7 @@ public static function remove_password_detection_usermeta( $user_id ) { * * @param int $user_id The user ID. */ - public static function get_password_detection_usermeta( $user_id ) { + public function get_password_detection_usermeta( $user_id ) { return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); } @@ -277,7 +277,7 @@ public static function get_password_detection_usermeta( $user_id ) { * @param string $error The error message to display. * @param string $masked_email The masked email address. */ - public static function render_password_detection_template( $reset, $context, $error, $masked_email ) { + public function render_password_detection_template( $reset, $context, $error, $masked_email ) { defined( 'ABSPATH' ) || exit; ?> diff --git a/projects/packages/account-protection/src/class-password-reset-email.php b/projects/packages/account-protection/src/class-password-reset-email.php index 359710d2b6d6a..bac4668b6ac6a 100644 --- a/projects/packages/account-protection/src/class-password-reset-email.php +++ b/projects/packages/account-protection/src/class-password-reset-email.php @@ -18,7 +18,7 @@ class Password_Reset_Email { * @param string $email The email address to mask. * @return string The masked email address. */ - public static function mask_email_address( $email ) { + public function mask_email_address( $email ) { $parts = explode( '@', $email ); $name = $parts[0]; $domain = $parts[1]; @@ -39,7 +39,7 @@ public static function mask_email_address( $email ) { * @param WP_User $user The user object. * @return bool True if the email was sent successfully, false otherwise. */ - public static function send( $user ) { + public function send( $user ) { // $site_url = home_url(); // $parsed_url = wp_parse_url( $site_url ); // $domain_name = $parsed_url['host']; diff --git a/projects/packages/account-protection/src/class-rest-controller.php b/projects/packages/account-protection/src/class-rest-controller.php index d8af3451bc861..e5a45972d1f9e 100644 --- a/projects/packages/account-protection/src/class-rest-controller.php +++ b/projects/packages/account-protection/src/class-rest-controller.php @@ -22,9 +22,9 @@ class REST_Controller { * * @return void */ - public static function register_rest_routes() { + public function register_rest_routes() { // Ensure routes are only initialized once. - static $routes_registered = false; + $routes_registered = false; if ( $routes_registered ) { return; } @@ -34,8 +34,8 @@ public static function register_rest_routes() { '/account-protection', array( 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_settings', - 'permission_callback' => __CLASS__ . '::permissions_callback', + 'callback' => array( $this, 'get_settings' ), + 'permission_callback' => array( $this, 'permissions_callback' ), ) ); @@ -44,8 +44,8 @@ public static function register_rest_routes() { '/account-protection', array( 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_settings', - 'permission_callback' => __CLASS__ . '::permissions_callback', + 'callback' => array( $this, 'update_settings' ), + 'permission_callback' => array( $this, 'permissions_callback' ), ) ); @@ -57,8 +57,8 @@ public static function register_rest_routes() { * * @return WP_REST_Response */ - public static function get_settings() { - $settings = Account_Protection::get_settings(); + public function get_settings() { + $settings = ( new Account_Protection() )->get_settings(); return rest_ensure_response( $settings ); } @@ -70,13 +70,13 @@ public static function get_settings() { * * @return WP_REST_Response|WP_Error */ - public static function update_settings( $request ) { + 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 self::get_settings(); + return $this->get_settings(); } /** @@ -84,7 +84,7 @@ public static function update_settings( $request ) { * * @return bool|WP_Error True if user can view the Jetpack admin page. */ - public static function permissions_callback() { + public function permissions_callback() { if ( current_user_can( 'manage_options' ) ) { return true; } diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index b84d338782098..554570f666289 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -15,4 +15,4 @@ use Automattic\Jetpack\Account_Protection\Account_Protection; -Account_Protection::init(); +( new Account_Protection() )->init(); diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 8c0ec4f1b5b99..fe13bd83a670e 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -138,7 +138,7 @@ public function init() { REST_Controller::init(); My_Jetpack_Initializer::init(); Site_Health::init(); - Account_Protection::init(); + ( new Account_Protection() )->init(); // Sets up JITMS. JITM::configure(); @@ -214,6 +214,7 @@ public function initial_state() { // phpcs:disable WordPress.Security.NonceVerification.Recommended $refresh_status_from_wpcom = isset( $_GET['checkPlan'] ); $status = Status::get_status( $refresh_status_from_wpcom ); + $account_protection = new Account_Protection(); $initial_state = array( 'apiRoot' => esc_url_raw( rest_url() ), @@ -233,8 +234,8 @@ public function initial_state() { 'hasPlan' => Plan::has_required_plan(), 'onboardingProgress' => Onboarding::get_current_user_progress(), 'accountProtection' => array( - 'isEnabled' => Account_Protection::is_enabled(), - 'settings' => Account_Protection::get_settings(), + 'isEnabled' => $account_protection->is_enabled(), + 'settings' => $account_protection->get_settings(), ), 'waf' => array( 'wafSupported' => Waf_Runner::is_supported_environment(), diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php index 32d85f5e8ad97..b6ddb432afa23 100644 --- a/projects/plugins/protect/src/class-rest-controller.php +++ b/projects/plugins/protect/src/class-rest-controller.php @@ -371,8 +371,9 @@ public static function api_scan() { * @return WP_REST_Response|WP_Error */ public static function api_toggle_account_protection() { - if ( Account_Protection::is_enabled() ) { - $disabled = Account_Protection::disable(); + $account_protection = new Account_Protection(); + if ( $account_protection->is_enabled() ) { + $disabled = $account_protection->disable(); if ( ! $disabled ) { return new WP_Error( 'account_protection_disable_failed', @@ -384,7 +385,7 @@ public static function api_toggle_account_protection() { return rest_ensure_response( true ); } - $enabled = Account_Protection::enable(); + $enabled = $account_protection->enable(); if ( ! $enabled ) { return new WP_Error( 'account_protection_enable_failed', @@ -402,7 +403,7 @@ public static function api_toggle_account_protection() { * @return WP_Rest_Response */ public static function api_get_account_protection() { - return new WP_REST_Response( Account_Protection::is_enabled() ); + return new WP_REST_Response( ( new Account_Protection() )->get_settings() ); } /** From 992f2889f0ef83bc8a354c306860f49216110823 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 14:30:51 -0800 Subject: [PATCH 27/61] Add constructors --- .../src/class-account-protection.php | 44 +++++++++++++++---- .../src/class-password-detection.php | 22 ++++++++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 42145fa3ede97..0d25dabfccfc2 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -18,6 +18,31 @@ class Account_Protection { const ACCOUNT_PROTECTION_MODULE_NAME = 'account-protection'; const STRICT_MODE_OPTION_NAME = 'jetpack_account_protection_strict_mode'; + /** + * Modules dependency. + * + * @var Modules + */ + private $modules; + + /** + * Password Detection dependency. + * + * @var Password_Detection + */ + private $password_detection; + + /** + * Constructor. + * + * @param Modules|null $modules Modules dependency. + * @param Password_Detection|null $password_detection Password detection dependency. + */ + public function __construct( Modules $modules = null, Password_Detection $password_detection = null ) { + $this->modules = $modules ?? new Modules(); + $this->password_detection = $password_detection ?? new Password_Detection(); + } + /** * Initializes the configurations needed for the account protection module. */ @@ -35,24 +60,24 @@ public function init() { if ( $this->is_enabled() ) { // Validate password after successful login - add_action( 'wp_authenticate_user', array( new Password_Detection(), 'login_form_password_detection' ), 10, 2 ); + add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 ); // Add password detection flow for users with unsafe passwords add_action( 'login_form_password-detection', - array( new Password_Detection(), 'render_password_detection_page' ), + array( $this->password_detection, 'render_password_detection_page' ), 10, 2 ); // Register AJAX resend password reset email action - add_action( 'wp_ajax_resend_password_reset', array( new Password_Detection(), 'ajax_resend_password_reset_email' ) ); + add_action( 'wp_ajax_resend_password_reset', array( $this->password_detection, 'ajax_resend_password_reset_email' ) ); // Remove password detection usermeta on password reset add_action( 'after_password_reset', function ( $user ) { - ( new Password_Detection() )->remove_password_detection_usermeta( $user->ID ); + $this->password_detection->remove_password_detection_usermeta( $user->ID ); }, 10, 2 @@ -68,7 +93,7 @@ function ( $user_id ) { ) { // Profile updates should include validation, but we should reset user meta to be safe if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - ( new Password_Detection() )->remove_password_detection_usermeta( $user_id ); + $this->password_detection->remove_password_detection_usermeta( $user_id ); } } }, @@ -83,6 +108,7 @@ function ( $user_id ) { * Activate the account protection on module activation. */ public function on_account_protection_activation() { + // Activation logic can be added here } /** @@ -92,7 +118,7 @@ public function on_account_protection_deactivation() { // Remove user meta on deactivation $users = get_users(); foreach ( $users as $user ) { - ( new Password_Detection() )->remove_password_detection_usermeta( $user->ID ); + $this->password_detection->remove_password_detection_usermeta( $user->ID ); // TODO: Remove usermeta on plugin deactivation } } @@ -103,7 +129,7 @@ public function on_account_protection_deactivation() { * @return bool */ public function is_enabled() { - return ( new Modules() )->is_active( 'account-protection' ); + return $this->modules->is_active( 'account-protection' ); } /** @@ -116,7 +142,7 @@ public function enable() { if ( $this->is_enabled() ) { return true; } - return ( new Modules() )->activate( 'account-protection', false, false ); + return $this->modules->activate( 'account-protection', false, false ); } /** @@ -129,7 +155,7 @@ public function disable() { if ( ! $this->is_enabled() ) { return true; } - return ( new Modules() )->deactivate( 'account-protection' ); + return $this->modules->deactivate( 'account-protection' ); } /** diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 0dcc178e1d2ba..1281c137d95de 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -17,6 +17,22 @@ class Password_Detection { const PASSWORD_DETECTION_USER_META_KEY = 'jetpack_account_protection_password_status'; + /** + * Password reset email dependency. + * + * @var Password_Reset_Email + */ + private $password_reset_email; + + /** + * Constructor. + * + * @param Password_Reset_Email|null $password_reset_email Password reset email dependency. + */ + public function __construct( Password_Reset_Email $password_reset_email = null ) { + $this->password_reset_email = $password_reset_email ?? new Password_Reset_Email(); + } + /** * Redirect to the password detection page. * @@ -99,7 +115,7 @@ public function render_password_detection_page() { if ( isset( $_POST['_wpnonce_reset_password'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_reset_password'] ) ), 'reset_password_action' ) ) { // Send password reset email if ( ! $email_sent_flag ) { - $email_sent = ( new Password_Reset_Email() )->send( $current_user ); + $email_sent = $this->password_reset_email->send( $current_user ); if ( $email_sent ) { // Set transient to mark the email as sent set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); @@ -126,7 +142,7 @@ public function render_password_detection_page() { } } - $this->render_password_detection_template( $reset, $context, $error, ( new Password_Reset_Email() )->mask_email_address( $current_user->user_email ) ); + $this->render_password_detection_template( $reset, $context, $error, $this->password_reset_email->mask_email_address( $current_user->user_email ) ); exit; } @@ -175,7 +191,7 @@ public function ajax_resend_password_reset_email() { $email = $current_user->user_email; // Resend the email - $email_sent = ( new Password_Reset_Email() )->send( $current_user, $email ); + $email_sent = $this->password_reset_email->send( $current_user, $email ); if ( $email_sent ) { wp_send_json_success( array( 'message' => 'Resend successful.' ) ); } else { From 43d4cd0ec57a8bdf18dd6c6b17453284426a9b5a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 14:47:08 -0800 Subject: [PATCH 28/61] Reorg user meta methods --- .../src/class-account-protection.php | 68 +++++++------------ .../src/class-password-detection.php | 44 +++++++++--- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 0d25dabfccfc2..70a53e41a3e09 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -47,6 +47,17 @@ public function __construct( Modules $modules = null, Password_Detection $passwo * Initializes the configurations needed for the account protection module. */ public function init() { + $this->register_hooks(); + + if ( $this->is_enabled() ) { + $this->register_runtime_hooks(); + } + } + + /** + * Register hooks for module activation and environment validation. + */ + private function register_hooks() { // Account protection activation/deactivation hooks add_action( 'jetpack_activate_module_account-protection', array( $this, 'on_account_protection_activation' ) ); add_action( 'jetpack_deactivate_module_account-protection', array( $this, 'on_account_protection_deactivation' ) ); @@ -57,51 +68,24 @@ public function init() { // Register REST routes add_action( 'rest_api_init', array( new REST_Controller(), 'register_rest_routes' ) ); + } - if ( $this->is_enabled() ) { - // Validate password after successful login - add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 ); - - // Add password detection flow for users with unsafe passwords - add_action( - 'login_form_password-detection', - array( $this->password_detection, 'render_password_detection_page' ), - 10, - 2 - ); + /** + * Register hooks for runtime operations. + */ + private function register_runtime_hooks() { + // Validate password after successful login + add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 ); - // Register AJAX resend password reset email action - add_action( 'wp_ajax_resend_password_reset', array( $this->password_detection, 'ajax_resend_password_reset_email' ) ); - - // Remove password detection usermeta on password reset - add_action( - 'after_password_reset', - function ( $user ) { - $this->password_detection->remove_password_detection_usermeta( $user->ID ); - }, - 10, - 2 - ); + // Add password detection flow for users with unsafe passwords + add_action( 'login_form_password-detection', array( $this->password_detection, 'render_password_detection_page' ), 10, 2 ); - // Remove password detection usermeta on profile password update - add_action( - 'profile_update', - function ( $user_id ) { - if ( - ! empty( $_POST['_wpnonce'] ) && - wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) - ) { - // Profile updates should include validation, but we should reset user meta to be safe - if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - $this->password_detection->remove_password_detection_usermeta( $user_id ); - } - } - }, - 10, - 2 - ); + // Remove password detection usermeta after password reset and on profile password update + add_action( 'after_password_reset', array( $this->password_detection, 'delete_password_detection_usermeta_after_password_reset' ), 10, 2 ); + add_action( 'profile_update', array( $this->password_detection, 'delete_password_detection_usermeta_on_profile_update' ), 10, 2 ); - } + // Register AJAX resend password reset email action + add_action( 'wp_ajax_resend_password_reset', array( $this->password_detection, 'ajax_resend_password_reset_email' ) ); } /** @@ -118,7 +102,7 @@ public function on_account_protection_deactivation() { // Remove user meta on deactivation $users = get_users(); foreach ( $users as $user ) { - $this->password_detection->remove_password_detection_usermeta( $user->ID ); + $this->password_detection->delete_password_detection_usermeta( $user->ID ); // TODO: Remove usermeta on plugin deactivation } } diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 1281c137d95de..74a3ebd21675a 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -62,13 +62,12 @@ public function login_form_password_detection( $user, $password ) { if ( ! $this->validate_password( $password ) ) { // TODO: Ensure this usermeta is always up to date - $this->add_password_detection_usermeta( $user->ID, 'unsafe' ); + $this->update_password_detection_usermeta( $user->ID, 'unsafe' ); // Redirect to the password detection page add_filter( 'login_redirect', array( $this, 'password_detection_redirect' ), 10, 3 ); } else { - $this->add_password_detection_usermeta( $user->ID, 'safe' ); - + $this->update_password_detection_usermeta( $user->ID, 'safe' ); } return $user; @@ -258,31 +257,56 @@ public function check_weak_passwords( $password ) { } /** - * Add the password detection usermeta. + * Get the password detection usermeta. + * + * @param int $user_id The user ID. + */ + public function get_password_detection_usermeta( $user_id ) { + return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); + } + + /** + * Update the password detection usermeta. * * @param int $user_id The user ID. * @param string $setting The password detection setting. */ - public function add_password_detection_usermeta( $user_id, $setting ) { + public function update_password_detection_usermeta( $user_id, $setting ) { update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); } /** - * Remove the password detection usermeta. + * Delete the password detection usermeta. * * @param int $user_id The user ID. */ - public function remove_password_detection_usermeta( $user_id ) { + public function delete_password_detection_usermeta( $user_id ) { delete_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY ); } /** - * Get the password detection usermeta. + * Delete the password detection usermeta after password reset. + * + * @param WP_User $user The user object. + */ + public function delete_password_detection_usermeta_after_password_reset( $user ) { + $this->delete_password_detection_usermeta( $user->ID ); + } + + /** + * Delete the password detection usermeta on profile password update. * * @param int $user_id The user ID. */ - public function get_password_detection_usermeta( $user_id ) { - return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); + public function delete_password_detection_usermeta_on_profile_update( $user_id ) { + if ( + ! empty( $_POST['_wpnonce'] ) && + wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) + ) { + if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { + $this->delete_password_detection_usermeta( $user_id ); + } + } } /** From 3cec8912b4482fdb5b1b515ab7b9ce9c3de02e0c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 15:09:09 -0800 Subject: [PATCH 29/61] Add type declarations and hinting --- .../src/class-account-protection.php | 37 ++++++++-------- .../src/class-password-detection.php | 43 ++++++++++--------- .../src/class-password-reset-email.php | 6 +-- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 70a53e41a3e09..25e8a4a0db525 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -13,7 +13,6 @@ * Class Account_Protection */ class Account_Protection { - const PACKAGE_VERSION = '1.0.0-alpha'; const ACCOUNT_PROTECTION_MODULE_NAME = 'account-protection'; const STRICT_MODE_OPTION_NAME = 'jetpack_account_protection_strict_mode'; @@ -46,7 +45,7 @@ public function __construct( Modules $modules = null, Password_Detection $passwo /** * Initializes the configurations needed for the account protection module. */ - public function init() { + public function init(): void { $this->register_hooks(); if ( $this->is_enabled() ) { @@ -57,10 +56,10 @@ public function init() { /** * Register hooks for module activation and environment validation. */ - private function register_hooks() { + private function register_hooks(): void { // Account protection activation/deactivation hooks - add_action( 'jetpack_activate_module_account-protection', array( $this, 'on_account_protection_activation' ) ); - add_action( 'jetpack_deactivate_module_account-protection', array( $this, 'on_account_protection_deactivation' ) ); + 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' ) ); // Do not run in unsupported environments add_action( 'jetpack_get_available_modules', array( $this, 'remove_module_on_unsupported_environments' ) ); @@ -73,11 +72,11 @@ private function register_hooks() { /** * Register hooks for runtime operations. */ - private function register_runtime_hooks() { + private function register_runtime_hooks(): void { // Validate password after successful login add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 ); - // Add password detection flow for users with unsafe passwords + // Add password detection flow add_action( 'login_form_password-detection', array( $this->password_detection, 'render_password_detection_page' ), 10, 2 ); // Remove password detection usermeta after password reset and on profile password update @@ -91,14 +90,14 @@ private function register_runtime_hooks() { /** * Activate the account protection on module activation. */ - public function on_account_protection_activation() { + public function on_account_protection_activation(): void { // Activation logic can be added here } /** * Deactivate the account protection on module deactivation. */ - public function on_account_protection_deactivation() { + public function on_account_protection_deactivation(): void { // Remove user meta on deactivation $users = get_users(); foreach ( $users as $user ) { @@ -113,7 +112,7 @@ public function on_account_protection_deactivation() { * @return bool */ public function is_enabled() { - return $this->modules->is_active( 'account-protection' ); + return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); } /** @@ -126,7 +125,7 @@ public function enable() { if ( $this->is_enabled() ) { return true; } - return $this->modules->activate( 'account-protection', false, false ); + return $this->modules->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); } /** @@ -134,12 +133,12 @@ public function enable() { * * @return bool */ - public function disable() { + public function disable(): bool { // Return true if already disabled. if ( ! $this->is_enabled() ) { return true; } - return $this->modules->deactivate( 'account-protection' ); + return $this->modules->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME ); } /** @@ -147,7 +146,7 @@ public function disable() { * * @return bool */ - public function is_supported_environment() { + public function is_supported_environment(): bool { // Do not run when killswitch is enabled if ( defined( 'DISABLE_JETPACK_ACCOUNT_PROTECTION' ) && DISABLE_JETPACK_ACCOUNT_PROTECTION ) { return false; @@ -163,10 +162,10 @@ public function is_supported_environment() { * * @return array Array of module slugs. */ - public function remove_module_on_unsupported_environments( $modules ) { + public function remove_module_on_unsupported_environments( array $modules ): array { if ( ! $this->is_supported_environment() ) { // Account protection should never be available on unsupported platforms. - unset( $modules['account-protection'] ); + unset( $modules[ self::ACCOUNT_PROTECTION_MODULE_NAME ] ); } return $modules; @@ -179,13 +178,13 @@ public function remove_module_on_unsupported_environments( $modules ) { * * @return array Array of module slugs. */ - public function remove_standalone_module_on_unsupported_environments( $modules ) { + public function remove_standalone_module_on_unsupported_environments( array $modules ): array { if ( ! $this->is_supported_environment() ) { // Account Protection should never be available on unsupported platforms. $modules = array_filter( $modules, function ( $module ) { - return $module !== 'account-protection'; + return $module !== self::ACCOUNT_PROTECTION_MODULE_NAME; } ); @@ -199,7 +198,7 @@ function ( $module ) { * * @return array */ - public function get_settings() { + public function get_settings(): array { $settings = array( self::STRICT_MODE_OPTION_NAME => get_option( self::STRICT_MODE_OPTION_NAME, false ), ); diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 74a3ebd21675a..383c12c08997b 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -38,18 +38,18 @@ public function __construct( Password_Reset_Email $password_reset_email = null ) * * @return string The URL to redirect to. */ - public function password_detection_redirect() { + public function password_detection_redirect(): string { return home_url( '/wp-login.php?action=password-detection' ); } /** * Check if the password is safe after login. * - * @param WP_User $user The user object. - * @param string $password The password. - * @return WP_User The user object. + * @param \WP_User $user The user object. + * @param string $password The password. + * @return \WP_User|\WP_Error The user object. */ - public function login_form_password_detection( $user, $password ) { + public function login_form_password_detection( \WP_User $user, string $password ): \WP_User { // Check if the user is already a WP_Error object if ( is_wp_error( $user ) ) { return $user; @@ -76,11 +76,9 @@ public function login_form_password_detection( $user, $password ) { /** * Render password detection page. * - * This page is shown to users with unsafe passwords after login. - * * @return void */ - public function render_password_detection_page() { + public function render_password_detection_page(): void { // Restrict direct access to logged in users $current_user = wp_get_current_user(); if ( 0 === $current_user->ID ) { @@ -147,8 +145,10 @@ public function render_password_detection_page() { /** * Enqueue the resend password reset email scripts. + * + * @return void */ - public function enqueue_resend_password_reset_scripts() { + public function enqueue_resend_password_reset_scripts(): void { wp_enqueue_script( 'resend-password-reset', plugin_dir_url( __FILE__ ) . 'js/resend-password-reset.js', array( 'jquery' ), Account_Protection::PACKAGE_VERSION, true ); // Pass AJAX URL and nonce to the script @@ -164,8 +164,10 @@ public function enqueue_resend_password_reset_scripts() { /** * Enqueue the password detection page styles. + * + * @return void */ - public function enqueue_password_detection_styles() { + public function enqueue_password_detection_styles(): void { wp_enqueue_style( 'password-detection-styles', plugin_dir_url( __FILE__ ) . 'css/password-detection.css', @@ -204,7 +206,7 @@ public function ajax_resend_password_reset_email() { * @param string $password The password to validate. * @return bool True if the password is valid, false otherwise. */ - public function validate_password( $password ) { + public function validate_password( string $password ): bool { // TODO: Uncomment out once endpoint is live // Check compromised and common passwords // $weak_password = self::check_weak_passwords( $password ); @@ -216,9 +218,9 @@ public function validate_password( $password ) { * Check if the password is in the list of common/compromised passwords. * * @param string $password The password to check. - * @return bool|WP_Error True if the password is in the list of common/compromised passwords, false otherwise. + * @return bool|\WP_Error True if the password is in the list of common/compromised passwords, false otherwise. */ - public function check_weak_passwords( $password ) { + public function check_weak_passwords( string $password ) { $api_url = '/jetpack-protect-weak-password'; $is_connected = ( new Connection_Manager() )->is_connected(); @@ -261,7 +263,7 @@ public function check_weak_passwords( $password ) { * * @param int $user_id The user ID. */ - public function get_password_detection_usermeta( $user_id ) { + public function get_password_detection_usermeta( int $user_id ) { return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); } @@ -271,7 +273,7 @@ public function get_password_detection_usermeta( $user_id ) { * @param int $user_id The user ID. * @param string $setting The password detection setting. */ - public function update_password_detection_usermeta( $user_id, $setting ) { + public function update_password_detection_usermeta( int $user_id, string $setting ) { update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); } @@ -280,16 +282,16 @@ public function update_password_detection_usermeta( $user_id, $setting ) { * * @param int $user_id The user ID. */ - public function delete_password_detection_usermeta( $user_id ) { + public function delete_password_detection_usermeta( int $user_id ) { delete_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY ); } /** * Delete the password detection usermeta after password reset. * - * @param WP_User $user The user object. + * @param \WP_User $user The user object. */ - public function delete_password_detection_usermeta_after_password_reset( $user ) { + public function delete_password_detection_usermeta_after_password_reset( \WP_User $user ) { $this->delete_password_detection_usermeta( $user->ID ); } @@ -298,7 +300,7 @@ public function delete_password_detection_usermeta_after_password_reset( $user ) * * @param int $user_id The user ID. */ - public function delete_password_detection_usermeta_on_profile_update( $user_id ) { + public function delete_password_detection_usermeta_on_profile_update( int $user_id ) { if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) @@ -316,8 +318,9 @@ public function delete_password_detection_usermeta_on_profile_update( $user_id ) * @param string $context The context for the password detection page. * @param string $error The error message to display. * @param string $masked_email The masked email address. + * @return void */ - public function render_password_detection_template( $reset, $context, $error, $masked_email ) { + public function render_password_detection_template( bool $reset, string $context, string $error, string $masked_email ): void { defined( 'ABSPATH' ) || exit; ?> diff --git a/projects/packages/account-protection/src/class-password-reset-email.php b/projects/packages/account-protection/src/class-password-reset-email.php index bac4668b6ac6a..5d2358317669a 100644 --- a/projects/packages/account-protection/src/class-password-reset-email.php +++ b/projects/packages/account-protection/src/class-password-reset-email.php @@ -18,7 +18,7 @@ class Password_Reset_Email { * @param string $email The email address to mask. * @return string The masked email address. */ - public function mask_email_address( $email ) { + public function mask_email_address( string $email ): string { $parts = explode( '@', $email ); $name = $parts[0]; $domain = $parts[1]; @@ -36,10 +36,10 @@ public function mask_email_address( $email ) { /** * Send password reset email. * - * @param WP_User $user The user object. + * @param \WP_User $user The user object. * @return bool True if the email was sent successfully, false otherwise. */ - public function send( $user ) { + public function send( \WP_User $user ): bool { // $site_url = home_url(); // $parsed_url = wp_parse_url( $site_url ); // $domain_name = $parsed_url['host']; From c61877b07709e7c9ff74b68e2e991d77745a7e17 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 17 Jan 2025 15:36:19 -0800 Subject: [PATCH 30/61] Simplify method naming --- .../src/class-account-protection.php | 15 +++---- .../src/class-password-detection.php | 44 ++++++++++++------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 25e8a4a0db525..a1c92f9c49093 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -77,11 +77,11 @@ private function register_runtime_hooks(): void { add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 ); // Add password detection flow - add_action( 'login_form_password-detection', array( $this->password_detection, 'render_password_detection_page' ), 10, 2 ); + add_action( 'login_form_password-detection', array( $this->password_detection, 'render_page' ), 10, 2 ); // Remove password detection usermeta after password reset and on profile password update - add_action( 'after_password_reset', array( $this->password_detection, 'delete_password_detection_usermeta_after_password_reset' ), 10, 2 ); - add_action( 'profile_update', array( $this->password_detection, 'delete_password_detection_usermeta_on_profile_update' ), 10, 2 ); + add_action( 'after_password_reset', array( $this->password_detection, 'delete_usermeta_after_password_reset' ), 10, 2 ); + add_action( 'profile_update', array( $this->password_detection, 'delete_usermeta_on_profile_update' ), 10, 2 ); // Register AJAX resend password reset email action add_action( 'wp_ajax_resend_password_reset', array( $this->password_detection, 'ajax_resend_password_reset_email' ) ); @@ -98,12 +98,9 @@ public function on_account_protection_activation(): void { * Deactivate the account protection on module deactivation. */ public function on_account_protection_deactivation(): void { - // Remove user meta on deactivation - $users = get_users(); - foreach ( $users as $user ) { - $this->password_detection->delete_password_detection_usermeta( $user->ID ); - // TODO: Remove usermeta on plugin deactivation - } + // Remove password detection user meta on deactivation + // TODO: Run on Jetpack and Protect deactivation + $this->password_detection->delete_all_usermeta(); } /** diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 383c12c08997b..c6e39b533a439 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -61,13 +61,13 @@ public function login_form_password_detection( \WP_User $user, string $password } if ( ! $this->validate_password( $password ) ) { - // TODO: Ensure this usermeta is always up to date - $this->update_password_detection_usermeta( $user->ID, 'unsafe' ); + // TODO: Ensure usermeta is always up to date + $this->update_usermeta( $user->ID, 'unsafe' ); // Redirect to the password detection page add_filter( 'login_redirect', array( $this, 'password_detection_redirect' ), 10, 3 ); } else { - $this->update_password_detection_usermeta( $user->ID, 'safe' ); + $this->update_usermeta( $user->ID, 'safe' ); } return $user; @@ -78,7 +78,7 @@ public function login_form_password_detection( \WP_User $user, string $password * * @return void */ - public function render_password_detection_page(): void { + public function render_page(): void { // Restrict direct access to logged in users $current_user = wp_get_current_user(); if ( 0 === $current_user->ID ) { @@ -87,7 +87,7 @@ public function render_password_detection_page(): void { } // Restrict direct access to users with unsafe passwords - $user_password_status = $this->get_password_detection_usermeta( $current_user->ID ); + $user_password_status = $this->get_usermeta( $current_user->ID ); if ( ! $user_password_status || 'safe' === $user_password_status ) { wp_safe_redirect( admin_url() ); exit; @@ -102,7 +102,7 @@ public function render_password_detection_page(): void { $context = 'Your current password was found in a public leak, which means your account might be at risk.'; $error = ''; - add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_password_detection_styles' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); // Handle reset_password_action form submission if ( isset( $_POST['reset-password'] ) ) { @@ -139,7 +139,7 @@ public function render_password_detection_page(): void { } } - $this->render_password_detection_template( $reset, $context, $error, $this->password_reset_email->mask_email_address( $current_user->user_email ) ); + $this->render_content( $reset, $context, $error, $this->password_reset_email->mask_email_address( $current_user->user_email ) ); exit; } @@ -167,7 +167,7 @@ public function enqueue_resend_password_reset_scripts(): void { * * @return void */ - public function enqueue_password_detection_styles(): void { + public function enqueue_styles(): void { wp_enqueue_style( 'password-detection-styles', plugin_dir_url( __FILE__ ) . 'css/password-detection.css', @@ -263,7 +263,7 @@ public function check_weak_passwords( string $password ) { * * @param int $user_id The user ID. */ - public function get_password_detection_usermeta( int $user_id ) { + public function get_usermeta( int $user_id ) { return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); } @@ -273,16 +273,26 @@ public function get_password_detection_usermeta( int $user_id ) { * @param int $user_id The user ID. * @param string $setting The password detection setting. */ - public function update_password_detection_usermeta( int $user_id, string $setting ) { + public function update_usermeta( int $user_id, string $setting ) { update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); } + /** + * Delete password detection usermeta for all users. + */ + public function delete_all_usermeta() { + $users = get_users(); + foreach ( $users as $user ) { + $this->delete_usermeta( $user->ID ); + } + } + /** * Delete the password detection usermeta. * * @param int $user_id The user ID. */ - public function delete_password_detection_usermeta( int $user_id ) { + public function delete_usermeta( int $user_id ) { delete_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY ); } @@ -291,8 +301,8 @@ public function delete_password_detection_usermeta( int $user_id ) { * * @param \WP_User $user The user object. */ - public function delete_password_detection_usermeta_after_password_reset( \WP_User $user ) { - $this->delete_password_detection_usermeta( $user->ID ); + public function delete_usermeta_after_password_reset( \WP_User $user ) { + $this->delete_usermeta( $user->ID ); } /** @@ -300,19 +310,19 @@ public function delete_password_detection_usermeta_after_password_reset( \WP_Use * * @param int $user_id The user ID. */ - public function delete_password_detection_usermeta_on_profile_update( int $user_id ) { + public function delete_usermeta_on_profile_update( int $user_id ) { if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) ) { if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - $this->delete_password_detection_usermeta( $user_id ); + $this->delete_usermeta( $user_id ); } } } /** - * Render template for password detection page. + * Render content for password detection page. * * @param bool $reset Whether the user is resetting their password. * @param string $context The context for the password detection page. @@ -320,7 +330,7 @@ public function delete_password_detection_usermeta_on_profile_update( int $user_ * @param string $masked_email The masked email address. * @return void */ - public function render_password_detection_template( bool $reset, string $context, string $error, string $masked_email ): void { + public function render_content( bool $reset, string $context, string $error, string $masked_email ): void { defined( 'ABSPATH' ) || exit; ?> From 7634ed26a0a64f057a29a51de7a9b258bb93c4a5 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 10:06:39 -0800 Subject: [PATCH 31/61] Use dynamic classes --- .../src/class-account-protection.php | 26 +++++++++---------- .../src/class-rest-controller.php | 20 +++++++------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index f7df6a27ca5fd..69cc546a74533 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -21,10 +21,10 @@ class Account_Protection { /** * Initializes the configurations needed for the account protection module. */ - public static function init() { + public function init() { // Account protection activation/deactivation hooks - add_action( 'jetpack_activate_module_account-protection', __CLASS__ . '::on_account_protection_activation' ); - add_action( 'jetpack_deactivate_module_account-protection', __CLASS__ . '::on_account_protection_deactivation' ); + 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' ) ); @@ -33,14 +33,14 @@ public static function init() { /** * Activate the account protection on module activation. */ - public static function on_account_protection_activation() { + public function on_account_protection_activation() { // Account protection activated } /** * Deactivate the account protection on module activation. */ - public static function on_account_protection_deactivation() { + public function on_account_protection_deactivation() { // Account protection deactivated } @@ -49,8 +49,8 @@ public static function on_account_protection_deactivation() { * * @return bool */ - public static function is_enabled() { - return ( new Modules() )->is_active( 'account-protection' ); + public function is_enabled() { + return ( new Modules() )->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); } /** @@ -58,12 +58,12 @@ public static function is_enabled() { * * @return bool */ - public static function enable() { + public function enable() { // Return true if already enabled. - if ( self::is_enabled() ) { + if ( $this->is_enabled() ) { return true; } - return ( new Modules() )->activate( 'account-protection', false, false ); + return ( new Modules() )->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); } /** @@ -71,11 +71,11 @@ public static function enable() { * * @return bool */ - public static function disable() { + public function disable() { // Return true if already disabled. - if ( ! self::is_enabled() ) { + if ( ! $this->is_enabled() ) { return true; } - return ( new Modules() )->deactivate( 'account-protection' ); + return ( new 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 index 60e6f71f6cc74..998f150941873 100644 --- a/projects/packages/account-protection/src/class-rest-controller.php +++ b/projects/packages/account-protection/src/class-rest-controller.php @@ -22,9 +22,9 @@ class REST_Controller { * * @return void */ - public static function register_rest_routes() { + public function register_rest_routes() { // Ensure routes are only initialized once. - static $routes_registered = false; + $routes_registered = false; if ( $routes_registered ) { return; } @@ -34,8 +34,8 @@ public static function register_rest_routes() { '/account-protection', array( 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_settings', - 'permission_callback' => __CLASS__ . '::permissions_callback', + 'callback' => array( $this, 'get_settings' ), + 'permission_callback' => array( $this, 'permissions_callback' ), ) ); @@ -44,8 +44,8 @@ public static function register_rest_routes() { '/account-protection', array( 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_settings', - 'permission_callback' => __CLASS__ . '::permissions_callback', + 'callback' => array( $this, 'update_settings' ), + 'permission_callback' => array( $this, 'permissions_callback' ), ) ); @@ -57,7 +57,7 @@ public static function register_rest_routes() { * * @return WP_REST_Response */ - public static function get_settings() { + public function get_settings() { return rest_ensure_response( array( Account_Protection::STRICT_MODE_OPTION_NAME => get_option( Account_Protection::STRICT_MODE_OPTION_NAME ), @@ -72,13 +72,13 @@ public static function get_settings() { * * @return WP_REST_Response|WP_Error */ - public static function update_settings( $request ) { + 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 self::get_settings(); + return $this->get_settings(); } /** @@ -86,7 +86,7 @@ public static function update_settings( $request ) { * * @return bool|WP_Error True if user can view the Jetpack admin page. */ - public static function permissions_callback() { + public function permissions_callback() { if ( current_user_can( 'manage_options' ) ) { return true; } From 692db33338b4663ecc46532d6725465377fa11cb Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 10:12:46 -0800 Subject: [PATCH 32/61] Update class dependencies --- .../src/class-account-protection.php | 22 ++++++++++++++++--- .../jetpack/modules/account-protection.php | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 69cc546a74533..e08d0212867f0 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -18,6 +18,22 @@ class Account_Protection { const ACCOUNT_PROTECTION_MODULE_NAME = 'account-protection'; const STRICT_MODE_OPTION_NAME = 'jetpack_account_protection_strict_mode'; + /** + * Modules dependency. + * + * @var Modules + */ + private $modules; + + /** + * Constructor. + * + * @param Modules|null $modules Modules dependency. + */ + public function __construct( Modules $modules = null ) { + $this->modules = $modules ?? new Modules(); + } + /** * Initializes the configurations needed for the account protection module. */ @@ -50,7 +66,7 @@ public function on_account_protection_deactivation() { * @return bool */ public function is_enabled() { - return ( new Modules() )->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); + return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); } /** @@ -63,7 +79,7 @@ public function enable() { if ( $this->is_enabled() ) { return true; } - return ( new Modules() )->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); + return $this->modules->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); } /** @@ -76,6 +92,6 @@ public function disable() { if ( ! $this->is_enabled() ) { return true; } - return ( new Modules() )->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME ); + return $this->modules->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME ); } } diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index b84d338782098..554570f666289 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -15,4 +15,4 @@ use Automattic\Jetpack\Account_Protection\Account_Protection; -Account_Protection::init(); +( new Account_Protection() )->init(); From 22d267848fa4b12690cda417ab80ad5b4518c2aa Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 10:29:37 -0800 Subject: [PATCH 33/61] Fix copy --- .../jetpack/_inc/client/security/account-protection.jsx | 2 +- .../ai-assistant-plugin/components/breve/features/events.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/plugins/jetpack/_inc/client/security/account-protection.jsx b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx index 6b5e9126b7493..7334eb9e3ca7d 100644 --- a/projects/plugins/jetpack/_inc/client/security/account-protection.jsx +++ b/projects/plugins/jetpack/_inc/client/security/account-protection.jsx @@ -142,7 +142,7 @@ const AccountProtection = class extends Component { label={
- { __( 'Reqiure strong passwords', 'jetpack' ) } + { __( 'Require strong passwords', 'jetpack' ) } { ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( false ); - }, 100 ); + }, 100 ) as unknown as number; } export default function registerEvents( clientId: string ) { From 0fd3e4160b5cc1871b813aa56f706bd93509164e Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 10:30:43 -0800 Subject: [PATCH 34/61] Revert unrelated changes --- .../ai-assistant-plugin/components/breve/features/events.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index eb5eb0442d2e4..58075d8857569 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -97,7 +97,7 @@ async function handleMouseEnter( e: MouseEvent ) { target: el, virtual: virtual, } as Anchor ); - }, 500 ) as unknown as number; + }, 500 ); } function handleMouseLeave() { @@ -106,7 +106,7 @@ function handleMouseLeave() { highlightTimeout = setTimeout( () => { ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( false ); - }, 100 ) as unknown as number; + }, 100 ); } export default function registerEvents( clientId: string ) { From 4383b5e10167f0cd4b8cc76524e9eecdbeb402e0 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 10:48:26 -0800 Subject: [PATCH 35/61] Revert unrelated changes --- .../ai-assistant-plugin/components/breve/features/events.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index eb5eb0442d2e4..58075d8857569 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -97,7 +97,7 @@ async function handleMouseEnter( e: MouseEvent ) { target: el, virtual: virtual, } as Anchor ); - }, 500 ) as unknown as number; + }, 500 ); } function handleMouseLeave() { @@ -106,7 +106,7 @@ function handleMouseLeave() { highlightTimeout = setTimeout( () => { ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( false ); - }, 100 ) as unknown as number; + }, 100 ); } export default function registerEvents( clientId: string ) { From 9a70647126ee289dd64b27f796e9611408430860 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 11:10:13 -0800 Subject: [PATCH 36/61] Fix method calls --- .../account-protection/src/class-account-protection.php | 2 +- .../account-protection/src/class-rest-controller.php | 2 +- projects/plugins/protect/src/class-jetpack-protect.php | 7 ++++--- projects/plugins/protect/src/class-rest-controller.php | 9 +++++---- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index e7a33a482fecc..86241a74ac9b1 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -100,7 +100,7 @@ public function disable() { * * @return array */ - public static function get_settings() { + public function get_settings() { $settings = array( self::STRICT_MODE_OPTION_NAME => get_option( self::STRICT_MODE_OPTION_NAME, false ), ); diff --git a/projects/packages/account-protection/src/class-rest-controller.php b/projects/packages/account-protection/src/class-rest-controller.php index 9d53af5c9b80f..e5a45972d1f9e 100644 --- a/projects/packages/account-protection/src/class-rest-controller.php +++ b/projects/packages/account-protection/src/class-rest-controller.php @@ -57,7 +57,7 @@ public function register_rest_routes() { * * @return WP_REST_Response */ - public static function get_settings() { + public function get_settings() { $settings = ( new Account_Protection() )->get_settings(); return rest_ensure_response( $settings ); diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index c69b627931740..889bda69d50c9 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -138,7 +138,7 @@ public function init() { REST_Controller::init(); My_Jetpack_Initializer::init(); Site_Health::init(); - Account_Protection::init(); + ( new Account_Protection() )->init(); // Sets up JITMS. JITM::configure(); @@ -214,6 +214,7 @@ public function initial_state() { // phpcs:disable WordPress.Security.NonceVerification.Recommended $refresh_status_from_wpcom = isset( $_GET['checkPlan'] ); $status = Status::get_status( $refresh_status_from_wpcom ); + $account_protection = new Account_Protection(); $initial_state = array( 'apiRoot' => esc_url_raw( rest_url() ), @@ -233,8 +234,8 @@ public function initial_state() { 'hasPlan' => Plan::has_required_plan(), 'onboardingProgress' => Onboarding::get_current_user_progress(), 'accountProtection' => array( - 'isEnabled' => Account_Protection::is_enabled(), - 'settings' => Account_Protection::get_settings(), + 'isEnabled' => $account_protection->is_enabled(), + 'settings' => $account_protection->get_settings(), ), 'waf' => array( 'wafSupported' => Waf_Runner::is_supported_environment(), diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php index 32d85f5e8ad97..fb6dd1dba9fd4 100644 --- a/projects/plugins/protect/src/class-rest-controller.php +++ b/projects/plugins/protect/src/class-rest-controller.php @@ -371,8 +371,9 @@ public static function api_scan() { * @return WP_REST_Response|WP_Error */ public static function api_toggle_account_protection() { - if ( Account_Protection::is_enabled() ) { - $disabled = Account_Protection::disable(); + $account_protection = new Account_Protection(); + if ( $account_protection->is_enabled() ) { + $disabled = $account_protection->disable(); if ( ! $disabled ) { return new WP_Error( 'account_protection_disable_failed', @@ -384,7 +385,7 @@ public static function api_toggle_account_protection() { return rest_ensure_response( true ); } - $enabled = Account_Protection::enable(); + $enabled = $account_protection->enable(); if ( ! $enabled ) { return new WP_Error( 'account_protection_enable_failed', @@ -402,7 +403,7 @@ public static function api_toggle_account_protection() { * @return WP_Rest_Response */ public static function api_get_account_protection() { - return new WP_REST_Response( Account_Protection::is_enabled() ); + return new WP_REST_Response( ( new Account_Protection() )->is_enabled() ); } /** From 969102f5b4fa69a1c98424c1e1cf7bbdd18481cd Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 11:10:40 -0800 Subject: [PATCH 37/61] Do not activate by default --- projects/plugins/protect/src/class-jetpack-protect.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 889bda69d50c9..715a6b02cb7a1 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -286,13 +286,12 @@ public static function do_plugin_activation_activities() { } /** - * Activates the waf, brute force protection and account protection modules and disables the activation option + * Activates the waf and brute force protection modules and disables the activation option */ public static function activate_modules() { delete_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ); ( new Modules() )->activate( self::JETPACK_WAF_MODULE_SLUG, false, false ); ( new Modules() )->activate( self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, false, false ); - ( new Modules() )->activate( self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG, false, false ); } /** From 8356bd43747057556614134b54bf18034d8f2251 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 11:22:21 -0800 Subject: [PATCH 38/61] Fix phan errors --- .../src/class-account-protection.php | 22 +++---------------- .../src/class-rest-controller.php | 12 +++++++--- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index e08d0212867f0..69cc546a74533 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -18,22 +18,6 @@ class Account_Protection { const ACCOUNT_PROTECTION_MODULE_NAME = 'account-protection'; const STRICT_MODE_OPTION_NAME = 'jetpack_account_protection_strict_mode'; - /** - * Modules dependency. - * - * @var Modules - */ - private $modules; - - /** - * Constructor. - * - * @param Modules|null $modules Modules dependency. - */ - public function __construct( Modules $modules = null ) { - $this->modules = $modules ?? new Modules(); - } - /** * Initializes the configurations needed for the account protection module. */ @@ -66,7 +50,7 @@ public function on_account_protection_deactivation() { * @return bool */ public function is_enabled() { - return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); + return ( new Modules() )->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); } /** @@ -79,7 +63,7 @@ public function enable() { if ( $this->is_enabled() ) { return true; } - return $this->modules->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); + return ( new Modules() )->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); } /** @@ -92,6 +76,6 @@ public function disable() { if ( ! $this->is_enabled() ) { return true; } - return $this->modules->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME ); + return ( new 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 index 998f150941873..762fb90570c30 100644 --- a/projects/packages/account-protection/src/class-rest-controller.php +++ b/projects/packages/account-protection/src/class-rest-controller.php @@ -17,6 +17,13 @@ * Defines our endponts. */ class REST_Controller { + /** + * Tracks whether routes have already been registered. + * + * @var bool + */ + private $routes_registered = false; + /** * Register REST API endpoints. * @@ -24,8 +31,7 @@ class REST_Controller { */ public function register_rest_routes() { // Ensure routes are only initialized once. - $routes_registered = false; - if ( $routes_registered ) { + if ( $this->routes_registered ) { return; } @@ -49,7 +55,7 @@ public function register_rest_routes() { ) ); - $routes_registered = true; + $this->routes_registered = true; } /** From 32f3ef6b8a28580fbf9b285c5b8343ba98d5578e Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 13:36:55 -0800 Subject: [PATCH 39/61] Changelog --- .../add-jetpack-account-protection-security-settings | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/account-protection/changelog/add-jetpack-account-protection-security-settings 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 From b02d5113d62f05649926e02ff966722cb7dfbb01 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 13:57:12 -0800 Subject: [PATCH 40/61] Update composer deps --- projects/packages/account-protection/composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/packages/account-protection/composer.json b/projects/packages/account-protection/composer.json index b6a0271497be0..42431a12e7a10 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", From 7c255ac1b616930b01ee5e5a03f926aef603e3ea Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:02:24 -0800 Subject: [PATCH 41/61] Update lock files, add constructor method --- .../src/class-account-protection.php | 22 ++++++++++++++++--- projects/plugins/jetpack/composer.lock | 4 +++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 69cc546a74533..7d2547f9566fb 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -18,6 +18,22 @@ class Account_Protection { const ACCOUNT_PROTECTION_MODULE_NAME = 'account-protection'; const STRICT_MODE_OPTION_NAME = 'jetpack_account_protection_strict_mode'; + /** + * Modules instance. + * + * @var Modules + */ + private $modules; + + /** + * Account_Protection constructor. + * + * @param Modules $modules Modules instance. + */ + public function __construct( Modules $modules = null ) { + $this->modules = $modules ?? new Modules(); + } + /** * Initializes the configurations needed for the account protection module. */ @@ -50,7 +66,7 @@ public function on_account_protection_deactivation() { * @return bool */ public function is_enabled() { - return ( new Modules() )->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); + return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); } /** @@ -63,7 +79,7 @@ public function enable() { if ( $this->is_enabled() ) { return true; } - return ( new Modules() )->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); + return $this->modules->activate( self::ACCOUNT_PROTECTION_MODULE_NAME, false, false ); } /** @@ -76,6 +92,6 @@ public function disable() { if ( ! $this->is_enabled() ) { return true; } - return ( new Modules() )->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME ); + return $this->modules->deactivate( self::ACCOUNT_PROTECTION_MODULE_NAME ); } } diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index f9e12cf1d5ec8..5c4a01cd5a9f0 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -65,9 +65,11 @@ "dist": { "type": "path", "url": "../../packages/account-protection", - "reference": "c22829e6a80ff9f5cd10e4b4eece3d405f69e8f9" + "reference": "badc1036552f26a900a69608df22284e603981ed" }, "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-status": "@dev", "php": ">=7.2" }, "require-dev": { From cdb0ac8bea39d29a541a657c2d31bc3f68ae2d4b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:07:52 -0800 Subject: [PATCH 42/61] Fix php warning --- .../account-protection/src/class-account-protection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 7d2547f9566fb..745900f5d11a2 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -28,9 +28,9 @@ class Account_Protection { /** * Account_Protection constructor. * - * @param Modules $modules Modules instance. + * @param ?Modules $modules Modules instance. */ - public function __construct( Modules $modules = null ) { + public function __construct( ?Modules $modules = null ) { $this->modules = $modules ?? new Modules(); } From 1ce68b95e55a498c1c5ffff8b70027d854b1ceb4 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:16:59 -0800 Subject: [PATCH 43/61] Update lock file --- projects/plugins/protect/composer.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index ffb94a7ebba8d..a81f82f860992 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -65,9 +65,11 @@ "dist": { "type": "path", "url": "../../packages/account-protection", - "reference": "c22829e6a80ff9f5cd10e4b4eece3d405f69e8f9" + "reference": "badc1036552f26a900a69608df22284e603981ed" }, "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-status": "@dev", "php": ">=7.2" }, "require-dev": { From 7a065083ec0239b9f5d4266b1b62368c72c78830 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:19:20 -0800 Subject: [PATCH 44/61] Changelog --- .../changelog/add-protect-account-protection-settings | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/account-protection/changelog/add-protect-account-protection-settings diff --git a/projects/packages/account-protection/changelog/add-protect-account-protection-settings b/projects/packages/account-protection/changelog/add-protect-account-protection-settings new file mode 100644 index 0000000000000..fc22c90153950 --- /dev/null +++ b/projects/packages/account-protection/changelog/add-protect-account-protection-settings @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Moves get_settings method to primary class From c128cf5c51dca3f39c87060228fadb9f50dab9f2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:38:24 -0800 Subject: [PATCH 45/61] Fix Password_Detection constructor --- .../account-protection/src/class-password-detection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 29c100405cb85..c665ed9b387fe 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -26,9 +26,9 @@ class Password_Detection { /** * Password_Detection constructor. * - * @param Password_Reset_Email $password_reset_email Password reset email instance. + * @param ?Password_Reset_Email $password_reset_email Password reset email instance. */ - public function __construct( Password_Reset_Email $password_reset_email = null ) { + public function __construct( ?Password_Reset_Email $password_reset_email = null ) { $this->password_reset_email = $password_reset_email ?? new Password_Reset_Email(); } @@ -138,7 +138,7 @@ public function render_page(): void { } } - $this->render_content( $reset, $context, $error, $this->password_reset_email->mask_email_address( $current_user->user_email ) ); + $this->render_content( $reset, $context, $error, $this->password_reset_email->mask_email_address( $current_user->user_email ) ); exit; } From 7a56b48003b56b569e343e083ea5d3ba655f254d Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:40:12 -0800 Subject: [PATCH 46/61] Changelog --- .../add-packages-account-protection-password-detection-flow | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/account-protection/changelog/add-packages-account-protection-password-detection-flow diff --git a/projects/packages/account-protection/changelog/add-packages-account-protection-password-detection-flow b/projects/packages/account-protection/changelog/add-packages-account-protection-password-detection-flow new file mode 100644 index 0000000000000..dde7e7363b212 --- /dev/null +++ b/projects/packages/account-protection/changelog/add-packages-account-protection-password-detection-flow @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds the password detection flow From bc7aa77287e04964c2b11dff1334082c430ed8b1 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:44:21 -0800 Subject: [PATCH 47/61] More changelogs --- .../add-packages-account-protection-password-detection-flow | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/protect/changelog/add-packages-account-protection-password-detection-flow diff --git a/projects/plugins/protect/changelog/add-packages-account-protection-password-detection-flow b/projects/plugins/protect/changelog/add-packages-account-protection-password-detection-flow new file mode 100644 index 0000000000000..06faac5eedf17 --- /dev/null +++ b/projects/plugins/protect/changelog/add-packages-account-protection-password-detection-flow @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Fixes Account Protection endpoint callback From b28c8cf0ca5f2d02ee4f70854f1153998bb67d68 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 14:45:24 -0800 Subject: [PATCH 48/61] Remove comments --- .../src/class-password-reset-email.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/projects/packages/account-protection/src/class-password-reset-email.php b/projects/packages/account-protection/src/class-password-reset-email.php index 5d2358317669a..46331e3f5cc2f 100644 --- a/projects/packages/account-protection/src/class-password-reset-email.php +++ b/projects/packages/account-protection/src/class-password-reset-email.php @@ -40,17 +40,7 @@ public function mask_email_address( string $email ): string { * @return bool True if the email was sent successfully, false otherwise. */ public function send( \WP_User $user ): bool { - // $site_url = home_url(); - // $parsed_url = wp_parse_url( $site_url ); - // $domain_name = $parsed_url['host']; - // $username = $user->user_login; - // $email = $user->user_email; - - // $key = get_password_reset_key( $user ); - // $locale = get_user_locale( $user ); - // $password_reset_link = network_site_url( 'wp-login.php?login=' . rawurlencode( $username ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale; - - // TODO: Update to use custom email method when available, passing $domain_name, $email, $username, and $password_reset_link + // TODO: Update to use custom email method when available return $user ? true : false; } } From 4bb5401d7208a5bbf3eb52c0f80387060e1bd10d Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:10:39 -0800 Subject: [PATCH 49/61] Fix static analysis errors --- .../src/class-password-detection.php | 17 +++++++---------- .../src/class-password-reset-email.php | 5 ++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index c665ed9b387fe..1506940e8cddd 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -44,11 +44,11 @@ public function password_detection_redirect(): string { /** * Check if the password is safe after login. * - * @param \WP_User $user The user object. - * @param string $password The password. + * @param \WP_User|\WP_Error $user The user or error object. + * @param string $password The password. * @return \WP_User|\WP_Error The user object. */ - public function login_form_password_detection( \WP_User $user, string $password ): \WP_User { + public function login_form_password_detection( $user, string $password ) { // Check if the user is already a WP_Error object if ( is_wp_error( $user ) ) { return $user; @@ -75,9 +75,9 @@ public function login_form_password_detection( \WP_User $user, string $password /** * Render password detection page. * - * @return void + * @return never */ - public function render_page(): void { + public function render_page(): never { // Restrict direct access to logged in users $current_user = wp_get_current_user(); if ( 0 === $current_user->ID ) { @@ -111,7 +111,7 @@ public function render_page(): void { if ( isset( $_POST['_wpnonce_reset_password'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_reset_password'] ) ), 'reset_password_action' ) ) { // Send password reset email if ( ! $email_sent_flag ) { - $email_sent = $this->password_reset_email->send( $current_user ); + $email_sent = $this->password_reset_email->send(); if ( $email_sent ) { // Set transient to mark the email as sent set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); @@ -187,11 +187,8 @@ public function ajax_resend_password_reset_email() { wp_send_json_error( array( 'message' => 'User not authenticated' ) ); } - $current_user = wp_get_current_user(); - $email = $current_user->user_email; - // Resend the email - $email_sent = $this->password_reset_email->send( $current_user, $email ); + $email_sent = $this->password_reset_email->send(); if ( $email_sent ) { wp_send_json_success( array( 'message' => 'Resend successful.' ) ); } else { diff --git a/projects/packages/account-protection/src/class-password-reset-email.php b/projects/packages/account-protection/src/class-password-reset-email.php index 46331e3f5cc2f..7afde4aab79ae 100644 --- a/projects/packages/account-protection/src/class-password-reset-email.php +++ b/projects/packages/account-protection/src/class-password-reset-email.php @@ -36,11 +36,10 @@ public function mask_email_address( string $email ): string { /** * Send password reset email. * - * @param \WP_User $user The user object. * @return bool True if the email was sent successfully, false otherwise. */ - public function send( \WP_User $user ): bool { + public function send(): bool { // TODO: Update to use custom email method when available - return $user ? true : false; + return true; } } From 20dec014e6bac8f0960b540b882f9c214efc7284 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:14:20 -0800 Subject: [PATCH 50/61] Remove top level phpunit.xml.dist --- .../packages/account-protection/phpunit.xml.dist | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 projects/packages/account-protection/phpunit.xml.dist diff --git a/projects/packages/account-protection/phpunit.xml.dist b/projects/packages/account-protection/phpunit.xml.dist deleted file mode 100644 index 3223c32458db2..0000000000000 --- a/projects/packages/account-protection/phpunit.xml.dist +++ /dev/null @@ -1,14 +0,0 @@ - - - - tests/php - - - - - - - src - - - From 2bdbf8ebb96d19491d002b785ea37964a2c8509b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:17:41 -0800 Subject: [PATCH 51/61] Remove never return type --- .../account-protection/src/class-password-detection.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index 1506940e8cddd..c72cde6b45213 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -74,10 +74,8 @@ public function login_form_password_detection( $user, string $password ) { /** * Render password detection page. - * - * @return never */ - public function render_page(): never { + public function render_page() { // Restrict direct access to logged in users $current_user = wp_get_current_user(); if ( 0 === $current_user->ID ) { From a80c024442d3620045afb7e33df70901886b9a6d Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:39:40 -0800 Subject: [PATCH 52/61] Revert tests dir changes in favour of a dedicated task --- .../tests/action-test-coverage.sh | 9 -------- .../tests/php/integration/bootstrap.php | 16 -------------- .../tests/php/integration/phpunit.xml.dist | 21 ------------------- .../tests/php/unit/bootstrap.php | 11 ---------- .../tests/php/unit/phpunit.xml.dist | 21 ------------------- 5 files changed, 78 deletions(-) delete mode 100755 projects/packages/account-protection/tests/action-test-coverage.sh delete mode 100644 projects/packages/account-protection/tests/php/integration/bootstrap.php delete mode 100644 projects/packages/account-protection/tests/php/integration/phpunit.xml.dist delete mode 100644 projects/packages/account-protection/tests/php/unit/bootstrap.php delete mode 100644 projects/packages/account-protection/tests/php/unit/phpunit.xml.dist diff --git a/projects/packages/account-protection/tests/action-test-coverage.sh b/projects/packages/account-protection/tests/action-test-coverage.sh deleted file mode 100755 index 8a7a1e9de6565..0000000000000 --- a/projects/packages/account-protection/tests/action-test-coverage.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -veo pipefail - -EXIT=0 -php -dpcov.directory=. ./vendor/bin/phpunit --coverage-php "$COVERAGE_DIR/integration/php.cov" --configuration tests/php/integration/phpunit.xml.dist || EXIT=1 -php -dpcov.directory=. ./vendor/bin/phpunit --coverage-php "$COVERAGE_DIR/unit/php.cov" --configuration tests/php/unit/phpunit.xml.dist || EXIT=1 - -exit $EXIT diff --git a/projects/packages/account-protection/tests/php/integration/bootstrap.php b/projects/packages/account-protection/tests/php/integration/bootstrap.php deleted file mode 100644 index 4c1205f352a1d..0000000000000 --- a/projects/packages/account-protection/tests/php/integration/bootstrap.php +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - ../../../src - - - - - . - - - diff --git a/projects/packages/account-protection/tests/php/unit/bootstrap.php b/projects/packages/account-protection/tests/php/unit/bootstrap.php deleted file mode 100644 index e16bad0ecf0bf..0000000000000 --- a/projects/packages/account-protection/tests/php/unit/bootstrap.php +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - ../../../src - - - - - . - - - From f07e52ec53734c45fb5593cef7ae086d3fa05cba Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:41:22 -0800 Subject: [PATCH 53/61] Add tests dir --- .../packages/account-protection/phpunit.xml.dist | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 projects/packages/account-protection/phpunit.xml.dist diff --git a/projects/packages/account-protection/phpunit.xml.dist b/projects/packages/account-protection/phpunit.xml.dist new file mode 100644 index 0000000000000..3223c32458db2 --- /dev/null +++ b/projects/packages/account-protection/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + tests/php + + + + + + + src + + + From 80d0e927f94140b38069fd1deebdaf01003d9da7 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:44:10 -0800 Subject: [PATCH 54/61] Reapply default test infrastructure --- .../account-protection/tests/php/bootstrap.php | 11 +++++++++++ .../packages/account-protection/tests/php/php.dir.xml | 4 ++++ 2 files changed, 15 insertions(+) create mode 100644 projects/packages/account-protection/tests/php/bootstrap.php create mode 100644 projects/packages/account-protection/tests/php/php.dir.xml diff --git a/projects/packages/account-protection/tests/php/bootstrap.php b/projects/packages/account-protection/tests/php/bootstrap.php new file mode 100644 index 0000000000000..46763b04a2cdb --- /dev/null +++ b/projects/packages/account-protection/tests/php/bootstrap.php @@ -0,0 +1,11 @@ + + + + From c03d626898489fad9ae02a0e177ed4eed41b6633 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:45:28 -0800 Subject: [PATCH 55/61] Reorg and rename --- .../account-protection/tests/{php/php.dir.xml => .phpcs.dir.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename projects/packages/account-protection/tests/{php/php.dir.xml => .phpcs.dir.xml} (100%) diff --git a/projects/packages/account-protection/tests/php/php.dir.xml b/projects/packages/account-protection/tests/.phpcs.dir.xml similarity index 100% rename from projects/packages/account-protection/tests/php/php.dir.xml rename to projects/packages/account-protection/tests/.phpcs.dir.xml From ae3b6b6722ab9915b6e1d8cdc02666d5cc9276f6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 15:46:42 -0800 Subject: [PATCH 56/61] Update @package --- projects/packages/account-protection/tests/php/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/packages/account-protection/tests/php/bootstrap.php b/projects/packages/account-protection/tests/php/bootstrap.php index 46763b04a2cdb..c53f9cb5415c3 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 */ /** From 30f23292f63df71726b521fb885bf0c75773ce56 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 20 Jan 2025 16:02:37 -0800 Subject: [PATCH 57/61] Use never phpdoc return type as per static analysis error --- .../account-protection/src/class-password-detection.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index c72cde6b45213..ca195ed1207e6 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -74,6 +74,8 @@ public function login_form_password_detection( $user, string $password ) { /** * Render password detection page. + * + * @return never */ public function render_page() { // Restrict direct access to logged in users From aad7ff6c09bf85a041e1d059b20ade9aa97e2b8e Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 21 Jan 2025 12:52:21 -0800 Subject: [PATCH 58/61] Enable module by default --- projects/plugins/jetpack/modules/account-protection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/modules/account-protection.php b/projects/plugins/jetpack/modules/account-protection.php index 554570f666289..c552efec4cc41 100644 --- a/projects/plugins/jetpack/modules/account-protection.php +++ b/projects/plugins/jetpack/modules/account-protection.php @@ -6,7 +6,7 @@ * First Introduced: 14.3 * Requires Connection: Yes * Requires User Connection: No - * Auto Activate: No + * Auto Activate: Yes * Module Tags: Account Protection * Feature: Security * From 448079b41dc8fa17a95dcfc51c2e4f9f4c5901bf Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 21 Jan 2025 12:53:39 -0800 Subject: [PATCH 59/61] Enable module by default --- projects/plugins/protect/src/class-jetpack-protect.php | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 715a6b02cb7a1..ea02244a44e5a 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -292,6 +292,7 @@ public static function activate_modules() { delete_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ); ( new Modules() )->activate( self::JETPACK_WAF_MODULE_SLUG, false, false ); ( new Modules() )->activate( self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, false, false ); + ( new Modules() )->activate( self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG, false, false ); } /** From b07ecd188557bec697ed426bfb53f749871ea29c Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Fri, 24 Jan 2025 10:56:24 -0800 Subject: [PATCH 60/61] Update projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts Co-authored-by: Kolja Zuelsdorf --- .../data/account-protection/use-account-protection-mutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts index abd7749a797cd..592c5b983c37a 100644 --- a/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts +++ b/projects/plugins/protect/src/js/data/account-protection/use-account-protection-mutation.ts @@ -48,7 +48,7 @@ export default function useAccountProtectionMutation(): UseMutationResult< showSuccessNotice( __( 'Changes saved.', 'jetpack-protect' ) ); }, onError: ( error, variables, context ) => { - // Reset the WAF config to its previous state. + // Reset the account protection config to its previous state. queryClient.setQueryData( [ QUERY_ACCOUNT_PROTECTION_KEY ], context.initialValue ); showErrorNotice( __( 'Error saving changes.', 'jetpack-protect' ) ); From 5edfacaef36b84a4a29df9918025ba570e397c47 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 24 Jan 2025 11:02:01 -0800 Subject: [PATCH 61/61] Update lock files --- projects/plugins/protect/composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index cbf786cc82f0e..f612f3daf0389 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "34bd79ff42a1f8b65263255346bb624f", + "content-hash": "bd73d22e7c4f74ba29b6f34356434a71", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats",