Issues (102)

src/BackupCode/VerifyHandler.php (1 issue)

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