@owlmeans/api 0.1.18-rc.38 → 0.1.18-rc.39
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/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/api/SKILL.md +6 -2
- package/build/consts.d.ts +2 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +2 -0
- package/build/consts.js.map +1 -1
- package/build/status/index.d.ts +4 -0
- package/build/status/index.d.ts.map +1 -1
- package/build/status/index.js +6 -0
- package/build/status/index.js.map +1 -1
- package/build/utils/handler.d.ts.map +1 -1
- package/build/utils/handler.js +8 -3
- package/build/utils/handler.js.map +1 -1
- package/package.json +3 -3
- package/src/consts.ts +2 -0
- package/src/status/index.ts +9 -0
- package/src/utils/handler.ts +9 -3
- package/tests/handler.spec.ts +7 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ HTTP API client service for OwlMeans client contexts — carries entrypoint call
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
bun add @owlmeans/api@^0.1.18-rc.
|
|
15
|
+
bun add @owlmeans/api@^0.1.18-rc.39
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -65,7 +65,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
65
65
|
your project's skill store (`.agents/skills/`):
|
|
66
66
|
|
|
67
67
|
```sh
|
|
68
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
68
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.40
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/api",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.39",
|
|
5
|
+
"generatedAt": "2026-09-25T21:19:38.017Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/api
|
|
9
9
|
|
|
10
10
|
**Layer:** Core
|
|
11
|
-
**Install:** `"@owlmeans/api": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/api": "^0.1.18-rc.39"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
## Key Exports
|
|
14
14
|
|
|
@@ -18,13 +18,17 @@ user-invocable: false
|
|
|
18
18
|
| `appendApiClient(ctx, alias?)` | Register it and make it the context's default `webService` when none is set |
|
|
19
19
|
| `ApiClient` | Service interface — a single `handler(req, reply)` |
|
|
20
20
|
| `ApiError`, `ApiClientError`, `ServerCrashedError`, `ServerAuthError`, `ApiStatusError` | Typed transport errors; an `ApiClientError` carries the answered `status` and the server's `incidentId` |
|
|
21
|
-
| `httpStatusOf`, `incidentIdOf`, `isIncidentBody`, `parseClientMarker`, `API_CLIENT_MARKER`, `API_STATUS_MARKER` | Read a failure's HTTP status
|
|
21
|
+
| `httpStatusOf`, `incidentIdOf`, `isAccessDenied`, `isIncidentBody`, `parseClientMarker`, `API_CLIENT_MARKER`, `API_STATUS_MARKER` | Read a failure's HTTP status, incident id and IAM denial marker (also the `./status` subpath) |
|
|
22
22
|
| Constants | Status codes (`OK`, `CREATED`, `ACCEPTED`, `FINISHED`, `UNAUTHORIZED_ERROR`, `FORBIDDEN_ERROR`, `SERVER_ERROR`), `INCIDENT_ID_HEADER` (`X-Incident-ID`), `DEFAULT_ALIAS` (`web-client`) |
|
|
23
23
|
|
|
24
24
|
Subpath `./status` — `httpStatusOf`, `incidentIdOf`, `isIncidentBody`, `parseClientMarker`, the
|
|
25
25
|
markers and `INCIDENT_ID_HEADER`, importing only `@owlmeans/error`: a browser package reads a
|
|
26
26
|
status without pulling axios in.
|
|
27
27
|
|
|
28
|
+
`isAccessDenied(error)` requires the server's `X-OwlMeans-Denial: access-denied` marker and a 403.
|
|
29
|
+
Do not classify all 403 responses as IAM denials: entitlement and other refusals use that status
|
|
30
|
+
too. The API client stamps the marker on its rejection even under production error exposure.
|
|
31
|
+
|
|
28
32
|
## How a call is carried
|
|
29
33
|
|
|
30
34
|
`ep.call(...)` / `ep.invoke(...)` hand the request to whatever is bound to the route's protocol. When
|
package/build/consts.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare const FINISHED = 204;
|
|
|
10
10
|
* server log entry that owns a failure's full detail.
|
|
11
11
|
*/
|
|
12
12
|
export declare const INCIDENT_ID_HEADER = "X-Incident-ID";
|
|
13
|
+
export declare const DENIAL_KIND_HEADER = "X-OwlMeans-Denial";
|
|
14
|
+
export declare const ACCESS_DENIED_KIND = "access-denied";
|
|
13
15
|
export declare const DEFAULT_ALIAS = "web-client";
|
|
14
16
|
export declare const protocols: string[];
|
|
15
17
|
//# sourceMappingURL=consts.d.ts.map
|
package/build/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B,eAAO,MAAM,kBAAkB,MAAM,CAAA;AAErC,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC,eAAO,MAAM,EAAE,MAAM,CAAA;AAErB,eAAO,MAAM,OAAO,MAAM,CAAA;AAE1B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B;;;GAGG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAEjD,eAAO,MAAM,aAAa,eAAe,CAAA;AAEzC,eAAO,MAAM,SAAS,UAAiC,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B,eAAO,MAAM,kBAAkB,MAAM,CAAA;AAErC,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC,eAAO,MAAM,EAAE,MAAM,CAAA;AAErB,eAAO,MAAM,OAAO,MAAM,CAAA;AAE1B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B;;;GAGG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AACjD,eAAO,MAAM,kBAAkB,sBAAsB,CAAA;AACrD,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAEjD,eAAO,MAAM,aAAa,eAAe,CAAA;AAEzC,eAAO,MAAM,SAAS,UAAiC,CAAA"}
|
package/build/consts.js
CHANGED
|
@@ -10,6 +10,8 @@ export const FINISHED = 204;
|
|
|
10
10
|
* server log entry that owns a failure's full detail.
|
|
11
11
|
*/
|
|
12
12
|
export const INCIDENT_ID_HEADER = 'X-Incident-ID';
|
|
13
|
+
export const DENIAL_KIND_HEADER = 'X-OwlMeans-Denial';
|
|
14
|
+
export const ACCESS_DENIED_KIND = 'access-denied';
|
|
13
15
|
export const DEFAULT_ALIAS = 'web-client';
|
|
14
16
|
export const protocols = ['http', 'https', 'wss', 'ws'];
|
|
15
17
|
//# sourceMappingURL=consts.js.map
|
package/build/consts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAErC,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,CAAA;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAA;AAE1B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAA;AAEjD,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAA;AAEzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAErC,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,CAAA;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAA;AAE1B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAA;AACjD,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAAmB,CAAA;AACrD,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAA;AAEjD,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAA;AAEzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA"}
|
package/build/status/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export { INCIDENT_ID_HEADER } from '../consts.js';
|
|
2
|
+
export { DENIAL_KIND_HEADER, ACCESS_DENIED_KIND } from '../consts.js';
|
|
2
3
|
export { API_CLIENT_MARKER, API_STATUS_MARKER, parseClientMarker } from './marker.js';
|
|
3
4
|
export type { ClientMarker } from './marker.js';
|
|
4
5
|
/** The HTTP status the API client stamps on an error it rebuilt from a response body. */
|
|
5
6
|
export interface ResponseStatusCarrier {
|
|
6
7
|
responseStatus?: number;
|
|
8
|
+
denialKind?: string;
|
|
7
9
|
}
|
|
10
|
+
/** True only for a server-labelled auth/IAM refusal, never an unrelated 403. */
|
|
11
|
+
export declare const isAccessDenied: (error: unknown) => boolean;
|
|
8
12
|
/**
|
|
9
13
|
* The HTTP status an error answers to, or `null` when nothing states one.
|
|
10
14
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/status/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACrF,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/status/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAErE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACrF,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,gFAAgF;AAChF,eAAO,MAAM,cAAc,UAAW,OAAO,KAAG,OAGuB,CAAA;AAavE;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,GAAG,IAkBtD,CAAA;AAED,iGAAiG;AACjG,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,GAAG,IAUtD,CAAA;AAID;;;;GAIG;AACH,eAAO,MAAM,cAAc,SAAU,OAAO,KAAG,IAAI,IAAI,MACyC,CAAA"}
|
package/build/status/index.js
CHANGED
|
@@ -2,7 +2,13 @@ import { ResilientError } from '@owlmeans/error';
|
|
|
2
2
|
import { ApiClientError } from '../errors.js';
|
|
3
3
|
import { parseClientMarker } from './marker.js';
|
|
4
4
|
export { INCIDENT_ID_HEADER } from '../consts.js';
|
|
5
|
+
export { DENIAL_KIND_HEADER, ACCESS_DENIED_KIND } from '../consts.js';
|
|
6
|
+
import { ACCESS_DENIED_KIND } from '../consts.js';
|
|
5
7
|
export { API_CLIENT_MARKER, API_STATUS_MARKER, parseClientMarker } from './marker.js';
|
|
8
|
+
/** True only for a server-labelled auth/IAM refusal, never an unrelated 403. */
|
|
9
|
+
export const isAccessDenied = (error) => httpStatusOf(error) === 403
|
|
10
|
+
&& typeof error === 'object' && error != null
|
|
11
|
+
&& error.denialKind === ACCESS_DENIED_KIND;
|
|
6
12
|
const httpStatus = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599 ? value : null;
|
|
7
13
|
/** The first `api:client:*` marker in an error's `message`, else in its `type`. */
|
|
8
14
|
const markerOf = (error) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/status/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/status/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/utils/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/utils/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAM1C,OAAO,EAAE,cAAc,EAAuD,MAAM,cAAc,CAAA;AAIlG,eAAO,MAAM,eAAe,aAAc,aAAa,SAAS,gBAAgB,CAAC,GAAG,CAAC,SAoBpF,CAAA;AAkCD;;;;GAIG;AACH,eAAO,MAAM,WAAW,WAAY,MAAM,eAAe,MAAM,KAAG,cAUjE,CAAA"}
|
package/build/utils/handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntrypointOutcome } from '@owlmeans/entrypoint';
|
|
2
|
-
import { ACCEPTED, CREATED, FINISHED, FORBIDDEN_ERROR, INCIDENT_ID_HEADER, OK, SERVER_ERROR, UNAUTHORIZED_ERROR, } from '../consts.js';
|
|
2
|
+
import { ACCEPTED, CREATED, FINISHED, FORBIDDEN_ERROR, INCIDENT_ID_HEADER, DENIAL_KIND_HEADER, ACCESS_DENIED_KIND, OK, SERVER_ERROR, UNAUTHORIZED_ERROR, } from '../consts.js';
|
|
3
3
|
import { ResilientError } from '@owlmeans/error';
|
|
4
4
|
import { ApiClientError, ApiStatusError, ServerAuthError, ServerCrashedError } from '../errors.js';
|
|
5
5
|
import { isIncidentBody } from '../status/index.js';
|
|
@@ -33,18 +33,23 @@ export const processResponse = (response, reply) => {
|
|
|
33
33
|
const failureOf = (response) => {
|
|
34
34
|
const incidentId = headerOf(response.headers, INCIDENT_ID_HEADER)
|
|
35
35
|
?? (isIncidentBody(response.data) ? response.data.trim() : undefined);
|
|
36
|
+
const denialKind = response.status === FORBIDDEN_ERROR
|
|
37
|
+
&& headerOf(response.headers, DENIAL_KIND_HEADER) === ACCESS_DENIED_KIND
|
|
38
|
+
? ACCESS_DENIED_KIND : undefined;
|
|
36
39
|
if (typeof response.data === 'string' && response.data.includes(ResilientError.separator)) {
|
|
37
40
|
try {
|
|
38
41
|
const error = ResilientError.ensure(response.data, true);
|
|
39
42
|
error.incidentId ??= incidentId;
|
|
40
|
-
Object.assign(error, {
|
|
43
|
+
Object.assign(error, {
|
|
44
|
+
responseStatus: response.status, denialKind,
|
|
45
|
+
});
|
|
41
46
|
return error;
|
|
42
47
|
}
|
|
43
48
|
catch {
|
|
44
49
|
console.error(`Server returned an unrecognizable text error: ${response.status} ${response.data}`);
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
|
-
return statusError(response.status, incidentId);
|
|
52
|
+
return Object.assign(statusError(response.status, incidentId), { denialKind });
|
|
48
53
|
};
|
|
49
54
|
/**
|
|
50
55
|
* The typed error of a bare status: `ServerCrashedError` (500), `ServerAuthError` (401),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/utils/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,OAAO,EACL,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,kBAAkB,EAAE,EAAE,EAAE,YAAY,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/utils/handler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/api",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.39",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
32
|
+
"@owlmeans/auth-common": "^0.1.18-rc.39",
|
|
33
33
|
"@owlmeans/client-config": "^0.1.18-rc.38",
|
|
34
|
-
"@owlmeans/client-route": "^0.1.18-rc.
|
|
34
|
+
"@owlmeans/client-route": "^0.1.18-rc.40",
|
|
35
35
|
"@owlmeans/config": "^0.1.18-rc.38",
|
|
36
36
|
"@owlmeans/context": "^0.1.18-rc.32",
|
|
37
37
|
"@owlmeans/error": "^0.1.18-rc.36",
|
package/src/consts.ts
CHANGED
|
@@ -18,6 +18,8 @@ export const FINISHED = 204
|
|
|
18
18
|
* server log entry that owns a failure's full detail.
|
|
19
19
|
*/
|
|
20
20
|
export const INCIDENT_ID_HEADER = 'X-Incident-ID'
|
|
21
|
+
export const DENIAL_KIND_HEADER = 'X-OwlMeans-Denial'
|
|
22
|
+
export const ACCESS_DENIED_KIND = 'access-denied'
|
|
21
23
|
|
|
22
24
|
export const DEFAULT_ALIAS = 'web-client'
|
|
23
25
|
|
package/src/status/index.ts
CHANGED
|
@@ -3,14 +3,23 @@ import { ApiClientError } from '../errors.js'
|
|
|
3
3
|
import { parseClientMarker } from './marker.js'
|
|
4
4
|
|
|
5
5
|
export { INCIDENT_ID_HEADER } from '../consts.js'
|
|
6
|
+
export { DENIAL_KIND_HEADER, ACCESS_DENIED_KIND } from '../consts.js'
|
|
7
|
+
import { ACCESS_DENIED_KIND } from '../consts.js'
|
|
6
8
|
export { API_CLIENT_MARKER, API_STATUS_MARKER, parseClientMarker } from './marker.js'
|
|
7
9
|
export type { ClientMarker } from './marker.js'
|
|
8
10
|
|
|
9
11
|
/** The HTTP status the API client stamps on an error it rebuilt from a response body. */
|
|
10
12
|
export interface ResponseStatusCarrier {
|
|
11
13
|
responseStatus?: number
|
|
14
|
+
denialKind?: string
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
/** True only for a server-labelled auth/IAM refusal, never an unrelated 403. */
|
|
18
|
+
export const isAccessDenied = (error: unknown): boolean =>
|
|
19
|
+
httpStatusOf(error) === 403
|
|
20
|
+
&& typeof error === 'object' && error != null
|
|
21
|
+
&& (error as ResponseStatusCarrier).denialKind === ACCESS_DENIED_KIND
|
|
22
|
+
|
|
14
23
|
const httpStatus = (value: unknown): number | null =>
|
|
15
24
|
typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599 ? value : null
|
|
16
25
|
|
package/src/utils/handler.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { AbstractResponse } from '@owlmeans/entrypoint'
|
|
|
2
2
|
import { EntrypointOutcome } from '@owlmeans/entrypoint'
|
|
3
3
|
import type { AxiosResponse } from 'axios'
|
|
4
4
|
import {
|
|
5
|
-
ACCEPTED, CREATED, FINISHED, FORBIDDEN_ERROR, INCIDENT_ID_HEADER,
|
|
5
|
+
ACCEPTED, CREATED, FINISHED, FORBIDDEN_ERROR, INCIDENT_ID_HEADER, DENIAL_KIND_HEADER,
|
|
6
|
+
ACCESS_DENIED_KIND, OK, SERVER_ERROR, UNAUTHORIZED_ERROR,
|
|
6
7
|
} from '../consts.js'
|
|
7
8
|
import { ResilientError } from '@owlmeans/error'
|
|
8
9
|
import { ApiClientError, ApiStatusError, ServerAuthError, ServerCrashedError } from '../errors.js'
|
|
@@ -42,12 +43,17 @@ export const processResponse = (response: AxiosResponse, reply: AbstractResponse
|
|
|
42
43
|
const failureOf = (response: AxiosResponse): Error => {
|
|
43
44
|
const incidentId = headerOf(response.headers, INCIDENT_ID_HEADER)
|
|
44
45
|
?? (isIncidentBody(response.data) ? response.data.trim() : undefined)
|
|
46
|
+
const denialKind = response.status === FORBIDDEN_ERROR
|
|
47
|
+
&& headerOf(response.headers, DENIAL_KIND_HEADER) === ACCESS_DENIED_KIND
|
|
48
|
+
? ACCESS_DENIED_KIND : undefined
|
|
45
49
|
|
|
46
50
|
if (typeof response.data === 'string' && response.data.includes(ResilientError.separator)) {
|
|
47
51
|
try {
|
|
48
52
|
const error = ResilientError.ensure(response.data, true)
|
|
49
53
|
error.incidentId ??= incidentId
|
|
50
|
-
Object.assign(error, {
|
|
54
|
+
Object.assign(error, {
|
|
55
|
+
responseStatus: response.status, denialKind,
|
|
56
|
+
} satisfies ResponseStatusCarrier)
|
|
51
57
|
|
|
52
58
|
return error
|
|
53
59
|
} catch {
|
|
@@ -55,7 +61,7 @@ const failureOf = (response: AxiosResponse): Error => {
|
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
return statusError(response.status, incidentId)
|
|
64
|
+
return Object.assign(statusError(response.status, incidentId), { denialKind })
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/**
|
package/tests/handler.spec.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ResilientError } from '@owlmeans/error'
|
|
|
7
7
|
import {
|
|
8
8
|
ApiClientError, ApiStatusError, ServerAuthError, ServerCrashedError, httpStatusOf, incidentIdOf,
|
|
9
9
|
INCIDENT_ID_HEADER,
|
|
10
|
+
DENIAL_KIND_HEADER, ACCESS_DENIED_KIND,
|
|
10
11
|
} from '../src/index.js'
|
|
11
12
|
import { processResponse, statusError } from '../src/utils/handler.js'
|
|
12
13
|
import * as status from '../src/status/index.js'
|
|
@@ -59,6 +60,12 @@ describe('2xx', () => {
|
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
describe('a production incident body keeps its status and incident id', () => {
|
|
63
|
+
test('only a labelled 403 is an auth/IAM denial', () => {
|
|
64
|
+
const denied = respond(403, INCIDENT, { [DENIAL_KIND_HEADER]: ACCESS_DENIED_KIND }).error
|
|
65
|
+
expect(status.isAccessDenied(denied)).toBe(true)
|
|
66
|
+
expect(status.isAccessDenied(respond(403, INCIDENT).error)).toBe(false)
|
|
67
|
+
expect(status.isAccessDenied(respond(401, INCIDENT, { [DENIAL_KIND_HEADER]: ACCESS_DENIED_KIND }).error)).toBe(false)
|
|
68
|
+
})
|
|
62
69
|
test('428 with the header becomes ApiStatusError', () => {
|
|
63
70
|
const { error } = respond(428, INCIDENT, { [INCIDENT_ID_HEADER.toLowerCase()]: INCIDENT })
|
|
64
71
|
expect(error).toBeInstanceOf(ApiStatusError)
|