Skip to content

Commit

Permalink
Feature: JWT authenticated security
Browse files Browse the repository at this point in the history
  • Loading branch information
ok200paul committed Sep 5, 2024
1 parent e437937 commit 2ead2e4
Show file tree
Hide file tree
Showing 25 changed files with 15,663 additions and 1,827 deletions.
9 changes: 4 additions & 5 deletions .env.testing
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ APP_ENV=testing
APP_KEY=base64:E/+xI5BYR6yy+PoKz6nv60FVET0yEcmB0d+bjDM4X2E=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://vine.test
APP_PRODUCTION_URL=http://vine.test
APP_URL=https://vine.test
APP_PRODUCTION_URL=https://vine.test

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
Expand All @@ -31,9 +31,8 @@ SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=open-food-network-vouchers

SANCTUM_STATEFUL_DOMAINS=open-food-network-vouchers.test
SESSION_DOMAIN=vine.test
SANCTUM_STATEFUL_DOMAINS=vine.test

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
Expand Down
45 changes: 38 additions & 7 deletions app/Console/Commands/TestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
namespace App\Console\Commands;

use App\Models\AuditItem;
use App\Models\PersonalAccessToken;
use App\Models\Team;
use App\Models\User;
use App\Models\Voucher;
use App\Models\VoucherSet;
use App\Services\PersonalAccessTokenService;
use Illuminate\Console\Command;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Token\Builder;

class TestCommand extends Command
{
Expand All @@ -30,12 +37,36 @@ class TestCommand extends Command
*/
public function handle()
{
$users = User::factory(100)->createQuietly();
$teams = Team::factory(100)->createQuietly();
$vouchers = Voucher::factory(100)->createQuietly();
$voucherSets = VoucherSet::factory(100)->createQuietly();
$auditItems = AuditItem::factory(100)->createQuietly([
'team_id' => 1,
]);
$model = PersonalAccessToken::find(5);
$jwt = PersonalAccessTokenService::generateJwtForPersonalAccessToken($model);
dd($jwt);
$tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default()));
$algorithm = new Sha256();
$signingKey = InMemory::plainText('BC0HYlfCbvFcn3BqbcwGkOdTOVilkdn3');

$now = new \DateTimeImmutable();
$token = $tokenBuilder
// Configures the issuer (iss claim)
->issuedBy(env('APP_URL'))
// Configures the audience (aud claim)
// ->permittedFor('http://example.org')
// Configures the subject of the token (sub claim)
// ->relatedTo('component1')
// Configures the id (jti claim)
// ->identifiedBy('4f1g23a12aa')
// Configures the time that the token was issue (iat claim)
->issuedAt($now)
// Configures the time that the token can be used (nbf claim)
// ->canOnlyBeUsedAfter($now->modify('+1 minute'))
// Configures the expiration time of the token (exp claim)
->expiresAt($now->modify('+1 hour'))
// Configures a new claim, called "uid"
// ->withClaim('uid', 1)
// Configures a new header, called "foo"
// ->withHeader('foo', 'bar')
// Builds a new token
->getToken($algorithm, $signingKey);

echo $token->toString();
}
}
25 changes: 16 additions & 9 deletions app/Enums/ApiResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@
*/
enum ApiResponse: string
{
case RESPONSE_DELETED = 'Deleted';
case RESPONSE_ERROR = 'Error';
case RESPONSE_METHOD_NOT_ALLOWED = 'Method Not Allowed';
case RESPONSE_NOT_FOUND = 'Not found';
case RESPONSE_OK = 'OK';
case RESPONSE_SAVED = 'Saved';
case RESPONSE_TOKEN_NOT_ALLOWED_TO_DO_THIS = 'Token not allowed to do this.';
case RESPONSE_QUERY_FILTER_DISALLOWED = 'Query filter disallowed';
case RESPONSE_UPDATED = 'Updated';
case RESPONSE_DELETED = 'Deleted';
case RESPONSE_ERROR = 'Error';
case RESPONSE_METHOD_NOT_ALLOWED = 'Method Not Allowed';
case RESPONSE_NOT_FOUND = 'Not found';
case RESPONSE_OK = 'OK';
case RESPONSE_SAVED = 'Saved';
case RESPONSE_TOKEN_NOT_ALLOWED_TO_DO_THIS = 'Token not allowed to do this.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT = 'Incorrect authorization signature.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT_JWT_HEADER_REQUIRED = 'JWT Authorization header required.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT_IAT_CLAIM_REQUIRED = 'IAT claim required.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT_EXP_CLAIM_REQUIRED = 'EXP claim required.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT_IAT_EXPIRED = 'IAT claim expired.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT_IAT_EXP_TOO_LARGE = 'IAT and EXP too far apart. Max diff: 1 minute.';
case RESPONSE_AUTHORIZATION_SIGNATURE_INCORRECT_EXPIRED = 'Token expired.';
case RESPONSE_QUERY_FILTER_DISALLOWED = 'Query filter disallowed';
case RESPONSE_UPDATED = 'Updated';
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use App\Models\User;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Expand Down Expand Up @@ -103,7 +104,10 @@ public function store(): JsonResponse
$token = $user->createToken($name, $tokenAbilities);

$this->message = ApiResponse::RESPONSE_SAVED->value;
$this->data = ['token' => $token->plainTextToken];
$this->data = [
'token' => $token->plainTextToken,
'secret' => Crypt::decrypt($token->accessToken->secret),
];

event(new PersonalAccessTokenWasCreated($token->accessToken));

Expand Down
68 changes: 64 additions & 4 deletions app/Http/Controllers/Api/V1/ApiMyTeamAuditItemsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@
use App\Http\Controllers\Api\HandlesAPIRequests;
use App\Http\Controllers\Controller;
use App\Models\AuditItem;
use Auth;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Knuckles\Scribe\Attributes\Authenticated;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\QueryParam;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\Subgroup;
use Knuckles\Scribe\Attributes\UrlParam;

#[Group('App Endpoints')]
#[Subgroup('/my-team-audit-items', 'Retrieve your team audit trail.')]
class ApiMyTeamAuditItemsController extends Controller
{
use HandlesAPIRequests;
Expand All @@ -32,6 +41,31 @@ class ApiMyTeamAuditItemsController extends Controller
*
* @throws DisallowedApiFieldException
*/
#[Endpoint(
title : 'GET /',
description : 'Retrieve your audit items. Automatically filtered to your current team.',
authenticated: true
)]
#[Authenticated]
#[QueryParam(
name : 'cached',
type : 'bool',
description: 'Request the response to be cached. Default: `true`.',
required : false,
example : true
)]
#[QueryParam(
name : 'fields',
type : 'string',
description: 'Comma-separated list of database fields to return within the object.',
required : false,
example : 'id,created_at'
)]
#[Response(
content : '{"meta": {"responseCode": 200, "limit": 50, "offset": 0, "message": "", "cached": false, "availableRelations": []}, "data": {"current_page": 1, "data": {"id": 1, "name": "Team A", "created_at": "2024-08-16T06:54:28.000000Z", "updated_at": "2024-08-16T06:54:28.000000Z", "deleted_at": null}], "first_page_url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team-audit-items?page=1", "from": 1, "last_page": 1, "last_page_url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team-audit-items?page=1", "links": [{"url": null, "label": "« Previous", "active": false}, {"url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team-audit-items?page=1", "label": "1", "active": true}, {"url": null, "label": "Next »", "active": false}], "next_page_url": null, "path": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team-audit-items", "per_page": 50, "prev_page_url": null, "to": 2, "total": 2}}',
status : 200,
description: ''
)]
public function index(): JsonResponse
{
$this->query = AuditItem::where('auditable_team_id', Auth::user()->current_team_id)->with($this->associatedData);
Expand All @@ -43,7 +77,7 @@ public function index(): JsonResponse

/**
* POST /
*
* @hideFromAPIDocumentation
* @return JsonResponse
*/
public function store(): JsonResponse
Expand All @@ -63,6 +97,32 @@ public function store(): JsonResponse
*
* @throws DisallowedApiFieldException
*/
#[Endpoint(
title : 'GET /{id}',
description : 'Retrieve a single audit item for your team.',
authenticated: true,
)]
#[Authenticated]
#[UrlParam(
name : 'id',
type : 'int',
description: 'ID.',
example : '1234'
)]
#[QueryParam(
name : 'cached',
type : 'bool',
description: 'Request the response to be cached. Default: `true`.',
required : false,
example : 1
)]
#[QueryParam(
name : 'fields',
type : 'string',
description: 'Comma-separated list of database fields to return within the object.',
required : false,
example : 'id,created_at'
)]
public function show(int $id)
{
$this->query = AuditItem::where('auditable_team_id', Auth::user()->current_team_id)->with($this->associatedData);
Expand All @@ -76,7 +136,7 @@ public function show(int $id)
* PUT /{id}
*
* @param string $id
*
* @hideFromAPIDocumentation
* @return JsonResponse
*/
public function update(string $id)
Expand All @@ -89,7 +149,7 @@ public function update(string $id)

/**
* DELETE / {id}
*
* @hideFromAPIDocumentation
* @param string $id
*
* @return JsonResponse
Expand Down
6 changes: 4 additions & 2 deletions app/Http/Controllers/Api/V1/ApiMyTeamController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\QueryParam;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\Subgroup;

#[Group('/my-team', 'Retrieve your team details.')]
#[Group('App Endpoints')]
#[Subgroup('/my-team', 'Retrieve your team details.')]
class ApiMyTeamController extends Controller
{
use HandlesAPIRequests;
Expand Down Expand Up @@ -61,7 +63,7 @@ class ApiMyTeamController extends Controller
example : 'id,created_at'
)]
#[Response(
content : '{"meta": {"responseCode": 200, "limit": 50, "offset": 0, "message": "", "cached": false, "availableRelations": []}, "data": {"current_page": 1, "data": {"id": 1, "name": "Team A", "created_at": "2024-08-16T06:54:28.000000Z", "updated_at": "2024-08-16T06:54:28.000000Z", "deleted_at": null}], "first_page_url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-teams?page=1", "from": 1, "last_page": 1, "last_page_url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-teams?page=1", "links": [{"url": null, "label": "« Previous", "active": false}, {"url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-teams?page=1", "label": "1", "active": true}, {"url": null, "label": "Next »", "active": false}], "next_page_url": null, "path": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-teams", "per_page": 50, "prev_page_url": null, "to": 2, "total": 2}}',
content : '{"meta": {"responseCode": 200, "limit": 50, "offset": 0, "message": "", "cached": false, "availableRelations": []}, "data": {"current_page": 1, "data": {"id": 1, "name": "Team A", "created_at": "2024-08-16T06:54:28.000000Z", "updated_at": "2024-08-16T06:54:28.000000Z", "deleted_at": null}], "first_page_url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team?page=1", "from": 1, "last_page": 1, "last_page_url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team?page=1", "links": [{"url": null, "label": "« Previous", "active": false}, {"url": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team?page=1", "label": "1", "active": true}, {"url": null, "label": "Next »", "active": false}], "next_page_url": null, "path": "https:\/\/vine.openfoodnetwork.org.au\/api\/v1\/my-team", "per_page": 50, "prev_page_url": null, "to": 2, "total": 2}}',
status : 200,
description: ''
)]
Expand Down
4 changes: 3 additions & 1 deletion app/Http/Controllers/Api/V1/ApiMyTeamVouchersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\QueryParam;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\Subgroup;
use Knuckles\Scribe\Attributes\UrlParam;

#[Group('/my-team-vouchers', 'Manage your team\'s vouchers. Returns vouchers generated by your team, and allocated to your team.')]
#[Group('App Endpoints')]
#[Subgroup('/my-team-vouchers', 'Manage your team\'s vouchers. Returns vouchers generated by your team, and allocated to your team.')]
class ApiMyTeamVouchersController extends Controller
{
use HandlesAPIRequests;
Expand Down
4 changes: 3 additions & 1 deletion app/Http/Controllers/Api/V1/ApiMyTeamsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\QueryParam;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\Subgroup;

#[Group('/my-teams', 'Teams you are a member of.')]
#[Group('App Endpoints')]
#[Subgroup('/my-teams', 'Teams you are a member of.')]
class ApiMyTeamsController extends Controller
{
use HandlesAPIRequests;
Expand Down
4 changes: 3 additions & 1 deletion app/Http/Controllers/Api/V1/ApiShopsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\Subgroup;

#[Group('/shops', 'API for managing shops')]
#[Group('App Endpoints')]
#[Subgroup('/shops', 'API for managing shops')]
class ApiShopsController extends Controller
{
use HandlesAPIRequests;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\QueryParam;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\Subgroup;
use Knuckles\Scribe\Attributes\UrlParam;

#[Group('/system-statistics', 'Check out some Vine platform statistics.')]
#[Group('App Endpoints')]
#[Subgroup('/system-statistics', 'Vine platform statistics.')]
class ApiSystemStatisticsController extends Controller
{
use HandlesAPIRequests;
Expand Down
Loading

0 comments on commit 2ead2e4

Please sign in to comment.