Skip to content

Commit

Permalink
Merge pull request #24 from tractorcow/pulls/pull-back-work
Browse files Browse the repository at this point in the history
Major upgrade: Changes documented
  • Loading branch information
Firesphere authored Jul 24, 2019
2 parents 2a45ae2 + f2b6ecb commit 76e80ae
Show file tree
Hide file tree
Showing 30 changed files with 1,240 additions and 561 deletions.
6 changes: 0 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ jobs:

working_directory: ~/var/www

branches:
only:
- master
- develop
- /feature.*/

steps:
- run: composer create-project firesphere/graphql-jwt:dev-master ~/var/www -n
- run: vendor/bin/sake dev/build
Expand Down
26 changes: 26 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# For more information about the properties used in
# this file, please see the EditorConfig documentation:
# http://editorconfig.org/

root = true

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

[*.md]
trim_trailing_whitespace = false

[*.yml]
indent_size = 2
indent_style = space

[*.{yml,json}]
indent_size = 2

[composer.json]
indent_size = 4
7 changes: 7 additions & 0 deletions _config/anonymous.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
Name: firesphere-jwt-anonymous
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\Member.anonymous:
class: SilverStripe\Security\Member
factory: Firesphere\GraphQLJWT\Authentication\AnonymousUserFactory
36 changes: 12 additions & 24 deletions _config/config.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
---
after: graphqlconfig
Name: firesphere-jwt-injections
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\AuthenticationHandler:
properties:
Handlers:
jwt: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticationHandler
jwt: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticationHandler
Firesphere\GraphQLJWT\Authentication\JWTAuthenticationHandler:
properties:
Authenticator: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticator
---
after: graphqlroutes
---
SilverStripe\Control\Director:
rules:
graphql:
Controller: 'SilverStripe\GraphQL\Controller'
# @internal - Experimental config
# @todo - move this to a per-schema configuration, and simply register the named schema for this endpoint
# https://github.com/silverstripe/silverstripe-graphql/issues/52
Stage: Live
Permissions: false
---
name: graphqljwt
after:
- '#coresecurity'
---
Firesphere\GraphQLJWT\Authentication\JWTAuthenticator:
nbf_time: 0
nbf_expiration: 3600
anonymous_allowed: false
JWTAuthenticator: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticator
Firesphere\GraphQLJWT\Mutations\CreateTokenMutationCreator:
properties:
JWTAuthenticator: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticator
Firesphere\GraphQLJWT\Mutations\RefreshTokenMutationCreator:
properties:
JWTAuthenticator: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticator
Firesphere\GraphQLJWT\Queries\ValidateTokenQueryCreator:
properties:
JWTAuthenticator: %$Firesphere\GraphQLJWT\Authentication\JWTAuthenticator
10 changes: 0 additions & 10 deletions _config/graphql.yml

This file was deleted.

68 changes: 32 additions & 36 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
{
"name": "firesphere/graphql-jwt",
"description": "JWT Authentication for GraphQL",
"type": "silverstripe-vendormodule",
"license": "bsd-3-clause",
"require": {
"php": ">=5.6",
"silverstripe/recipe-core": "^4.0",
"silverstripe/versioned": "^1.0",
"silverstripe/graphql": "^3.0",
"lcobucci/jwt": "^3.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.4",
"phpunit/PHPUnit": "^5.7",
"scriptfusion/phpunit-immediate-exception-printer": "^1"
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"name": "firesphere/graphql-jwt",
"description": "JWT Authentication for GraphQL",
"type": "silverstripe-vendormodule",
"license": "bsd-3-clause",
"require": {
"php": ">=7.1",
"silverstripe/framework": "^4.3",
"silverstripe/graphql": "^3.0",
"lcobucci/jwt": "^3.2",
"ext-json": "*"
},
"installer-name": "graphql-jwt"
},
"config": {
"process-timeout": 600
},
"autoload": {
"psr-4": {
"Firesphere\\GraphQLJWT\\Authentication\\": "src/Authentication",
"Firesphere\\GraphQLJWT\\Extensions\\": "src/Extensions",
"Firesphere\\GraphQLJWT\\Helpers\\": "src/Helpers",
"Firesphere\\GraphQLJWT\\Mutations\\": "src/Mutations",
"Firesphere\\GraphQLJWT\\Queries\\": "src/Queries",
"Firesphere\\GraphQLJWT\\Types\\": "src/Types"
}
},
"prefer-stable": true,
"minimum-stability": "dev"
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.4",
"phpunit/phpunit": "^5.7",
"scriptfusion/phpunit-immediate-exception-printer": "^1"
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
},
"installer-name": "graphql-jwt"
},
"config": {
"process-timeout": 600
},
"autoload": {
"psr-4": {
"Firesphere\\GraphQLJWT\\": "src/",
"Firesphere\\GraphQLJWT\\Tests\\": "tests/unit/"
}
},
"prefer-stable": true,
"minimum-stability": "dev"
}
79 changes: 74 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,63 @@ JWT_SIGNER_KEY="[your secret key]"
You can also use public/private key files, using the following:

```ini
JWT_SIGNER_KEY="/path/to/private.key"
JWT_PUBLIC_KEY="/path/to/public.key"
JWT_SIGNER_KEY="./path/to/private.key"
JWT_PUBLIC_KEY="./path/to/public.key"
```

Note: Relative paths will be relative to your BASE_PATH (prefixed with `./`)

Currently, only RSA keys are supported. ECDSA is not supported. The keys in the test-folder are generated by an online RSA key generator.

The signer key [for HMAC can be of any length (keys longer than B bytes are first hashed using H). However, less than L bytes is strongly discouraged as it would decrease the security strength of the function.](https://tools.ietf.org/html/rfc2104#section-3). Thus, for SHA-256 the signer key should be between 16 and 64 bytes in length.

**The keys in `tests/keys` should not be trusted!**

## Configuration

Since admin/graphql is reserved exclusively for CMS graphql access, it will be necessary for you to register a custom schema for
your front-end application, and apply the provided queries and mutations to that.

For example, given you've decided to create a schema named `frontend` at the url `/api`

```yml
---
Name: my-graphql-schema
---
SilverStripe\GraphQL\Manager:
schemas:
frontend:
types:
MemberToken: 'Firesphere\GraphQLJWT\Types\MemberTokenTypeCreator'
Member: 'Firesphere\GraphQLJWT\Types\MemberTypeCreator'
mutations:
createToken: 'Firesphere\GraphQLJWT\Mutations\CreateTokenMutationCreator'
refreshToken: 'Firesphere\GraphQLJWT\Mutations\RefreshTokenMutationCreator'
queries:
validateToken: 'Firesphere\GraphQLJWT\Queries\ValidateTokenQueryCreator'
---
Name: my-graphql-injections
---
SilverStripe\Core\Injector\Injector:
SilverStripe\GraphQL\Manager.frontend:
class: SilverStripe\GraphQL\Manager
constructor:
identifier: frontend
SilverStripe\GraphQL\Controller.frontend:
class: SilverStripe\GraphQL\Controller
constructor:
manager: '%$SilverStripe\GraphQL\Manager.frontend'
---
Name: my-graphql-routes
---
SilverStripe\Control\Director:
rules:
api:
Controller: '%$SilverStripe\GraphQL\Controller.frontend'
Stage: Live
```
## Log in
To generate a JWT token, send a login request to the `createToken` mutator:
Expand Down Expand Up @@ -84,13 +131,35 @@ If the token is invalid, `Valid` will be `false`.

## Anonymous tokens

Although not advised, it's possible to use anonymous tokens. A member ID of `0` will be returned, along with `"Anonymous"` as a name. To enable anonymous tokens, add the following to your configuration `.yml`:
Although not advised, it's possible to use anonymous tokens. When using an anonymous authenticator, SilverStripe
will generate a default database record in the Members table with the Email `anonymous` and no permissions by default.

To enable anonymous tokens, add the following to your configuration `.yml`:

```yaml
Firesphere\GraphQLJWT\JWTAuthenticator:
anonymous_allowed: true
SilverStripe\Core\Injector\Injector:
Firesphere\GraphQLJWT\Mutations\CreateTokenMutationCreator:
properties:
CustomAuthenticators:
- Firesphere\GraphQLJWT\Authentication\AnonymousUserAuthenticator
```

You can then create an anonymous login with the below query.

```graphql
mutation {
createToken(Email: "anonymous") {
Token
}
}
```

Note: If the default anonymous authenticator doesn't suit your purposes, you can inject any other
core SilverStripe authenticator into `CustomAuthenticators`.

Warning: The default `AnonymousUserAuthenticator` is not appropriate for general usage, so don't
register this under the core `Security` class!

## Enable CORS

To use JWT, CORS needs to be enabled. This can be done by adding the following to your configuration `.yml`:
Expand Down
76 changes: 76 additions & 0 deletions src/Authentication/AnonymousUserAuthenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php declare(strict_types=1);

namespace Firesphere\GraphQLJWT\Authentication;

use BadMethodCallException;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Authenticator;
use SilverStripe\Security\Member;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;

class AnonymousUserAuthenticator extends MemberAuthenticator
{
use Configurable;
use Injectable;

/**
* Anonymous username
*
* @var string
*/
private static $anonymous_username = 'anonymous';

public function supportedServices(): int
{
return Authenticator::LOGIN | Authenticator::LOGOUT;
}

public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null): ?Member
{
// Only applies to request for anonymous user specifically
$email = $data['Email'] ?? null;
if ($email !== static::config()->get('anonymous_username')) {
return null;
}

return parent::authenticate($data, $request, $result);
}

/**
* Attempt to find and authenticate member if possible from the given data
*
* @skipUpgrade
* @param array $data Form submitted data
* @param ValidationResult $result
* @param Member $member This third parameter is used in the CMSAuthenticator(s)
* @return Member Found member, regardless of successful login
*/
protected function authenticateMember($data, ValidationResult &$result = null, Member $member = null): Member
{
// Get user, or create if not exists
$username = static::config()->get('anonymous_username');
$member = Injector::inst()->get(Member::class . '.anonymous', true, ['username' => $username]);

// Validate this member is still allowed to login
$result = $result ?: ValidationResult::create();
$member->validateCanLogin($result);

// Emit failure to member and form (if available)
if ($result->isValid()) {
$member->registerSuccessfulLogin();
} else {
$member->registerFailedLogin();
}

return $member;
}

public function checkPassword(Member $member, $password, ValidationResult &$result = null)
{
throw new BadMethodCallException("checkPassword not supported for anonymous users");
}
}
Loading

0 comments on commit 76e80ae

Please sign in to comment.