apiblaze 0.17.20 → 0.17.21
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/dist/index.js +79 -3
- package/dist/react/index.d.mts +32 -0
- package/dist/react/index.d.ts +32 -0
- package/dist/react/index.js +181 -0
- package/dist/react/index.mjs +146 -0
- package/dist/server/index.d.mts +74 -0
- package/dist/server/index.d.ts +74 -0
- package/dist/server/index.js +127 -0
- package/dist/server/index.mjs +102 -0
- package/package.json +25 -3
package/dist/index.js
CHANGED
|
@@ -703,7 +703,7 @@ var import_commander = require("commander");
|
|
|
703
703
|
var import_chalk38 = __toESM(require("chalk"));
|
|
704
704
|
|
|
705
705
|
// package.json
|
|
706
|
-
var version = "0.17.
|
|
706
|
+
var version = "0.17.21";
|
|
707
707
|
|
|
708
708
|
// src/index.ts
|
|
709
709
|
init_types();
|
|
@@ -4021,9 +4021,85 @@ var SETTINGS = [
|
|
|
4021
4021
|
type: "string",
|
|
4022
4022
|
desc: "Team-scoped tag grouping projects in the consumer portal",
|
|
4023
4023
|
toPatch: (v) => ({ product_slug: v })
|
|
4024
|
+
},
|
|
4025
|
+
// ── Advanced (specs/missing.md): all optional, default-off; null clears ────
|
|
4026
|
+
{
|
|
4027
|
+
key: "ip_access.mode",
|
|
4028
|
+
label: "IP filter mode",
|
|
4029
|
+
group: "Advanced",
|
|
4030
|
+
type: "enum",
|
|
4031
|
+
enum: ["off", "allow", "deny"],
|
|
4032
|
+
desc: "allow = only listed CIDRs may call; deny = listed CIDRs are blocked",
|
|
4033
|
+
read: (cfg) => dig(cfg, "ip_access.mode") ?? "off",
|
|
4034
|
+
toPatch: (v, cfg) => ({ ip_access: { cidrs: [], ...cfg.ip_access ?? {}, mode: v } })
|
|
4035
|
+
},
|
|
4036
|
+
{
|
|
4037
|
+
key: "ip_access.cidrs",
|
|
4038
|
+
label: "IP filter CIDR list",
|
|
4039
|
+
group: "Advanced",
|
|
4040
|
+
type: "json",
|
|
4041
|
+
desc: 'JSON array of CIDRs (max 100), e.g. ["203.0.113.0/24","2001:db8::/32"]',
|
|
4042
|
+
toPatch: (v, cfg) => ({ ip_access: { mode: "off", ...cfg.ip_access ?? {}, cidrs: v } })
|
|
4043
|
+
},
|
|
4044
|
+
{
|
|
4045
|
+
key: "ip_access.reject_status",
|
|
4046
|
+
label: "IP filter reject status",
|
|
4047
|
+
group: "Advanced",
|
|
4048
|
+
type: "enum",
|
|
4049
|
+
enum: ["403", "404"],
|
|
4050
|
+
desc: "403 = explicit denial; 404 = stealth (looks like no such API)",
|
|
4051
|
+
read: (cfg) => dig(cfg, "ip_access.reject_status") ?? 403,
|
|
4052
|
+
toPatch: (v, cfg) => ({ ip_access: { mode: "off", cidrs: [], ...cfg.ip_access ?? {}, reject_status: Number(v) } })
|
|
4053
|
+
},
|
|
4054
|
+
{
|
|
4055
|
+
key: "upstream_policy.timeout_ms",
|
|
4056
|
+
label: "Upstream timeout (ms)",
|
|
4057
|
+
group: "Advanced",
|
|
4058
|
+
type: "number",
|
|
4059
|
+
desc: "Per-attempt wall clock before the proxy answers 504 (1000\u201390000; default 30000)",
|
|
4060
|
+
read: (cfg) => dig(cfg, "upstream_policy.timeout_ms") ?? 3e4,
|
|
4061
|
+
toPatch: (v, cfg) => ({ upstream_policy: { ...cfg.upstream_policy ?? {}, timeout_ms: v } })
|
|
4062
|
+
},
|
|
4063
|
+
{
|
|
4064
|
+
key: "upstream_policy.retries.max",
|
|
4065
|
+
label: "Upstream retries",
|
|
4066
|
+
group: "Advanced",
|
|
4067
|
+
type: "number",
|
|
4068
|
+
desc: "Extra attempts on 502/503/504/timeouts (0\u20133, idempotent methods only; billing counts the request once)",
|
|
4069
|
+
read: (cfg) => dig(cfg, "upstream_policy.retries.max") ?? 0,
|
|
4070
|
+
toPatch: (v, cfg) => ({ upstream_policy: { ...cfg.upstream_policy ?? {}, retries: { ...cfg.upstream_policy?.retries ?? {}, max: v } } })
|
|
4071
|
+
},
|
|
4072
|
+
{
|
|
4073
|
+
key: "upstream_policy.breaker.enabled",
|
|
4074
|
+
label: "Circuit breaker",
|
|
4075
|
+
group: "Advanced",
|
|
4076
|
+
type: "boolean",
|
|
4077
|
+
desc: "Trip to fast 503s when the upstream is failing; auto-probes to recover",
|
|
4078
|
+
read: (cfg) => dig(cfg, "upstream_policy.breaker.enabled") === true,
|
|
4079
|
+
toPatch: (v, cfg) => ({ upstream_policy: { ...cfg.upstream_policy ?? {}, breaker: { ...cfg.upstream_policy?.breaker ?? {}, enabled: v } } })
|
|
4080
|
+
},
|
|
4081
|
+
{
|
|
4082
|
+
key: "request_validation.mode",
|
|
4083
|
+
label: "Request validation",
|
|
4084
|
+
group: "Advanced",
|
|
4085
|
+
type: "enum",
|
|
4086
|
+
enum: ["off", "report", "enforce"],
|
|
4087
|
+
desc: "Validate requests against your OpenAPI spec: report = log only, enforce = reject 422",
|
|
4088
|
+
read: (cfg) => dig(cfg, "request_validation.mode") ?? "off",
|
|
4089
|
+
toPatch: (v, cfg) => ({ request_validation: { ...cfg.request_validation ?? {}, mode: v } })
|
|
4090
|
+
},
|
|
4091
|
+
{
|
|
4092
|
+
key: "request_validation.unknown_routes",
|
|
4093
|
+
label: "Unknown routes",
|
|
4094
|
+
group: "Advanced",
|
|
4095
|
+
type: "enum",
|
|
4096
|
+
enum: ["forward", "reject"],
|
|
4097
|
+
desc: "What to do when a request matches no spec operation (default forward \u2014 traffic-built specs trail reality)",
|
|
4098
|
+
read: (cfg) => dig(cfg, "request_validation.unknown_routes") ?? "forward",
|
|
4099
|
+
toPatch: (v, cfg) => ({ request_validation: { mode: "off", ...cfg.request_validation ?? {}, unknown_routes: v } })
|
|
4024
4100
|
}
|
|
4025
4101
|
];
|
|
4026
|
-
var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & MCP"];
|
|
4102
|
+
var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & MCP", "Advanced"];
|
|
4027
4103
|
var FEATURES = [
|
|
4028
4104
|
{ go: "transforms", label: "Transforms", desc: "Rewrite requests/responses (headers, body fields) without touching your upstream" },
|
|
4029
4105
|
{ go: "mappings", label: "Mapping tables", desc: "Reusable value-mapping tables used by map transforms (values can be hidden/encrypted)" },
|
|
@@ -5398,7 +5474,7 @@ async function mintDurableProxyKey(teamId, tenant2) {
|
|
|
5398
5474
|
const out = await admin({
|
|
5399
5475
|
method: "POST",
|
|
5400
5476
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/admin/keys`,
|
|
5401
|
-
body: { permissions: ["call", "
|
|
5477
|
+
body: { permissions: ["call", "use-premium"], environment: "prod" },
|
|
5402
5478
|
// key_model §2: call the API + spend the team wallet; non-expiring service key
|
|
5403
5479
|
summary: "Mint a durable data-plane key for apichat"
|
|
5404
5480
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apiblaze/react — <ApiKeyWidget/>. Drop it into resiresi.com; it talks ONLY to your
|
|
5
|
+
* own backend route (which holds the CP key). Shows the logged-in user their API key
|
|
6
|
+
* with reveal + regenerate up front and full self-service in an advanced drawer.
|
|
7
|
+
*
|
|
8
|
+
* import { ApiKeyWidget } from 'apiblaze/react';
|
|
9
|
+
* <ApiKeyWidget theme={{ accent: '#e11d48' }} />
|
|
10
|
+
*
|
|
11
|
+
* `endpoint` defaults to /api/apiblaze/keys (where you mounted createApiblazeKeys().handler).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface ApiKeyWidgetTheme {
|
|
15
|
+
accent?: string;
|
|
16
|
+
background?: string;
|
|
17
|
+
surface?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
muted?: string;
|
|
20
|
+
border?: string;
|
|
21
|
+
radius?: string;
|
|
22
|
+
}
|
|
23
|
+
interface ApiKeyWidgetProps {
|
|
24
|
+
endpoint?: string;
|
|
25
|
+
theme?: ApiKeyWidgetTheme;
|
|
26
|
+
/** Show the advanced (multi-key) drawer. Default true. */
|
|
27
|
+
advanced?: boolean;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
declare function ApiKeyWidget({ endpoint, theme, advanced, className }: ApiKeyWidgetProps): React.JSX.Element;
|
|
31
|
+
|
|
32
|
+
export { ApiKeyWidget, type ApiKeyWidgetProps, type ApiKeyWidgetTheme, ApiKeyWidget as default };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apiblaze/react — <ApiKeyWidget/>. Drop it into resiresi.com; it talks ONLY to your
|
|
5
|
+
* own backend route (which holds the CP key). Shows the logged-in user their API key
|
|
6
|
+
* with reveal + regenerate up front and full self-service in an advanced drawer.
|
|
7
|
+
*
|
|
8
|
+
* import { ApiKeyWidget } from 'apiblaze/react';
|
|
9
|
+
* <ApiKeyWidget theme={{ accent: '#e11d48' }} />
|
|
10
|
+
*
|
|
11
|
+
* `endpoint` defaults to /api/apiblaze/keys (where you mounted createApiblazeKeys().handler).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface ApiKeyWidgetTheme {
|
|
15
|
+
accent?: string;
|
|
16
|
+
background?: string;
|
|
17
|
+
surface?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
muted?: string;
|
|
20
|
+
border?: string;
|
|
21
|
+
radius?: string;
|
|
22
|
+
}
|
|
23
|
+
interface ApiKeyWidgetProps {
|
|
24
|
+
endpoint?: string;
|
|
25
|
+
theme?: ApiKeyWidgetTheme;
|
|
26
|
+
/** Show the advanced (multi-key) drawer. Default true. */
|
|
27
|
+
advanced?: boolean;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
declare function ApiKeyWidget({ endpoint, theme, advanced, className }: ApiKeyWidgetProps): React.JSX.Element;
|
|
31
|
+
|
|
32
|
+
export { ApiKeyWidget, type ApiKeyWidgetProps, type ApiKeyWidgetTheme, ApiKeyWidget as default };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/react/index.tsx
|
|
31
|
+
var react_exports = {};
|
|
32
|
+
__export(react_exports, {
|
|
33
|
+
ApiKeyWidget: () => ApiKeyWidget,
|
|
34
|
+
default: () => react_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(react_exports);
|
|
37
|
+
var React = __toESM(require("react"));
|
|
38
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
var DEFAULTS = {
|
|
40
|
+
accent: "#4f46e5",
|
|
41
|
+
background: "transparent",
|
|
42
|
+
surface: "#ffffff",
|
|
43
|
+
text: "#111827",
|
|
44
|
+
muted: "#6b7280",
|
|
45
|
+
border: "#e5e7eb",
|
|
46
|
+
radius: "12px"
|
|
47
|
+
};
|
|
48
|
+
function ApiKeyWidget({ endpoint = "/api/apiblaze/keys", theme, advanced = true, className }) {
|
|
49
|
+
const t = { ...DEFAULTS, ...theme ?? {} };
|
|
50
|
+
const [keys, setKeys] = React.useState(null);
|
|
51
|
+
const [secret, setSecret] = React.useState(null);
|
|
52
|
+
const [busy, setBusy] = React.useState(false);
|
|
53
|
+
const [err, setErr] = React.useState(null);
|
|
54
|
+
const [open, setOpen] = React.useState(false);
|
|
55
|
+
const [copied, setCopied] = React.useState(false);
|
|
56
|
+
const call = React.useCallback(async (body) => {
|
|
57
|
+
setErr(null);
|
|
58
|
+
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
59
|
+
const data = await res.json().catch(() => ({}));
|
|
60
|
+
if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
|
|
61
|
+
return data;
|
|
62
|
+
}, [endpoint]);
|
|
63
|
+
const load = React.useCallback(async () => {
|
|
64
|
+
try {
|
|
65
|
+
const d = await call();
|
|
66
|
+
setKeys(d.keys ?? []);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
setErr(e instanceof Error ? e.message : "load failed");
|
|
69
|
+
setKeys([]);
|
|
70
|
+
}
|
|
71
|
+
}, [call]);
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
load();
|
|
74
|
+
}, [load]);
|
|
75
|
+
async function create() {
|
|
76
|
+
setBusy(true);
|
|
77
|
+
try {
|
|
78
|
+
const d = await call({ action: "create" });
|
|
79
|
+
if (d.key) setSecret(d.key);
|
|
80
|
+
await load();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
setErr(e instanceof Error ? e.message : "create failed");
|
|
83
|
+
} finally {
|
|
84
|
+
setBusy(false);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function reveal(keyId) {
|
|
88
|
+
setBusy(true);
|
|
89
|
+
try {
|
|
90
|
+
const d = await call({ action: "reveal", keyId });
|
|
91
|
+
if (d.key) setSecret(d.key);
|
|
92
|
+
else setErr("Key not revealable (expired reveal window).");
|
|
93
|
+
} catch (e) {
|
|
94
|
+
setErr(e instanceof Error ? e.message : "reveal failed");
|
|
95
|
+
} finally {
|
|
96
|
+
setBusy(false);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function rotate(keyId) {
|
|
100
|
+
setBusy(true);
|
|
101
|
+
try {
|
|
102
|
+
const d = await call({ action: "rotate", keyId });
|
|
103
|
+
if (d.key) setSecret(d.key);
|
|
104
|
+
await load();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
setErr(e instanceof Error ? e.message : "regenerate failed");
|
|
107
|
+
} finally {
|
|
108
|
+
setBusy(false);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function revoke(keyId) {
|
|
112
|
+
setBusy(true);
|
|
113
|
+
try {
|
|
114
|
+
await call({ action: "revoke", keyId });
|
|
115
|
+
await load();
|
|
116
|
+
} catch (e) {
|
|
117
|
+
setErr(e instanceof Error ? e.message : "revoke failed");
|
|
118
|
+
} finally {
|
|
119
|
+
setBusy(false);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function copy(v) {
|
|
123
|
+
navigator.clipboard?.writeText(v);
|
|
124
|
+
setCopied(true);
|
|
125
|
+
setTimeout(() => setCopied(false), 1500);
|
|
126
|
+
}
|
|
127
|
+
const vars = {
|
|
128
|
+
// @ts-expect-error CSS custom props
|
|
129
|
+
"--abz-accent": t.accent,
|
|
130
|
+
"--abz-surface": t.surface,
|
|
131
|
+
"--abz-text": t.text,
|
|
132
|
+
"--abz-muted": t.muted,
|
|
133
|
+
"--abz-border": t.border,
|
|
134
|
+
"--abz-radius": t.radius,
|
|
135
|
+
background: t.background,
|
|
136
|
+
color: t.text,
|
|
137
|
+
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
138
|
+
fontSize: 14
|
|
139
|
+
};
|
|
140
|
+
const card = { background: t.surface, border: `1px solid ${t.border}`, borderRadius: t.radius, padding: 16 };
|
|
141
|
+
const btn = (primary2 = false) => ({ cursor: busy ? "default" : "pointer", opacity: busy ? 0.6 : 1, border: `1px solid ${primary2 ? t.accent : t.border}`, background: primary2 ? t.accent : "transparent", color: primary2 ? "#fff" : t.text, borderRadius: 8, padding: "8px 14px", fontWeight: 600, fontSize: 13 });
|
|
142
|
+
const mask = (k) => `${k.key_prefix ?? "\u2022\u2022\u2022"}${"\u2022".repeat(8)}${k.key_suffix ?? "\u2022\u2022\u2022"}`;
|
|
143
|
+
const primary = (keys ?? []).find((k) => !k.disabled) ?? null;
|
|
144
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: vars, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: card, children: [
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
146
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 15 }, children: "Your API key" }),
|
|
147
|
+
advanced && (keys?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn(), padding: "4px 8px", fontSize: 12 }, onClick: () => setOpen((o) => !o), children: open ? "Hide" : "Advanced" })
|
|
148
|
+
] }),
|
|
149
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: "#b91c1c", fontSize: 13, marginBottom: 10 }, children: err }),
|
|
150
|
+
secret && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12, padding: 12, borderRadius: 8, border: `1px solid ${t.accent}`, background: "rgba(79,70,229,0.06)" }, children: [
|
|
151
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted, marginBottom: 6 }, children: "Copy it now \u2014 it won\u2019t be shown in full again." }),
|
|
152
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { flex: 1, wordBreak: "break-all", fontSize: 13 }, children: secret }),
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn(true), onClick: () => copy(secret), children: copied ? "Copied" : "Copy" })
|
|
155
|
+
] })
|
|
156
|
+
] }),
|
|
157
|
+
keys === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.muted }, children: "Loading\u2026" }) : primary ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
158
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { flex: 1, minWidth: 160, color: t.muted }, children: mask(primary) }),
|
|
159
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn(), disabled: busy, onClick: () => reveal(primary.key_id), children: "Reveal" }),
|
|
160
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn(true), disabled: busy, onClick: () => rotate(primary.key_id), children: "Regenerate" })
|
|
161
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn(true), disabled: busy, onClick: create, children: "Generate your API key" }),
|
|
162
|
+
advanced && open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 16, borderTop: `1px solid ${t.border}`, paddingTop: 12 }, children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }, children: [
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 13, color: t.muted }, children: "All keys" }),
|
|
165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn(), disabled: busy, onClick: create, children: "+ New key" })
|
|
166
|
+
] }),
|
|
167
|
+
(keys ?? []).length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.muted, fontSize: 13 }, children: "No keys yet." }),
|
|
168
|
+
(keys ?? []).map((k) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "8px 0", borderBottom: `1px solid ${t.border}` }, children: [
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { flex: 1, fontSize: 12, color: t.muted }, children: mask(k) }),
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: t.muted }, children: k.last_used ? "used" : "unused" }),
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn(), padding: "4px 8px", fontSize: 12 }, disabled: busy, onClick: () => reveal(k.key_id), children: "Reveal" }),
|
|
172
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn(), padding: "4px 8px", fontSize: 12, borderColor: "#ef4444", color: "#ef4444" }, disabled: busy, onClick: () => revoke(k.key_id), children: "Revoke" })
|
|
173
|
+
] }, k.key_id))
|
|
174
|
+
] })
|
|
175
|
+
] }) });
|
|
176
|
+
}
|
|
177
|
+
var react_default = ApiKeyWidget;
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
ApiKeyWidget
|
|
181
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/react/index.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var DEFAULTS = {
|
|
5
|
+
accent: "#4f46e5",
|
|
6
|
+
background: "transparent",
|
|
7
|
+
surface: "#ffffff",
|
|
8
|
+
text: "#111827",
|
|
9
|
+
muted: "#6b7280",
|
|
10
|
+
border: "#e5e7eb",
|
|
11
|
+
radius: "12px"
|
|
12
|
+
};
|
|
13
|
+
function ApiKeyWidget({ endpoint = "/api/apiblaze/keys", theme, advanced = true, className }) {
|
|
14
|
+
const t = { ...DEFAULTS, ...theme ?? {} };
|
|
15
|
+
const [keys, setKeys] = React.useState(null);
|
|
16
|
+
const [secret, setSecret] = React.useState(null);
|
|
17
|
+
const [busy, setBusy] = React.useState(false);
|
|
18
|
+
const [err, setErr] = React.useState(null);
|
|
19
|
+
const [open, setOpen] = React.useState(false);
|
|
20
|
+
const [copied, setCopied] = React.useState(false);
|
|
21
|
+
const call = React.useCallback(async (body) => {
|
|
22
|
+
setErr(null);
|
|
23
|
+
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
24
|
+
const data = await res.json().catch(() => ({}));
|
|
25
|
+
if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
|
|
26
|
+
return data;
|
|
27
|
+
}, [endpoint]);
|
|
28
|
+
const load = React.useCallback(async () => {
|
|
29
|
+
try {
|
|
30
|
+
const d = await call();
|
|
31
|
+
setKeys(d.keys ?? []);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
setErr(e instanceof Error ? e.message : "load failed");
|
|
34
|
+
setKeys([]);
|
|
35
|
+
}
|
|
36
|
+
}, [call]);
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
load();
|
|
39
|
+
}, [load]);
|
|
40
|
+
async function create() {
|
|
41
|
+
setBusy(true);
|
|
42
|
+
try {
|
|
43
|
+
const d = await call({ action: "create" });
|
|
44
|
+
if (d.key) setSecret(d.key);
|
|
45
|
+
await load();
|
|
46
|
+
} catch (e) {
|
|
47
|
+
setErr(e instanceof Error ? e.message : "create failed");
|
|
48
|
+
} finally {
|
|
49
|
+
setBusy(false);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function reveal(keyId) {
|
|
53
|
+
setBusy(true);
|
|
54
|
+
try {
|
|
55
|
+
const d = await call({ action: "reveal", keyId });
|
|
56
|
+
if (d.key) setSecret(d.key);
|
|
57
|
+
else setErr("Key not revealable (expired reveal window).");
|
|
58
|
+
} catch (e) {
|
|
59
|
+
setErr(e instanceof Error ? e.message : "reveal failed");
|
|
60
|
+
} finally {
|
|
61
|
+
setBusy(false);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function rotate(keyId) {
|
|
65
|
+
setBusy(true);
|
|
66
|
+
try {
|
|
67
|
+
const d = await call({ action: "rotate", keyId });
|
|
68
|
+
if (d.key) setSecret(d.key);
|
|
69
|
+
await load();
|
|
70
|
+
} catch (e) {
|
|
71
|
+
setErr(e instanceof Error ? e.message : "regenerate failed");
|
|
72
|
+
} finally {
|
|
73
|
+
setBusy(false);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function revoke(keyId) {
|
|
77
|
+
setBusy(true);
|
|
78
|
+
try {
|
|
79
|
+
await call({ action: "revoke", keyId });
|
|
80
|
+
await load();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
setErr(e instanceof Error ? e.message : "revoke failed");
|
|
83
|
+
} finally {
|
|
84
|
+
setBusy(false);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function copy(v) {
|
|
88
|
+
navigator.clipboard?.writeText(v);
|
|
89
|
+
setCopied(true);
|
|
90
|
+
setTimeout(() => setCopied(false), 1500);
|
|
91
|
+
}
|
|
92
|
+
const vars = {
|
|
93
|
+
// @ts-expect-error CSS custom props
|
|
94
|
+
"--abz-accent": t.accent,
|
|
95
|
+
"--abz-surface": t.surface,
|
|
96
|
+
"--abz-text": t.text,
|
|
97
|
+
"--abz-muted": t.muted,
|
|
98
|
+
"--abz-border": t.border,
|
|
99
|
+
"--abz-radius": t.radius,
|
|
100
|
+
background: t.background,
|
|
101
|
+
color: t.text,
|
|
102
|
+
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
103
|
+
fontSize: 14
|
|
104
|
+
};
|
|
105
|
+
const card = { background: t.surface, border: `1px solid ${t.border}`, borderRadius: t.radius, padding: 16 };
|
|
106
|
+
const btn = (primary2 = false) => ({ cursor: busy ? "default" : "pointer", opacity: busy ? 0.6 : 1, border: `1px solid ${primary2 ? t.accent : t.border}`, background: primary2 ? t.accent : "transparent", color: primary2 ? "#fff" : t.text, borderRadius: 8, padding: "8px 14px", fontWeight: 600, fontSize: 13 });
|
|
107
|
+
const mask = (k) => `${k.key_prefix ?? "\u2022\u2022\u2022"}${"\u2022".repeat(8)}${k.key_suffix ?? "\u2022\u2022\u2022"}`;
|
|
108
|
+
const primary = (keys ?? []).find((k) => !k.disabled) ?? null;
|
|
109
|
+
return /* @__PURE__ */ jsx("div", { className, style: vars, children: /* @__PURE__ */ jsxs("div", { style: card, children: [
|
|
110
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
111
|
+
/* @__PURE__ */ jsx("strong", { style: { fontSize: 15 }, children: "Your API key" }),
|
|
112
|
+
advanced && (keys?.length ?? 0) > 0 && /* @__PURE__ */ jsx("button", { style: { ...btn(), padding: "4px 8px", fontSize: 12 }, onClick: () => setOpen((o) => !o), children: open ? "Hide" : "Advanced" })
|
|
113
|
+
] }),
|
|
114
|
+
err && /* @__PURE__ */ jsx("div", { style: { color: "#b91c1c", fontSize: 13, marginBottom: 10 }, children: err }),
|
|
115
|
+
secret && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12, padding: 12, borderRadius: 8, border: `1px solid ${t.accent}`, background: "rgba(79,70,229,0.06)" }, children: [
|
|
116
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted, marginBottom: 6 }, children: "Copy it now \u2014 it won\u2019t be shown in full again." }),
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
118
|
+
/* @__PURE__ */ jsx("code", { style: { flex: 1, wordBreak: "break-all", fontSize: 13 }, children: secret }),
|
|
119
|
+
/* @__PURE__ */ jsx("button", { style: btn(true), onClick: () => copy(secret), children: copied ? "Copied" : "Copy" })
|
|
120
|
+
] })
|
|
121
|
+
] }),
|
|
122
|
+
keys === null ? /* @__PURE__ */ jsx("div", { style: { color: t.muted }, children: "Loading\u2026" }) : primary ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
123
|
+
/* @__PURE__ */ jsx("code", { style: { flex: 1, minWidth: 160, color: t.muted }, children: mask(primary) }),
|
|
124
|
+
/* @__PURE__ */ jsx("button", { style: btn(), disabled: busy, onClick: () => reveal(primary.key_id), children: "Reveal" }),
|
|
125
|
+
/* @__PURE__ */ jsx("button", { style: btn(true), disabled: busy, onClick: () => rotate(primary.key_id), children: "Regenerate" })
|
|
126
|
+
] }) : /* @__PURE__ */ jsx("button", { style: btn(true), disabled: busy, onClick: create, children: "Generate your API key" }),
|
|
127
|
+
advanced && open && /* @__PURE__ */ jsxs("div", { style: { marginTop: 16, borderTop: `1px solid ${t.border}`, paddingTop: 12 }, children: [
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }, children: [
|
|
129
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 13, color: t.muted }, children: "All keys" }),
|
|
130
|
+
/* @__PURE__ */ jsx("button", { style: btn(), disabled: busy, onClick: create, children: "+ New key" })
|
|
131
|
+
] }),
|
|
132
|
+
(keys ?? []).length === 0 && /* @__PURE__ */ jsx("div", { style: { color: t.muted, fontSize: 13 }, children: "No keys yet." }),
|
|
133
|
+
(keys ?? []).map((k) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "8px 0", borderBottom: `1px solid ${t.border}` }, children: [
|
|
134
|
+
/* @__PURE__ */ jsx("code", { style: { flex: 1, fontSize: 12, color: t.muted }, children: mask(k) }),
|
|
135
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: t.muted }, children: k.last_used ? "used" : "unused" }),
|
|
136
|
+
/* @__PURE__ */ jsx("button", { style: { ...btn(), padding: "4px 8px", fontSize: 12 }, disabled: busy, onClick: () => reveal(k.key_id), children: "Reveal" }),
|
|
137
|
+
/* @__PURE__ */ jsx("button", { style: { ...btn(), padding: "4px 8px", fontSize: 12, borderColor: "#ef4444", color: "#ef4444" }, disabled: busy, onClick: () => revoke(k.key_id), children: "Revoke" })
|
|
138
|
+
] }, k.key_id))
|
|
139
|
+
] })
|
|
140
|
+
] }) });
|
|
141
|
+
}
|
|
142
|
+
var react_default = ApiKeyWidget;
|
|
143
|
+
export {
|
|
144
|
+
ApiKeyWidget,
|
|
145
|
+
react_default as default
|
|
146
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apiblaze/server — the server-side half of the API-key widget.
|
|
3
|
+
*
|
|
4
|
+
* Holds the producer's CP key (server-only, never shipped to the browser) and turns
|
|
5
|
+
* a logged-in user into API-key operations against apiblaze.com. The browser widget
|
|
6
|
+
* (`apiblaze/react`) talks ONLY to your own backend; your backend talks to apiblaze —
|
|
7
|
+
* so there are no cross-domain cookies, no secrets in the client, and no auth wiring
|
|
8
|
+
* beyond "read my session."
|
|
9
|
+
*
|
|
10
|
+
* Usage (Next.js App Router — app/api/apiblaze/keys/route.ts):
|
|
11
|
+
*
|
|
12
|
+
* import { createApiblazeKeys } from 'apiblaze/server';
|
|
13
|
+
* import { auth } from '@/auth'; // NextAuth, Clerk, anything
|
|
14
|
+
*
|
|
15
|
+
* const keys = createApiblazeKeys({
|
|
16
|
+
* cpKey: process.env.APIBLAZE_CP_KEY!,
|
|
17
|
+
* getUser: async () => {
|
|
18
|
+
* const s = await auth();
|
|
19
|
+
* if (!s?.user) return null;
|
|
20
|
+
* return { tenant: s.user.orgId ?? s.user.id, userId: s.user.id, email: s.user.email ?? undefined, label: s.user.name ?? undefined };
|
|
21
|
+
* },
|
|
22
|
+
* resolveScopes: (u) => u.isEngineer ? ['call', 'use-premium'] : ['call'], // optional
|
|
23
|
+
* });
|
|
24
|
+
* export const GET = keys.handler;
|
|
25
|
+
* export const POST = keys.handler;
|
|
26
|
+
*/
|
|
27
|
+
interface AppUser {
|
|
28
|
+
/** The producer's stable id for the consumer COMPANY — becomes the tenant (isolation). */
|
|
29
|
+
tenant: string;
|
|
30
|
+
/** The producer's stable id for the PERSON — the key creator/owner (list scope). */
|
|
31
|
+
userId: string;
|
|
32
|
+
/** Optional, stored now for possible future portal parity. */
|
|
33
|
+
email?: string;
|
|
34
|
+
/** Optional display name (cosmetic). */
|
|
35
|
+
label?: string;
|
|
36
|
+
/** Any extra fields your resolveScopes wants (e.g. isEngineer). */
|
|
37
|
+
[k: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface ApiblazeKeysConfig {
|
|
40
|
+
/** The producer CP key from the apiblaze dashboard Developers section. Server-only. */
|
|
41
|
+
cpKey: string;
|
|
42
|
+
/** Return the logged-in user from the request/session, or null if unauthenticated. */
|
|
43
|
+
getUser: (req: Request) => Promise<AppUser | null> | AppUser | null;
|
|
44
|
+
/** Permission set to mint for a user. Default ['call']. Server-side only, so a browser
|
|
45
|
+
* can never escalate — return more for your own engineers here. */
|
|
46
|
+
resolveScopes?: (user: AppUser) => string[] | Promise<string[]>;
|
|
47
|
+
/** Default expiry for minted keys (seconds); enables re-reveal. Default 1 year. */
|
|
48
|
+
keyExpiresInSeconds?: number;
|
|
49
|
+
/** Override the apiblaze base (tests/self-host). Default https://apikeys.apiblaze.com */
|
|
50
|
+
base?: string;
|
|
51
|
+
}
|
|
52
|
+
type Action = {
|
|
53
|
+
action: 'list';
|
|
54
|
+
} | {
|
|
55
|
+
action: 'create';
|
|
56
|
+
} | {
|
|
57
|
+
action: 'reveal';
|
|
58
|
+
keyId: string;
|
|
59
|
+
} | {
|
|
60
|
+
action: 'rotate';
|
|
61
|
+
keyId: string;
|
|
62
|
+
} | {
|
|
63
|
+
action: 'revoke';
|
|
64
|
+
keyId: string;
|
|
65
|
+
};
|
|
66
|
+
declare function createApiblazeKeys(cfg: ApiblazeKeysConfig): {
|
|
67
|
+
handler: (req: Request) => Promise<Response>;
|
|
68
|
+
run: (user: AppUser, op: Action) => Promise<{
|
|
69
|
+
status: number;
|
|
70
|
+
data: unknown;
|
|
71
|
+
}>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { type ApiblazeKeysConfig, type AppUser, createApiblazeKeys };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apiblaze/server — the server-side half of the API-key widget.
|
|
3
|
+
*
|
|
4
|
+
* Holds the producer's CP key (server-only, never shipped to the browser) and turns
|
|
5
|
+
* a logged-in user into API-key operations against apiblaze.com. The browser widget
|
|
6
|
+
* (`apiblaze/react`) talks ONLY to your own backend; your backend talks to apiblaze —
|
|
7
|
+
* so there are no cross-domain cookies, no secrets in the client, and no auth wiring
|
|
8
|
+
* beyond "read my session."
|
|
9
|
+
*
|
|
10
|
+
* Usage (Next.js App Router — app/api/apiblaze/keys/route.ts):
|
|
11
|
+
*
|
|
12
|
+
* import { createApiblazeKeys } from 'apiblaze/server';
|
|
13
|
+
* import { auth } from '@/auth'; // NextAuth, Clerk, anything
|
|
14
|
+
*
|
|
15
|
+
* const keys = createApiblazeKeys({
|
|
16
|
+
* cpKey: process.env.APIBLAZE_CP_KEY!,
|
|
17
|
+
* getUser: async () => {
|
|
18
|
+
* const s = await auth();
|
|
19
|
+
* if (!s?.user) return null;
|
|
20
|
+
* return { tenant: s.user.orgId ?? s.user.id, userId: s.user.id, email: s.user.email ?? undefined, label: s.user.name ?? undefined };
|
|
21
|
+
* },
|
|
22
|
+
* resolveScopes: (u) => u.isEngineer ? ['call', 'use-premium'] : ['call'], // optional
|
|
23
|
+
* });
|
|
24
|
+
* export const GET = keys.handler;
|
|
25
|
+
* export const POST = keys.handler;
|
|
26
|
+
*/
|
|
27
|
+
interface AppUser {
|
|
28
|
+
/** The producer's stable id for the consumer COMPANY — becomes the tenant (isolation). */
|
|
29
|
+
tenant: string;
|
|
30
|
+
/** The producer's stable id for the PERSON — the key creator/owner (list scope). */
|
|
31
|
+
userId: string;
|
|
32
|
+
/** Optional, stored now for possible future portal parity. */
|
|
33
|
+
email?: string;
|
|
34
|
+
/** Optional display name (cosmetic). */
|
|
35
|
+
label?: string;
|
|
36
|
+
/** Any extra fields your resolveScopes wants (e.g. isEngineer). */
|
|
37
|
+
[k: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface ApiblazeKeysConfig {
|
|
40
|
+
/** The producer CP key from the apiblaze dashboard Developers section. Server-only. */
|
|
41
|
+
cpKey: string;
|
|
42
|
+
/** Return the logged-in user from the request/session, or null if unauthenticated. */
|
|
43
|
+
getUser: (req: Request) => Promise<AppUser | null> | AppUser | null;
|
|
44
|
+
/** Permission set to mint for a user. Default ['call']. Server-side only, so a browser
|
|
45
|
+
* can never escalate — return more for your own engineers here. */
|
|
46
|
+
resolveScopes?: (user: AppUser) => string[] | Promise<string[]>;
|
|
47
|
+
/** Default expiry for minted keys (seconds); enables re-reveal. Default 1 year. */
|
|
48
|
+
keyExpiresInSeconds?: number;
|
|
49
|
+
/** Override the apiblaze base (tests/self-host). Default https://apikeys.apiblaze.com */
|
|
50
|
+
base?: string;
|
|
51
|
+
}
|
|
52
|
+
type Action = {
|
|
53
|
+
action: 'list';
|
|
54
|
+
} | {
|
|
55
|
+
action: 'create';
|
|
56
|
+
} | {
|
|
57
|
+
action: 'reveal';
|
|
58
|
+
keyId: string;
|
|
59
|
+
} | {
|
|
60
|
+
action: 'rotate';
|
|
61
|
+
keyId: string;
|
|
62
|
+
} | {
|
|
63
|
+
action: 'revoke';
|
|
64
|
+
keyId: string;
|
|
65
|
+
};
|
|
66
|
+
declare function createApiblazeKeys(cfg: ApiblazeKeysConfig): {
|
|
67
|
+
handler: (req: Request) => Promise<Response>;
|
|
68
|
+
run: (user: AppUser, op: Action) => Promise<{
|
|
69
|
+
status: number;
|
|
70
|
+
data: unknown;
|
|
71
|
+
}>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { type ApiblazeKeysConfig, type AppUser, createApiblazeKeys };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server/index.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
createApiblazeKeys: () => createApiblazeKeys
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(server_exports);
|
|
26
|
+
var YEAR = 365 * 24 * 60 * 60;
|
|
27
|
+
function createApiblazeKeys(cfg) {
|
|
28
|
+
const base = (cfg.base ?? "https://apikeys.apiblaze.com").replace(/\/$/, "");
|
|
29
|
+
const expiresIn = cfg.keyExpiresInSeconds ?? YEAR;
|
|
30
|
+
const tenantCache = /* @__PURE__ */ new Map();
|
|
31
|
+
function planeFetch(method, path, user, opts = {}) {
|
|
32
|
+
const params = new URLSearchParams({ product: "apiblaze", ...opts.query ?? {} });
|
|
33
|
+
const headers = { "X-API-Key": cfg.cpKey, "X-End-User-Id": user.userId };
|
|
34
|
+
if (user.email) headers["X-End-User-Email"] = String(user.email);
|
|
35
|
+
let body;
|
|
36
|
+
if (opts.body !== void 0) {
|
|
37
|
+
headers["Content-Type"] = "application/json";
|
|
38
|
+
body = JSON.stringify(opts.body);
|
|
39
|
+
}
|
|
40
|
+
return fetch(`${base}${path}?${params.toString()}`, { method, headers, body, cache: "no-store" });
|
|
41
|
+
}
|
|
42
|
+
async function resolveTenant(user) {
|
|
43
|
+
const cached = tenantCache.get(user.tenant);
|
|
44
|
+
if (cached) return cached;
|
|
45
|
+
const res = await planeFetch("POST", "/tenants", user, { body: { ref: user.tenant } });
|
|
46
|
+
if (!res.ok) throw new PlaneError(res.status, await safeErr(res));
|
|
47
|
+
const { tenant } = await res.json();
|
|
48
|
+
tenantCache.set(user.tenant, tenant);
|
|
49
|
+
return tenant;
|
|
50
|
+
}
|
|
51
|
+
async function run(user, op) {
|
|
52
|
+
const tenant = await resolveTenant(user);
|
|
53
|
+
switch (op.action) {
|
|
54
|
+
case "list": {
|
|
55
|
+
const r = await planeFetch("GET", "/apikeys", user, { query: { tenant, limit: "200" } });
|
|
56
|
+
const d = await r.json();
|
|
57
|
+
return { status: r.status, data: { keys: d.keys ?? [] } };
|
|
58
|
+
}
|
|
59
|
+
case "create": {
|
|
60
|
+
const permissions = await cfg.resolveScopes?.(user) ?? ["call"];
|
|
61
|
+
const r = await planeFetch("POST", "/apikeys", user, { body: { tenant, permissions, environment: "prod", expires_in_seconds: expiresIn } });
|
|
62
|
+
const d = await r.json();
|
|
63
|
+
return { status: r.status, data: { key: d.key, key_id: d.metadata?.key_id } };
|
|
64
|
+
}
|
|
65
|
+
case "reveal": {
|
|
66
|
+
const r = await planeFetch("GET", `/apikeys/${encodeURIComponent(op.keyId)}/reveal`, user, { query: { tenant } });
|
|
67
|
+
return { status: r.status, data: await r.json().catch(() => ({})) };
|
|
68
|
+
}
|
|
69
|
+
case "rotate": {
|
|
70
|
+
const r = await planeFetch("POST", `/apikeys/${encodeURIComponent(op.keyId)}/rotate`, user, { body: { tenant } });
|
|
71
|
+
const d = await r.json();
|
|
72
|
+
return { status: r.status, data: { key: d.key, key_id: d.metadata?.key_id } };
|
|
73
|
+
}
|
|
74
|
+
case "revoke": {
|
|
75
|
+
const r = await planeFetch("DELETE", `/apikeys/${encodeURIComponent(op.keyId)}`, user, { query: { tenant } });
|
|
76
|
+
return { status: r.status, data: r.status < 300 ? { ok: true } : await r.json().catch(() => ({})) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function handler(req) {
|
|
81
|
+
let user;
|
|
82
|
+
try {
|
|
83
|
+
user = await cfg.getUser(req);
|
|
84
|
+
} catch {
|
|
85
|
+
user = null;
|
|
86
|
+
}
|
|
87
|
+
if (!user || !user.tenant || !user.userId) return json({ error: "Unauthorized" }, 401);
|
|
88
|
+
let op;
|
|
89
|
+
if (req.method === "GET") op = { action: "list" };
|
|
90
|
+
else {
|
|
91
|
+
const raw = await req.json().catch(() => ({}));
|
|
92
|
+
const a = raw.action ?? "create";
|
|
93
|
+
if ((a === "reveal" || a === "rotate" || a === "revoke") && !raw.keyId) return json({ error: "keyId required" }, 400);
|
|
94
|
+
op = { action: a, keyId: raw.keyId };
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const { status, data } = await run(user, op);
|
|
98
|
+
return json(data, status >= 400 ? status : op.action === "create" ? 201 : 200);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
if (e instanceof PlaneError) return json({ error: e.detail }, e.status);
|
|
101
|
+
return json({ error: e instanceof Error ? e.message : "Unknown error" }, 500);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { handler, run };
|
|
105
|
+
}
|
|
106
|
+
var PlaneError = class extends Error {
|
|
107
|
+
constructor(status, detail) {
|
|
108
|
+
super(detail);
|
|
109
|
+
this.status = status;
|
|
110
|
+
this.detail = detail;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
async function safeErr(res) {
|
|
114
|
+
try {
|
|
115
|
+
const b = await res.json();
|
|
116
|
+
return typeof b.error === "string" ? b.error : b.error?.message ?? `apiblaze ${res.status}`;
|
|
117
|
+
} catch {
|
|
118
|
+
return `apiblaze ${res.status}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function json(data, status = 200) {
|
|
122
|
+
return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } });
|
|
123
|
+
}
|
|
124
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
125
|
+
0 && (module.exports = {
|
|
126
|
+
createApiblazeKeys
|
|
127
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/server/index.ts
|
|
2
|
+
var YEAR = 365 * 24 * 60 * 60;
|
|
3
|
+
function createApiblazeKeys(cfg) {
|
|
4
|
+
const base = (cfg.base ?? "https://apikeys.apiblaze.com").replace(/\/$/, "");
|
|
5
|
+
const expiresIn = cfg.keyExpiresInSeconds ?? YEAR;
|
|
6
|
+
const tenantCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function planeFetch(method, path, user, opts = {}) {
|
|
8
|
+
const params = new URLSearchParams({ product: "apiblaze", ...opts.query ?? {} });
|
|
9
|
+
const headers = { "X-API-Key": cfg.cpKey, "X-End-User-Id": user.userId };
|
|
10
|
+
if (user.email) headers["X-End-User-Email"] = String(user.email);
|
|
11
|
+
let body;
|
|
12
|
+
if (opts.body !== void 0) {
|
|
13
|
+
headers["Content-Type"] = "application/json";
|
|
14
|
+
body = JSON.stringify(opts.body);
|
|
15
|
+
}
|
|
16
|
+
return fetch(`${base}${path}?${params.toString()}`, { method, headers, body, cache: "no-store" });
|
|
17
|
+
}
|
|
18
|
+
async function resolveTenant(user) {
|
|
19
|
+
const cached = tenantCache.get(user.tenant);
|
|
20
|
+
if (cached) return cached;
|
|
21
|
+
const res = await planeFetch("POST", "/tenants", user, { body: { ref: user.tenant } });
|
|
22
|
+
if (!res.ok) throw new PlaneError(res.status, await safeErr(res));
|
|
23
|
+
const { tenant } = await res.json();
|
|
24
|
+
tenantCache.set(user.tenant, tenant);
|
|
25
|
+
return tenant;
|
|
26
|
+
}
|
|
27
|
+
async function run(user, op) {
|
|
28
|
+
const tenant = await resolveTenant(user);
|
|
29
|
+
switch (op.action) {
|
|
30
|
+
case "list": {
|
|
31
|
+
const r = await planeFetch("GET", "/apikeys", user, { query: { tenant, limit: "200" } });
|
|
32
|
+
const d = await r.json();
|
|
33
|
+
return { status: r.status, data: { keys: d.keys ?? [] } };
|
|
34
|
+
}
|
|
35
|
+
case "create": {
|
|
36
|
+
const permissions = await cfg.resolveScopes?.(user) ?? ["call"];
|
|
37
|
+
const r = await planeFetch("POST", "/apikeys", user, { body: { tenant, permissions, environment: "prod", expires_in_seconds: expiresIn } });
|
|
38
|
+
const d = await r.json();
|
|
39
|
+
return { status: r.status, data: { key: d.key, key_id: d.metadata?.key_id } };
|
|
40
|
+
}
|
|
41
|
+
case "reveal": {
|
|
42
|
+
const r = await planeFetch("GET", `/apikeys/${encodeURIComponent(op.keyId)}/reveal`, user, { query: { tenant } });
|
|
43
|
+
return { status: r.status, data: await r.json().catch(() => ({})) };
|
|
44
|
+
}
|
|
45
|
+
case "rotate": {
|
|
46
|
+
const r = await planeFetch("POST", `/apikeys/${encodeURIComponent(op.keyId)}/rotate`, user, { body: { tenant } });
|
|
47
|
+
const d = await r.json();
|
|
48
|
+
return { status: r.status, data: { key: d.key, key_id: d.metadata?.key_id } };
|
|
49
|
+
}
|
|
50
|
+
case "revoke": {
|
|
51
|
+
const r = await planeFetch("DELETE", `/apikeys/${encodeURIComponent(op.keyId)}`, user, { query: { tenant } });
|
|
52
|
+
return { status: r.status, data: r.status < 300 ? { ok: true } : await r.json().catch(() => ({})) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function handler(req) {
|
|
57
|
+
let user;
|
|
58
|
+
try {
|
|
59
|
+
user = await cfg.getUser(req);
|
|
60
|
+
} catch {
|
|
61
|
+
user = null;
|
|
62
|
+
}
|
|
63
|
+
if (!user || !user.tenant || !user.userId) return json({ error: "Unauthorized" }, 401);
|
|
64
|
+
let op;
|
|
65
|
+
if (req.method === "GET") op = { action: "list" };
|
|
66
|
+
else {
|
|
67
|
+
const raw = await req.json().catch(() => ({}));
|
|
68
|
+
const a = raw.action ?? "create";
|
|
69
|
+
if ((a === "reveal" || a === "rotate" || a === "revoke") && !raw.keyId) return json({ error: "keyId required" }, 400);
|
|
70
|
+
op = { action: a, keyId: raw.keyId };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const { status, data } = await run(user, op);
|
|
74
|
+
return json(data, status >= 400 ? status : op.action === "create" ? 201 : 200);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
if (e instanceof PlaneError) return json({ error: e.detail }, e.status);
|
|
77
|
+
return json({ error: e instanceof Error ? e.message : "Unknown error" }, 500);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { handler, run };
|
|
81
|
+
}
|
|
82
|
+
var PlaneError = class extends Error {
|
|
83
|
+
constructor(status, detail) {
|
|
84
|
+
super(detail);
|
|
85
|
+
this.status = status;
|
|
86
|
+
this.detail = detail;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
async function safeErr(res) {
|
|
90
|
+
try {
|
|
91
|
+
const b = await res.json();
|
|
92
|
+
return typeof b.error === "string" ? b.error : b.error?.message ?? `apiblaze ${res.status}`;
|
|
93
|
+
} catch {
|
|
94
|
+
return `apiblaze ${res.status}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function json(data, status = 200) {
|
|
98
|
+
return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } });
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
createApiblazeKeys
|
|
102
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apiblaze",
|
|
3
|
-
"version": "0.17.
|
|
4
|
-
"description": "APIblaze CLI
|
|
3
|
+
"version": "0.17.21",
|
|
4
|
+
"description": "APIblaze CLI \u2014 Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apiblaze",
|
|
7
7
|
"dev-tunnel",
|
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
"import": "./dist/sidecar/index.mjs",
|
|
26
26
|
"require": "./dist/sidecar/index.js",
|
|
27
27
|
"default": "./dist/sidecar/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./server": {
|
|
30
|
+
"types": "./dist/server/index.d.ts",
|
|
31
|
+
"import": "./dist/server/index.mjs",
|
|
32
|
+
"require": "./dist/server/index.js",
|
|
33
|
+
"default": "./dist/server/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./react": {
|
|
36
|
+
"types": "./dist/react/index.d.ts",
|
|
37
|
+
"import": "./dist/react/index.mjs",
|
|
38
|
+
"require": "./dist/react/index.js",
|
|
39
|
+
"default": "./dist/react/index.js"
|
|
28
40
|
}
|
|
29
41
|
},
|
|
30
42
|
"files": [
|
|
@@ -54,6 +66,16 @@
|
|
|
54
66
|
"@types/node": "^20.0.0",
|
|
55
67
|
"@types/ws": "^8.5.10",
|
|
56
68
|
"tsup": "^8.0.0",
|
|
57
|
-
"typescript": "^5.4.0"
|
|
69
|
+
"typescript": "^5.4.0",
|
|
70
|
+
"@types/react": "^18.2.0",
|
|
71
|
+
"react": "^18.2.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"react": ">=17"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"react": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
58
80
|
}
|
|
59
81
|
}
|