Skip to content

Commit

Permalink
DOC Linting
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Apr 8, 2024
1 parent 0a50694 commit 3449c80
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 108 deletions.
1 change: 1 addition & 0 deletions .doclintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docs/en/
54 changes: 2 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -52,6 +53,11 @@
"client/lang"
]
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"autoload": {
"psr-4": {
"SilverStripe\\MFA\\": "src/",
Expand Down
7 changes: 5 additions & 2 deletions docs/en/debugging.md → docs/en/01_debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions docs/en/02_creating-new-mfa-methods/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Creating new MFA methods
---

# Creating new MFA methods

[CHILDREN includeFolders]
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions docs/en/encryption.md → docs/en/04_encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 10 additions & 6 deletions docs/en/datastores.md → docs/en/05_datastores.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
{
Expand Down
12 changes: 6 additions & 6 deletions docs/en/security.md → docs/en/06_security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
}
}
```
Expand All @@ -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 = [
Expand All @@ -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');
}
}
```
Loading

0 comments on commit 3449c80

Please sign in to comment.