Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
TokenIssuer
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
2 / 2
5
100.00% covered (success)
100.00%
1 / 1
 issue
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 refreshToken
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace Albet\SanctumRefresh\Services;
4
5use Albet\SanctumRefresh\Exceptions\MustHaveTraitException;
6use Albet\SanctumRefresh\Models\RefreshToken;
7use Albet\SanctumRefresh\Services\Factories\Token;
8use Albet\SanctumRefresh\Services\Factories\TokenConfig;
9use Illuminate\Database\Eloquent\Model;
10use Illuminate\Support\Str;
11use Laravel\Sanctum\HasApiTokens;
12
13class TokenIssuer
14{
15    /**
16     * @throws MustHaveTraitException
17     */
18    public static function issue(Model $tokenable, string $tokenName = 'web', TokenConfig $tokenConfig = new TokenConfig()): Token
19    {
20        $tokenableTraits = array_values(class_uses($tokenable));
21
22        if (!in_array(HasApiTokens::class, $tokenableTraits)) {
23            throw new MustHaveTraitException(get_class($tokenable), HasApiTokens::class);
24        }
25
26        /* @phpstan-ignore-next-line */
27        $token = $tokenable->createToken(
28            $tokenName,
29            $tokenConfig->abilities,
30            $tokenConfig->tokenExpireAt
31        );
32
33        $plainRefreshToken = Str::random(40);
34
35        $refreshToken = RefreshToken::create([
36            'token' => hash('sha256', $plainRefreshToken),
37            'expires_at' => $tokenConfig->refreshTokenExpireAt,
38            'token_id' => $token->accessToken->id,
39        ]);
40
41        return new Token($token, $plainRefreshToken, $refreshToken);
42    }
43
44    public static function refreshToken(string $refreshToken, string $tokenName = 'web', TokenConfig $tokenConfig = new TokenConfig()): Token|false
45    {
46        if (!str_contains($refreshToken, '|')) {
47            return false;
48        }
49
50        // Parse the token id
51        $tokenId = explode('|', $refreshToken)[0];
52
53        // Find token from given id
54        $token = RefreshToken::with('accessToken')
55            ->where('expires_at', '>', now())
56            ->find($tokenId);
57
58        if (!$token) {
59            return false;
60        }
61
62        // Regenerate token.
63        $newToken = $token->accessToken->tokenable
64            ->createToken(
65                $tokenName,
66                $tokenConfig->abilities,
67                $tokenConfig->tokenExpireAt
68            );
69
70        $plainRefreshToken = Str::random(40);
71
72        $refreshToken = RefreshToken::create([
73            'token_id' => $newToken->accessToken->id,
74            'token' => hash('sha256', $plainRefreshToken),
75            'expires_at' => $tokenConfig->refreshTokenExpireAt,
76        ]);
77
78        // Delete current token (revoke refresh token)
79        $token->accessToken->delete();
80        $token->delete();
81
82        return new Token($newToken, $plainRefreshToken, $refreshToken);
83    }
84}