@endora-commerce/mod-api-keys 0.100.0
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/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +43 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/ApiKeysPage.d.ts +3 -0
- package/dist/admin/pages/ApiKeysPage.d.ts.map +1 -0
- package/dist/admin/pages/ApiKeysPage.js +182 -0
- package/dist/admin/pages/ApiKeysPage.js.map +1 -0
- package/dist/backend/entities/api-key.entity.d.ts +33 -0
- package/dist/backend/entities/api-key.entity.d.ts.map +1 -0
- package/dist/backend/entities/api-key.entity.js +118 -0
- package/dist/backend/entities/api-key.entity.js.map +1 -0
- package/dist/backend/index.d.ts +71 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +57 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +65 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +75 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.d.ts +26 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +51 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/api-key-service.d.ts +72 -0
- package/dist/backend/services/api-key-service.d.ts.map +1 -0
- package/dist/backend/services/api-key-service.js +186 -0
- package/dist/backend/services/api-key-service.js.map +1 -0
- package/dist/manifest.d.ts +169 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +155 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.d.ts +50 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.d.ts.map +1 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.js +117 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/api_keys.md +89 -0
- package/i18n/en.json +5 -0
- package/i18n/pl.json +5 -0
- package/package.json +93 -0
- package/tailwind.css +14 -0
package/docs/api_keys.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: api_keys
|
|
3
|
+
description: Bearer-token integration credentials
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `api_keys`
|
|
7
|
+
|
|
8
|
+
Scoped bearer-token credentials for machine-to-machine integrations. The
|
|
9
|
+
plaintext token is shown once at creation; only its SHA-256 hash is stored.
|
|
10
|
+
A key may additionally carry a **distributor binding**
|
|
11
|
+
(Organization + Sales Channel + service Customer Account) and an optional
|
|
12
|
+
expiry, turning it into a partner credential for the `/api/v1/external/*`
|
|
13
|
+
namespace, which the *Partner API access* integration guide documents in full.
|
|
14
|
+
|
|
15
|
+
## Public surface
|
|
16
|
+
|
|
17
|
+
| Verb + Path | Audience | Purpose |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `GET /api/v1/admin/api-keys` | admin | List keys + last-used timestamps, binding, expiry |
|
|
20
|
+
| `POST /api/v1/admin/api-keys` | admin | Create; returns the raw bearer **once** |
|
|
21
|
+
| `DELETE /api/v1/admin/api-keys/:id` | admin | Revoke |
|
|
22
|
+
|
|
23
|
+
External calls authenticate by sending `Authorization: Bearer sk_live_…`.
|
|
24
|
+
`requireApiKey(scope)` and `requireBoundApiKey(scope)` are Fastify
|
|
25
|
+
pre-handlers exposed by the `api_keys` plugin; route surfaces gate themselves
|
|
26
|
+
with them (`requireApiKey('catalog:write')`,
|
|
27
|
+
`requireBoundApiKey('orders:write')`, etc.).
|
|
28
|
+
|
|
29
|
+
## Scope enum
|
|
30
|
+
|
|
31
|
+
Creation validates scopes against the typed catalog in
|
|
32
|
+
`packages/contracts/src/api-keys.ts` (`apiKeyScopeSchema`):
|
|
33
|
+
|
|
34
|
+
| Scope | Meaning |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `catalog:read` | PIM reads (unbound) and the external catalog surface (bound) |
|
|
37
|
+
| `catalog:write` | PIM by-SKU upsert — **unbound keys only** |
|
|
38
|
+
| `orders:read` | External order reads — **bound keys only** |
|
|
39
|
+
| `orders:write` | External order intake — **bound keys only** |
|
|
40
|
+
|
|
41
|
+
Enforcement stays membership-based, so legacy free-text scopes on existing
|
|
42
|
+
keys remain readable and enforceable — only creation is validated.
|
|
43
|
+
|
|
44
|
+
## Binding model
|
|
45
|
+
|
|
46
|
+
The binding is all-or-none and **immutable post-create** (token-shown-once
|
|
47
|
+
lifecycle; rebinding means revoking and issuing a new key). Creation rules,
|
|
48
|
+
validated server-side and mirrored inline in the admin form:
|
|
49
|
+
|
|
50
|
+
| Rule | Detail |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| B1 | Any `orders:*` scope ⇒ binding required |
|
|
53
|
+
| B2 | Binding present ⇒ `catalog:write` forbidden (PIM writes stay unbound-only) |
|
|
54
|
+
| B3 | The service Customer Account must be active and belong to the bound Organization |
|
|
55
|
+
| B4 | Organization and Sales Channel must exist (the channel need not be active at creation — the key simply fails closed while it is inactive) |
|
|
56
|
+
| B5 | `expiresAt`, when present, must be a future instant |
|
|
57
|
+
|
|
58
|
+
At request time a bound key derives a single-org tenant context and a pinned
|
|
59
|
+
sales channel (an explicit `X-Sales-Channel` header naming a different channel
|
|
60
|
+
is refused with `403 API_KEY_CHANNEL_MISMATCH`). An unbound key keeps the
|
|
61
|
+
legacy system context and header/host/default channel resolution.
|
|
62
|
+
|
|
63
|
+
## Expiry
|
|
64
|
+
|
|
65
|
+
`expiresAt` is optional and applies to both key modes. Once the instant
|
|
66
|
+
passes, `authenticate` refuses the key with `401 UNAUTHORIZED` — the same
|
|
67
|
+
refusal as a revoked key.
|
|
68
|
+
|
|
69
|
+
## Entities
|
|
70
|
+
|
|
71
|
+
`ApiKey` (name, keyHash, lastFour, scopes, status, lastUsedAt, and the
|
|
72
|
+
nullable binding/expiry columns `organizationId`, `salesChannelId`,
|
|
73
|
+
`customerAccountId`, `expiresAt`).
|
|
74
|
+
|
|
75
|
+
## Out-of-scope / gate behaviour
|
|
76
|
+
|
|
77
|
+
- Wrong scope ⇒ `403 API_KEY_OUT_OF_SCOPE` + audit row `api_key.out_of_scope`.
|
|
78
|
+
- Unbound key on a bound-only endpoint ⇒ `403 API_KEY_NOT_BOUND` + audit row
|
|
79
|
+
`api_key.not_bound`.
|
|
80
|
+
- Channel mismatch ⇒ `403 API_KEY_CHANNEL_MISMATCH` + audit row
|
|
81
|
+
`api_key.channel_mismatch`.
|
|
82
|
+
|
|
83
|
+
## Extension points
|
|
84
|
+
|
|
85
|
+
- **New scopes** — extend `apiKeyScopeSchema` in `@endora-commerce/contracts` and gate the
|
|
86
|
+
new surface at its call site; the service is scope-name-agnostic at
|
|
87
|
+
enforcement time.
|
|
88
|
+
- **Per-key rate limit** — `api-key-service.authenticate` returns the key
|
|
89
|
+
id; layer a per-key counter in the pre-handler or a downstream middleware.
|
package/i18n/en.json
ADDED
package/i18n/pl.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endora-commerce/mod-api-keys",
|
|
3
|
+
"version": "0.100.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Programmatic API keys (Bearer tokens) used by integrations and webhooks.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"endora": {
|
|
9
|
+
"type": "module",
|
|
10
|
+
"id": "api_keys"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/endora-commerce/endora-commerce.git",
|
|
15
|
+
"directory": "packages/modules/api_keys"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/manifest.d.ts",
|
|
23
|
+
"default": "./dist/manifest.js"
|
|
24
|
+
},
|
|
25
|
+
"./backend": {
|
|
26
|
+
"types": "./dist/backend/index.d.ts",
|
|
27
|
+
"default": "./dist/backend/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./migrations": {
|
|
30
|
+
"types": "./dist/migrations/index.d.ts",
|
|
31
|
+
"default": "./dist/migrations/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./admin": {
|
|
34
|
+
"types": "./dist/admin/index.d.ts",
|
|
35
|
+
"default": "./dist/admin/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./tailwind.css": "./tailwind.css",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"i18n",
|
|
43
|
+
"docs",
|
|
44
|
+
"tailwind.css"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.18.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@mikro-orm/core": "^6",
|
|
51
|
+
"@mikro-orm/migrations": "^6",
|
|
52
|
+
"@mikro-orm/postgresql": "^6",
|
|
53
|
+
"fastify": "^5",
|
|
54
|
+
"lucide-react": "^1",
|
|
55
|
+
"react": "^19",
|
|
56
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
57
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
58
|
+
"@endora-commerce/platform": "0.100.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@endora-commerce/admin-kit": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"lucide-react": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"react": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@fastify/type-provider-zod": "^1.0.0",
|
|
73
|
+
"@mikro-orm/core": "^6.6.13",
|
|
74
|
+
"@mikro-orm/migrations": "^6.6.13",
|
|
75
|
+
"@mikro-orm/postgresql": "^6.6.13",
|
|
76
|
+
"@types/node": "^22.9.0",
|
|
77
|
+
"@types/react": "^19.2.14",
|
|
78
|
+
"fastify": "^5.12.5",
|
|
79
|
+
"lucide-react": "^1.11.0",
|
|
80
|
+
"react": "^19.2.5",
|
|
81
|
+
"typescript": "^5.9.3",
|
|
82
|
+
"vitest": "^4.1.11",
|
|
83
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
84
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
85
|
+
"@endora-commerce/platform": "0.100.0"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
|
|
89
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
|
|
90
|
+
"lint": "eslint src",
|
|
91
|
+
"test": "vitest run"
|
|
92
|
+
}
|
|
93
|
+
}
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @endora-commerce/mod-api-keys — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
|
|
2
|
+
*
|
|
3
|
+
* The `@source` directives this package asks its host to scan
|
|
4
|
+
* (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
|
|
5
|
+
* They resolve relative to **this file**, so they hold wherever the package is
|
|
6
|
+
* installed — a workspace link here, `node_modules` in a client's instance.
|
|
7
|
+
*
|
|
8
|
+
* The `dist` line is what a published tarball ships and is what an instance
|
|
9
|
+
* scans; the `src` line is inert there and is what keeps `pnpm --filter admin
|
|
10
|
+
* run dev` reading source in this repository. Do not edit: run
|
|
11
|
+
* `pnpm --filter backend run manifests:generate`.
|
|
12
|
+
*/
|
|
13
|
+
@source "./dist/admin";
|
|
14
|
+
@source "./src/admin";
|