Completed
Push — develop ( f77fd9...d910ff )
by Abdelrahman
10:08
created

EmailVerificationBroker::sendVerificationLink()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Auth\Services;
6
7
use Closure;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use UnexpectedValueException;
11
use Illuminate\Contracts\Auth\UserProvider;
12
use Rinvex\Auth\Contracts\CanVerifyEmailContract;
13
use Rinvex\Auth\Contracts\EmailVerificationBrokerContract;
14
15
class EmailVerificationBroker implements EmailVerificationBrokerContract
16
{
17
    /**
18
     * The application key.
19
     *
20
     * @var string
21
     */
22
    protected $key;
23
24
    /**
25
     * The user provider implementation.
26
     *
27
     * @var \Illuminate\Contracts\Auth\UserProvider
28
     */
29
    protected $users;
30
31
    /**
32
     * The number of minutes that the reset token should be considered valid.
33
     *
34
     * @var int
35
     */
36
    protected $expiration;
37
38
    /**
39
     * Create a new verification broker instance.
40
     *
41
     * @param \Illuminate\Contracts\Auth\UserProvider $users
42
     * @param string                                  $key
43
     * @param int                                     $expiration
44
     */
45
    public function __construct(UserProvider $users, $key, $expiration)
46
    {
47
        $this->key = $key;
48
        $this->users = $users;
49
        $this->expiration = $expiration;
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function sendVerificationLink(array $credentials): string
56
    {
57
        // First we will check to see if we found a user at the given credentials and
58
        // if we did not we will redirect back to this current URI with a piece of
59
        // "flash" data in the session to indicate to the developers the errors.
60
        if (is_null($user = $this->getUser($credentials))) {
61
            return static::INVALID_USER;
62
        }
63
64
        $expiration = now()->addMinutes($this->expiration)->timestamp;
65
66
        // Once we have the verification token, we are ready to send the message out to
67
        // this user with a link to verify their email. We will then redirect back to
68
        // the current URI having nothing set in the session to indicate any errors
69
        $user->sendEmailVerificationNotification($this->createToken($user, $expiration), $expiration);
70
71
        return static::LINK_SENT;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function verify(array $credentials, Closure $callback)
78
    {
79
        // If the responses from the validate method is not a user instance, we will
80
        // assume that it is a redirect and simply return it from this method and
81
        // the user is properly redirected having an error message on the post.
82
        $user = $this->validateVerification($credentials);
83
84
        if (! $user instanceof CanVerifyEmailContract) {
85
            return $user;
86
        }
87
88
        // Once the email has been verified, we'll call the given
89
        // callback, then we'll delete the token and return.
90
        // in their persistent storage.
91
        $callback($user);
92
93
        return static::EMAIL_VERIFIED;
94
    }
95
96
    /**
97
     * Get the user for the given credentials.
98
     *
99
     * @param array $credentials
100
     *
101
     * @throws \UnexpectedValueException
102
     *
103
     * @return \Rinvex\Auth\Contracts\CanVerifyEmailContract
104
     */
105
    public function getUser(array $credentials): CanVerifyEmailContract
106
    {
107
        $user = $this->users->retrieveByCredentials(Arr::only($credentials, ['email']));
108
109
        if ($user && ! $user instanceof CanVerifyEmailContract) {
110
            throw new UnexpectedValueException('User must implement CanVerifyEmailContract interface.');
111
        }
112
113
        return $user;
114
    }
115
116
    /**
117
     * Create a new email verification token for the given user.
118
     *
119
     * @param \Rinvex\Auth\Contracts\CanVerifyEmailContract $user
120
     * @param int                                           $expiration
121
     *
122
     * @return string
123
     */
124
    public function createToken(CanVerifyEmailContract $user, $expiration): string
125
    {
126
        $payload = $this->buildPayload($user, $user->getEmailForVerification(), $expiration);
127
128
        return hash_hmac('sha256', $payload, $this->getKey());
129
    }
130
131
    /**
132
     * Validate the given email verification token.
133
     *
134
     * @param \Rinvex\Auth\Contracts\CanVerifyEmailContract $user
135
     * @param array                                         $credentials
136
     *
137
     * @return bool
138
     */
139
    public function validateToken(CanVerifyEmailContract $user, array $credentials): bool
140
    {
141
        $payload = $this->buildPayload($user, $credentials['email'], $credentials['expiration']);
142
143
        return hash_equals($credentials['token'], hash_hmac('sha256', $payload, $this->getKey()));
144
    }
145
146
    /**
147
     * Validate the given expiration timestamp.
148
     *
149
     * @param int $expiration
150
     *
151
     * @return bool
152
     */
153
    public function validateTimestamp($expiration): bool
154
    {
155
        return now()->createFromTimestamp($expiration)->isFuture();
156
    }
157
158
    /**
159
     * Return the application key.
160
     *
161
     * @return string
162
     */
163
    public function getKey(): string
164
    {
165
        if (Str::startsWith($this->key, 'base64:')) {
166
            return base64_decode(mb_substr($this->key, 7));
167
        }
168
169
        return $this->key;
170
    }
171
172
    /**
173
     * Returns the payload string containing.
174
     *
175
     * @param \Rinvex\Auth\Contracts\CanVerifyEmailContract $user
176
     * @param string                                        $email
177
     * @param int                                           $expiration
178
     *
179
     * @return string
180
     */
181
    protected function buildPayload(CanVerifyEmailContract $user, $email, $expiration): string
182
    {
183
        return implode(';', [
184
            $email,
185
            $expiration,
186
            $user->getKey(),
0 ignored issues
show
Bug introduced by
The method getKey() does not seem to exist on object<Rinvex\Auth\Contr...CanVerifyEmailContract>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
187
            $user->password,
0 ignored issues
show
Bug introduced by
Accessing password on the interface Rinvex\Auth\Contracts\CanVerifyEmailContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
188
        ]);
189
    }
190
191
    /**
192
     * Validate an email verification for the given credentials.
193
     *
194
     * @param array $credentials
195
     *
196
     * @return \Rinvex\Auth\Contracts\CanVerifyEmailContract|string
197
     */
198
    protected function validateVerification(array $credentials)
199
    {
200
        if (is_null($user = $this->getUser($credentials))) {
201
            return static::INVALID_USER;
202
        }
203
204
        if (! $this->validateToken($user, $credentials)) {
205
            return static::INVALID_TOKEN;
206
        }
207
208
        if (! $this->validateTimestamp($credentials['expiration'])) {
209
            return static::EXPIRED_TOKEN;
210
        }
211
212
        return $user;
213
    }
214
}
215