DatabaseTokensRepository.php 5.79 KB
Newer Older
Jacob Priddy's avatar
Jacob Priddy committed
1
2
3
4
5
<?php


namespace Source\Gateways\Tokens;

Jacob Priddy's avatar
Jacob Priddy committed
6
use Carbon\Carbon;
Jacob Priddy's avatar
Jacob Priddy committed
7
use Source\Entities\Token;
Jacob Priddy's avatar
Jacob Priddy committed
8
use Illuminate\Support\Str;
Jacob Priddy's avatar
Jacob Priddy committed
9
use Source\Sanitize\CastsTo;
10
use Source\Entities\RawToken;
Jacob Priddy's avatar
Jacob Priddy committed
11
use Illuminate\Support\Facades\Log;
12
use Source\Entities\HashedSearchable;
13
use Source\Gateways\PostgresSQLCodes;
Jacob Priddy's avatar
Jacob Priddy committed
14
use Illuminate\Database\QueryException;
15
use Illuminate\Database\Eloquent\Builder;
Jacob Priddy's avatar
Jacob Priddy committed
16
17
use Source\Exceptions\EntityNotFoundException;

Jacob Priddy's avatar
Jacob Priddy committed
18
19
class DatabaseTokensRepository implements TokensRepository
{
Jacob Priddy's avatar
Jacob Priddy committed
20
21
    use CastsTo;

Jacob Priddy's avatar
Jacob Priddy committed
22
    /**
Jacob Priddy's avatar
Jacob Priddy committed
23
24
25
     * @param \App\Token             $dbToken
     * @param \Source\Entities\Token $token
     * @throws \Source\Exceptions\EntityNotFoundException
Jacob Priddy's avatar
Jacob Priddy committed
26
     */
Jacob Priddy's avatar
Jacob Priddy committed
27
    protected function saveToken(\App\Token $dbToken, Token $token): void
Jacob Priddy's avatar
Jacob Priddy committed
28
    {
29
        $dbToken->setAttribute('name', $token->getName());
30
        $dbToken->setAttribute('user_id', $token->getUserId());
31
        $dbToken->setAttribute('api_token', $token->getToken()->getHash());
32
        $dbToken->setAttribute('expires_at', $token->getExpiresAt());
Jacob Priddy's avatar
Jacob Priddy committed
33
34
35
36

        try {
            $dbToken->save();
        } catch (QueryException $e) {
37
            if ($e->getCode() === PostgresSQLCodes::FOREIGN_KEY_VIOLATION) {
Jacob Priddy's avatar
Jacob Priddy committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
                throw new EntityNotFoundException('Cannot save token for non existent user');
            }

            Log::error('Cannot save token: ' . $e);

            throw $e;
        }
    }

    /**
     * @inheritDoc
     */
    public function create(Token $token): Token
    {
        $dbToken = new \App\Token();

        $this->saveToken($dbToken, $token);
55

56
57
58
        return self::dbTokenToToken($dbToken);
    }

59
60
61
    /**
     * @inheritDoc
     */
62
    public function createLoginToken(string $userId, string $salt): RawToken
63
    {
64
65
        $raw = self::generateTokenString();
        return new RawToken($raw, $this->create(new Token(
66
67
            0,
            $userId,
68
            HashedSearchable::hash($salt, $raw),
69
70
            null,
            Carbon::now()->addDay()
71
        )));
72
73
    }

74
75
76
77
78
    /**
     * @inheritDoc
     */
    public function update(string $tokenId, Token $token): Token
    {
79
        /** @var \App\Token|null $dbToken */
80
        $dbToken = \App\Token::query()->find(self::castToInt($tokenId));
81
82

        if (!$dbToken) {
Jacob Priddy's avatar
Jacob Priddy committed
83
            throw new EntityNotFoundException('Token with id "' . $tokenId . '" not found.');
84
85
        }

Jacob Priddy's avatar
Jacob Priddy committed
86
        $this->saveToken($dbToken, $token);
87
88
89
90
91
92
93
94
95

        return self::dbTokenToToken($dbToken);
    }

    /**
     * @inheritDoc
     */
    public function get(string $tokenId): ?Token
    {
96
        /** @var \App\Token|null $token */
97
        $token = \App\Token::query()->find(self::castToInt($tokenId));
98
99
100
101
102
103

        if (!$token) {
            return null;
        }

        return self::dbTokenToToken($token);
Jacob Priddy's avatar
Jacob Priddy committed
104
105
    }

Jacob Priddy's avatar
Jacob Priddy committed
106
107
108
109
    /**
     * @param \App\Token $token
     * @return \Source\Entities\Token
     */
110
    protected static function dbTokenToToken(\App\Token $token): Token
Jacob Priddy's avatar
Jacob Priddy committed
111
112
    {
        return new Token(
113
114
            $token->getAttribute('id'),
            $token->getAttribute('user_id'),
115
            new HashedSearchable($token->getAttribute('api_token')),
116
117
118
119
            $token->getAttribute('name'),
            $token->getAttribute('expires_at'),
            $token->getAttribute('created_at'),
            $token->getAttribute('updated_at')
Jacob Priddy's avatar
Jacob Priddy committed
120
121
122
        );
    }

Jacob Priddy's avatar
Jacob Priddy committed
123
124
125
    /**
     * @inheritDoc
     */
126
    public function findValidToken(?HashedSearchable $hash): ?Token
Jacob Priddy's avatar
Jacob Priddy committed
127
    {
128
129
130
131
        if (!$hash) {
            return null;
        }

132
        /** @var \App\Token|null $found */
133
134
135
        $found = \App\Token::query()->where('api_token', $hash->getHash())->where('expires_at', '>', Carbon::now())->orWhere(
            static function (Builder $query) use ($hash) {
                $query->where('api_token', $hash->getHash())->where('expires_at', null);
Jacob Priddy's avatar
Jacob Priddy committed
136
137
            }
        )->first();
Jacob Priddy's avatar
Jacob Priddy committed
138

139

Jacob Priddy's avatar
Jacob Priddy committed
140
141
142
143
        if (!$found) {
            return null;
        }

144
        return self::dbTokenToToken($found);
145
146
147
148
149
    }

    /**
     * @inheritDoc
     */
150
    public function invalidateToken(?HashedSearchable $hash): void
Jacob Priddy's avatar
Jacob Priddy committed
151
    {
152
153
154
155
        if (!$hash) {
            return;
        }

156
        /** @var \App\Token|null $found */
157
158
159
        $found = \App\Token::query()->where('api_token', $hash)->where('expires_at', '>', Carbon::now())->orWhere(
            static function (Builder $query) use ($hash) {
                $query->where('api_token', $hash->getHash())->where('expires_at', null);
Jacob Priddy's avatar
Jacob Priddy committed
160
161
            }
        )->first();
162
163

        if ($found) {
164
            $found->setAttribute('expires_at', Carbon::now());
165
166
            $found->save();
        }
Jacob Priddy's avatar
Jacob Priddy committed
167
    }
Jacob Priddy's avatar
Jacob Priddy committed
168
169
170
171
172
173
174
175

    /**
     * @inheritDoc
     */
    public static function generateTokenString(): string
    {
        return Str::random(60);
    }
Jacob Priddy's avatar
Jacob Priddy committed
176
177
178
179
180
181

    /**
     * @inheritDoc
     */
    public function invalidateTokenById(string $tokenId): void
    {
182
        /** @var \App\Token|null $dbToken */
183
        $dbToken = \App\Token::query()->find(self::castToInt($tokenId));
Jacob Priddy's avatar
Jacob Priddy committed
184
185

        if (!$dbToken) {
Jacob Priddy's avatar
Jacob Priddy committed
186
            throw new EntityNotFoundException('Token not found.');
Jacob Priddy's avatar
Jacob Priddy committed
187
188
        }

189
        $token = self::dbTokenToToken($dbToken);
Jacob Priddy's avatar
Jacob Priddy committed
190
191

        if ($token->isValid()) {
192
193
            $dbToken->setAttribute('expires_at', Carbon::now());
            $dbToken->save();
Jacob Priddy's avatar
Jacob Priddy committed
194
195
        }
    }
196
197
198
199

    /**
     * @inheritDoc
     */
200
    public function filter(?string $userId = null, ?Carbon $validAt = null): array
201
    {
202
        $query = \App\Token::query()->whereNotNull('name')->orderByDesc('created_at');
203

204
        if ($userId) {
205
            $query->where('user_id', self::castToInt($userId));
206
        }
207

208
209
210
211
212
213
        if ($validAt) {
            $query->where(static function (Builder $builder) use ($validAt) {
                $builder->where('expires_at', '>', $validAt)
                    ->orWhereNull('expires_at');
            });
        }
214
215
216

        return array_map(static function (\App\Token $token) {
            return self::dbTokenToToken($token);
217
        }, $query->get()->values()->all());
218
    }
Jacob Priddy's avatar
Jacob Priddy committed
219
}