# Authorization

By default we use client scopes for authorizing access to certain parts of the
API. While this is a good and quick solution for a start when using a client
credentials flow, it might not be suitable for your environment and can be
easily customized.

## Default Privileges

We will use the term *privilege* when access to an API resource in Tilores is
required. The default privileges are defined in the `cognito` module in
`deployment/tilores/main.tf` in the `available_scopes` property.

These default privileges are then assigned within the schema to certain API
fields. E.g. the privilege `mutation.submit` is used in
`schema/mutation.graphls` using the `@hasPrivilege` directive.

Each client that is defined in the `cognito` module then can be assigned a list
of the allowed requestable privileges. When the client then makes a token
request, it can narrow the requested privileges down even further.

## Add New Privilege

Let's assume we want to allow only certain clients to request the
`myCustomField` from the `Record`. First we need to introduce this new
permission by extending the `available_scopes` and assigning it to a client in
`deployment/tilores/main.tf`:

```
module "cognito" {
  # other properties
  available_scopes = [
    # other scopes
    {
      "name": "record.myCustomField"
      "description": "allows querying the myCustomField on a record"
    }
  ]
  clients = {
    # other clients
    special_client = {
      allowed_scopes = [
        "tilores/query.search",
        "tilores/record.myCustomField"
      ]
    }
  }
}
```
!!!
The name of your newly defined scope can be anything, but we advise you to
follow a schema that fits for you. Furthermore, every scope must have a
non-empty description.
!!!

!!!
In the default implementation the `allowed_scopes` must be prefixed with
`tilores/`.
!!!

Now we only need to assign that new privilege to the `Record` in
`schema/record.graphqls`:

```graphql
type Record {
  id: ID!
  myCustomField: String! @hasPrivilege(privilege: "tilores/record.myCustomField")
}
```

After redeploying, the client can request a new token like this:

```
curl -u <client_id>:<client_secret> \
--url <token_url> \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data 'scope=tilores/query.search tilores/record.myCustomField'
```

!!!
The `tilores/query.search` privilege still needs to be requested, because the
search itself is already protected.
!!!

It is also possible to assign a privilege to more than one field or even assign
it to an object, ensuring that wherever that object is used in your API, that it
is properly secured.

```graphql
type CustomObject @hasPrivilege(privilege: "tilores/customObject") {
  someField: String!
}

type Record {
  customObjectA: CustomObject!
  customObjectB: CustomObject!
}
```

In that example the client must have the `tilores/customObject` privilege for
accessing `customObjectA` or `customObjectB`.

## Custom Privileges

We are fully aware, that this is not how scopes were intended to be used. But we
believe, that for a machine to machine communication this approach is totally
fine. However, if you want to implement your own logic, that is not dependent on
scopes, you can change that.

Let's assume we want to load the privileges from somewhere else. In that case
all you have to do is to modify the implementation for the `hasPrivilege`
directive. The default implementation for that can be found in
`graph/directive/hasprivilege.go`.

The `PreparePrivilegeContext` function is responsible for enriching the context
with the information that is needed to perform the actual privilege check. That
can be enriching user information or even already enriching the list of
privileges a user has. This function will be called exactly once per GraphQL
request.

The `HasPrivilege` function is responsible for verifying that a client has the
required privilege. This function is called multiple times, perhaps even in
parallel, for each object or field that needs to be accessed. If the client has
the required privilege, then this function must invoke the `next` resolver.

A very basic implementation might looks something like this:

```go
type contextKey string

var privilegesContext = contextKey("privileges")

func PreparePrivilegeContext(ctx context.Context, request *events.APIGatewayProxyRequest) (context.Context, error) {
	// load privileges e.g. for a user extracted from the request
	privileges := map[string]bool{
		"privilege.a": true,
		"privilege.b": false,
	}
	return context.WithValue(ctx, privilegesContext, privileges), nil
}

func HasPrivilege(ctx context.Context, obj interface{}, next graphql.Resolver, privilege string) (interface{}, error) {
	privileges := ctx.Value(privilegesContext).(map[string]bool)
	hasPrivilege, ok := privileges[privilege]
	if ok && hasPrivilege {
		return next(ctx)
	}
	return nil, fmt.Errorf("access denied, required privilege: %s", privilege)
}
```

When a user tries to access a resource annotated with
`@hasPrivilege(privilege: "privilege.a")` the value for that resource will be
returned. For resources with `@hasPrivilege(privilege: "privilege.a")`
directives access will be denied.

Obviously, we skipped the part where we extract the user information from the
`request` and make a lookup for the actual privileges with an external service
or database.
