Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
19 / 19 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
| Tokens | |
100.00% |
19 / 19 |
|
100.00% |
4 / 4 |
8 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| create | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
5 | |||
| list | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| revoke | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Cosray\User; |
| 6 | |
| 7 | use Celema\Quma\Database; |
| 8 | use Cosray\Actor; |
| 9 | use Cosray\Uid; |
| 10 | use Cosray\User; |
| 11 | use DateTimeImmutable; |
| 12 | use DateTimeInterface; |
| 13 | use ValueError; |
| 14 | |
| 15 | /** |
| 16 | * A user's API tokens. A token acts as its user, so the user's roles decide |
| 17 | * what it may do; only its sha256 is stored. |
| 18 | */ |
| 19 | final class Tokens |
| 20 | { |
| 21 | public const int MAX_NAME_LENGTH = 255; |
| 22 | |
| 23 | public function __construct( |
| 24 | private readonly Database $db, |
| 25 | ) {} |
| 26 | |
| 27 | /** |
| 28 | * The token itself is returned only here; it cannot be read again. |
| 29 | * |
| 30 | * @return array{uid: string, token: string} |
| 31 | */ |
| 32 | public function create(User $user, string $name, Actor $actor, ?DateTimeInterface $expires = null): array |
| 33 | { |
| 34 | $name = trim($name); |
| 35 | |
| 36 | if ($name === '' || mb_strlen($name) > self::MAX_NAME_LENGTH) { |
| 37 | throw new ValueError('A token needs a name of 1 to ' . self::MAX_NAME_LENGTH . ' characters'); |
| 38 | } |
| 39 | |
| 40 | if ($expires !== null && $expires <= new DateTimeImmutable()) { |
| 41 | throw new ValueError('A token must expire in the future'); |
| 42 | } |
| 43 | |
| 44 | $token = bin2hex(random_bytes(32)); |
| 45 | $uid = new Uid(Uid::ALPHABET_LOWERCASE_WORD_SAFE, 13)->generate(); |
| 46 | |
| 47 | $this->db->users->createToken([ |
| 48 | 'token' => hash('sha256', $token), |
| 49 | 'uid' => $uid, |
| 50 | 'usr' => $user->id, |
| 51 | 'name' => $name, |
| 52 | 'expires' => $expires?->format(DATE_ATOM), |
| 53 | 'actor' => $actor->id, |
| 54 | ])->run(); |
| 55 | |
| 56 | return ['uid' => $uid, 'token' => $token]; |
| 57 | } |
| 58 | |
| 59 | /** @return list<array{uid: string, name: string, created: string, expires: ?string, used: ?string}> */ |
| 60 | public function list(User $user): array |
| 61 | { |
| 62 | /** @var list<array{uid: string, name: string, created: string, expires: ?string, used: ?string}> */ |
| 63 | return $this->db->users->tokens(['usr' => $user->id])->all(); |
| 64 | } |
| 65 | |
| 66 | public function revoke(User $user, string $uid): bool |
| 67 | { |
| 68 | return $this->db->users->deleteToken(['uid' => $uid, 'usr' => $user->id])->len() > 0; |
| 69 | } |
| 70 | } |