From 3449c80f8cd2d41ba149cfc7276c0ff54e156616 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 8 Apr 2024 18:50:05 +1200 Subject: [PATCH] DOC Linting --- .doclintrc | 1 + README.md | 54 +----------------- composer.json | 6 ++ docs/en/{debugging.md => 01_debugging.md} | 7 ++- .../01_creating-mfa-method-frontend.md} | 4 +- .../02_creating-mfa-method-backend.md} | 5 +- docs/en/02_creating-new-mfa-methods/index.md | 7 +++ ...development.md => 03_local-development.md} | 6 +- docs/en/{encryption.md => 04_encryption.md} | 9 +-- docs/en/{datastores.md => 05_datastores.md} | 16 ++++-- docs/en/{security.md => 06_security.md} | 12 ++-- ...ticators.md => 07_other-authenticators.md} | 56 ++++++++++++------- .../{backup-codes.md => 08_backup-codes.md} | 23 ++++++-- ...a.md => 09_broadening-the-scope-of-mfa.md} | 10 ++-- docs/en/index.md | 48 ++++++++++++++++ 15 files changed, 156 insertions(+), 108 deletions(-) create mode 100644 .doclintrc rename docs/en/{debugging.md => 01_debugging.md} (86%) rename docs/en/{creating-mfa-method-frontend.md => 02_creating-new-mfa-methods/01_creating-mfa-method-frontend.md} (97%) rename docs/en/{creating-mfa-method-backend.md => 02_creating-new-mfa-methods/02_creating-mfa-method-backend.md} (81%) create mode 100644 docs/en/02_creating-new-mfa-methods/index.md rename docs/en/{local-development.md => 03_local-development.md} (86%) rename docs/en/{encryption.md => 04_encryption.md} (61%) rename docs/en/{datastores.md => 05_datastores.md} (65%) rename docs/en/{security.md => 06_security.md} (50%) rename docs/en/{other-authenticators.md => 07_other-authenticators.md} (67%) rename docs/en/{backup-codes.md => 08_backup-codes.md} (75%) rename docs/en/{broadening-the-scope-of-mfa.md => 09_broadening-the-scope-of-mfa.md} (80%) create mode 100644 docs/en/index.md diff --git a/.doclintrc b/.doclintrc new file mode 100644 index 00000000..a6b65270 --- /dev/null +++ b/.doclintrc @@ -0,0 +1 @@ +docs/en/ diff --git a/README.md b/README.md index 8add4818..c81f4fc3 100644 --- a/README.md +++ b/README.md @@ -3,65 +3,15 @@ [![CI](https://github.com/silverstripe/silverstripe-mfa/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-mfa/actions/workflows/ci.yml) [![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/) -### With thanks to Simon `Firesphere` Erkelens +## With thanks to Simon `Firesphere` Erkelens This module was based on pioneering work by Simon. It differs from the original implementation in its use of a pluggable React UI + JSON API architecture, and its enhanced management UI within the CMS. You can find Simon's original module [here](https://github.com/firesphere/silverstripe-bootstrapmfa). -## Installation - -```sh -composer require silverstripe/mfa -``` - -You should also install one of the additional multi-factor authenticator modules: - -* [silverstripe/totp-authenticator](https://github.com/silverstripe/silverstripe-totp-authenticator) -* [silverstripe/webauthn-authenticator](https://github.com/silverstripe/silverstripe-webauthn-authenticator) - -## Setup - -After installing this module _and_ a supported factor method module (e.g. TOTP), the default member authenticator -will be replaced with the MFA authenticator instead. This will provide no change in the steps taken to log in until -an MFA Method has also been configured for the site. The TOTP and WebAuthn modules will configure themselves -automatically. - -After installing the MFA module and having at least one method configured, MFA will automatically be enabled. By default -it will be optional (users can skip MFA registration). You can make it mandatory via the Settings tab in the admin area. - -The MFA flow will only be applied to members with access to the CMS or administration area. See '[Broadening the scope of MFA](docs/en/broadening-the-scope-of-mfa.md)' for more detail. - -You can disable MFA on an environment by setting a `BYPASS_MFA=1` environment variable, -or via YAML config - see [local development](docs/en/local-development) for details. - -### Configuring custom methods - -If you have built your own MFA method, you can register it with the `MethodRegistry` to enable it: - -```yaml -SilverStripe\MFA\Service\MethodRegistry: - methods: - - MyCustomMethod - - Another\Custom\Method\Here -``` - ## Documentation -This module provides two distinct processes for MFA; verification and registration. This module provides a decoupled -architecture where front-end and back-end are separate. Provided with the module is a React app that interfaces with -default endpoints added by this module. Please refer to the docs for specific information about the included -functionality: - -- [Debugging](docs/en/debugging.md) -- Creating new MFA methods - - [Frontend](docs/en/creating-mfa-method-frontend.md) - - [Backend](docs/en/creating-mfa-method-backend.md) -- [Local development](docs/en/local-development.md) -- [Encryption providers](docs/en/encryption.md) -- [Data store interfaces](docs/en/datastores.md) -- [Security](docs/en/security.md) -- [Integrating with other authenticators](docs/en/other-authenticators.md) +Read the [documentation](docs/en/index.md). ## Module development diff --git a/composer.json b/composer.json index f8c503cf..42aa7d2d 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "require-dev": { "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "^3", + "silverstripe/documentation-lint": "^1", "silverstripe/standards": "^1", "phpstan/extension-installer": "^1.3" }, @@ -52,6 +53,11 @@ "client/lang" ] }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, "autoload": { "psr-4": { "SilverStripe\\MFA\\": "src/", diff --git a/docs/en/debugging.md b/docs/en/01_debugging.md similarity index 86% rename from docs/en/debugging.md rename to docs/en/01_debugging.md index 1ced038a..211e217c 100644 --- a/docs/en/debugging.md +++ b/docs/en/01_debugging.md @@ -2,10 +2,10 @@ The MFA module ships with a PSR-3 logger configured by default (a [Monolog](https://github.com/Seldaek/monolog/) implementation), however no Monolog handlers are attached by default. To enable developer logging, you can -[attach a handler](https://docs.silverstripe.org/en/4/developer_guides/debugging/error_handling/#configuring-error-logging). +[attach a handler](https://docs.silverstripe.org/en/developer_guides/debugging/error_handling/#configuring-error-logging). An example that will log to a `mfa.log` file in the project root: -```yaml +```yml SilverStripe\Core\Injector\Injector: Psr\Log\LoggerInterface.mfa: calls: @@ -20,6 +20,9 @@ SilverStripe\Core\Injector\Injector: You can inject this logger into any MFA authenticator module, or custom app code, by using dependency injection: ```php +// app/src/MFA/Handlers/MyCustomLoginHandler.php +namespace App\MFA\Handlers; + class MyCustomLoginHandler implements LoginHandlerInterface { private static $dependencies = [ diff --git a/docs/en/creating-mfa-method-frontend.md b/docs/en/02_creating-new-mfa-methods/01_creating-mfa-method-frontend.md similarity index 97% rename from docs/en/creating-mfa-method-frontend.md rename to docs/en/02_creating-new-mfa-methods/01_creating-mfa-method-frontend.md index 4dcf6431..4676a635 100644 --- a/docs/en/creating-mfa-method-frontend.md +++ b/docs/en/02_creating-new-mfa-methods/01_creating-mfa-method-frontend.md @@ -8,7 +8,7 @@ with React / Redux is recommended. The front-end components of MFA make use of [`react-injector`](https://github.com/silverstripe/react-injector/) (Injector) to allow sharing of React components and Redux reducers between separate JS bundles. You can find more -documentation on the Injector API in the [Silverstripe docs](https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/reactjs_redux_and_graphql/#the-injector-api). +documentation on the Injector API in the [Silverstripe docs](https://docs.silverstripe.org/en/developer_guides/customising_the_admin_interface/reactjs_redux_and_graphql/#the-injector-api). You'll find it easiest to get up and running by matching the NPM dependencies and Webpack configuration used in the TOTP and WebAuthn modules, with a single entry point that handles registering your components with Injector. We also suggest @@ -182,7 +182,7 @@ You can then specify the component names via `VerifyHandlerInterface::getCompone ## Method availability If your method needs to rely on frontend environment state to determine whether it's available (such as the browser -being used), you can [define a Redux reducer](https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/reactjs_redux_and_graphql/#using-injector-to-customise-redux-state-data) +being used), you can [define a Redux reducer](https://docs.silverstripe.org/en/developer_guides/customising_the_admin_interface/reactjs_redux_and_graphql/#using-injector-to-customise-redux-state-data) that will initialise some "availability" information in the Redux store, which the MFA module will look for when it determines whether a method is available to be used or not. For example: diff --git a/docs/en/creating-mfa-method-backend.md b/docs/en/02_creating-new-mfa-methods/02_creating-mfa-method-backend.md similarity index 81% rename from docs/en/creating-mfa-method-backend.md rename to docs/en/02_creating-new-mfa-methods/02_creating-mfa-method-backend.md index 1b7baf03..e7d106a7 100644 --- a/docs/en/creating-mfa-method-backend.md +++ b/docs/en/02_creating-new-mfa-methods/02_creating-mfa-method-backend.md @@ -3,9 +3,12 @@ ## Method availability If your method isn't available in some situations, and you can determine this via server-side state, you can provide -this information to the frontend via `MethodInterface::isAvailable()`, for example: +this information to the frontend via [`MethodInterface::isAvailable()`](api:SilverStripe\MFA\Method\MethodInterface::isAvailable()), for example: ```php +// app/src/MFA/Methods/MyMethod.php +namespace App\MFA\Methods; + class MyMethod implements MethodInterface { public function isAvailable(): bool diff --git a/docs/en/02_creating-new-mfa-methods/index.md b/docs/en/02_creating-new-mfa-methods/index.md new file mode 100644 index 00000000..4314854f --- /dev/null +++ b/docs/en/02_creating-new-mfa-methods/index.md @@ -0,0 +1,7 @@ +--- +title: Creating new MFA methods +--- + +# Creating new MFA methods + +[CHILDREN includeFolders] diff --git a/docs/en/local-development.md b/docs/en/03_local-development.md similarity index 86% rename from docs/en/local-development.md rename to docs/en/03_local-development.md index 1f5c798a..4572b2a1 100644 --- a/docs/en/local-development.md +++ b/docs/en/03_local-development.md @@ -3,15 +3,15 @@ When running development versions of a project using this module, you may want to disable multi-factor authentication while you test other features. This will not redirect you to multi-factor authentication registration or verification screens when logging in. -The easiest way is to set an [environment variable](https://docs.silverstripe.org/en/4/developer_guides/configuration/environment_variables/): +The easiest way is to set an [environment variable](https://docs.silverstripe.org/en/developer_guides/configuration/environment_variables/): -``` +```env BYPASS_MFA=1 ``` Alternatively, YAML configuration affords you more control over the conditions: -```yaml +```yml --- Name: mydevconfig Only: diff --git a/docs/en/encryption.md b/docs/en/04_encryption.md similarity index 61% rename from docs/en/encryption.md rename to docs/en/04_encryption.md index a6137d31..09b9d89f 100644 --- a/docs/en/encryption.md +++ b/docs/en/04_encryption.md @@ -4,14 +4,15 @@ By default this module uses [defuse/php-encryption](https://github.com/defuse/ph for secret information that must be persisted to a data store, such as a TOTP secret. You can add your own implementation if you would like to use something different, by implementing -`EncryptionAdapterInterface` and configuring your service class with Injector. The interface is deliberately simple, +[`EncryptionAdapterInterface`](api:SilverStripe\MFA\Service\EncryptionAdapterInterface) and configuring your service class with Injector. The interface is deliberately simple, and takes `encrypt()` and `decrypt()` methods with a payload and an encryption key argument. -```yaml +```yml SilverStripe\Core\Injector\Injector: SilverStripe\MFA\Service\EncryptionAdapterInterface: class: App\MFA\ReallyStrongEncryptionAdapter ``` -**Please note:** this is different from the `PasswordEncryptor` API provided by silverstripe/framework -because we need two-way encryption (as opposed to one-way hashing) for MFA. +> [!Note] +> This is different from the `PasswordEncryptor` API provided by silverstripe/framework +> because we need two-way encryption (as opposed to one-way hashing) for MFA. diff --git a/docs/en/datastores.md b/docs/en/05_datastores.md similarity index 65% rename from docs/en/datastores.md rename to docs/en/05_datastores.md index a3ed7761..b16802e1 100644 --- a/docs/en/datastores.md +++ b/docs/en/05_datastores.md @@ -1,20 +1,21 @@ # Data store interfaces -Since the MFA architecture is largely designed to be decoupled, we use a `StoreInterface` implementation to retain -data between requests. The default implementation for this interface is `SessionStore` which stores data using the -Silverstripe CMS `Session` API provided by silverstripe/framework. +Since the MFA architecture is largely designed to be decoupled, we use a [`StoreInterface`](api:SilverStripe\MFA\Store\StoreInterface) implementation to retain +data between requests. The default implementation for this interface is [`SessionStore`](api:SilverStripe\MFA\Store\SessionStore) which stores data using the +Silverstripe CMS [`Session`](api:SilverStripe\Control\Session) API provided by silverstripe/framework. If you need to use a different storage mechanism (e.g. Redis, DynamoDB etc) you can implement and configure your own `StoreInterface`, and register it with Injector: -```yaml +```yml SilverStripe\Core\Injector\Injector: SilverStripe\MFA\Store\StoreInterface: class: App\MFA\RedisStoreInterface ``` -Please note that the store should always be treated as a server side implementation. It's not a good idea to implement -a client store e.g. cookies. +> [!Note] +> The store should always be treated as a server side implementation. It's not a good idea to implement +> a client store e.g. cookies. ## Adjusting what goes into the store @@ -23,6 +24,9 @@ exclude the `Password` field from the request by default, but if you need to exc extension, for example: ```php +// app/src/MFA/Extensions/MyLoginHandlerExtension.php +namespace App\MFA\Extensions; + // Apply extension to \SilverStripe\MFA\Authenticator\LoginHandler class MyLoginHandlerExtension extends Extension { diff --git a/docs/en/security.md b/docs/en/06_security.md similarity index 50% rename from docs/en/security.md rename to docs/en/06_security.md index a646a2ef..a6259df4 100644 --- a/docs/en/security.md +++ b/docs/en/06_security.md @@ -2,19 +2,19 @@ ## Login attempts -The MFA module makes use of the framework's `LoginAttempt` API to ensure that a user can only attempt to register +The MFA module makes use of the framework's [`LoginAttempt`](api:SilverStripe\Security\LoginAttempt) API to ensure that a user can only attempt to register or verify a MFA method a certain number of times. Since it re-uses the core API, it also shares the maximum number of attempts with login attempts themselves. -For example: if the maximum number of login attempts (`Member.lock_out_after_incorrect_logins`) is 5, and a user +For example: if the maximum number of login attempts ([`Member.lock_out_after_incorrect_logins`](api:SilverStripe\Security\Member->lock_out_after_incorrect_logins)) is 5, and a user incorrectly enters their password twice, correctly enters it once, then incorrectly enters a TOTP code three times, -they will be registered as locked out for a specified period of time (`Member.lock_out_delay_mins`). In this case, +they will be registered as locked out for a specified period of time ([`Member.lock_out_delay_mins`](api:SilverStripe\Security\Member->lock_out_delay_mins)). In this case, the user will be shown a message when trying to verify their TOTP code similar to "Your account is temporarily locked. Please try again later." -For more information on this, see [Secure Coding](https://docs.silverstripe.org/en/4/developer_guides/security/secure_coding/#other-options). +For more information on this, see [Secure Coding](https://docs.silverstripe.org/en/developer_guides/security/secure_coding/#other-options). ## Related links -* [MFA encryption providers](encryption.md) -* [silverstripe/security-extensions documentation](https://github.com/silverstripe/silverstripe-security-extensions) +- [MFA encryption providers](encryption.md) +- [silverstripe/security-extensions documentation](https://github.com/silverstripe/silverstripe-security-extensions) diff --git a/docs/en/other-authenticators.md b/docs/en/07_other-authenticators.md similarity index 67% rename from docs/en/other-authenticators.md rename to docs/en/07_other-authenticators.md index 130faa55..4c7effd6 100644 --- a/docs/en/other-authenticators.md +++ b/docs/en/07_other-authenticators.md @@ -1,12 +1,10 @@ # Integrating with other authenticators -**This version relates to Silverstripe CMS 4.x configuration.** - -If your project uses a non-standard authentication module, such as silverstripe/ldap, you will +If your project uses a non-standard authentication module, such as [silverstripe/ldap](https://github.com/silverstripe/silverstripe-ldap), you will need to implement some customisations to connect the modules together. The following notes should serve as a guide for parts of the code to be aware of, and things to do in order to achieve this. -For the purposes of comparisons in this document, we will use [silverstripe/ldap](https://github.com/silverstripe/silverstripe-ldap)'s authenticator. +For the purposes of comparisons in this document, we will use `silverstripe/ldap`'s authenticator. ## Concepts @@ -18,28 +16,27 @@ down for a hypothetical example. ### Authenticator -The Authenticator entrypoint class in the MFA module is `SilverStripe\MFA\Authenticator\MemberAuthenticator`. This -class extends the default `SilverStripe\Security\MemberAuthenticator` class in order to override the default login -form with `SilverStripe\MFA\Authenticator\LoginForm`, and the change password handler with -`SilverStripe\MFA\Authenticator\ChangePasswordHandler`. +The Authenticator entrypoint class in the MFA module is [`SilverStripe\MFA\Authenticator\MemberAuthenticator`](api:SilverStripe\MFA\Authenticator\MemberAuthenticator). This +class extends the default [`SilverStripe\Security\MemberAuthenticator`](api:SilverStripe\Security\MemberAuthenticator) class in order to override the default login +form with [`LoginForm`](api:SilverStripe\MFA\Authenticator\LoginForm), and the change password handler with [`ChangePasswordHandler`](api:SilverStripe\MFA\Authenticator\ChangePasswordHandler). -silverstripe/ldap does the same thing - it also configures itself to override the `default` authenticator. Since the +`silverstripe/ldap` does the same thing - it also configures itself to override the `default` authenticator. Since the MFA replacement for the default authenticator has MFA logic added to it, and LDAP has the same with LDAP logic added, you will need to reimplement it so that both MFA and LDAP apply their logic together. In order to combine these two authenticators, you may choose to add your own `LDAPMFAAuthenticator` class and configure that instead of either MFA or LDAP's authenticators. See further down for a hypothetical example. -### LoginHandler +### `LoginHandler` -The MFA `LoginHandler` class is the point where MFA flows are injected into core. In silverstripe/ldap, this +The MFA [`LoginHandler`](api:SilverStripe\MFA\Authenticator\LoginHandler) class is the point where MFA flows are injected into core. In silverstripe/ldap, this class performs the same function: to inject LDAP authentication logic into core. As above, in order to have both work together you may choose to add your own `LDAPMFALoginHandler` class and configure that in your custom Authenticator. This class would need to combine the logic from both `SilverStripe\LDAP\Forms\LDAPLoginHandler` and `SilverStripe\MFA\Authenticator\LoginHandler` in order to function correctly for both cases. -### ChangePasswordHandler +### `ChangePasswordHandler` Both the LDAP and MFA modules provide their own implementations of the `ChangePasswordHandler`, and in both cases these are referenced from the `MemberAuthenticator` subclass of each module. Similarly to the `LoginForm` example @@ -49,7 +46,7 @@ Similarly to `LoginForm` above, in order to reduce duplication of code we recomm `\SilverStripe\MFA\Authenticator\LoginHandler` and duplicating the contents of `SilverStripe\LDAP\Authenticators\LDAPChangePasswordHandler` which is substantially smaller. -### LoginForm and ChangePasswordForm +### `LoginForm` and `ChangePasswordForm` The LDAP module overrides a couple of the default Form implementations: `LDAPLoginForm` and `LDAPChangePasswordForm` form. The way that these classes are written likely indicates that there will not be any conflicts here with the @@ -70,19 +67,26 @@ configuration to register the method and set it as the default authenticator to the LDAP authenticator](https://github.com/silverstripe/silverstripe-ldap/blob/master/docs/en/developer.md#show-the-ldap-login-button-on-login-form) you will want to remove this now - MFA configures itself automatically. -### A custom MemberAuthenticator +### A custom `MemberAuthenticator` ```php -class LDAPMFAMemberAuthenticator extends \SilverStripe\LDAP\Authenticators\LDAPAuthenticator +// app/src/MFA/Authenticators/LDAPMFAMemberAuthenticator.php +namespace App\MFA\Authenticators; + +use SilverStripe\LDAP\Authenticators\LDAPAuthenticator; +use SilverStripe\MFA\Authenticator\ChangePasswordHandler; +use SilverStripe\MFA\Authenticator\LoginHandler + +class LDAPMFAMemberAuthenticator extends LDAPAuthenticator { public function getLoginHandler($link) { - return \SilverStripe\MFA\Authenticator\LoginHandler::create($link, $this); + return LoginHandler::create($link, $this); } public function getChangePasswordHandler($link) { - return \SilverStripe\MFA\Authenticator\ChangePasswordHandler::create($link, $this); + return ChangePasswordHandler::create($link, $this); } } ``` @@ -91,29 +95,39 @@ In this example, we have copied the small amount of logic from the MFA module in class from the core `MemberAuthenticator` to `LDAPAuthenticator`, and will change the injection class name with the configuration above so it is used instead of MFA or LDAP. -### A custom LoginHandler +### A custom `LoginHandler` In this example, the logic from silverstripe/ldap is much smaller, so is preferable to duplicate while extending the MFA `LoginHandler` which contains much more logic. ```php +// app/src/MFA/Handlers/LDAPMFALoginHandler.php +namespace App\MFAHandlers; + +use SilverStripe\LDAP\Forms\LDAPLoginForm; + class LDAPMFALoginHandler extends \SilverStripe\MFA\Authenticator\LoginHandler { private static $allowed_actions = ['LoginForm']; public function loginForm() { - return \SilverStripe\LDAP\Forms\LDAPLoginForm::create($this, get_class($this->authenticator), 'LoginForm'); + return LDAPLoginForm::create($this, get_class($this->authenticator), 'LoginForm'); } } ``` -### A custom ChangePasswordHandler +### A custom `ChangePasswordHandler` As with the `LoginHandler` example above, the logic from silverstripe/ldap's `ChangePasswordHandler` is much smaller, so is used for this example. ```php +// app/src/MFA/Handlers/LDAPMFAChangePasswordHandler.php +namespace App\MFA\Handlers; + +use SilverStripe\LDAP\Forms\LDAPChangePasswordForm; + class LDAPMFAChangePasswordHandler extends \SilverStripe\MFA\Authenticator\ChangePasswordHandler { private static $allowed_actions = [ @@ -123,7 +137,7 @@ class LDAPMFAChangePasswordHandler extends \SilverStripe\MFA\Authenticator\Chang public function changePasswordForm() { - return \SilverStripe\LDAP\Forms\LDAPChangePasswordForm::create($this, 'ChangePasswordForm'); + return LDAPChangePasswordForm::create($this, 'ChangePasswordForm'); } } ``` diff --git a/docs/en/backup-codes.md b/docs/en/08_backup-codes.md similarity index 75% rename from docs/en/backup-codes.md rename to docs/en/08_backup-codes.md index ef0dffa9..294812f0 100644 --- a/docs/en/backup-codes.md +++ b/docs/en/08_backup-codes.md @@ -5,14 +5,18 @@ verify with their registered multi-factor authentication methods, e.g. they lost ## Generating backup codes -Backup codes are generated by the `BackupCodeGeneratorInterface` service, which has a default implementation of -`SilverStripe\MFA\Service\BackupCodeGenerator`. This service will hash the generated backup codes using the -default Silverstripe CMS `PasswordEncryptor` service and algorithm, and returns an array of -`SilverStripe\MFA\State\BackupCode` instances. +Backup codes are generated by the [`BackupCodeGeneratorInterface`](api:SilverStripe\MFA\Service\BackupCodeGeneratorInterface) service, which has a default implementation of +[`BackupCodeGenerator`](api:SilverStripe\MFA\Service\BackupCodeGenerator). This service will hash the generated backup codes using the +default Silverstripe CMS [`PasswordEncryptor`](api:SilverStripe\Security\PasswordEncryptor) service and algorithm, and returns an array of +[`BackupCode`](api:SilverStripe\MFA\State\BackupCode) instances. You can use this service class to generate codes: ```php +use SilverStripe\MFA\Service\BackupCodeGeneratorInterface; + +// ... + $generator = Injector::inst()->get(BackupCodeGeneratorInterface::class); $codes = $generator->generate(); ``` @@ -23,6 +27,10 @@ To verify a backup code, you can reconstruct a `BackupCode` instance from stored instance and use `BackupCode::isValid()` to verify the input. For example: ```php +use SilverStripe\MFA\State\BackupCode; + +// ... + $storedCodeData = json_decode($registeredMethod->Data, true); foreach ($storedCodeData as $codeCandidate) { // Expected structure: ['hash' => 'abc123', 'algorithm' => 'blowfish', 'salt' => 'bae'] @@ -37,7 +45,7 @@ foreach ($storedCodeData as $codeCandidate) { The default implementation of this logic is in `VerifyHandler::verify()`. -## BackupCodeGenerator +## `BackupCodeGenerator` This configuration documentation applies to the default `BackupCodeGenerator` implementation of `BackupCodeGeneratorInterface`. @@ -47,7 +55,7 @@ This configuration documentation applies to the default `BackupCodeGenerator` im You can adjust either the length of the backup code strings, or the number of them that are generated, by setting YAML configuration: -```yaml +```yml SilverStripe\MFA\Service\BackupCodeGenerator: # Should be 12 characters long backup_code_length: 12 @@ -61,6 +69,9 @@ By default, backup codes will be generated using lowercase letters. If you would the entropy of your backup codes by adding extra character types, you can do so by adding an extension: ```php +// app/src/MFA/Extension/BackupCodeGeneratorExtension.php +namespace App\MFA\Extension; + class BackupCodeGeneratorExtension extends Extension { /** diff --git a/docs/en/broadening-the-scope-of-mfa.md b/docs/en/09_broadening-the-scope-of-mfa.md similarity index 80% rename from docs/en/broadening-the-scope-of-mfa.md rename to docs/en/09_broadening-the-scope-of-mfa.md index db5e8a9e..62dab1ea 100644 --- a/docs/en/broadening-the-scope-of-mfa.md +++ b/docs/en/09_broadening-the-scope-of-mfa.md @@ -10,15 +10,15 @@ By default, the MFA flow will only be presented during the login process to memb You can broaden the scope of the MFA flow so it applies to all members, regardless of whether they have CMS or administration privileges or not by setting the following configuration: -```yaml +```yml SilverStripe\MFA\Service\EnforcementManager: requires_admin_access: false ``` However, note that users without access to the CMS will be unable to access their personal MFA settings and perform actions such as: -* adding additional MFA methods; -* removing, resetting, and changing default MFA methods; and -* resetting recovery codes. +- adding additional MFA methods; +- removing, resetting, and changing default MFA methods; and +- resetting recovery codes. -A custom implementation would be required to provide this functionality. Otherwise it would be limited to Silverstripe CMS Administrators to [reset MFA settings](https://userhelp.silverstripe.org/en/4/optional_features/multi-factor_authentication/administrator_manual/resetting_accounts/) for a member on their behalf. +A custom implementation would be required to provide this functionality. Otherwise it would be limited to Silverstripe CMS Administrators to [reset MFA settings](https://userhelp.silverstripe.org/en/optional_features/multi-factor_authentication/administrator_manual/resetting_accounts/) for a member on their behalf. diff --git a/docs/en/index.md b/docs/en/index.md new file mode 100644 index 00000000..49c1b9cb --- /dev/null +++ b/docs/en/index.md @@ -0,0 +1,48 @@ +--- +title: Multi-factor Authentication (MFA) +--- + +# Multi-factor Authentication (MFA) + +This module provides bases classes for implementing multi-factor authentication (MFA) in Silverstripe CMS. You should also install one of the additional multi-factor authenticator modules: + +- [silverstripe/totp-authenticator](https://github.com/silverstripe/silverstripe-totp-authenticator) +- [silverstripe/webauthn-authenticator](https://github.com/silverstripe/silverstripe-webauthn-authenticator) + +This module provides two distinct processes for MFA; verification and registration. This module provides a decoupled +architecture where front-end and back-end are separate. Provided with the module is a React app that interfaces with +default endpoints added by this module. + +## Installation + +```bash +composer require silverstripe/mfa +``` + +## Setup + +After installing this module _and_ a supported factor method module (e.g. TOTP), the default member authenticator +will be replaced with the MFA authenticator instead. This will provide no change in the steps taken to log in until +an MFA Method has also been configured for the site. The TOTP and WebAuthn modules will configure themselves +automatically. + +After installing the MFA module and having at least one method configured, MFA will automatically be enabled. By default +it will be optional (users can skip MFA registration). You can make it mandatory via the Settings tab in the admin area. + +The MFA flow will only be applied to members with access to the CMS or administration area. See '[Broadening the scope of MFA](docs/en/broadening-the-scope-of-mfa.md)' for more detail. + +You can disable MFA on an environment by setting a `BYPASS_MFA=1` environment variable, +or via YAML config - see [local development](docs/en/local-development) for details. + +### Configuring custom methods + +If you have built your own MFA method, you can register it with the [`MethodRegistry`](api:SilverStripe\MFA\Service\MethodRegistry) to enable it: + +```yml +SilverStripe\MFA\Service\MethodRegistry: + methods: + - MyCustomMethod + - Another\Custom\Method\Here +``` + +[CHILDREN includeFolders]