Skip to content

Commit

Permalink
fix: add Scoped API Key docs
Browse files Browse the repository at this point in the history
  • Loading branch information
blefebvre committed Aug 21, 2024
1 parent 5bf8d3d commit 6e51aac
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 4 deletions.
14 changes: 12 additions & 2 deletions packages/spacecat-shared-data-access/src/models/api-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ import {
} from '@adobe/spacecat-shared-utils';
import { Base } from './base.js';

const scopeNames = ['sites.read_all', 'sites.write_all', 'organizations.read_all', 'organizations.write_all',
'audits.read_all', 'audits.write_all', 'imports.read', 'imports.write', 'imports.read_all'];
// List of known scope names that can be used with scoped API keys
const scopeNames = [
'sites.read_all',
'sites.write_all',
'organizations.read_all',
'organizations.write_all',
'audits.read_all',
'audits.write_all',
'imports.read',
'imports.write',
'imports.read_all',
];

const ApiKey = (data) => {
const self = Base(data);
Expand Down
71 changes: 69 additions & 2 deletions packages/spacecat-shared-http-utils/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Response Helper Functions.
# Spacecat Shared - HTTP Utilities

A set of TypeScript functions for creating HTTP responses with standardized formats.
A set of TypeScript functions for creating HTTP responses with standardized formats, and classes for dealing with
authenticating HTTP requests.

## Table of Contents

Expand Down Expand Up @@ -76,6 +77,72 @@ Creates a response for a not found scenario with an error message and optional h

Creates a response for an internal server error with an error message and optional headers.

## Authentication

This package includes classes for dealing with authenticating HTTP requests.

### ScopedApiKeyHandler

Scoped API keys are defined in the datalayer and can be used to authenticate requests to the Spacecat API. They employ
"scopes" to enable fine-grained access to resources. An example API key entity looks like this (`id` omitted):

```
{
"name": "Example API Key",
"hashedApiKey": "4c806362b613f7496abf284146efd31da90e4b16169fe001841ca17290f427c4",
"createdAt": "2024-08-21T19:00:00.000Z",
"expiresAt": "2024-12-21T19:00:00.000Z",
"scopes": [
{ "name": "imports.write" },
{ "name": "imports.read" }
]
}
```

Key points on the above:
- `hashedApiKey` is the SHA-256 hash of the actual API key ("test-api-key" above)
- `scopes` are the permissions granted to the API key
- Each `scope` object can contain additional data, but the `name` field is required

The `ScopedApiKeyHandler` class is used to authenticate requests using scoped API keys. To support the existing
Legacy API keys, it should be ordered after the `LegacyApiKeyHandler` in the `authHandlers` array. This enables requests
with the existing API keys to be authenticated quickly without requiring a database lookup.

#### Checking for scope access

To enable a new scope, first refer to the `scopeNames` array in the ApiKey model (/packages/spacecat-shared-data-access/src/models/api-key.js).
If the scope you need is not listed here, please add it. Note the convention for scope names is `resource.action`,
e.g. `imports.write` or `sites.read_all`. The `_all` action suffix indicates access beyond resources created (or
jobs initiated by) the current API key.

Next, you will want to check that the API used to make the request has access to the required scope(s) from your
controller. The `authWrapper` adds an `auth` helper to the context which makes this easy. Here's an example of how to
check for scope access from a controller:

```
// This route requires the 'imports.write' scope
function protectedRoute(context) {
const { auth } = context;
try {
auth.checkScopes(['imports.write']);
} catch (error) {
throw new ErrorWithStatusCode('Missing required scopes', 401);
}
return ok('You have access to this resource');
}
```

Need additional details from the API key entity object? The `authWrapper` places the authenticated `authInfo` object
into the context at `context.attributes.authInfo`, with the API key entity available in its `profile` property.

#### Creating a new API key

This is currently a manual process, and involves duplicating an existing API key entity in the datalayer and updating
its properties. For the table to update, refer to the `TABLE_NAME_API_KEYS` constant (which will be overridden on prod).

In the future we are planning to support a way for clients to request their own API key, given a valid IMS token.

## Contributing

Expand Down

0 comments on commit 6e51aac

Please sign in to comment.