Passed
Push — master ( 7e81b0...7eb007 )
by Robbie
12:39 queued 11s
created

src/BackupCode/VerifyHandler.php (1 issue)

Severity
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\BackupCode;
4
5
use RuntimeException;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Core\Manifest\ModuleLoader;
8
use SilverStripe\MFA\Method\Handler\VerifyHandlerInterface;
9
use SilverStripe\MFA\Model\RegisteredMethod;
10
use SilverStripe\MFA\Service\Notification;
11
use SilverStripe\MFA\State\BackupCode;
12
use SilverStripe\MFA\State\Result;
13
use SilverStripe\MFA\Store\StoreInterface;
14
15
class VerifyHandler implements VerifyHandlerInterface
16
{
17
    private static $dependencies = [
0 ignored issues
show
The private property $dependencies is not used, and could be removed.
Loading history...
18
        'NotificationService' => '%$' . Notification::class
19
    ];
20
21
    /**
22
     * @var Notification
23
     */
24
    protected $notification;
25
26
    public function setNotificationService(Notification $notification): self
27
    {
28
        $this->notification = $notification;
29
        return $this;
30
    }
31
32
    /**
33
     * Stores any data required to handle a login process with a method, and returns relevant state to be applied to the
34
     * front-end application managing the process.
35
     *
36
     * @param StoreInterface $store An object that hold session data (and the Member) that can be mutated
37
     * @param RegisteredMethod $method The RegisteredMethod instance that is being verified
38
     * @return array Props to be passed to a front-end component
39
     */
40
    public function start(StoreInterface $store, RegisteredMethod $method): array
41
    {
42
        // Provide a path to the graphic shown
43
        return [
44
            'graphic' => ModuleLoader::getModule('silverstripe/mfa')
45
                ->getResource('client/dist/images/recovery-codes.svg')->getURL(),
46
        ];
47
    }
48
49
    /**
50
     * Verify the request has provided the right information to verify the member that aligns with any sessions state
51
     * that may have been set prior
52
     *
53
     * @param HTTPRequest $request
54
     * @param StoreInterface $store
55
     * @param RegisteredMethod $registeredMethod The RegisteredMethod instance that is being verified
56
     * @return Result
57
     */
58
    public function verify(HTTPRequest $request, StoreInterface $store, RegisteredMethod $registeredMethod): Result
59
    {
60
        $bodyJSON = json_decode($request->getBody(), true);
61
62
        if (!isset($bodyJSON['code'])) {
63
            throw new RuntimeException(
64
                'Verification of backup codes requires the code to be provided but it was not given'
65
            );
66
        }
67
68
        $code = $bodyJSON['code'];
69
70
        $candidates = json_decode($registeredMethod->Data, true);
71
72
        foreach ($candidates as $index => $candidate) {
73
            $candidateData = json_decode($candidate, true) ?? [];
74
            $backupCode = BackupCode::create(
75
                $code,
76
                $candidateData['hash'] ?? '',
77
                $candidateData['algorithm'] ?? '',
78
                $candidateData['salt'] ?? ''
79
            );
80
            if (!$backupCode->isValid()) {
81
                continue;
82
            }
83
84
85
            // Remove the verified code from the valid list of codes
86
            array_splice($candidates, $index, 1);
87
            $registeredMethod->Data = json_encode($candidates);
88
            $registeredMethod->write();
89
            $this->notification->send(
90
                $registeredMethod->Member(),
91
                'SilverStripe/MFA/Email/Notification_backupcodeused',
92
                [
93
                    'subject' => _t(self::class . '.MFAREMOVED', 'A recovery code was used to access your account'),
94
                    'CodesRemaining' => count($candidates),
95
                ]
96
            );
97
            return Result::create();
98
        }
99
100
        return Result::create(false, _t(__CLASS__ . '.INVALID_CODE', 'Invalid code'));
101
    }
102
103
    /**
104
     * Get the key that a React UI component is registered under (with @silverstripe/react-injector on the front-end)
105
     *
106
     * @return string
107
     */
108
    public function getComponent(): string
109
    {
110
        return 'BackupCodeVerify';
111
    }
112
}
113