@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/admin/index.d.ts +37 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +43 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/ApiKeysPage.d.ts +3 -0
  8. package/dist/admin/pages/ApiKeysPage.d.ts.map +1 -0
  9. package/dist/admin/pages/ApiKeysPage.js +182 -0
  10. package/dist/admin/pages/ApiKeysPage.js.map +1 -0
  11. package/dist/backend/entities/api-key.entity.d.ts +33 -0
  12. package/dist/backend/entities/api-key.entity.d.ts.map +1 -0
  13. package/dist/backend/entities/api-key.entity.js +118 -0
  14. package/dist/backend/entities/api-key.entity.js.map +1 -0
  15. package/dist/backend/index.d.ts +71 -0
  16. package/dist/backend/index.d.ts.map +1 -0
  17. package/dist/backend/index.js +57 -0
  18. package/dist/backend/index.js.map +1 -0
  19. package/dist/backend/plugin.d.ts +65 -0
  20. package/dist/backend/plugin.d.ts.map +1 -0
  21. package/dist/backend/plugin.js +75 -0
  22. package/dist/backend/plugin.js.map +1 -0
  23. package/dist/backend/routes.d.ts +26 -0
  24. package/dist/backend/routes.d.ts.map +1 -0
  25. package/dist/backend/routes.js +51 -0
  26. package/dist/backend/routes.js.map +1 -0
  27. package/dist/backend/services/api-key-service.d.ts +72 -0
  28. package/dist/backend/services/api-key-service.d.ts.map +1 -0
  29. package/dist/backend/services/api-key-service.js +186 -0
  30. package/dist/backend/services/api-key-service.js.map +1 -0
  31. package/dist/manifest.d.ts +169 -0
  32. package/dist/manifest.d.ts.map +1 -0
  33. package/dist/manifest.js +155 -0
  34. package/dist/manifest.js.map +1 -0
  35. package/dist/migrations/20260724T173916_api_keys_distributor_binding.d.ts +50 -0
  36. package/dist/migrations/20260724T173916_api_keys_distributor_binding.d.ts.map +1 -0
  37. package/dist/migrations/20260724T173916_api_keys_distributor_binding.js +117 -0
  38. package/dist/migrations/20260724T173916_api_keys_distributor_binding.js.map +1 -0
  39. package/dist/migrations/index.d.ts +27 -0
  40. package/dist/migrations/index.d.ts.map +1 -0
  41. package/dist/migrations/index.js +29 -0
  42. package/dist/migrations/index.js.map +1 -0
  43. package/docs/api_keys.md +89 -0
  44. package/i18n/en.json +5 -0
  45. package/i18n/pl.json +5 -0
  46. package/package.json +93 -0
  47. package/tailwind.css +14 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ <!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
2
+
3
+ # @endora-commerce/mod-api-keys
4
+
5
+ Programmatic API keys (Bearer tokens) used by integrations and webhooks.
6
+
7
+ ## What this is
8
+
9
+ An **Endora Commerce module package**. Its module id is `api_keys` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
10
+
11
+ A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
12
+
13
+ ## Entry points
14
+
15
+ | Import | Contents |
16
+ | --- | --- |
17
+ | `@endora-commerce/mod-api-keys` | the module manifest — its id, version, dependencies, settings and activation |
18
+ | `@endora-commerce/mod-api-keys/admin` | the Admin UI contribution: the screens, navigation and palette actions it adds |
19
+ | `@endora-commerce/mod-api-keys/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
20
+ | `@endora-commerce/mod-api-keys/migrations` | the module’s own schema migrations, in the order the platform runs them |
21
+ | `@endora-commerce/mod-api-keys/tailwind.css` | the Tailwind source declaration for the admin code above |
22
+
23
+ ## Depends on
24
+
25
+ Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
26
+
27
+ **Endora packages**
28
+
29
+ - `@endora-commerce/admin-kit` — *optional*
30
+ - `@endora-commerce/contracts`
31
+ - `@endora-commerce/platform`
32
+
33
+ **Third-party**
34
+
35
+ - `@mikro-orm/core` ^6
36
+ - `@mikro-orm/migrations` ^6
37
+ - `@mikro-orm/postgresql` ^6
38
+ - `fastify` ^5
39
+ - `lucide-react` ^1 — *optional*
40
+ - `react` ^19 — *optional*
41
+
42
+ ## What the tarball carries
43
+
44
+ - `dist/` — the compiled JavaScript and its type declarations
45
+ - `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
46
+ - `docs/` — the package’s own pages in the Endora Commerce documentation site
47
+ - `tailwind.css` — the Tailwind source declaration
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pnpm add @endora-commerce/mod-api-keys
53
+ ```
54
+
55
+ ## Licence
56
+
57
+ MIT — the text is in `LICENSE`, beside this file.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `api_keys`' admin surface — one route and one sidebar entry, declared by the
3
+ * module that owns them (feature 091, Phase 4, the plan's batch 6;
4
+ * `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
5
+ *
6
+ * **The batch this module belongs to was unblocked by P2 rather than chosen by
7
+ * size.** `plan.md`'s sequence table records this module's drain as **2**, the
8
+ * highest of the three, and both keys were `ApiKeysPage.tsx`'s reaches into
9
+ * `admin/src/components/organization-picker` and
10
+ * `admin/src/components/sales-channel-picker` — Group A entries in
11
+ * `backend/scripts/ledgers/admin-surface.ts`, retired when those pickers became
12
+ * `@endora-commerce/admin-kit/components`'. The screen names the published
13
+ * components now, so this batch pays no reach of its own.
14
+ *
15
+ * **`CustomerPicker` was never a Group A key and is worth saying so.** The
16
+ * screen reaches it too, and it has been the kit's since Phase 1b — it builds
17
+ * its own request from the published `apiClient`, which is the exit P2 took for
18
+ * the other three. It is in no ledger because there is nothing to ledger.
19
+ *
20
+ * **The permission the screen is gated on is shared with `webhooks`.**
21
+ * `integrations:manage` guards every route in both modules' admin surfaces and
22
+ * both manifests declare it (issue #213), so the code stays grantable while
23
+ * either surface is on. That is why this entry names the same code the API
24
+ * enforces rather than an `api_keys:`-prefixed one that does not exist.
25
+ *
26
+ * **This entry exports data and nothing else** (R2), like every `./admin`
27
+ * layer: `check:module-boundary`'s D-171 rule designates a subpath as contract
28
+ * surface when it emits no runtime binding, and `./admin` deliberately does not
29
+ * qualify, so a consumer reaching into another module's `./admin` stays a
30
+ * counted boundary reach.
31
+ *
32
+ * **The component is a dynamic-import factory** (R6), so Vite has a split point
33
+ * whether or not anybody remembers to ask for one.
34
+ */
35
+ import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
36
+ export declare const contributions: AdminContributions;
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAcnF,eAAO,MAAM,aAAa,EAAE,kBA+B3B,CAAC"}
@@ -0,0 +1,43 @@
1
+ /** The module's only route: issuing, binding and revoking machine keys. */
2
+ const ROUTE_PATH = '/api-keys';
3
+ /**
4
+ * The code every route of this module's admin API enforces —
5
+ * `requireAdmin('integrations:manage')` on `GET /api/v1/admin/api-keys` and on
6
+ * each of its siblings. It is the same code the module's `open-api-keys`
7
+ * palette action declares, which `check:action-route-permissions` holds to this
8
+ * very route.
9
+ */
10
+ const PERMISSION = 'integrations:manage';
11
+ export const contributions = {
12
+ routes: [
13
+ {
14
+ path: ROUTE_PATH,
15
+ component: () => import('./pages/ApiKeysPage.js'),
16
+ requiredPermission: PERMISSION,
17
+ index: true,
18
+ },
19
+ ],
20
+ nav: [
21
+ {
22
+ to: ROUTE_PATH,
23
+ // Module-relative (R8), resolved in this module's own namespace out of
24
+ // `packages/modules/api_keys/i18n/`. It was `appShell.nav.apiKeys` in the
25
+ // shared `_i18n` bundle, one of the four shared files a module author had
26
+ // to edit.
27
+ labelKey: 'nav.apiKeys.label',
28
+ // The glyph `AppShell.tsx` rendered by hand, and one already on the
29
+ // allowlist — `credentials`' row renders it too.
30
+ icon: 'KeyRound',
31
+ section: 'system',
32
+ // The hand-written position in *System* times a hundred, which is batch
33
+ // four's convention: `/api-keys` was the seventh row of that section
34
+ // before the drain began, one above `/webhooks`. *System* still holds
35
+ // host-declared entries, so `composeNav` appends every registry row after
36
+ // all of them whatever the weight says — what the weight can still do is
37
+ // keep the converted rows in the order the hand-written table had.
38
+ weight: 700,
39
+ requiredPermission: PERMISSION,
40
+ },
41
+ ],
42
+ };
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAoCA,2EAA2E;AAC3E,MAAM,UAAU,GAAG,WAAW,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEzC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC;YACjD,kBAAkB,EAAE,UAAU;YAC9B,KAAK,EAAE,IAAI;SACZ;KACF;IACD,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,0EAA0E;YAC1E,0EAA0E;YAC1E,WAAW;YACX,QAAQ,EAAE,mBAAmB;YAC7B,oEAAoE;YACpE,iDAAiD;YACjD,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,QAAQ;YACjB,wEAAwE;YACxE,qEAAqE;YACrE,sEAAsE;YACtE,0EAA0E;YAC1E,yEAAyE;YACzE,mEAAmE;YACnE,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,UAAU;SAC/B;KACF;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type ReactNode } from 'react';
2
+ export default function ApiKeysPage(): ReactNode;
3
+ //# sourceMappingURL=ApiKeysPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiKeysPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/ApiKeysPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAwDlG,MAAM,CAAC,OAAO,UAAU,WAAW,IAAI,SAAS,CA2O/C"}
@@ -0,0 +1,182 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { Check, Copy, Trash2 } from 'lucide-react';
4
+ import { apiKeyScopeSchema, } from '@endora-commerce/contracts';
5
+ import { ApiError, apiClient, formatDateTime } from '@endora-commerce/admin-kit/lib';
6
+ import { useTranslation } from '@endora-commerce/admin-kit/i18n';
7
+ import { Alert, AlertDescription, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, Input, Label, PageHeader, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@endora-commerce/admin-kit/ui';
8
+ import { CustomerPicker, OrganizationPicker, SalesChannelPicker, } from '@endora-commerce/admin-kit/components';
9
+ // Feature 062 — the scope catalog is the typed contract enum (research §R3);
10
+ // `integrations:manage` is an admin permission, not an api-key scope.
11
+ const KNOWN_SCOPES = apiKeyScopeSchema.options;
12
+ const ORDERS_SCOPES = new Set(['orders:read', 'orders:write']);
13
+ export default function ApiKeysPage() {
14
+ const t = useTranslation('core');
15
+ const [keys, setKeys] = useState([]);
16
+ const [loading, setLoading] = useState(true);
17
+ const [error, setError] = useState(null);
18
+ const [revealedToken, setRevealedToken] = useState(null);
19
+ const [copied, setCopied] = useState(false);
20
+ // Feature 062 — best-effort id → name lookup for the binding column.
21
+ const [orgNames, setOrgNames] = useState(new Map());
22
+ const copyToken = useCallback(async (token) => {
23
+ try {
24
+ await navigator.clipboard.writeText(token);
25
+ setCopied(true);
26
+ window.setTimeout(() => setCopied(false), 2000);
27
+ }
28
+ catch {
29
+ /* clipboard unavailable — the input is selectable as a fallback */
30
+ }
31
+ }, []);
32
+ const refresh = useCallback(async () => {
33
+ setLoading(true);
34
+ setError(null);
35
+ try {
36
+ const res = await apiClient.get('/api/v1/admin/api-keys');
37
+ setKeys(res.data);
38
+ }
39
+ catch (err) {
40
+ setError(err instanceof ApiError ? err.envelope.error.message : t('apiKeys.error.load'));
41
+ }
42
+ finally {
43
+ setLoading(false);
44
+ }
45
+ }, [t]);
46
+ useEffect(() => {
47
+ void refresh();
48
+ }, [refresh]);
49
+ // Resolve organization names for bound keys (same pattern as WebhooksPage).
50
+ useEffect(() => {
51
+ const missing = Array.from(new Set(keys
52
+ .map((k) => k.organizationId)
53
+ .filter((id) => typeof id === 'string' && !orgNames.has(id))));
54
+ if (missing.length === 0)
55
+ return;
56
+ void (async () => {
57
+ const entries = await Promise.all(missing.map(async (id) => {
58
+ try {
59
+ const res = await apiClient.get(`/api/v1/admin/organizations/${id}`);
60
+ return [id, res.data.name];
61
+ }
62
+ catch {
63
+ return [id, id];
64
+ }
65
+ }));
66
+ setOrgNames((prev) => {
67
+ const next = new Map(prev);
68
+ for (const [id, name] of entries)
69
+ next.set(id, name);
70
+ return next;
71
+ });
72
+ })();
73
+ }, [keys, orgNames]);
74
+ const handleCreate = useCallback(async (input) => {
75
+ try {
76
+ const res = await apiClient.post('/api/v1/admin/api-keys', input);
77
+ setRevealedToken({ name: input.name, token: res.data.bearerToken });
78
+ await refresh();
79
+ }
80
+ catch (err) {
81
+ setError(err instanceof ApiError ? err.envelope.error.message : t('apiKeys.error.create'));
82
+ throw err;
83
+ }
84
+ }, [refresh, t]);
85
+ const handleRevoke = useCallback(async (id) => {
86
+ if (!confirm(t('apiKeys.action.revokeConfirm')))
87
+ return;
88
+ try {
89
+ await apiClient.delete(`/api/v1/admin/api-keys/${id}`);
90
+ await refresh();
91
+ }
92
+ catch (err) {
93
+ setError(err instanceof ApiError ? err.envelope.error.message : t('apiKeys.error.revoke'));
94
+ }
95
+ }, [refresh, t]);
96
+ return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('apiKeys.page.title'), description: t('apiKeys.page.description') }), error ? (_jsx(Alert, { variant: "destructive", className: "mb-4", children: _jsx(AlertDescription, { children: error }) })) : null, revealedToken ? (_jsxs(Card, { className: "mb-4", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t('apiKeys.revealed.title') }) }), _jsxs(CardContent, { children: [_jsx("p", { className: "text-xs text-muted-foreground", children: revealedToken.name }), _jsxs("div", { className: "mt-2 flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: revealedToken.token, className: "flex-1 bg-muted font-mono text-sm text-foreground", onFocus: (e) => e.currentTarget.select(), "aria-label": t('apiKeys.revealed.title') }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "shrink-0", onClick: () => void copyToken(revealedToken.token), children: [copied ? _jsx(Check, { className: "mr-1 h-4 w-4" }) : _jsx(Copy, { className: "mr-1 h-4 w-4" }), copied ? t('apiKeys.revealed.copied') : t('apiKeys.revealed.copy')] })] }), _jsx(Button, { variant: "outline", size: "sm", className: "mt-3", onClick: () => {
97
+ setRevealedToken(null);
98
+ setCopied(false);
99
+ }, children: t('apiKeys.revealed.confirm') })] })] })) : null, _jsxs(Card, { className: "mb-4", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t('apiKeys.create.title') }) }), _jsx(CardContent, { children: _jsx(CreateKeyForm, { onSubmit: handleCreate }) })] }), _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('common.state.loading') })) : keys.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('apiKeys.empty') })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('apiKeys.column.name') }), _jsx(TableHead, { children: t('apiKeys.column.status') }), _jsx(TableHead, { children: t('apiKeys.column.scopes') }), _jsx(TableHead, { children: t('apiKeys.column.binding') }), _jsx(TableHead, { children: t('apiKeys.column.expires') }), _jsx(TableHead, { children: t('apiKeys.column.last4') }), _jsx(TableHead, { children: t('apiKeys.column.lastUsed') }), _jsx(TableHead, { children: t('apiKeys.column.created') }), _jsx(TableHead, {})] }) }), _jsx(TableBody, { children: keys.map((k) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: k.name }), _jsx(TableCell, { children: _jsx(Badge, { variant: k.status === 'active' ? 'success' : 'destructive', children: t(`apiKeys.status.${k.status}`) }) }), _jsx(TableCell, { children: _jsx("div", { className: "flex flex-wrap gap-1", children: k.scopes.map((s) => (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: s }, s))) }) }), _jsx(TableCell, { children: k.organizationId ? (_jsx(Badge, { variant: "outline", children: orgNames.get(k.organizationId) ?? k.organizationId })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: t('apiKeys.binding.unbound') })) }), _jsx(TableCell, { children: k.expiresAt ? formatDateTime(k.expiresAt) : '—' }), _jsxs(TableCell, { className: "font-mono text-xs", children: ["\u2026", k.lastFour] }), _jsx(TableCell, { children: formatDateTime(k.lastUsedAt) }), _jsx(TableCell, { children: formatDateTime(k.createdAt) }), _jsx(TableCell, { children: k.status === 'active' ? (_jsxs(Button, { variant: "destructive", size: "sm", onClick: () => {
100
+ void handleRevoke(k.id);
101
+ }, children: [_jsx(Trash2, {}), t('apiKeys.action.revoke')] })) : null })] }, k.id))) })] })) }) })] }));
102
+ }
103
+ function CreateKeyForm(props) {
104
+ const t = useTranslation('core');
105
+ const [name, setName] = useState('');
106
+ const [scopes, setScopes] = useState(new Set());
107
+ const [organizationId, setOrganizationId] = useState(null);
108
+ const [salesChannelId, setSalesChannelId] = useState(null);
109
+ const [customerAccountId, setCustomerAccountId] = useState(null);
110
+ const [expiresAt, setExpiresAt] = useState('');
111
+ const [submitting, setSubmitting] = useState(false);
112
+ const toggle = (scope) => {
113
+ setScopes((prev) => {
114
+ const next = new Set(prev);
115
+ if (next.has(scope))
116
+ next.delete(scope);
117
+ else
118
+ next.add(scope);
119
+ return next;
120
+ });
121
+ };
122
+ const handleOrganizationChange = (id) => {
123
+ setOrganizationId(id);
124
+ // B3 by construction — the service account must belong to the chosen
125
+ // org, so a changed org invalidates the previous selection.
126
+ setCustomerAccountId(null);
127
+ };
128
+ const hasOrdersScope = useMemo(() => Array.from(scopes).some((s) => ORDERS_SCOPES.has(s)), [scopes]);
129
+ const bindingStarted = organizationId !== null || salesChannelId !== null || customerAccountId !== null;
130
+ const bindingComplete = organizationId !== null && salesChannelId !== null && customerAccountId !== null;
131
+ // Inline mirrors of the server-side creation rules B1–B5
132
+ // (specs/062-distributor-api/contracts/api-key-binding.md §2).
133
+ const validationIssues = useMemo(() => {
134
+ const issues = [];
135
+ if (hasOrdersScope && !bindingComplete) {
136
+ issues.push(t('apiKeys.validation.bindingRequiredForOrders'));
137
+ }
138
+ if (bindingStarted && scopes.has('catalog:write')) {
139
+ issues.push(t('apiKeys.validation.catalogWriteBound'));
140
+ }
141
+ if (bindingStarted && !bindingComplete && !hasOrdersScope) {
142
+ issues.push(t('apiKeys.validation.bindingIncomplete'));
143
+ }
144
+ if (expiresAt !== '' && new Date(expiresAt).getTime() <= Date.now()) {
145
+ issues.push(t('apiKeys.validation.expiryFuture'));
146
+ }
147
+ return issues;
148
+ }, [hasOrdersScope, bindingStarted, bindingComplete, scopes, expiresAt, t]);
149
+ const canSubmit = name.trim().length > 0 && scopes.size > 0 && validationIssues.length === 0 && !submitting;
150
+ const onSubmit = async (event) => {
151
+ event.preventDefault();
152
+ if (!canSubmit)
153
+ return;
154
+ setSubmitting(true);
155
+ try {
156
+ await props.onSubmit({
157
+ name: name.trim(),
158
+ scopes: Array.from(scopes),
159
+ ...(bindingComplete
160
+ ? { binding: { organizationId, salesChannelId, customerAccountId } }
161
+ : {}),
162
+ ...(expiresAt !== '' ? { expiresAt: new Date(expiresAt).toISOString() } : {}),
163
+ });
164
+ setName('');
165
+ setScopes(new Set());
166
+ setOrganizationId(null);
167
+ setSalesChannelId(null);
168
+ setCustomerAccountId(null);
169
+ setExpiresAt('');
170
+ }
171
+ catch {
172
+ /* surfaced by the page-level error alert; keep the form state */
173
+ }
174
+ finally {
175
+ setSubmitting(false);
176
+ }
177
+ };
178
+ return (_jsxs("form", { className: "space-y-4", onSubmit: (e) => {
179
+ void onSubmit(e);
180
+ }, children: [_jsx("p", { className: "text-xs text-muted-foreground", children: t('apiKeys.create.modesHelp') }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "api-key-name", children: t('apiKeys.create.nameLabel') }), _jsx(Input, { id: "api-key-name", value: name, onChange: (e) => setName(e.target.value), placeholder: t('apiKeys.create.namePlaceholder'), required: true })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: t('apiKeys.create.scopesLabel') }), _jsx("div", { className: "flex flex-wrap gap-3", children: KNOWN_SCOPES.map((scope) => (_jsxs("label", { className: "inline-flex items-center gap-2 rounded-md border bg-background px-3 py-1.5 text-xs", children: [_jsx(Checkbox, { checked: scopes.has(scope), onChange: () => toggle(scope) }), _jsx("code", { className: "font-mono", children: scope })] }, scope))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('apiKeys.create.scopesHelp') })] }), _jsxs("div", { className: "space-y-3 rounded-md border p-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: t('apiKeys.create.bindingTitle') }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('apiKeys.create.bindingHelp') })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "api-key-organization", children: t('apiKeys.create.organizationLabel') }), _jsx(OrganizationPicker, { id: "api-key-organization", value: organizationId, onChange: handleOrganizationChange, ariaLabel: t('apiKeys.create.organizationLabel') })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "api-key-sales-channel", children: t('apiKeys.create.salesChannelLabel') }), _jsx(SalesChannelPicker, { id: "api-key-sales-channel", value: salesChannelId, onChange: setSalesChannelId, ariaLabel: t('apiKeys.create.salesChannelLabel') })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "api-key-service-account", children: t('apiKeys.create.serviceAccountLabel') }), _jsx(CustomerPicker, { id: "api-key-service-account", value: customerAccountId, onChange: setCustomerAccountId, status: "active", ...(organizationId !== null ? { organizationId } : {}), disabled: organizationId === null, ariaLabel: t('apiKeys.create.serviceAccountLabel') }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('apiKeys.create.serviceAccountHelp') })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "api-key-expires-at", children: t('apiKeys.create.expiresLabel') }), _jsx(Input, { id: "api-key-expires-at", type: "datetime-local", className: "md:max-w-xs", value: expiresAt, onChange: (e) => setExpiresAt(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('apiKeys.create.expiresHelp') })] }), validationIssues.length > 0 ? (_jsx("div", { className: "space-y-1", children: validationIssues.map((issue) => (_jsx("p", { className: "text-xs text-destructive", children: issue }, issue))) })) : null, _jsx(Button, { type: "submit", disabled: !canSubmit, children: submitting ? t('apiKeys.create.submitting') : t('apiKeys.create.submit') })] }));
181
+ }
182
+ //# sourceMappingURL=ApiKeysPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiKeysPage.js","sourceRoot":"","sources":["../../../src/admin/pages/ApiKeysPage.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { OptionalProps } from '@mikro-orm/core';
2
+ /**
3
+ * Long-lived API key for external integrations (FR-120).
4
+ *
5
+ * The plaintext token is shown to the operator once at creation time
6
+ * (`sk_live_<base64url>`). We store only its sha256 hash.
7
+ *
8
+ * `scopes` is a JSONB array of permission strings (e.g. `catalog:read`,
9
+ * `orders:write`). The api-key auth gate checks `scopes.includes(scope)`.
10
+ */
11
+ export declare class ApiKey {
12
+ [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'status' | 'scopes' | 'lastUsedAt' | 'revokedAt' | 'createdByAdminUserId' | 'organizationId' | 'salesChannelId' | 'customerAccountId' | 'expiresAt';
13
+ id: string;
14
+ /** Human-readable label shown in the admin panel ("PIM sync", "ERP nightly"). */
15
+ name: string;
16
+ /** SHA-256 hex of the raw bearer token. Indexed for O(1) authenticate(). */
17
+ keyHash: string;
18
+ /** Last 4 chars of the raw token, shown in the UI for identification. */
19
+ lastFour: string;
20
+ scopes: string[];
21
+ status: 'active' | 'revoked';
22
+ lastUsedAt?: Date | null;
23
+ revokedAt?: Date | null;
24
+ createdByAdminUserId?: string | null;
25
+ organizationId?: string | null;
26
+ salesChannelId?: string | null;
27
+ customerAccountId?: string | null;
28
+ /** NULL = non-expiring. `authenticate()` refuses keys past this instant. */
29
+ expiresAt?: Date | null;
30
+ createdAt: Date;
31
+ updatedAt: Date;
32
+ }
33
+ //# sourceMappingURL=api-key.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/api-key.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAI7F;;;;;;;;GAQG;AACH,qBAEa,MAAM;IACjB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,YAAY,GACZ,WAAW,GACX,sBAAsB,GACtB,gBAAgB,GAChB,gBAAgB,GAChB,mBAAmB,GACnB,WAAW,CAAC;IAGhB,EAAE,EAAE,MAAM,CAAgB;IAE1B,iFAAiF;IAEjF,IAAI,EAAG,MAAM,CAAC;IAEd,4EAA4E;IAI5E,OAAO,EAAG,MAAM,CAAC;IAEjB,yEAAyE;IAEzE,QAAQ,EAAG,MAAM,CAAC;IAGlB,MAAM,EAAE,MAAM,EAAE,CAAM;IAGtB,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAY;IAGxC,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAGzB,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAGxB,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAWrC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG/B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC,4EAA4E;IAE5E,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAGxB,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
@@ -0,0 +1,118 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * Long-lived API key for external integrations (FR-120).
15
+ *
16
+ * The plaintext token is shown to the operator once at creation time
17
+ * (`sk_live_<base64url>`). We store only its sha256 hash.
18
+ *
19
+ * `scopes` is a JSONB array of permission strings (e.g. `catalog:read`,
20
+ * `orders:write`). The api-key auth gate checks `scopes.includes(scope)`.
21
+ */
22
+ let ApiKey = class ApiKey {
23
+ [OptionalProps];
24
+ id = randomUUID();
25
+ /** Human-readable label shown in the admin panel ("PIM sync", "ERP nightly"). */
26
+ name;
27
+ /** SHA-256 hex of the raw bearer token. Indexed for O(1) authenticate(). */
28
+ keyHash;
29
+ /** Last 4 chars of the raw token, shown in the UI for identification. */
30
+ lastFour;
31
+ scopes = [];
32
+ status = 'active';
33
+ lastUsedAt;
34
+ revokedAt;
35
+ createdByAdminUserId;
36
+ // ── Feature 062 — distributor binding (all-or-none, immutable) ──────────
37
+ // The three binding columns are either all set (bound distributor key:
38
+ // single-org tenant scope + pinned channel + designated service account)
39
+ // or all NULL (legacy unbound platform key). Enforced by the
40
+ // `api_keys_binding_all_or_none` CHECK; the cross-row org-membership rule
41
+ // is validated at creation in ApiKeyService (research §R4).
42
+ organizationId;
43
+ salesChannelId;
44
+ customerAccountId;
45
+ /** NULL = non-expiring. `authenticate()` refuses keys past this instant. */
46
+ expiresAt;
47
+ createdAt = new Date();
48
+ updatedAt = new Date();
49
+ };
50
+ __decorate([
51
+ PrimaryKey({ type: 'uuid' }),
52
+ __metadata("design:type", String)
53
+ ], ApiKey.prototype, "id", void 0);
54
+ __decorate([
55
+ Property({ type: 'string', length: 160 }),
56
+ __metadata("design:type", String)
57
+ ], ApiKey.prototype, "name", void 0);
58
+ __decorate([
59
+ Property({ type: 'string', length: 128 }),
60
+ Unique(),
61
+ Index(),
62
+ __metadata("design:type", String)
63
+ ], ApiKey.prototype, "keyHash", void 0);
64
+ __decorate([
65
+ Property({ type: 'string', length: 8 }),
66
+ __metadata("design:type", String)
67
+ ], ApiKey.prototype, "lastFour", void 0);
68
+ __decorate([
69
+ Property({ type: 'json' }),
70
+ __metadata("design:type", Array)
71
+ ], ApiKey.prototype, "scopes", void 0);
72
+ __decorate([
73
+ Property({ type: 'string', length: 16 }),
74
+ __metadata("design:type", String)
75
+ ], ApiKey.prototype, "status", void 0);
76
+ __decorate([
77
+ Property({ type: 'datetime', nullable: true }),
78
+ __metadata("design:type", Object)
79
+ ], ApiKey.prototype, "lastUsedAt", void 0);
80
+ __decorate([
81
+ Property({ type: 'datetime', nullable: true }),
82
+ __metadata("design:type", Object)
83
+ ], ApiKey.prototype, "revokedAt", void 0);
84
+ __decorate([
85
+ Property({ type: 'uuid', nullable: true }),
86
+ __metadata("design:type", Object)
87
+ ], ApiKey.prototype, "createdByAdminUserId", void 0);
88
+ __decorate([
89
+ Property({ type: 'uuid', nullable: true }),
90
+ Index(),
91
+ __metadata("design:type", Object)
92
+ ], ApiKey.prototype, "organizationId", void 0);
93
+ __decorate([
94
+ Property({ type: 'uuid', nullable: true }),
95
+ __metadata("design:type", Object)
96
+ ], ApiKey.prototype, "salesChannelId", void 0);
97
+ __decorate([
98
+ Property({ type: 'uuid', nullable: true }),
99
+ __metadata("design:type", Object)
100
+ ], ApiKey.prototype, "customerAccountId", void 0);
101
+ __decorate([
102
+ Property({ type: 'datetime', nullable: true }),
103
+ __metadata("design:type", Object)
104
+ ], ApiKey.prototype, "expiresAt", void 0);
105
+ __decorate([
106
+ Property({ type: 'datetime', onCreate: () => new Date() }),
107
+ __metadata("design:type", Date)
108
+ ], ApiKey.prototype, "createdAt", void 0);
109
+ __decorate([
110
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
111
+ __metadata("design:type", Date)
112
+ ], ApiKey.prototype, "updatedAt", void 0);
113
+ ApiKey = __decorate([
114
+ GlobalEntity(),
115
+ Entity({ tableName: 'api_keys' })
116
+ ], ApiKey);
117
+ export { ApiKey };
118
+ //# sourceMappingURL=api-key.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/api-key.entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
5
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ import type { ApiKeyService } from './services/api-key-service.js';
7
+ import { integrationsModule } from './plugin.js';
8
+ import { ApiKey } from './entities/api-key.entity.js';
9
+ /**
10
+ * `api_keys` — the module `auth` reads at every request (feature 072, wave 2,
11
+ * T100).
12
+ *
13
+ * The task note asks for one thing specifically: this module is composed
14
+ * **first** in production so its authenticator reaches the auth plugin, and
15
+ * that ordering has to survive the conversion. It does, by a different
16
+ * mechanism than a line position. `auth` resolves `apiKeyResolver` from the
17
+ * cradle *inside its request hook*, so what matters is that the name is
18
+ * registered before a request arrives, not before `auth` composes — and every
19
+ * module registers before the first route is even attached, so it is true by
20
+ * construction rather than by a comment asking the next editor not to move a
21
+ * `const`. (This module used to carry an `EARLY_PASS_MODULE_IDS` entry saying
22
+ * so; D-45 deleted the list, and nothing about the guarantee moved.)
23
+ *
24
+ * `apiKeyResolver` becomes a port this module provides, and its
25
+ * `HOST_REGISTERED_PORTS` entry goes — the third such entry retired, after
26
+ * `dictionaries` took back two. What a root used to register on this module's
27
+ * behalf was a one-line closure over `apiKeyService.authenticate`; the module
28
+ * can say that itself.
29
+ *
30
+ * `requireApiKey` and `requireBoundApiKey` are ports too. `catalog`'s external
31
+ * namespace is gated by both, across a module boundary, and an operator
32
+ * switching API keys off should get the explicit 503 rather than a gate that
33
+ * quietly stops asserting anything.
34
+ *
35
+ * `auditLogService` stops being optional. It is the sink for the audited 403s
36
+ * these gates emit — `api_key.out_of_scope` and `api_key.not_bound` — so an
37
+ * absent one means the denials happen and nothing records them, which is the
38
+ * worst version of an audit trail: present enough to be trusted, incomplete
39
+ * enough to mislead.
40
+ */
41
+ export interface ApiKeysCradle {
42
+ readonly emFactory: () => EntityManager;
43
+ readonly auditLogService: AuditPort;
44
+ readonly requireAdmin: RequireAdminFactory;
45
+ /**
46
+ * Who the acting admin is, from the production actor (feature 080, T051).
47
+ * Root-supplied, like the other request resolvers; both roots answer it from
48
+ * `request.actor` and throw 401 for a non-admin.
49
+ */
50
+ readonly adminContextResolver: (req: FastifyRequest) => {
51
+ adminUserId: string;
52
+ };
53
+ readonly apiKeys: ReturnType<typeof integrationsModule>;
54
+ readonly apiKeyService: ApiKeyService;
55
+ readonly requireApiKey: ReturnType<typeof integrationsModule>['handle']['requireApiKey'];
56
+ readonly requireBoundApiKey: ReturnType<typeof integrationsModule>['handle']['requireBoundApiKey'];
57
+ }
58
+ export declare function registerModule(ctx: ModuleContext): void;
59
+ /**
60
+ * The module's persisted entity classes, on the `./backend` subpath, as one
61
+ * array and **no named class export** (D-168).
62
+ *
63
+ * This is the shape the platform reads when the package is *installed*: the
64
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
65
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
66
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
67
+ * the package pass of `check-entity-tenant-classification`. A missing array is
68
+ * answered with `[]` — zero entities registered, no error anywhere.
69
+ */
70
+ export declare const entities: (typeof ApiKey)[];
71
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAChF,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;IACxD,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,CAAC;IACzF,QAAQ,CAAC,kBAAkB,EAAE,UAAU,CACrC,OAAO,kBAAkB,CAC1B,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,CAAC;CACnC;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmEvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,mBAEpB,CAAC"}