@calimero-network/registry-cli 1.22.3 → 1.23.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/dist/index.js +113 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var DEFAULT_REGISTRY_URL = "https://apps.calimero.network";
|
|
|
15
15
|
function defaultConfig() {
|
|
16
16
|
return { registry: { url: DEFAULT_REGISTRY_URL } };
|
|
17
17
|
}
|
|
18
|
-
var RemoteConfig = class {
|
|
18
|
+
var RemoteConfig = class _RemoteConfig {
|
|
19
19
|
configPath;
|
|
20
20
|
config;
|
|
21
21
|
constructor() {
|
|
@@ -57,7 +57,13 @@ var RemoteConfig = class {
|
|
|
57
57
|
...this.config.registry.apiKey ? { apiKey: this.config.registry.apiKey } : {}
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
fs7.writeFileSync(this.configPath, JSON.stringify(configToSave, null, 2)
|
|
60
|
+
fs7.writeFileSync(this.configPath, JSON.stringify(configToSave, null, 2), {
|
|
61
|
+
mode: 384
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
fs7.chmodSync(this.configPath, 384);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
61
67
|
}
|
|
62
68
|
/**
|
|
63
69
|
* Get registry URL with priority:
|
|
@@ -72,9 +78,44 @@ var RemoteConfig = class {
|
|
|
72
78
|
* Set registry URL
|
|
73
79
|
*/
|
|
74
80
|
setRegistryUrl(url) {
|
|
81
|
+
if (_RemoteConfig.isInsecureRemoteUrl(url)) {
|
|
82
|
+
console.warn(
|
|
83
|
+
"\u26A0\uFE0F Registry URL uses http:// on a non-local host. Your API key would be sent in cleartext \u2014 prefer https:// for remote registries."
|
|
84
|
+
);
|
|
85
|
+
}
|
|
75
86
|
this.config.registry.url = url;
|
|
76
87
|
this.saveConfig();
|
|
77
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* True when `url` is http:// to a host other than localhost/loopback, i.e. a
|
|
91
|
+
* Bearer token would traverse the network in cleartext.
|
|
92
|
+
*/
|
|
93
|
+
static isInsecureRemoteUrl(url) {
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = new URL(url);
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (parsed.protocol !== "http:") return false;
|
|
101
|
+
const host = parsed.hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
102
|
+
const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".localhost");
|
|
103
|
+
return !isLocal;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Throws when an API key would be sent to `url` over plain http:// to a
|
|
107
|
+
* non-loopback host. Called wherever the effective registry URL (flag, env
|
|
108
|
+
* var or config file) is about to receive the Bearer key. https:// and
|
|
109
|
+
* http://localhost / 127.0.0.1 / [::1] are always allowed.
|
|
110
|
+
*/
|
|
111
|
+
static assertApiKeyTransport(url, apiKey) {
|
|
112
|
+
if (!apiKey) return;
|
|
113
|
+
if (_RemoteConfig.isInsecureRemoteUrl(url)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Refusing to send the API key to ${url}: plain http:// is only allowed for localhost / 127.0.0.1 / [::1]. Use an https:// registry URL (check --url, CALIMERO_REGISTRY_URL and \`calimero-registry config get registry-url\`).`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
78
119
|
/**
|
|
79
120
|
* Get API key with priority:
|
|
80
121
|
* 1. Environment variable CALIMERO_API_KEY
|
|
@@ -749,6 +790,18 @@ var LocalArtifactServer = class {
|
|
|
749
790
|
};
|
|
750
791
|
}
|
|
751
792
|
};
|
|
793
|
+
function isAllowedOrigin(origin, allowedHosts) {
|
|
794
|
+
let parsed;
|
|
795
|
+
try {
|
|
796
|
+
parsed = new URL(origin);
|
|
797
|
+
} catch {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
return allowedHosts.has(parsed.hostname.toLowerCase());
|
|
804
|
+
}
|
|
752
805
|
var LocalRegistryServer = class {
|
|
753
806
|
config;
|
|
754
807
|
dataStore;
|
|
@@ -794,6 +847,43 @@ var LocalRegistryServer = class {
|
|
|
794
847
|
});
|
|
795
848
|
}
|
|
796
849
|
});
|
|
850
|
+
const allowedOriginHosts = /* @__PURE__ */ new Set();
|
|
851
|
+
for (const name of allowedHosts) {
|
|
852
|
+
allowedOriginHosts.add(name.replace(/:\d+$/, ""));
|
|
853
|
+
}
|
|
854
|
+
this.server.addHook("onRequest", async (request, reply) => {
|
|
855
|
+
const method = request.method.toUpperCase();
|
|
856
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const origin = request.headers.origin;
|
|
860
|
+
if (origin !== void 0 && !isAllowedOrigin(origin, allowedOriginHosts)) {
|
|
861
|
+
return reply.code(403).send({
|
|
862
|
+
statusCode: 403,
|
|
863
|
+
error: "Forbidden",
|
|
864
|
+
message: "Cross-origin request not allowed"
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
const fetchSite = request.headers["sec-fetch-site"];
|
|
868
|
+
if (fetchSite !== void 0 && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
869
|
+
return reply.code(403).send({
|
|
870
|
+
statusCode: 403,
|
|
871
|
+
error: "Forbidden",
|
|
872
|
+
message: "Cross-site request not allowed"
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
const pathname = request.url.split("?")[0];
|
|
876
|
+
if (pathname.startsWith("/local/")) {
|
|
877
|
+
const contentType = String(request.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
878
|
+
if (contentType !== "application/json") {
|
|
879
|
+
return reply.code(415).send({
|
|
880
|
+
statusCode: 415,
|
|
881
|
+
error: "Unsupported Media Type",
|
|
882
|
+
message: "Content-Type must be application/json"
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
797
887
|
await this.registerRoutes();
|
|
798
888
|
await this.server.listen({ port: serverPort, host });
|
|
799
889
|
this.isRunning = true;
|
|
@@ -1728,11 +1818,12 @@ Note:
|
|
|
1728
1818
|
- Config file values are used unless overridden by flags or environment variables
|
|
1729
1819
|
- Priority: flag > environment variable > config file > default
|
|
1730
1820
|
`
|
|
1731
|
-
).action(async (bundleFile, options) => {
|
|
1821
|
+
).action(async (bundleFile, options, command) => {
|
|
1822
|
+
const urlFlag = command.optsWithGlobals().url;
|
|
1732
1823
|
let mpkPath;
|
|
1733
1824
|
let tempMpk = null;
|
|
1734
1825
|
try {
|
|
1735
|
-
if (!options.remote && (
|
|
1826
|
+
if (!options.remote && (urlFlag || options.apiKey)) {
|
|
1736
1827
|
console.warn(
|
|
1737
1828
|
"\u26A0\uFE0F Warning: --url and --api-key are only used with --remote flag"
|
|
1738
1829
|
);
|
|
@@ -1862,8 +1953,9 @@ Note:
|
|
|
1862
1953
|
);
|
|
1863
1954
|
} else if (useRemote) {
|
|
1864
1955
|
const remoteConfig = new RemoteConfig();
|
|
1865
|
-
const registryUrl =
|
|
1956
|
+
const registryUrl = urlFlag || process.env.CALIMERO_REGISTRY_URL || remoteConfig.getRegistryUrl();
|
|
1866
1957
|
const apiKey = options.apiKey || process.env.CALIMERO_API_KEY || remoteConfig.getApiKey();
|
|
1958
|
+
RemoteConfig.assertApiKeyTransport(registryUrl, apiKey);
|
|
1867
1959
|
await pushToRemote(mpkPath, manifest, registryUrl, apiKey);
|
|
1868
1960
|
}
|
|
1869
1961
|
} catch (error) {
|
|
@@ -1922,7 +2014,8 @@ Examples:
|
|
|
1922
2014
|
# 3) With custom registry URL:
|
|
1923
2015
|
$ calimero-registry bundle edit com.calimero.myapp 1.0.0 --remote --url https://apps.calimero.network --manifest signed-manifest.json
|
|
1924
2016
|
`
|
|
1925
|
-
).action(async (pkg, version2, options) => {
|
|
2017
|
+
).action(async (pkg, version2, options, command) => {
|
|
2018
|
+
const urlFlag = command.optsWithGlobals().url;
|
|
1926
2019
|
try {
|
|
1927
2020
|
if (!options.remote) {
|
|
1928
2021
|
console.error(
|
|
@@ -1931,8 +2024,9 @@ Examples:
|
|
|
1931
2024
|
process.exit(1);
|
|
1932
2025
|
}
|
|
1933
2026
|
const remoteConfig = new RemoteConfig();
|
|
1934
|
-
const registryUrl =
|
|
2027
|
+
const registryUrl = urlFlag || process.env.CALIMERO_REGISTRY_URL || remoteConfig.getRegistryUrl();
|
|
1935
2028
|
const apiKey = options.apiKey || process.env.CALIMERO_API_KEY || remoteConfig.getApiKey();
|
|
2029
|
+
RemoteConfig.assertApiKeyTransport(registryUrl, apiKey);
|
|
1936
2030
|
const baseUrl = registryUrl.replace(/\/$/, "");
|
|
1937
2031
|
const bundleUrl = `${baseUrl}/api/v2/bundles/${encodeURIComponent(pkg)}/${encodeURIComponent(version2)}`;
|
|
1938
2032
|
const headers = {
|
|
@@ -2399,7 +2493,7 @@ function getGlobalOpts(command) {
|
|
|
2399
2493
|
timeout: parseInt(String(opts.timeout ?? DEFAULT_TIMEOUT), 10) || DEFAULT_TIMEOUT
|
|
2400
2494
|
};
|
|
2401
2495
|
}
|
|
2402
|
-
function getAuthHeaders() {
|
|
2496
|
+
function getAuthHeaders(url) {
|
|
2403
2497
|
const remoteConfig = new RemoteConfig();
|
|
2404
2498
|
const apiKey = remoteConfig.getApiKey();
|
|
2405
2499
|
if (!apiKey) {
|
|
@@ -2407,6 +2501,7 @@ function getAuthHeaders() {
|
|
|
2407
2501
|
"API token required for org write operations.\nGet a token from the Organizations page in the web UI, then run:\n calimero-registry config set api-key <token>"
|
|
2408
2502
|
);
|
|
2409
2503
|
}
|
|
2504
|
+
RemoteConfig.assertApiKeyTransport(url, apiKey);
|
|
2410
2505
|
return { Authorization: `Bearer ${apiKey}` };
|
|
2411
2506
|
}
|
|
2412
2507
|
async function fetchJson(url, options = {}) {
|
|
@@ -2434,9 +2529,9 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2434
2529
|
const spinner2 = ora4("Resolving identity...").start();
|
|
2435
2530
|
let authHeaders;
|
|
2436
2531
|
try {
|
|
2437
|
-
authHeaders = getAuthHeaders();
|
|
2532
|
+
authHeaders = getAuthHeaders(url);
|
|
2438
2533
|
} catch (e) {
|
|
2439
|
-
spinner2.fail("API token
|
|
2534
|
+
spinner2.fail("Cannot use API token");
|
|
2440
2535
|
console.error(chalk4.red(e instanceof Error ? e.message : String(e)));
|
|
2441
2536
|
process.exit(1);
|
|
2442
2537
|
}
|
|
@@ -2511,7 +2606,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2511
2606
|
const { url, timeout } = getGlobalOpts(command);
|
|
2512
2607
|
const spinner2 = ora4("Creating organization...").start();
|
|
2513
2608
|
try {
|
|
2514
|
-
const authHeaders = getAuthHeaders();
|
|
2609
|
+
const authHeaders = getAuthHeaders(url);
|
|
2515
2610
|
const pathname = "/api/v2/orgs";
|
|
2516
2611
|
const body = {
|
|
2517
2612
|
name: options.name.trim(),
|
|
@@ -2572,7 +2667,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2572
2667
|
const { url, timeout } = getGlobalOpts(command);
|
|
2573
2668
|
const spinner2 = ora4("Updating organization...").start();
|
|
2574
2669
|
try {
|
|
2575
|
-
const authHeaders = getAuthHeaders();
|
|
2670
|
+
const authHeaders = getAuthHeaders(url);
|
|
2576
2671
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}`;
|
|
2577
2672
|
const body = {};
|
|
2578
2673
|
if (options.name !== void 0) body.name = options.name;
|
|
@@ -2637,7 +2732,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2637
2732
|
}
|
|
2638
2733
|
const spinner2 = ora4("Deleting organization...").start();
|
|
2639
2734
|
try {
|
|
2640
|
-
const authHeaders = getAuthHeaders();
|
|
2735
|
+
const authHeaders = getAuthHeaders(url);
|
|
2641
2736
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}`;
|
|
2642
2737
|
const base = url.replace(/\/$/, "");
|
|
2643
2738
|
const { data, status } = await fetchJson(`${base}${pathname}`, {
|
|
@@ -2693,7 +2788,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2693
2788
|
const { url, timeout } = getGlobalOpts(command);
|
|
2694
2789
|
const spinner2 = ora4("Adding member...").start();
|
|
2695
2790
|
try {
|
|
2696
|
-
const authHeaders = getAuthHeaders();
|
|
2791
|
+
const authHeaders = getAuthHeaders(url);
|
|
2697
2792
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members`;
|
|
2698
2793
|
const body = {
|
|
2699
2794
|
email: email.trim(),
|
|
@@ -2730,7 +2825,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2730
2825
|
const role = options.role === "admin" ? "admin" : "member";
|
|
2731
2826
|
const spinner2 = ora4("Updating member role...").start();
|
|
2732
2827
|
try {
|
|
2733
|
-
const authHeaders = getAuthHeaders();
|
|
2828
|
+
const authHeaders = getAuthHeaders(url);
|
|
2734
2829
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(email.trim())}`;
|
|
2735
2830
|
const body = { role };
|
|
2736
2831
|
const base = url.replace(/\/$/, "");
|
|
@@ -2763,7 +2858,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2763
2858
|
const { url, timeout } = getGlobalOpts(command);
|
|
2764
2859
|
const spinner2 = ora4("Removing member...").start();
|
|
2765
2860
|
try {
|
|
2766
|
-
const authHeaders = getAuthHeaders();
|
|
2861
|
+
const authHeaders = getAuthHeaders(url);
|
|
2767
2862
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(email.trim())}`;
|
|
2768
2863
|
const base = url.replace(/\/$/, "");
|
|
2769
2864
|
const { data, status } = await fetchJson(`${base}${pathname}`, {
|
|
@@ -2796,7 +2891,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2796
2891
|
const { url, timeout } = getGlobalOpts(command);
|
|
2797
2892
|
const spinner2 = ora4("Linking package...").start();
|
|
2798
2893
|
try {
|
|
2799
|
-
const authHeaders = getAuthHeaders();
|
|
2894
|
+
const authHeaders = getAuthHeaders(url);
|
|
2800
2895
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/packages`;
|
|
2801
2896
|
const body = { package: pkg.trim() };
|
|
2802
2897
|
const base = url.replace(/\/$/, "");
|
|
@@ -2829,7 +2924,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2829
2924
|
const { url, timeout } = getGlobalOpts(command);
|
|
2830
2925
|
const spinner2 = ora4("Unlinking package...").start();
|
|
2831
2926
|
try {
|
|
2832
|
-
const authHeaders = getAuthHeaders();
|
|
2927
|
+
const authHeaders = getAuthHeaders(url);
|
|
2833
2928
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/packages/${encodeURIComponent(pkg.trim())}`;
|
|
2834
2929
|
const base = url.replace(/\/$/, "");
|
|
2835
2930
|
const { data, status } = await fetchJson(`${base}${pathname}`, {
|