Completed
Push — master ( 48c4c4...9a2426 )
by Alex
10:10 queued 05:00
created

src/JWTGenerator.php (1 issue)

Severity
1
<?php
2
3
namespace AlexLisenkov\LaravelWebPush;
4
5
use AlexLisenkov\LaravelWebPush\Contracts\JWTGeneratorContract;
6
use Base64Url\Base64Url;
7
use Illuminate\Contracts\Config\Repository as ConfigRepository;
8
use Jose\Component\Core\AlgorithmManager;
9
use Jose\Component\Core\Converter\StandardConverter;
10
use Jose\Component\Core\JWK;
11
use Jose\Component\Signature\Algorithm\ES256;
12
use Jose\Component\Signature\JWS;
13
use Jose\Component\Signature\JWSBuilder;
14
use Jose\Component\Signature\Serializer\CompactSerializer;
15
16
class JWTGenerator implements JWTGeneratorContract
17
{
18
    /**
19
     * @var int
20
     */
21
    private $expires_at;
22
    /**
23
     * @var string
24
     */
25
    private $audience;
26
    /**
27
     * @var ConfigRepository
28
     */
29
    private $config_repository;
30
31 16
    public function __construct(ConfigRepository $config_repository)
32
    {
33 16
        $this->config_repository = $config_repository;
34 16
    }
35
36
    /**
37
     * @param string $aud
38
     *
39
     * @return JWTGeneratorContract
40
     */
41 7
    public function withAudience(string $aud): JWTGeneratorContract
42
    {
43 7
        $this->audience = $aud;
44
45 7
        return $this;
46
    }
47
48
    /**
49
     * @param int $time
50
     *
51
     * @return JWTGeneratorContract
52
     */
53 1
    public function willExpireAt(int $time): JWTGeneratorContract
54
    {
55 1
        $this->expires_at = $time;
56
57 1
        return $this;
58
    }
59
60
    /**
61
     * @return string
62
     * @throws \Exception
63
     */
64 1
    public function serialize(): string
65
    {
66 1
        $jsonConverter = new StandardConverter();
67 1
        $jwsCompactSerializer = new CompactSerializer($jsonConverter);
68
69 1
        return $jwsCompactSerializer->serialize($this->getJWS(), 0);
70
    }
71
72
    /**
73
     * @return JWS
74
     * @throws \Exception
75
     */
76 3
    public function getJWS(): JWS
77
    {
78 3
        $jsonConverter = new StandardConverter();
79 3
        $jwsBuilder = new JWSBuilder($jsonConverter, AlgorithmManager::create([new ES256()]));
80
81
        return $jwsBuilder
82 3
            ->create()
83 3
            ->withPayload($this->getPayload())
84 3
            ->addSignature($this->getJWK(), $this->getHeader())
85 3
            ->build();
86
    }
87
88
    /**
89
     * @return string
90
     * @throws \InvalidArgumentException
91
     */
92 8
    public function getPayload(): string
93
    {
94 8
        if (!is_string($this->audience)) {
0 ignored issues
show
The condition is_string($this->audience) is always true.
Loading history...
95 1
            throw new \InvalidArgumentException("Audience must be string, " . gettype($this->audience) . " given.");
96
        }
97
98 7
        if (!$this->getExpiresAt()) {
99 1
            $this->willExpireIn($this->getConfigVariable('expiration', Constants::DEFAULT_EXPIRE));
100
        }
101
102 7
        return json_encode([
103 7
            'aud' => $this->audience,
104 7
            'exp' => $this->getExpiresAt(),
105 7
            'sub' => $this->getConfigVariable('subject', env('APP_URL', 'mailto:[email protected]')),
106 7
        ], JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
107
    }
108
109
    /**
110
     * Get ExpiresAt
111
     *
112
     * @return int|null
113
     */
114 8
    public function getExpiresAt(): ?int
115
    {
116 8
        return $this->expires_at;
117
    }
118
119
    /**
120
     * @param int $time
121
     *
122
     * @return JWTGeneratorContract
123
     */
124 7
    public function willExpireIn(int $time): JWTGeneratorContract
125
    {
126 7
        $this->expires_at = time() + $time;
127
128 7
        return $this;
129
    }
130
131
    /**
132
     * @param string $key
133
     *
134
     * @param $default
135
     *
136
     * @return mixed
137
     */
138 13
    private function getConfigVariable(string $key, $default = null)
139
    {
140 13
        return $this->config_repository->get(Constants::CONFIG_KEY . '.' . $key, $default);
141
    }
142
143
    /**
144
     * @return JWK
145
     */
146 9
    public function getJWK(): JWK
147
    {
148 9
        $local_server_key = Base64Url::decode($this->getConfigVariable('public_key'));
149 9
        $public = $this->unserializePublicKey($local_server_key);
150
151 8
        return JWK::create([
152 8
            'kty' => 'EC',
153 8
            'crv' => 'P-256',
154 8
            'x' => Base64Url::encode($public['x']),
155 8
            'y' => Base64Url::encode($public['y']),
156 8
            'd' => $this->getConfigVariable('private_key'),
157
        ]);
158
    }
159
160
    /**
161
     * @param string $data
162
     *
163
     * @return array
164
     */
165 9
    private function unserializePublicKey(string $data): array
166
    {
167 9
        $data = bin2hex($data);
168 9
        $first_byte = mb_substr($data, 0, 2, '8bit');
169
170 9
        if ($first_byte !== '04') {
171 1
            throw new \InvalidArgumentException('Invalid data: only uncompressed keys are supported.');
172
        }
173
174 8
        $data = mb_substr($data, 2, null, '8bit');
175 8
        $center = mb_strlen($data) / 2;
176
177
        return [
178 8
            'x' => hex2bin(mb_substr($data, 0, $center, '8bit')),
179 8
            'y' => hex2bin(mb_substr($data, $center, null, '8bit')),
180
        ];
181
    }
182
183
    /**
184
     * @return array
185
     */
186 4
    public function getHeader(): array
187
    {
188
        return [
189 4
            'typ' => 'JWT',
190
            'alg' => 'ES256',
191
        ];
192
    }
193
}
194