diff --git a/src/Controllers/ApiController.php b/src/Controllers/ApiController.php index faad6ab5..77af8e32 100644 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/ApiController.php @@ -47,16 +47,14 @@ function actionDebug() { function actionIndex() { - $token = false; + $settings = CraftQL::getInstance()->getSettings(); - $authorization = Craft::$app->request->headers->get('authorization'); - preg_match('/^(?:b|B)earer\s+(?.+)/', $authorization, $matches); - $token = Token::findId(@$matches['tokenId']); + $token = static::findToken($settings->authorizationHeader, Craft::$app->request->headers); // @todo, check user permissions when PRO license $response = \Craft::$app->getResponse(); - if ($allowedOrigins = CraftQL::getInstance()->getSettings()->allowedOrigins) { + if ($allowedOrigins = $settings->allowedOrigins) { if (is_string($allowedOrigins)) { $allowedOrigins = [$allowedOrigins]; } @@ -65,9 +63,15 @@ function actionIndex() $response->headers->add('Access-Control-Allow-Origin', $origin); } $response->headers->add('Access-Control-Allow-Credentials', 'true'); - $response->headers->add('Access-Control-Allow-Headers', 'Authorization, Content-Type'); + + $allowedHeaders = ['Authorization', 'Content-Type']; + if ($settings->authorizationHeader) { + $allowedHeaders[] = $settings->authorizationHeader; + } + + $response->headers->add('Access-Control-Allow-Headers', implode(', ', $allowedHeaders)); } - $response->headers->add('Allow', implode(', ', CraftQL::getInstance()->getSettings()->verbs)); + $response->headers->add('Allow', implode(', ', $settings->verbs)); if (\Craft::$app->getRequest()->isOptions) { return ''; @@ -120,7 +124,7 @@ function actionIndex() $result = $this->graphQl->execute($schema, $input, $variables); Craft::trace('CraftQL: Execution complete'); - $customHeaders = CraftQL::getInstance()->getSettings()->headers ?: []; + $customHeaders = $settings->headers ?: []; foreach ($customHeaders as $key => $value) { if (is_callable($value)) { $value = $value($schema, $input, $variables, $result); @@ -148,4 +152,23 @@ function actionIndex() return $this->asJson($result); } + + private static function findToken($authorizationHeader, $headers) + { + // Default, for cases when token header is not found to use user based token + // resolution, if possible. + $tokenId = null; + + if ($authorizationHeader) { + if ($headers->has($authorizationHeader)) { + $tokenId = $headers->get($authorizationHeader); + } + } else if ($headers->has('authorization')) { + $authorization = $headers->get('authorization'); + preg_match('/^(?:b|B)earer\s+(?.+)/', $authorization, $matches); + $tokenId = @$matches['tokenId']; + } + + return Token::findId($tokenId); + } } diff --git a/src/Controllers/CpController.php b/src/Controllers/CpController.php index 64494477..b05c3893 100644 --- a/src/Controllers/CpController.php +++ b/src/Controllers/CpController.php @@ -64,6 +64,7 @@ function actionGraphiql() $this->renderTemplate('craftql/graphiql', [ 'url' => "{$url}{$uri}", 'token' => false, + 'authorizationHeader' => $instance->settings->authorizationHeader, ]); } @@ -78,6 +79,7 @@ function actionGraphiqlas($token) $this->renderTemplate('craftql/graphiql', [ 'url' => "{$url}{$uri}", 'token' => $token, + 'authorizationHeader' => $instance->settings->authorizationHeader, ]); } } diff --git a/src/CraftQL.php b/src/CraftQL.php index d98eb169..502a0baa 100644 --- a/src/CraftQL.php +++ b/src/CraftQL.php @@ -107,8 +107,13 @@ protected function createSettingsModel() */ protected function settingsHtml() { + $settings = $this->getSettings(); + + $curlAuthorizationHeader = $settings->authorizationHeader ? "{$settings->authorizationHeader}:" : 'Authorization: Bearer'; + return \Craft::$app->getView()->renderTemplate('craftql/settings', [ - 'settings' => $this->getSettings() + 'settings' => $settings, + 'curlAuthorizationHeader' => $curlAuthorizationHeader ]); } diff --git a/src/Models/Settings.php b/src/Models/Settings.php index 5b3c4ba3..712bbfa8 100644 --- a/src/Models/Settings.php +++ b/src/Models/Settings.php @@ -7,6 +7,7 @@ class Settings extends Model { + public $authorizationHeader = ''; public $uri = 'api'; public $verbs = ['POST']; public $allowedOrigins = []; diff --git a/src/templates/graphiql.html b/src/templates/graphiql.html index 50fd0006..97582f7a 100644 --- a/src/templates/graphiql.html +++ b/src/templates/graphiql.html @@ -148,7 +148,11 @@ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', + {% if authorizationHeader and token %} + '{{ authorizationHeader }}': '{{ token }}', + {% elseif token %} 'Authorization': 'Bearer {{ token }}', + {% endif %} }, body: JSON.stringify(graphQLParams), credentials: 'include', diff --git a/src/templates/settings.html b/src/templates/settings.html index b05f9062..213eee33 100644 --- a/src/templates/settings.html +++ b/src/templates/settings.html @@ -55,7 +55,7 @@ 2. **Curl** {% if settings.tokens|length %}

You can query your schema directly by passing a GraphQL statement throuh a `query` variable. The following Curl statement should get you started,

-

$ curl -H "Authorization: bearer {{ settings.tokens[0].token|default('{TOKEN}') }}" -H "Content-type: application/json" -d '{"query":"{ helloWorld }"}' {{ siteUrl }}{{ settings.uri }}

+

$ curl -H "{{ curlAuthorizationHeader }} {{ settings.tokens[0].token|default('{TOKEN}') }}" -H "Content-type: application/json" -d '{"query":"{ helloWorld }"}' {{ siteUrl }}{{ settings.uri }}

{% else %}

Before you can use Curl you need to [add a token]({{ url('craftql/token-gen') }}) for authenticated access in to Craft.

{% endif %} @@ -68,6 +68,15 @@
+

Custom Authorization Header

+ + {{ forms.textField({ + first: true, + name: 'authorizationHeader', + value: settings.authorizationHeader, + instructions: 'Custom header to be used to check for authorization token. Useful if site is behind Basic Auth and conflicts with default Authorization Bearer.' + }) }} +

URI

{{ forms.textField({