Skip to content

Commit

Permalink
Merge pull request #6 from luttje/master
Browse files Browse the repository at this point in the history
Upgrade lcobucci/jwt to ^4.0
  • Loading branch information
bartjroos authored Apr 19, 2022
2 parents 49dc5ca + c59933a commit b83ca04
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 55 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor/
composer.lock
64 changes: 47 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@ Now using curio.codes!

__!!__ Please make sure your app is using _https_, to prevent unwanted exposure of token, secrets, etc.

In your laravel project run: `composer require studiokaa/amoclient`
To use amoclient in your project:

1. In your laravel project run: `composer require studiokaa/amoclient`
2. Set these keys in your .env file:
* AMO_CLIENT_ID
* AMO_CLIENT_SECRET
* AMO_API_LOG (optional)
* Default: no
* Set to 'yes' to make Amoclient log all usage of access_tokens and refresh_tokens to the default log-channel.
* AMO_APP_FOR (optional)
* Default: teachers
* This key determines if students can login to your application.
* May be one of:
* _all_: everyone can login, you may restrict access using guards or middleware.
* _teachers_: a student will be completely blocked and no user will be created when they try to login.
* AMO_USE_MIGRATION (optional)
* Default: yes
* Set to no if you want to use your own migration instead of the users migration this package provides
3. Alter your User model and add the line: `public $incrementing = false;`
4. (Recommended) Remove any default users-migration from your app, because Amoclient will conflict with it. Do _not_ remove the user-model. If you want to keep using your own migration, in your .env file set: `AMO_USE_MIGRATION=no`
5. Lastly, run `php artisan migrate`.

Now set these keys in your .env file:
* AMO_CLIENT_ID
* AMO_CLIENT_SECRET
* AMO_API_LOG
* Set to 'yes' to make Amoclient log all usage of access_tokens and refresh_tokens to the default log-channel.
* AMO_APP_FOR
* This key determines if students can login to your application.
* May be one of:
* _all_: everyone can login, you may restrict access using guards or middleware.
* _teachers_: a student will be completely blocked and no user will be created when they try to login.

Alter your User model by adding the line: `public $incrementing = false;`

You should remove any default users-migration from your app, because Amoclient will conflict with it. Do _not_ remove the user-model. If you want to keep using your own migration, in your .env file set: `AMO_USE_MIGRATION=no`

Lastly, run `php artisan migrate`.

## Usage

Expand Down Expand Up @@ -59,11 +63,15 @@ _Please note:_ a real logout cannot be accomplished at this time. If you log-out
### Laravel's `make:auth`
Don't use this in combination with Amoclient.


## AmoAPI

Apart from being the central login-server, _login.amo.rocks_ also exposes an api. Please note this api is currently undocumented, although there are options to explore the api:
* Refer to _amologin_'s [routes/api.php](https://github.com/StudioKaa/amologin/blob/master/routes/api.php) file.
* Play around at [apitest.amo.rocks](https://apitest.amo.rocks/).

### Amoclient API Interface

An example of calling the api through Amoclient;
```
namespace App\Http\Controllers;
Expand All @@ -81,7 +89,29 @@ class MyController extends Controller
```

**Known 'bug':** Currently the AmoAPI class doesn't check if the token expired but just refreshes it anytime you use it.

### `AmoAPI::get($endpoint)`
* Performs an HTTP-request like `GET https://api.amo.rocks/$endpoint`.
* This method relies on a user being authenticated through the amoclient first. Please do call this method only from routes and/or controllers protected by the _auth_ middlware.
* Returns a Laravel-collection


## Contributing

1. Clone this repository to your device
2. Inside the root of this repository run `composer install`
3. Create a test project in which you will use this package (Follow [Usage](#usage) instructions above)
4. Add the package locally using the following additions to your composer.json:
```json
"repositories": [
{
"type": "path",
"url": "../amoclient"
}
],
```
* **Note:** `../amoclient` should point to where you cloned this package
5. Run `composer require "vendorname/packagename @dev"` inside the test project

You can now test and modify this package. Changes will immediately be reflected in the test project.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"minimum-stability": "stable",
"require": {
"lcobucci/jwt": "3.3.3",
"lcobucci/jwt": "^4.0",
"guzzlehttp/guzzle": "^7.0"
},
"autoload": {
Expand Down
20 changes: 12 additions & 8 deletions src/AmoAPI.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

namespace StudioKaa\Amoclient;
use Lcobucci\JWT\Parser;

use Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
Expand All @@ -26,6 +26,7 @@ public function get($endpoint)
private function call($endpoint = 'user', $method = 'GET')
{
$access_token = session('access_token');

if($access_token == null)
{
abort(401, 'No access token: probably not logged-in');
Expand All @@ -35,15 +36,16 @@ private function call($endpoint = 'user', $method = 'GET')

$this->log('START using access_token');

if($access_token->isExpired())
// TODO: Don't needlesly refresh the token
//if($access_token->isExpired())
{
$this->log('access_token expired, trying to refresh');
$access_token = $this->refresh(session('refresh_token'));
}
else
{
$this->log('Succesfully using current access_token');
}
// else
// {
// $this->log('Succesfully using current access_token');
// }

$response = $this->client->request($method, 'https://api.curio.codes' . $endpoint, [
'headers' => [
Expand All @@ -59,6 +61,8 @@ private function call($endpoint = 'user', $method = 'GET')

private function refresh($refresh_token)
{
$config = AmoclientHelper::getTokenConfig();

try
{
$response = $this->client->post('https://login.curio.codes/oauth/token', [
Expand All @@ -73,8 +77,8 @@ private function refresh($refresh_token)
$this->log('new access_token acquired');

$tokens = json_decode( (string) $response->getBody() );
$access_token = (new Parser())->parse((string) $tokens->access_token);
session()->put('access_token', $access_token);
$access_token = $config->parser()->parse((string) $tokens->access_token)->toString();
session()->put('access_token', $tokens->access_token);
session()->put('refresh_token', $tokens->refresh_token);

return $access_token;
Expand Down
50 changes: 24 additions & 26 deletions src/AmoclientController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@

namespace StudioKaa\Amoclient;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Models\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;

use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;

class AmoclientController extends Controller
{

public function redirect()
{
$client_id = config('amoclient.client_id');
if($client_id == null)
{
dd('Please set AMO_CLIENT_ID and AMO_CLIENT_SECRET in .env file');
abort(500, 'Please set AMO_CLIENT_ID and AMO_CLIENT_SECRET in .env file.');
}

return redirect('https://login.curio.codes/oauth/authorize?client_id=' . $client_id . '&redirect_id=' . url('amoclient/callback') . '&response_type=code');
Expand All @@ -41,25 +38,30 @@ public function callback(Request $request)
]
]);

//Get id_token from the reponse
$tokens = json_decode( (string) $response->getBody() );
$id_token = (new Parser())->parse((string) $tokens->id_token);
$config = AmoclientHelper::getTokenConfig();
$tokens = json_decode((string) $response->getBody());

//Verify id_token
if(!$id_token->verify(new Sha256(), config('amoclient.client_secret')))
{
dd("The id_token cannot be verified.");
}
try {
$token = $config->parser()->parse($tokens->id_token);
} catch (\Lcobucci\JWT\Exception $exception) {
abort(400, 'Access token could not be parsed!');
}

//Get 'user' claim
$id_token->getClaims();
$token_user = $id_token->getClaim('user');
try {
$constraints = $config->validationConstraints();
$config->validator()->assert($token, ...$constraints);
} catch (RequiredConstraintsViolated $exception) {
abort(400, 'Access token could not be verified!');
}

$claims = $token->claims();
$token_user = $claims->get('user');
$token_user = json_decode($token_user);

//Check if user may login
if(config('amoclient.app_for') == 'teachers' && $token_user->type != 'teacher')
{
dd('Oops: this app is only availble to teacher-accounts');
abort(403, 'Oops: This app is only available to teacher-accounts!');
}

//Create new user if not exists
Expand All @@ -74,19 +76,15 @@ public function callback(Request $request)
$user->save();
}

//Login
Auth::login($user);

//Store access- and refresh-token in session
$access_token = (new Parser())->parse((string) $tokens->access_token);
$request->session()->put('access_token', $access_token);
$request->session()->put('access_token', $tokens->access_token);
$request->session()->put('refresh_token', $tokens->refresh_token);

//Redirect
return redirect('/amoclient/ready');

} catch (\GuzzleHttp\Exception\BadResponseException $e) {
dd("Unable to retrieve access token: " . $e->getResponse()->getBody());
abort(500, 'Unable to retrieve access token: '. $e->getResponse()->getBody());
}
}

Expand Down
40 changes: 40 additions & 0 deletions src/AmoclientHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace StudioKaa\Amoclient;

use DateTimeZone;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Lcobucci\JWT\Validation\Constraint\SignedWith;

class AmoclientHelper{
private static $cachedConfig = null;

public static function getTokenConfig()
{
if(self::$cachedConfig !== null)
return self::$cachedConfig;

$client_id = config('amoclient.client_secret');

if($client_id == null)
{
abort(500, 'Please set AMO_CLIENT_ID and AMO_CLIENT_SECRET in .env file.');
}

self::$cachedConfig = Configuration::forSymmetricSigner(
new Sha256(),
InMemory::plainText('')
);

self::$cachedConfig->setValidationConstraints(
new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))),
new SignedWith(new Sha256(), InMemory::plainText($client_id))
);

return self::$cachedConfig;
}
}
4 changes: 2 additions & 2 deletions src/AmoclientServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function boot()
if(config('amoclient.use_migration') == 'yes')
{
$this->loadMigrationsFrom(__DIR__.'/migrations');
}
}
}
/**
* Register the application services.
Expand All @@ -33,5 +33,5 @@ public function register()
$this->app->singleton('StudioKaa\AmoAPI', function () {
return new AmoAPI();
});
}
}
}
2 changes: 1 addition & 1 deletion src/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
Route::get('amoclient/redirect', 'StudioKaa\Amoclient\AmoclientController@redirect');
Route::get('amoclient/callback', 'StudioKaa\Amoclient\AmoclientController@callback');
Route::get('amoclient/logout', 'StudioKaa\Amoclient\AmoclientController@logout');
});
});

0 comments on commit b83ca04

Please sign in to comment.