Identity::twoFactorAuthenticationEnabled()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
4
namespace PersonalGalaxy\Identity\Entity;
5
6
use PersonalGalaxy\Identity\{
7
    Entity\Identity\Email,
8
    Entity\Identity\Password,
9
    Entity\Identity\SecretKey,
10
    Entity\Identity\RecoveryCode,
11
    Event\IdentityWasCreated,
12
    Event\Identity\PasswordWasChanged,
13
    Event\Identity\TwoFactorAuthenticationWasEnabled,
14
    Event\Identity\TwoFactorAuthenticationWasDisabled,
15
    Event\Identity\RecoveryCodeWasUsed,
16
    Event\IdentityWasDeleted,
17
    TwoFactorAuthentication\Code,
18
    Exception\LogicException,
19
};
20
use Innmind\EventBus\{
21
    ContainsRecordedEventsInterface,
22
    EventRecorder,
23
};
24
use Innmind\TimeContinuum\TimeContinuumInterface;
25
use Innmind\Immutable\Set;
26
use ParagonIE\MultiFactor\{
27
    OTP\TOTP,
28
    FIDOU2F,
29
};
30
31
final class Identity implements ContainsRecordedEventsInterface
32
{
33
    use EventRecorder;
34
35
    private $identity;
36
    private $email;
37
    private $password;
38
    private $secretKey;
39
    private $recoveryCodes;
40
41 15
    private function __construct(
42
        Identity\Identity $identity,
43
        Email $email,
44
        Password $password
45
    ) {
46 15
        $this->identity = $identity;
47 15
        $this->email = $email;
48 15
        $this->password = $password;
49 15
    }
50
51 15
    public static function create(
52
        Identity\Identity $identity,
53
        Email $email,
54
        Password $password
55
    ): self {
56 15
        $self = new self($identity, $email, $password);
57 15
        $self->record(new IdentityWasCreated($identity, $email));
58
59 15
        return $self;
60
    }
61
62 5
    public function identity(): Identity\Identity
63
    {
64 5
        return $this->identity;
65
    }
66
67 2
    public function email(): Email
68
    {
69 2
        return $this->email;
70
    }
71
72 2
    public function changePassword(Password $password): self
73
    {
74 2
        $this->password = $password;
75 2
        $this->record(new PasswordWasChanged($this->identity));
76
77 2
        return $this;
78
    }
79
80 6
    public function verify(string $password): bool
81
    {
82 6
        return $this->password->verify($password);
83
    }
84
85 6
    public function twoFactorAuthenticationEnabled(): bool
86
    {
87 6
        return $this->secretKey instanceof SecretKey;
88
    }
89
90 6
    public function enableTwoFactorAuthentication(): self
91
    {
92 6
        $this->secretKey = new SecretKey;
93 6
        $this->recoveryCodes = Set::of(
94 6
            RecoveryCode::class,
95 6
            new RecoveryCode,
96 6
            new RecoveryCode,
97 6
            new RecoveryCode,
98 6
            new RecoveryCode,
99 6
            new RecoveryCode,
100 6
            new RecoveryCode,
101 6
            new RecoveryCode,
102 6
            new RecoveryCode,
103 6
            new RecoveryCode,
104 6
            new RecoveryCode
105
        );
106 6
        $this->record(new TwoFactorAuthenticationWasEnabled(
107 6
            $this->identity,
108 6
            $this->secretKey,
109 6
            $this->recoveryCodes
110
        ));
111
112 6
        return $this;
113
    }
114
115 2
    public function disableTwoFactorAuthentication(): self
116
    {
117 2
        $this->secretKey = null;
118 2
        $this->recoveryCodes = null;
119 2
        $this->record(new TwoFactorAuthenticationWasDisabled($this->identity));
120
121 2
        return $this;
122
    }
123
124 4
    public function validate(Code $code, TimeContinuumInterface $clock): bool
125
    {
126 4
        if (!$this->twoFactorAuthenticationEnabled()) {
127 1
            throw new LogicException;
128
        }
129
130 4
        $factor = new FIDOU2F((string) $this->secretKey, new TOTP);
131 4
        $time = (int) ($clock->now()->milliseconds() / 1000);
132
133 4
        if ($factor->validateCode((string) $code, $time)) {
134 1
            return true;
135
        }
136
137
        $codes = $this
138 4
            ->recoveryCodes
139
            ->filter(static function(RecoveryCode $recoveryCode) use ($code): bool {
140 4
                return $recoveryCode->equals($code);
141 4
            });
142
143 4
        if ($codes->size() === 0) {
144 3
            return false;
145
        }
146
147 2
        $this->recoveryCodes = $this->recoveryCodes->remove($codes->current());
148 2
        $this->record(new RecoveryCodeWasUsed($this->identity));
149
150 2
        return true;
151
    }
152
153 2
    public function delete(): void
154
    {
155 2
        $this->record(new IdentityWasDeleted($this->identity));
156 2
    }
157
}
158