Passed
Push — master ( e4bd8f...4069ad )
by Anton
02:47
created

TokenStorage::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Auth\Cycle;
13
14
use Cycle\ORM\ORMInterface;
15
use Cycle\ORM\Transaction;
16
use Spiral\Auth\Exception\TokenStorageException;
17
use Spiral\Auth\TokenInterface;
18
use Spiral\Auth\TokenStorageInterface;
19
20
/**
21
 * Provides the ability to fetch token information from the database via Cycle ORM.
22
 */
23
final class TokenStorage implements TokenStorageInterface
24
{
25
    /** @var ORMInterface */
26
    private $orm;
27
28
    /**
29
     * @param ORMInterface $orm
30
     */
31
    public function __construct(ORMInterface $orm)
32
    {
33
        $this->orm = $orm;
34
    }
35
36
    /**
37
     * @inheritDoc
38
     */
39
    public function load(string $id): ?TokenInterface
40
    {
41
        if (strpos($id, ':') === false) {
42
            return null;
43
        }
44
45
        [$pk, $hash] = explode(':', $id, 2);
46
47
        /** @var Token $token */
48
        $token = $this->orm->getRepository(Token::class)->findByPK($pk);
49
50
        if ($token === null || !hash_equals($token->getHashedValue(), hash('sha512', $hash))) {
51
            // hijacked or deleted
52
            return null;
53
        }
54
55
        $token->setSecretValue($hash);
56
57
        if ($token->getExpiresAt() !== null && $token->getExpiresAt() < new \DateTime()) {
58
            $this->delete($token);
59
            return null;
60
        }
61
62
        return $token;
63
    }
64
65
    /**
66
     * @inheritDoc
67
     */
68
    public function create(array $payload, \DateTimeInterface $expiresAt = null): TokenInterface
69
    {
70
        try {
71
            $token = new Token(
72
                $this->issueID(),
73
                $this->randomHash(128),
74
                $payload,
75
                new \DateTimeImmutable(),
76
                $expiresAt
77
            );
78
79
            (new Transaction($this->orm))->persist($token)->run();
80
81
            return $token;
82
        } catch (\Throwable $e) {
83
            throw new TokenStorageException('Unable to create token', $e->getCode(), $e);
84
        }
85
    }
86
87
    /**
88
     * @inheritDoc
89
     */
90
    public function delete(TokenInterface $token): void
91
    {
92
        try {
93
            (new Transaction($this->orm))->delete($token)->run();
94
        } catch (\Throwable $e) {
95
            throw new TokenStorageException('Unable to delete token', $e->getCode(), $e);
96
        }
97
    }
98
99
    /**
100
     * Issue unique token id.
101
     *
102
     * @return string
103
     * @throws \Exception
104
     */
105
    private function issueID(): string
106
    {
107
        $id = $this->randomHash(64);
108
109
        $query = $this->orm->getSource(Token::class)->getDatabase()->select()->from(
110
            $this->orm->getSource(Token::class)->getTable()
111
        );
112
113
        while ((clone $query)->where('id', $id)->count('id') !== 0) {
114
            $id = $this->randomHash(64);
115
        }
116
117
        return $id;
118
    }
119
120
    /**
121
     * @param int $length
122
     * @return string
123
     *
124
     * @throws \Exception
125
     */
126
    private function randomHash(int $length): string
127
    {
128
        return substr(bin2hex(random_bytes($length)), 0, $length);
129
    }
130
}
131