@firetail/finchly 0.0.0-stage → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@firetail/finchly`. Versions follow semver.
4
+
5
+ ## Unreleased
6
+
7
+ ## 0.0.4 - 2026-11-12
8
+
9
+ No change to the package. The first release published by the release
10
+ workflow: 0.0.2 and 0.0.3 were tagged but did not reach npm, because the
11
+ workflow published with a placeholder token in place of the trusted
12
+ publisher. Everything listed under them arrives in this release.
13
+
14
+ ## 0.0.3 - 2026-11-12
15
+
16
+ Tagged but not published to npm.
17
+
18
+ - Every request to the platform carries three headers: `x-platform-sdk`
19
+ (the package name), `x-platform-sdk-version` (this release) and
20
+ `x-platform-api-version` (the platform API version it speaks). A caller
21
+ cannot replace them. Exported as `SDK_REQUEST_HEADERS`.
22
+
23
+ ## 0.0.2 - 2026-11-12
24
+
25
+ Tagged but not published to npm.
26
+
27
+ - An app can register itself: `createServiceClient` takes an `enrolment`
28
+ (token, manifest, address) in place of a credential, and
29
+ `enrolmentFrom(config, manifest)` builds one from the platform settings.
30
+ Registration is what gets an app's instance key accepted.
31
+ - New error codes: `app_pending_approval`, `app_registration_refused`.
32
+
33
+ ## 0.0.1 - 2026-11-11
34
+
35
+ The first release, published by hand so that the package exists and
36
+ trusted publishing can be set up. Later releases come from the release
37
+ workflow.
38
+
39
+ - Package skeleton under its final name.
40
+ - `loadConfig`: read and validate the `PLATFORM_*` settings.
41
+ - `loadManifest`, `checkManifest`: load a manifest and check the fields the
42
+ SDK depends on.
43
+ - `PLATFORM_API_VERSION`, `SUPPORTED_PLATFORM_API_VERSIONS`, `SDK_NAME`,
44
+ `SDK_VERSION`.
45
+ - `createIdentityVerifier`: verify the `x-platform-identity` token against
46
+ the gateway's published keys. Rejections are an `IdentityError` with a
47
+ `reason` from the specification. Tested against the identity token
48
+ conformance vectors.
49
+ - New dependency: `jose`.
50
+ - `createServiceClient`: call another service through the gateway, as the
51
+ service itself or for a user. Makes the instance key, registers it with
52
+ the service credential, and extends it before it expires.
53
+ - `loadConfig` reads `PLATFORM_SERVICE_CREDENTIAL`.
54
+ - New error codes: `service_call_invalid`, `service_key_rejected`.
package/README.md CHANGED
@@ -1,3 +1,281 @@
1
- # Temporary Holding Version
1
+ # @firetail/finchly
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Node SDK for the [Finchly](https://github.com/firetail-io/finchly) platform.
4
+ A service or app uses it to register with a Finchly deployment, verify who
5
+ is calling it, and reach the platform's database, object store, queue and
6
+ other services.
7
+
8
+ Status: early. The package builds and is tested, but only the first four
9
+ modules exist.
10
+
11
+ The SDK implements the platform API, which is specified in
12
+ `firetail-io/finchly` under `spec/`. This release speaks platform API `v1`
13
+ (draft).
14
+
15
+ ## Install
16
+
17
+ ```
18
+ npm install @firetail/finchly
19
+ ```
20
+
21
+ Requires Node 22 or later. ESM only.
22
+
23
+ ## Use
24
+
25
+ ```js
26
+ import { loadConfig, loadManifest } from "@firetail/finchly";
27
+
28
+ const config = loadConfig(); // reads PLATFORM_* settings
29
+ const manifest = await loadManifest(config.manifestPath);
30
+ ```
31
+
32
+ Both throw a `FinchlyError` with a stable `code` and a `problems` list that
33
+ names everything wrong, so a misconfigured container fails once with the
34
+ full picture.
35
+
36
+ ### Who is calling
37
+
38
+ The gateway puts a short-lived signed token in the `x-platform-identity`
39
+ header of every request it proxies. It is the only identity a service
40
+ should accept.
41
+
42
+ ```js
43
+ import { IdentityError, createIdentityVerifier } from "@firetail/finchly";
44
+
45
+ // Once, at startup. It caches the gateway's issuer and signing keys.
46
+ const identity = createIdentityVerifier({
47
+ slug: manifest.slug,
48
+ gatewayUrl: config.gatewayUrl,
49
+ });
50
+
51
+ // Per request.
52
+ try {
53
+ const caller = await identity.fromHeaders(req.headers);
54
+ caller.subject; // user id, or "service:<slug>"
55
+ caller.email; // null for a service calling as itself
56
+ caller.groups; // platform groups
57
+ caller.roles; // roles for this service: "user", "operator"
58
+ caller.actor; // "service:<slug>" when a service acts for the user
59
+ } catch (err) {
60
+ if (err instanceof IdentityError) {
61
+ // Unauthenticated: respond 401. err.reason is for logs only.
62
+ } else {
63
+ // The gateway could not be reached: respond 503.
64
+ throw err;
65
+ }
66
+ }
67
+ ```
68
+
69
+ A token is rejected unless it is signed with ES256 by a key the gateway
70
+ publishes, names that gateway as issuer and this service as its only
71
+ audience, and has not expired. The rules are in `spec/identity-token.md` in
72
+ `firetail-io/finchly`.
73
+
74
+ No other header is ever consulted. On a path the manifest declares in
75
+ `publicPaths` there is no token, and the service authenticates the request
76
+ itself.
77
+
78
+ ### Calling another service
79
+
80
+ Services never address each other directly. A call goes through the
81
+ gateway, signed by this instance's own key.
82
+
83
+ ```js
84
+ import { createServiceClient } from "@firetail/finchly";
85
+
86
+ // Once, at startup.
87
+ const service = createServiceClient({
88
+ slug: manifest.slug,
89
+ gatewayUrl: config.gatewayUrl,
90
+ credential: config.serviceCredential,
91
+ });
92
+ await service.start(); // optional: fail now, not on the first call
93
+
94
+ // As the service itself.
95
+ const res = await service.call("notification-service", "/api/send", {
96
+ method: "POST",
97
+ headers: { "content-type": "application/json" },
98
+ body: JSON.stringify({ text: "hello" }),
99
+ });
100
+
101
+ // For a user: the target sees that user, and this service acting for them.
102
+ await service.call("notification-service", "/api/inbox", { asUser: caller.subject });
103
+ ```
104
+
105
+ `call` takes the same options as `fetch` and resolves with the response,
106
+ whatever its status.
107
+
108
+ - **403** means this service's manifest does not list the target in
109
+ `calls`, or the user has no access to the target.
110
+ - **A redirect is handed back, not followed.**
111
+ - **Nothing is retried.** After a 401 the next call registers the key
112
+ again, in case the gateway no longer knows it.
113
+
114
+ The instance makes an ES256 key pair in memory the first time it is needed.
115
+ The private half never leaves the process. The public half is registered
116
+ with the gateway using the credential the service was deployed with, and
117
+ extended by itself for as long as the instance runs. The rules are in
118
+ `spec/service-token.md` and `spec/registration.md` in `firetail-io/finchly`.
119
+
120
+ ### An app registers itself
121
+
122
+ A built-in service has a credential. An app does not: it registers itself
123
+ with the platform, and that is what gets its key accepted. Give the client
124
+ an `enrolment` in place of a `credential`:
125
+
126
+ ```js
127
+ import { createServiceClient, enrolmentFrom, loadConfig, loadManifest } from "@firetail/finchly";
128
+
129
+ const config = loadConfig();
130
+ const manifest = await loadManifest(config.manifestPath);
131
+
132
+ const service = createServiceClient({
133
+ slug: manifest.slug,
134
+ gatewayUrl: config.gatewayUrl,
135
+ credential: config.serviceCredential, // a built-in service
136
+ enrolment: enrolmentFrom(config, manifest), // an app
137
+ });
138
+ await service.start();
139
+ ```
140
+
141
+ `enrolmentFrom` reads the enrolment token (`PLATFORM_ENROLMENT_TOKEN`) and
142
+ the app's address (`PLATFORM_APP_ADDRESS`, or this machine's hostname and
143
+ the manifest's port). It returns `undefined` when there is no token, so the
144
+ same code serves a built-in service and an app.
145
+
146
+ Registration is the first call the client makes, and it sends the manifest
147
+ exactly as it was loaded. What can come back:
148
+
149
+ | Error code | Meaning | What to do |
150
+ |---|---|---|
151
+ | none | Registered. Calls work | |
152
+ | `app_pending_approval` | The release waits for an administrator: it asks for more than the last one, or the app is at an address the deployment does not expect | Keep running and try again. The client does not ask the platform again before it was told to |
153
+ | `app_registration_refused` | The platform refused, and `problems` says why: a bad token, a manifest or address it will not accept, a lower version than the one running | Fix the deployment. Trying again will not help |
154
+ | `gateway_unreachable` | The gateway could not be reached | Try again |
155
+
156
+ The protocol is in `spec/registration.md` in `firetail-io/finchly`.
157
+
158
+ The other half of the `app` module is not built: serving the
159
+ `/{slug}/_platform/*` endpoints, which let an administrator register an app
160
+ that is already running, and fetching the settings the platform creates for
161
+ an app.
162
+
163
+ ### What every request carries
164
+
165
+ Every request this SDK makes to the platform says which SDK and which
166
+ release is asking:
167
+
168
+ | Header | Value |
169
+ |---|---|
170
+ | `x-platform-sdk` | `@firetail/finchly` |
171
+ | `x-platform-sdk-version` | This release, for example `0.0.4` |
172
+ | `x-platform-api-version` | The platform API version it speaks, `v1` |
173
+
174
+ That covers fetching the gateway's issuer and keys, registering an instance
175
+ key, registering an app, and every call to another service. On a call the
176
+ headers reach the target service too, and a caller cannot replace them.
177
+
178
+ ### Settings
179
+
180
+ | Variable | Required | Meaning |
181
+ |---|---|---|
182
+ | `PLATFORM_GATEWAY_URL` | yes | The gateway's internal address |
183
+ | `PLATFORM_MANIFEST_PATH` | no | Manifest path in the container. Default `/finchly/manifest.json` |
184
+ | `PLATFORM_APP_ADDRESS` | no | Address the gateway should use to reach this service |
185
+ | `PLATFORM_ENROLMENT_TOKEN` | no | Token for self-registration |
186
+ | `PLATFORM_SERVICE_CREDENTIAL` | no | What a built-in service registers its instance key with |
187
+
188
+ The enrolment token and the service credential are kept off the enumerable
189
+ properties of the config object, so logging or serialising the config does
190
+ not leak them.
191
+
192
+ ## Modules
193
+
194
+ | Module | What it does | State |
195
+ |---|---|---|
196
+ | `config` | Read and validate the platform settings | Done |
197
+ | `manifest` | Load the manifest and check what the SDK depends on | Done |
198
+ | `identity` | Verify `x-platform-identity` | Done. Directory lookups (`listUsers`, `membersOf`) and `require*` helpers are planned |
199
+ | `app` | Register with the platform, serve `/_platform/*`, renew keys, fetch settings | Self-registration and key renewal are done, through `service`. Serving `/_platform/*` and fetching settings are not |
200
+ | `service` | Call other services through the gateway, as the service or for a user | Done, for built-in services and for apps |
201
+ | `db` | PostgreSQL: queries, transactions, advisory locks, migrations | Planned |
202
+ | `blobs` | Object storage with conditional writes | Planned |
203
+ | `queue` | Publish, lease, ack, nack | Planned |
204
+ | `secrets` | Secrets and envelope encryption | Planned |
205
+ | `llm` | Model calls through the platform | Planned |
206
+ | `audit`, `usage`, `notify`, `schedule`, `connectors` | Clients for platform services | Planned |
207
+ | `ui` | Shared header, footer and theme | Planned |
208
+
209
+ The manifest check here is not the full schema. The platform runs the full
210
+ check at registration and is the authority.
211
+
212
+ ## Develop
213
+
214
+ ```
215
+ npm install
216
+ npm test
217
+ ```
218
+
219
+ `npm test` compiles with `tsc` and runs the tests in `test/` against the
220
+ build, which is what a consumer imports.
221
+
222
+ ### Conformance vectors
223
+
224
+ `test/conformance/` holds test vectors copied from `firetail-io/finchly`
225
+ (`conformance/vectors/`). Both SDKs must reach the verdict each vector
226
+ states. Do not edit them here: change them in `finchly`, regenerate, and
227
+ copy the file to both SDK repositories. Until `finchly` publishes its
228
+ conformance suite as an image, the copy is made by hand.
229
+
230
+ ## Release
231
+
232
+ Each package version is semver, independent of the Python SDK and of the
233
+ platform.
234
+
235
+ 1. Changes land on `main` by pull request. Each one adds a line to
236
+ `CHANGELOG.md` under "Unreleased".
237
+ 2. To release, set the version in `package.json`, move the changelog
238
+ entries under that version, merge, and tag the merge commit `vX.Y.Z`.
239
+ 3. The `release` workflow checks the tag matches `package.json`, runs the
240
+ tests, and publishes.
241
+ 4. A version with a pre-release part (`1.2.0-rc.1`) is published under the
242
+ `next` dist-tag. Anything else is published as `latest`.
243
+ 5. A bad release is deprecated with `npm deprecate` and followed by a patch
244
+ release. It is not unpublished.
245
+
246
+ Publishing is meant to use npm trusted publishing: the workflow proves its
247
+ identity to npm with its GitHub Actions token, so no npm token is stored,
248
+ and npm attaches a provenance attestation.
249
+
250
+ npm matches the trusted publisher against the identity GitHub issues to the
251
+ workflow, and every field must match exactly, including case:
252
+
253
+ | Field on npmjs.com | Value |
254
+ | -------------------- | ------------- |
255
+ | Organization or user | `FireTail-io` |
256
+ | Repository | `finchly-sdk-js` |
257
+ | Workflow filename | `release.yml` |
258
+ | Environment name | `npm` |
259
+
260
+ The organisation is `FireTail-io`, not `firetail-io`. GitHub accepts either
261
+ spelling in a URL; npm does not, and answers "package not found" when the
262
+ two differ. `repository.url` in `package.json` must use the same spelling.
263
+ The workflow prints the identity it was issued, so a refusal can be compared
264
+ with what npmjs.com holds. A trusted publisher's fields cannot be edited: to
265
+ correct one, delete it and create another.
266
+
267
+ The workflow can also be given a token, for a release that trusted
268
+ publishing refuses:
269
+
270
+ - `NPM_TOKEN`, a secret of the `npm` environment in this repository. It is
271
+ a granular access token that can publish `@firetail/finchly` and nothing
272
+ else, with a short expiry.
273
+ - npm tries the trusted publisher first and uses the token only when that
274
+ is refused. Without the secret, nothing in the workflow needs to change.
275
+ - While a token is in use, the package's "Publishing access" on npmjs.com
276
+ must allow a granular access token with bypass 2FA.
277
+ - No provenance attestation is attached to a release published with the
278
+ token. npm also does not generate one from a private repository.
279
+
280
+ Still to add: the conformance suite from `firetail-io/finchly`, run in CI on
281
+ every change, once that suite exists.
@@ -0,0 +1,30 @@
1
+ /** Where the manifest lives in an image unless PLATFORM_MANIFEST_PATH says otherwise. */
2
+ export declare const DEFAULT_MANIFEST_PATH = "/finchly/manifest.json";
3
+ /** Settings a service is deployed with. See spec/registration.md, section 1. */
4
+ export interface PlatformConfig {
5
+ /** The gateway's internal address, without a trailing slash. */
6
+ readonly gatewayUrl: string;
7
+ /** Path of the manifest inside the container. */
8
+ readonly manifestPath: string;
9
+ /** Address the gateway should use to reach this service, if set. */
10
+ readonly appAddress?: string;
11
+ /**
12
+ * Enrolment token for self-registration, if set. Not enumerable, so it
13
+ * does not appear when the config is logged or serialised.
14
+ */
15
+ readonly enrolmentToken?: string;
16
+ /**
17
+ * The credential a built-in service registers its instance key with, if
18
+ * set. Not enumerable, like the enrolment token.
19
+ */
20
+ readonly serviceCredential?: string;
21
+ }
22
+ type Env = Readonly<Record<string, string | undefined>>;
23
+ /**
24
+ * Read the platform settings from the environment.
25
+ *
26
+ * Throws a FinchlyError listing every problem, so a misconfigured container
27
+ * fails once with the full picture.
28
+ */
29
+ export declare function loadConfig(env?: Env): PlatformConfig;
30
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,68 @@
1
+ import { FinchlyError } from "./errors.js";
2
+ /** Where the manifest lives in an image unless PLATFORM_MANIFEST_PATH says otherwise. */
3
+ export const DEFAULT_MANIFEST_PATH = "/finchly/manifest.json";
4
+ function httpUrl(name, value, problems) {
5
+ let url;
6
+ try {
7
+ url = new URL(value);
8
+ }
9
+ catch {
10
+ problems.push(`${name} is not a URL`);
11
+ return undefined;
12
+ }
13
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
14
+ problems.push(`${name} must be an http or https URL`);
15
+ return undefined;
16
+ }
17
+ if (url.username || url.password) {
18
+ problems.push(`${name} must not contain credentials`);
19
+ return undefined;
20
+ }
21
+ if (url.search || url.hash) {
22
+ problems.push(`${name} must not have a query string or fragment`);
23
+ return undefined;
24
+ }
25
+ return url.toString().replace(/\/+$/, "");
26
+ }
27
+ /**
28
+ * Read the platform settings from the environment.
29
+ *
30
+ * Throws a FinchlyError listing every problem, so a misconfigured container
31
+ * fails once with the full picture.
32
+ */
33
+ export function loadConfig(env = process.env) {
34
+ const problems = [];
35
+ const rawGateway = env.PLATFORM_GATEWAY_URL?.trim();
36
+ let gatewayUrl;
37
+ if (!rawGateway) {
38
+ problems.push("PLATFORM_GATEWAY_URL is not set");
39
+ }
40
+ else {
41
+ gatewayUrl = httpUrl("PLATFORM_GATEWAY_URL", rawGateway, problems);
42
+ }
43
+ const rawAddress = env.PLATFORM_APP_ADDRESS?.trim();
44
+ const appAddress = rawAddress ? httpUrl("PLATFORM_APP_ADDRESS", rawAddress, problems) : undefined;
45
+ const manifestPath = env.PLATFORM_MANIFEST_PATH?.trim() || DEFAULT_MANIFEST_PATH;
46
+ if (!manifestPath.startsWith("/")) {
47
+ problems.push("PLATFORM_MANIFEST_PATH must be an absolute path");
48
+ }
49
+ const enrolmentToken = env.PLATFORM_ENROLMENT_TOKEN?.trim() || undefined;
50
+ const serviceCredential = env.PLATFORM_SERVICE_CREDENTIAL?.trim() || undefined;
51
+ if (problems.length > 0 || gatewayUrl === undefined) {
52
+ throw new FinchlyError("config_invalid", "Platform settings are invalid", problems);
53
+ }
54
+ const config = {
55
+ gatewayUrl,
56
+ manifestPath,
57
+ ...(appAddress !== undefined ? { appAddress } : {}),
58
+ };
59
+ for (const [name, value] of [
60
+ ["enrolmentToken", enrolmentToken],
61
+ ["serviceCredential", serviceCredential],
62
+ ]) {
63
+ if (value !== undefined) {
64
+ Object.defineProperty(config, name, { value, enumerable: false });
65
+ }
66
+ }
67
+ return Object.freeze(config);
68
+ }
@@ -0,0 +1,21 @@
1
+ export type FinchlyErrorCode = "config_invalid" | "manifest_invalid" | "manifest_unreadable" | "identity_invalid" | "gateway_unreachable" | "platform_api_unsupported" | "service_call_invalid" | "service_key_rejected" | "app_registration_refused" | "app_pending_approval";
2
+ /**
3
+ * Every error this SDK throws on purpose. `code` is stable and safe to
4
+ * branch on; `problems` lists each thing that was wrong, so a caller can
5
+ * report all of them at once instead of one per restart.
6
+ */
7
+ export declare class FinchlyError extends Error {
8
+ readonly code: FinchlyErrorCode;
9
+ readonly problems: readonly string[];
10
+ constructor(code: FinchlyErrorCode, message: string, problems?: readonly string[]);
11
+ }
12
+ /** Why an identity token was rejected. Defined in spec/identity-token.md. */
13
+ export type IdentityRejection = "missing" | "malformed" | "algorithm" | "unknown_key" | "signature" | "issuer" | "audience" | "expired" | "claims";
14
+ /**
15
+ * The request carries no acceptable identity. Treat it as unauthenticated
16
+ * (HTTP 401). `reason` is for logs; do not show it to the caller.
17
+ */
18
+ export declare class IdentityError extends FinchlyError {
19
+ readonly reason: IdentityRejection;
20
+ constructor(reason: IdentityRejection);
21
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Every error this SDK throws on purpose. `code` is stable and safe to
3
+ * branch on; `problems` lists each thing that was wrong, so a caller can
4
+ * report all of them at once instead of one per restart.
5
+ */
6
+ export class FinchlyError extends Error {
7
+ code;
8
+ problems;
9
+ constructor(code, message, problems = []) {
10
+ super(problems.length > 0 ? `${message}: ${problems.join("; ")}` : message);
11
+ this.name = "FinchlyError";
12
+ this.code = code;
13
+ this.problems = Object.freeze([...problems]);
14
+ }
15
+ }
16
+ /**
17
+ * The request carries no acceptable identity. Treat it as unauthenticated
18
+ * (HTTP 401). `reason` is for logs; do not show it to the caller.
19
+ */
20
+ export class IdentityError extends FinchlyError {
21
+ reason;
22
+ constructor(reason) {
23
+ super("identity_invalid", `Identity token rejected: ${reason}`);
24
+ this.name = "IdentityError";
25
+ this.reason = reason;
26
+ }
27
+ }
@@ -0,0 +1,58 @@
1
+ import type { JSONWebKeySet, JWTPayload } from "jose";
2
+ /** Header the gateway sets on every request it proxies. */
3
+ export declare const IDENTITY_HEADER = "x-platform-identity";
4
+ /** Who is calling, as asserted by the gateway. */
5
+ export interface Identity {
6
+ /** The user's platform id, or `service:<slug>` for a service calling as itself. */
7
+ readonly subject: string;
8
+ /** True when the caller is a service calling as itself, with no user. */
9
+ readonly service: boolean;
10
+ readonly email: string | null;
11
+ readonly name: string | null;
12
+ /** Platform group names. */
13
+ readonly groups: readonly string[];
14
+ /** The user's roles for this service. */
15
+ readonly roles: readonly string[];
16
+ /** How the caller was authenticated. */
17
+ readonly amr: readonly string[];
18
+ /** `service:<slug>` when a service is acting on the user's behalf. */
19
+ readonly actor: string | null;
20
+ readonly sessionId: string | null;
21
+ /** Every claim in the token. */
22
+ readonly claims: Readonly<JWTPayload>;
23
+ }
24
+ export interface IdentityVerifierOptions {
25
+ /** This service's slug. A token for any other service is rejected. */
26
+ readonly slug: string;
27
+ /**
28
+ * The gateway's internal address (PLATFORM_GATEWAY_URL). The issuer and
29
+ * signing keys are fetched from it. This is the normal way to configure
30
+ * the verifier.
31
+ */
32
+ readonly gatewayUrl?: string;
33
+ /** Fixed issuer, used with `jwks` in place of `gatewayUrl`. For tests. */
34
+ readonly issuer?: string;
35
+ /** Fixed key set, used with `issuer` in place of `gatewayUrl`. For tests. */
36
+ readonly jwks?: JSONWebKeySet;
37
+ /** Allowed clock difference with the gateway. Default 30. */
38
+ readonly clockSkewSeconds?: number;
39
+ /** Clock, for tests. */
40
+ readonly now?: () => Date;
41
+ }
42
+ type HeaderBag = {
43
+ get(name: string): string | null;
44
+ } | Readonly<Record<string, string | readonly string[] | undefined>>;
45
+ export interface IdentityVerifier {
46
+ /** Verify a token. Throws IdentityError if it is not acceptable. */
47
+ verify(token: string | null | undefined): Promise<Identity>;
48
+ /** Verify the token in a request's headers (Node or Fetch style). */
49
+ fromHeaders(headers: HeaderBag): Promise<Identity>;
50
+ }
51
+ /**
52
+ * Create a verifier for this service.
53
+ *
54
+ * Create one at startup and reuse it: it caches the gateway's issuer and
55
+ * signing keys.
56
+ */
57
+ export declare function createIdentityVerifier(options: IdentityVerifierOptions): IdentityVerifier;
58
+ export {};