Commit 61ec041b authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

So apparently I wasn't hashing tokens... oops

parent 8e2a6cb0
...@@ -53,13 +53,13 @@ class AuthController extends ApiController ...@@ -53,13 +53,13 @@ class AuthController extends ApiController
*/ */
public function login(AuthenticateUseCase $authenticateUseCase): JsonResponse public function login(AuthenticateUseCase $authenticateUseCase): JsonResponse
{ {
$presenter = new APIPresenter();
$this->validate($this->request, [ $this->validate($this->request, [
'email' => 'required|string|email', 'email' => 'required|string|email',
'password' => 'required|string', 'password' => 'required|string',
]); ]);
$presenter = new APIPresenter();
$authenticateUseCase->attempt($presenter, $this->request->all()); $authenticateUseCase->attempt($presenter, $this->request->all());
return $this->respondWithData($presenter->getViewModel())->withCookie( return $this->respondWithData($presenter->getViewModel())->withCookie(
......
...@@ -38,7 +38,7 @@ class DoorsController extends ApiController ...@@ -38,7 +38,7 @@ class DoorsController extends ApiController
* *
* @authenticated * @authenticated
* @paginated * @paginated
* @queryParam query Searches doors for location, name, and version. * @queryParam query Searches doors for location, name, and version. Example: bat
* *
* @param \Source\UseCases\Doors\GetDoors\GetDoorsUseCase $getDoors * @param \Source\UseCases\Doors\GetDoors\GetDoorsUseCase $getDoors
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
......
...@@ -15,9 +15,25 @@ use Source\UseCases\Tokens\CreateToken\APIPresenter as CreateTokenAPIPresenter; ...@@ -15,9 +15,25 @@ use Source\UseCases\Tokens\CreateToken\APIPresenter as CreateTokenAPIPresenter;
use Source\UseCases\Tokens\ExpireToken\APIPresenter as ExpireTokenAPIPresenter; use Source\UseCases\Tokens\ExpireToken\APIPresenter as ExpireTokenAPIPresenter;
use Source\UseCases\Tokens\UpdateToken\APIPresenter as UpdateTokenAPIPresenter; use Source\UseCases\Tokens\UpdateToken\APIPresenter as UpdateTokenAPIPresenter;
/**
* @group Token Management
*
* This set of routes is responsible for management of tokens for users. You cannot delete tokens for records sake, but
* you can expire them, which makes them unusable.
*/
class TokensController extends ApiController class TokensController extends ApiController
{ {
/** /**
* Filter Tokens
*
* This route filters all tokens by user_id or valid date. If valid_at is set, only tokens valid on that date will
* be returned.
*
* @authenticated
* @paginated
* @queryParam user_id The user id ot filter on. Example: 1
* @queryParam valid_at The date to filter when tokens are valid: Example: 2020-06-04 19:41:55
*
* @param \Source\UseCases\Tokens\GetTokens\GetTokensUseCase $allTokens * @param \Source\UseCases\Tokens\GetTokens\GetTokensUseCase $allTokens
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException * @throws \Source\Exceptions\AuthorizationException
...@@ -49,6 +65,15 @@ class TokensController extends ApiController ...@@ -49,6 +65,15 @@ class TokensController extends ApiController
} }
/** /**
* Get Token
*
* This endpoint retrieves all metadata about the token.
*
* @authenticated
* @urlParam tokenId required The ID of the token to get information for. Example: 1
*
* @response 404 {"status":"error","code":404,"message":"Entity not found"}
*
* @param \Source\UseCases\Tokens\GetToken\GetTokenUseCase $token * @param \Source\UseCases\Tokens\GetToken\GetTokenUseCase $token
* @param string $tokenId * @param string $tokenId
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
...@@ -67,6 +92,18 @@ class TokensController extends ApiController ...@@ -67,6 +92,18 @@ class TokensController extends ApiController
} }
/** /**
* Create Token
*
* This route generates a new token for a given user.
*
* @authenticated
* @bodyParam name string required The name of the token for identifying it. Example: CSLab Self-Serve Token
* @bodyParam user_id string required The id of the user the token will authenticate. Example: 1
* @bodyParam expires_at string The datetime that the token will no longer be usable. Example: 2020-06-04 19:35:05
*
* @response 422
* {"message":"The given data was invalid.","errors":{"name":["The name field is required."],"user_id":["The user id field is required."]}}
*
* @param \Source\UseCases\Tokens\CreateToken\CreateTokenUseCase $createToken * @param \Source\UseCases\Tokens\CreateToken\CreateTokenUseCase $createToken
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException * @throws \Illuminate\Validation\ValidationException
...@@ -80,7 +117,7 @@ class TokensController extends ApiController ...@@ -80,7 +117,7 @@ class TokensController extends ApiController
$this->validate($this->request, [ $this->validate($this->request, [
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'user_id' => 'required|numeric', 'user_id' => 'required|numeric',
'expires_at' => 'date', 'expires_at' => 'nullable|date',
]); ]);
$presenter = new CreateTokenAPIPresenter(); $presenter = new CreateTokenAPIPresenter();
...@@ -92,6 +129,14 @@ class TokensController extends ApiController ...@@ -92,6 +129,14 @@ class TokensController extends ApiController
} }
/** /**
* Update Token
*
* This route updates a stored token. One can only update the name and expiry date.
* @authenticated
* @urlParam tokenId required The token id to update. Example: 2
* @bodyParam name string The new name for the token. Example: New token name
* @bodyParam expires_at datetime The new expiry date. Can be null to never expire. Example: 2023-06-04 19:46:40
*
* @param \Source\UseCases\Tokens\UpdateToken\UpdateTokenUseCase $updateToken * @param \Source\UseCases\Tokens\UpdateToken\UpdateTokenUseCase $updateToken
* @param string $tokenId * @param string $tokenId
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
...@@ -105,19 +150,24 @@ class TokensController extends ApiController ...@@ -105,19 +150,24 @@ class TokensController extends ApiController
$this->validate($this->request, [ $this->validate($this->request, [
'name' => 'string|max:255', 'name' => 'string|max:255',
'expires_at' => 'date', 'expires_at' => 'nullable|date',
]); ]);
$attributes = $this->request->all();
$presenter = new UpdateTokenAPIPresenter(); $presenter = new UpdateTokenAPIPresenter();
$updateToken->update($tokenId, $attributes, $presenter); $updateToken->update($tokenId, $this->request->all(), $presenter);
return $this->respondWithData($presenter->getViewModel()); return $this->respondWithData($presenter->getViewModel());
} }
/** /**
* Expire Token
*
* This endpoint will instantly expire the specified token.
*
* @authenticated
* @urlParam tokenId required The id of the token to expire. Example: 2
*
* @param \Source\UseCases\Tokens\ExpireToken\ExpireTokenUseCase $expireToken * @param \Source\UseCases\Tokens\ExpireToken\ExpireTokenUseCase $expireToken
* @param string $tokenId * @param string $tokenId
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
......
...@@ -36,11 +36,11 @@ class UsersController extends ApiController ...@@ -36,11 +36,11 @@ class UsersController extends ApiController
* List/Search Users * List/Search Users
* *
* This endpoint can list/search/query the list of users. If the parameter is not given it returns a paginated list * This endpoint can list/search/query the list of users. If the parameter is not given it returns a paginated list
* of all doors * of all doors. This endpoint can search first, last, display name, email, and employee id.
* *
* @authenticated * @authenticated
* @paginated * @paginated
* @queryParam query Searches for first, last, and display names as well as email and peoplesoft employee id. * @queryParam query The query to search on Example: admin
* *
* @param \Source\UseCases\Users\GetUsers\GetUsersUseCase $getAllUsers * @param \Source\UseCases\Users\GetUsers\GetUsersUseCase $getAllUsers
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
......
...@@ -15,6 +15,7 @@ class UsersSeeder extends Seeder ...@@ -15,6 +15,7 @@ class UsersSeeder extends Seeder
* @param \Source\Gateways\GroupUser\DatabaseGroupUserRepository $repository * @param \Source\Gateways\GroupUser\DatabaseGroupUserRepository $repository
* @return void * @return void
* @throws \Source\Exceptions\EntityNotFoundException * @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\EntityExistsException
*/ */
public function run(DatabaseUsersRepository $users, DatabaseGroupUserRepository $repository): void public function run(DatabaseUsersRepository $users, DatabaseGroupUserRepository $repository): void
{ {
......
...@@ -54,4 +54,12 @@ class HashedSearchable ...@@ -54,4 +54,12 @@ class HashedSearchable
{ {
return $this->hash; return $this->hash;
} }
/**
* @return string
*/
public function __toString(): string
{
return $this->getHash();
}
} }
<?php
namespace Source\Entities;
class RawToken
{
protected string $raw;
/**
* @var \Source\Entities\Token
*/
protected Token $token;
public function __construct(string $raw, Token $token)
{
$this->raw = $raw;
$this->token = $token;
}
/**
* @return string
*/
public function getRaw(): string
{
return $this->raw;
}
/**
* @return \Source\Entities\Token
*/
public function getToken(): Token
{
return $this->token;
}
}
...@@ -23,9 +23,9 @@ class Token ...@@ -23,9 +23,9 @@ class Token
protected int $userId; protected int $userId;
/** /**
* @var string * @var \Source\Entities\HashedSearchable
*/ */
protected string $tokenString; protected HashedSearchable $tokenHash;
/** /**
* @var string|null * @var string|null
...@@ -50,8 +50,8 @@ class Token ...@@ -50,8 +50,8 @@ class Token
/** /**
* @param int $id * @param int $id
* @param int $userId * @param int $userId
* @param \Source\Entities\HashedSearchable $tokenHash
* @param string|null $name * @param string|null $name
* @param string $tokenString
* @param Carbon|null $expiresAt * @param Carbon|null $expiresAt
* @param Carbon|null $createdAt * @param Carbon|null $createdAt
* @param Carbon|null $updatedAt * @param Carbon|null $updatedAt
...@@ -59,7 +59,7 @@ class Token ...@@ -59,7 +59,7 @@ class Token
public function __construct( public function __construct(
int $id, int $id,
int $userId, int $userId,
string $tokenString, HashedSearchable $tokenHash,
?string $name = null, ?string $name = null,
?Carbon $expiresAt = null, ?Carbon $expiresAt = null,
?Carbon $createdAt = null, ?Carbon $createdAt = null,
...@@ -68,19 +68,23 @@ class Token ...@@ -68,19 +68,23 @@ class Token
$this->id = $id; $this->id = $id;
$this->userId = $userId; $this->userId = $userId;
$this->name = $name; $this->name = $name;
$this->tokenString = $tokenString; $this->tokenHash = $tokenHash;
$this->expiresAt = $expiresAt; $this->expiresAt = $expiresAt;
$this->createdAt = $createdAt; $this->createdAt = $createdAt;
$this->updatedAt = $updatedAt; $this->updatedAt = $updatedAt;
} }
/** /**
* @param string|null $token * @param \Source\Entities\HashedSearchable|null $token
* @return bool * @return bool
*/ */
public function matches(?string $token): bool public function matches(?HashedSearchable $token): bool
{ {
return $this->tokenString === $token; if (!$token) {
return false;
}
return $this->getToken()->getHash() === $token->getHash();
} }
/** /**
...@@ -121,11 +125,11 @@ class Token ...@@ -121,11 +125,11 @@ class Token
} }
/** /**
* @return string * @return \Source\Entities\HashedSearchable
*/ */
public function getTokenString(): string public function getToken(): HashedSearchable
{ {
return $this->tokenString; return $this->tokenHash;
} }
/** /**
...@@ -205,12 +209,4 @@ class Token ...@@ -205,12 +209,4 @@ class Token
return $this->userId === (int)$id; return $this->userId === (int)$id;
} }
/**
* @return bool
*/
public function hasName(): bool
{
return (bool)$this->name;
}
} }
...@@ -217,7 +217,7 @@ class User ...@@ -217,7 +217,7 @@ class User
} }
/** /**
* @param string $doorcode * @param \Source\Entities\HashedSearchable|null $doorcode
* @return bool * @return bool
*/ */
public function hasDoorcodeOf(?HashedSearchable $doorcode): bool public function hasDoorcodeOf(?HashedSearchable $doorcode): bool
......
...@@ -8,6 +8,8 @@ use Carbon\Carbon; ...@@ -8,6 +8,8 @@ use Carbon\Carbon;
use Source\Entities\Token; use Source\Entities\Token;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Source\Sanitize\CastsTo; use Source\Sanitize\CastsTo;
use Source\Entities\RawToken;
use Source\Entities\HashedSearchable;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Source\Exceptions\EntityNotFoundException; use Source\Exceptions\EntityNotFoundException;
...@@ -30,7 +32,7 @@ class DatabaseTokensRepository implements TokensRepository ...@@ -30,7 +32,7 @@ class DatabaseTokensRepository implements TokensRepository
$dbToken = new \App\Token(); $dbToken = new \App\Token();
$dbToken->setAttribute('name', $token->getName()); $dbToken->setAttribute('name', $token->getName());
$dbToken->setAttribute('user_id', $token->getUserId()); $dbToken->setAttribute('user_id', $token->getUserId());
$dbToken->setAttribute('api_token', $token->getTokenString()); $dbToken->setAttribute('api_token', $token->getToken()->getHash());
$dbToken->setAttribute('expires_at', $token->getExpiresAt()); $dbToken->setAttribute('expires_at', $token->getExpiresAt());
$dbToken->save(); $dbToken->save();
...@@ -40,15 +42,16 @@ class DatabaseTokensRepository implements TokensRepository ...@@ -40,15 +42,16 @@ class DatabaseTokensRepository implements TokensRepository
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function createLoginToken(string $userId): Token public function createLoginToken(string $userId, string $salt): RawToken
{ {
return $this->create(new Token( $raw = self::generateTokenString();
return new RawToken($raw, $this->create(new Token(
0, 0,
$userId, $userId,
self::generateTokenString(), HashedSearchable::hash($salt, $raw),
null, null,
Carbon::now()->addDay() Carbon::now()->addDay()
)); )));
} }
/** /**
...@@ -65,7 +68,7 @@ class DatabaseTokensRepository implements TokensRepository ...@@ -65,7 +68,7 @@ class DatabaseTokensRepository implements TokensRepository
$dbToken->setAttribute('name', $token->getName()); $dbToken->setAttribute('name', $token->getName());
$dbToken->setAttribute('expired_at', $token->getExpiresAt()); $dbToken->setAttribute('expired_at', $token->getExpiresAt());
$dbToken->setAttribute('api_token', $token->getTokenString()); $dbToken->setAttribute('api_token', $token->getToken()->getHash());
$dbToken->save(); $dbToken->save();
return self::dbTokenToToken($dbToken); return self::dbTokenToToken($dbToken);
...@@ -95,7 +98,7 @@ class DatabaseTokensRepository implements TokensRepository ...@@ -95,7 +98,7 @@ class DatabaseTokensRepository implements TokensRepository
return new Token( return new Token(
$token->getAttribute('id'), $token->getAttribute('id'),
$token->getAttribute('user_id'), $token->getAttribute('user_id'),
$token->getAttribute('api_token'), new HashedSearchable($token->getAttribute('api_token')),
$token->getAttribute('name'), $token->getAttribute('name'),
$token->getAttribute('expires_at'), $token->getAttribute('expires_at'),
$token->getAttribute('created_at'), $token->getAttribute('created_at'),
...@@ -106,12 +109,16 @@ class DatabaseTokensRepository implements TokensRepository ...@@ -106,12 +109,16 @@ class DatabaseTokensRepository implements TokensRepository
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function findValidToken(string $token): ?Token public function findValidToken(?HashedSearchable $hash): ?Token
{ {
if (!$hash) {
return null;
}
/** @var \App\Token|null $found */ /** @var \App\Token|null $found */
$found = \App\Token::query()->where('api_token', $token)->where('expires_at', '>', Carbon::now())->orWhere( $found = \App\Token::query()->where('api_token', $hash->getHash())->where('expires_at', '>', Carbon::now())->orWhere(
static function (Builder $query) use ($token) { static function (Builder $query) use ($hash) {
$query->where('api_token', $token)->where('expires_at', null); $query->where('api_token', $hash->getHash())->where('expires_at', null);
} }
)->first(); )->first();
...@@ -126,12 +133,16 @@ class DatabaseTokensRepository implements TokensRepository ...@@ -126,12 +133,16 @@ class DatabaseTokensRepository implements TokensRepository
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function invalidateToken(string $token): void public function invalidateToken(?HashedSearchable $hash): void
{ {
if (!$hash) {
return;
}
/** @var \App\Token|null $found */ /** @var \App\Token|null $found */
$found = \App\Token::query()->where('api_token', $token)->where('expires_at', '>', Carbon::now())->orWhere( $found = \App\Token::query()->where('api_token', $hash)->where('expires_at', '>', Carbon::now())->orWhere(
static function (Builder $query) use ($token) { static function (Builder $query) use ($hash) {
$query->where('api_token', $token)->where('expires_at', null); $query->where('api_token', $hash->getHash())->where('expires_at', null);
} }
)->first(); )->first();
......
...@@ -5,6 +5,8 @@ namespace Source\Gateways\Tokens; ...@@ -5,6 +5,8 @@ namespace Source\Gateways\Tokens;
use Carbon\Carbon; use Carbon\Carbon;
use Source\Entities\Token; use Source\Entities\Token;
use Source\Entities\RawToken;
use Source\Entities\HashedSearchable;
use Source\Exceptions\EntityNotFoundException; use Source\Exceptions\EntityNotFoundException;
class InMemoryTokensRepository implements TokensRepository class InMemoryTokensRepository implements TokensRepository
...@@ -31,17 +33,16 @@ class InMemoryTokensRepository implements TokensRepository ...@@ -31,17 +33,16 @@ class InMemoryTokensRepository implements TokensRepository
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function createLoginToken(string $userId): Token public function createLoginToken(string $userId, string $salt): RawToken
{ {
$token = new Token( $raw = self::generateTokenString();
static::$idCounter++, return new RawToken($raw, $this->create(new Token(
0,
$userId, $userId,
self::generateTokenString(), HashedSearchable::hash($salt, $raw),
null, null,
Carbon::now()->addDay() Carbon::now()->addDay()
); )));
$this->tokens[] = $token;
return $token;
} }
/** /**
...@@ -83,10 +84,10 @@ class InMemoryTokensRepository implements TokensRepository ...@@ -83,10 +84,10 @@ class InMemoryTokensRepository implements TokensRepository
} }
/** @inheritDoc */ /** @inheritDoc */
public function findValidToken(string $tokenToMatch): ?Token public function findValidToken(?HashedSearchable $hash): ?Token