Skip to content

Commit

Permalink
CORS headers by default, disable_cors setting - closes #24
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Aug 6, 2020
1 parent 7ff58e3 commit bbd5389
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 14 deletions.
20 changes: 20 additions & 0 deletions README.md
Expand Up @@ -180,6 +180,26 @@ You can turn on automatic camelCase using the `"auto_camelcase"` plugin configur
}
```

### CORS

By default this plugin adds the following CORS HTTP headers to allow JavaScript running on other domains to access the GraphQL API:

access-control-allow-headers: content-type
access-control-allow-method: POST
access-control-allow-origin: *

You can turn these headers off using the `disable_cors` plugin setting:

```json
{
"plugins": {
"datasette-graphql": {
"disable_cors": true
}
}
}
```

## Still to come

See [issues](https://github.com/simonw/datasette-graphql/issues) for a full list. Planned improvements include:
Expand Down
42 changes: 28 additions & 14 deletions datasette_graphql/__init__.py
Expand Up @@ -21,14 +21,22 @@ async def post_body(request):


async def view_graphql(request, datasette):
config = datasette.plugin_config("datasette-graphql") or {}
body = await post_body(request)
database = request.url_vars.get("database")

if not body:
return Response.html(
await datasette.render_template(
"graphiql.html", {"database": database,}, request=request
)
),
headers={
"Access-Control-Allow-Headers": "content-type",
"Access-Control-Allow-Method": "POST",
"Access-Control-Allow-Origin": "*",
}
if not config.get("disable_cors")
else {},
)

incoming = json.loads(body)
Expand All @@ -48,7 +56,17 @@ async def view_graphql(request, datasette):
if result.errors:
response["errors"] = [format_error(error) for error in result.errors]

return Response.json(response, status=200 if not result.errors else 500)
return Response.json(
response,
status=200 if not result.errors else 500,
headers={
"Access-Control-Allow-Headers": "content-type",
"Access-Control-Request-Method": "POST",
"Access-Control-Allow-Origin": "*",
}
if not config.get("disable_cors")
else {},
)


@hookimpl
Expand All @@ -64,16 +82,12 @@ def startup(datasette):
# Validate configuration
config = datasette.plugin_config("datasette-graphql") or {}
if "databases" in config:
if len(config["databases"].keys()) > 1:
raise ClickException(
"datasette-graphql currently only supports a single database"
)
database_name = list(config["databases"].keys())[0]
try:
datasette.get_database(database_name)
except KeyError:
raise ClickException(
"datasette-graphql config error: '{}' is not a connected database".format(
database_name
for database_name in config["databases"].keys():
try:
datasette.get_database(database_name)
except KeyError:
raise ClickException(
"datasette-graphql config error: '{}' is not a connected database".format(
database_name
)
)
)
21 changes: 21 additions & 0 deletions tests/test_graphql.py
Expand Up @@ -416,3 +416,24 @@ async def test_graphql_multiple_databases(db_path, db_path2):
assert response.json() == {
"data": {"test": {"nodes": [{"body": "This is test two"}]}}
}


@pytest.mark.asyncio
@pytest.mark.parametrize("cors_enabled", [True, False])
async def test_cors_headers(db_path, cors_enabled):
ds = Datasette(
[db_path],
metadata={"plugins": {"datasette-graphql": {"disable_cors": not cors_enabled}}},
)
async with httpx.AsyncClient(app=ds.app()) as client:
response = await client.options("http://localhost/graphql")
assert response.status_code == 200
desired_headers = {
"access-control-allow-headers": "content-type",
"access-control-allow-method": "POST",
"access-control-allow-origin": "*",
}.items()
if cors_enabled:
assert desired_headers <= dict(response.headers).items()
else:
assert not desired_headers <= dict(response.headers).items()

0 comments on commit bbd5389

Please sign in to comment.