@canonry/canonry 7.17.0 → 7.17.2
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/assets/assets/{AuditHistoryPanel-CUde9U_6.js → AuditHistoryPanel-Dga03Niw.js} +1 -1
- package/assets/assets/BacklinksPage-CfodWuIt.js +1 -0
- package/assets/assets/Hint-DICI5Jq9.js +1 -0
- package/assets/assets/{HistoryPage-DZSa1gYf.js → HistoryPage-BN_vTU5L.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DgW17Jpd.js → MeasurementPropertyPage-CjiyzOmE.js} +1 -1
- package/assets/assets/ProjectPage-AxclA3YC.js +8 -0
- package/assets/assets/{RunsPage-DRfNd-Jp.js → RunsPage-Ca_ziCGC.js} +1 -1
- package/assets/assets/{SettingsPage-D38Gl1Nv.js → SettingsPage-SO1xl0d3.js} +1 -1
- package/assets/assets/SiteHealthSection-C9JgRzzs.js +4 -0
- package/assets/assets/{TrafficPage-DX3qRPgQ.js → TrafficPage-DrcbXNQB.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-CigB82vX.js → TrafficSourceDetailPage-HDZGKgyd.js} +1 -1
- package/assets/assets/{extract-error-message-DsMnnkxT.js → extract-error-message-D48vwe7I.js} +1 -1
- package/assets/assets/index-BaBHBReI.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-CkYxzGpi.js → react-sigma_core.esm.min-DVHs4npB.js} +1 -1
- package/assets/assets/{v2-overview-adapter-5x8UiA07.js → v2-overview-adapter-CTEXuiRt.js} +1 -1
- package/assets/assets/{vendor-lucide-N6vOpZmh.js → vendor-lucide-Y6jANGfG.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-WEKVQK3O.js → chunk-3LPQ4JV5.js} +130 -103
- package/dist/{chunk-2MBI5SYB.js → chunk-4EDH2ORA.js} +3 -3
- package/dist/{chunk-PVWGKDY6.js → chunk-MIRAKDRW.js} +111 -79
- package/dist/{chunk-QPOXDS43.js → chunk-NPNU6UHD.js} +5 -4
- package/dist/{chunk-WVBLFG7H.js → chunk-P2XY5YX7.js} +2 -2
- package/dist/{chunk-RBQUGZM4.js → chunk-QAIJH74T.js} +1 -1
- package/dist/{chunk-3NAZXITW.js → chunk-YJS4OE6I.js} +615 -579
- package/dist/cli.js +15 -16
- package/dist/{demo-server-BVJFYR4V.js → demo-server-54SLPSJF.js} +6 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-FK2W72X7.js → intelligence-service-62CEP6M2.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +10 -10
- package/assets/assets/BacklinksPage-DMQPw8ks.js +0 -1
- package/assets/assets/ProjectPage-C8vdtqtA.js +0 -8
- package/assets/assets/SiteHealthSection-CR3zB84D.js +0 -4
- package/assets/assets/index-DBr6ISbx.js +0 -86
|
@@ -20,23 +20,25 @@ import {
|
|
|
20
20
|
saveConfig,
|
|
21
21
|
saveConfigPatch,
|
|
22
22
|
sentimentInstallReadiness
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-P2XY5YX7.js";
|
|
24
24
|
import {
|
|
25
25
|
computeFactorAverages,
|
|
26
26
|
deleteSiteCrawlGraphLayout,
|
|
27
27
|
persistSiteCrawlGraphLayout,
|
|
28
28
|
prepareSiteCrawlGraphLayout,
|
|
29
29
|
recordedSharePct
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-NPNU6UHD.js";
|
|
31
31
|
import {
|
|
32
32
|
CanonryMcpToolNames,
|
|
33
33
|
CliError,
|
|
34
34
|
PACKAGE_VERSION,
|
|
35
35
|
canonryMcpTools
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-QAIJH74T.js";
|
|
37
37
|
import {
|
|
38
38
|
CC_CACHE_DIR,
|
|
39
39
|
DUCKDB_SPEC,
|
|
40
|
+
EgressFailedError,
|
|
41
|
+
EgressRefusedError,
|
|
40
42
|
GBP_DAILY_METRICS,
|
|
41
43
|
GSC_DATA_LAG_DAYS,
|
|
42
44
|
GSC_REPORTING_TIME_ZONE,
|
|
@@ -65,6 +67,7 @@ import {
|
|
|
65
67
|
apiRoutes,
|
|
66
68
|
applyCompetitorAutoAliases,
|
|
67
69
|
assessConversionTrackingIntegrity,
|
|
70
|
+
auditFromRequest,
|
|
68
71
|
auditLog,
|
|
69
72
|
backfillProjectAnswerMentions,
|
|
70
73
|
backfillProjectAnswerMentionsInChunks,
|
|
@@ -86,6 +89,7 @@ import {
|
|
|
86
89
|
countPopulatedGroups,
|
|
87
90
|
createCredentialChecker,
|
|
88
91
|
createFastifyLogger,
|
|
92
|
+
createGuardedFetch,
|
|
89
93
|
createLogger,
|
|
90
94
|
createProjectPassQueue,
|
|
91
95
|
createRunCompetitorResolver,
|
|
@@ -133,6 +137,7 @@ import {
|
|
|
133
137
|
gtmConnections,
|
|
134
138
|
gtmRawSnapshots,
|
|
135
139
|
hasOutstandingProviderBatch,
|
|
140
|
+
hasProxyHeaders,
|
|
136
141
|
hashApiKey,
|
|
137
142
|
hashAttributes,
|
|
138
143
|
hashLodging,
|
|
@@ -216,8 +221,9 @@ import {
|
|
|
216
221
|
siteCrawlSnapshots,
|
|
217
222
|
siteLivenessState,
|
|
218
223
|
toAlertView,
|
|
219
|
-
usageCounters
|
|
220
|
-
|
|
224
|
+
usageCounters,
|
|
225
|
+
writeAuditLog
|
|
226
|
+
} from "./chunk-YJS4OE6I.js";
|
|
221
227
|
import {
|
|
222
228
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
223
229
|
AGENT_NONE,
|
|
@@ -337,6 +343,7 @@ import {
|
|
|
337
343
|
effectiveDomains,
|
|
338
344
|
embedClientConfigForRequest,
|
|
339
345
|
embedProjectTabsError,
|
|
346
|
+
escapeRegExp,
|
|
340
347
|
extractProviderHttpStatus,
|
|
341
348
|
extractRecommendedCompetitors,
|
|
342
349
|
factorStatusFromScore,
|
|
@@ -432,7 +439,7 @@ import {
|
|
|
432
439
|
winnabilityClassLabel,
|
|
433
440
|
withRetry,
|
|
434
441
|
withSkippedProviders
|
|
435
|
-
} from "./chunk-
|
|
442
|
+
} from "./chunk-MIRAKDRW.js";
|
|
436
443
|
|
|
437
444
|
// src/runtime-context.ts
|
|
438
445
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -1240,9 +1247,6 @@ function buildDailyConversionMetricsQuery(options) {
|
|
|
1240
1247
|
}
|
|
1241
1248
|
|
|
1242
1249
|
// ../integration-google-ads/src/google-ads-client.ts
|
|
1243
|
-
function escapeRegExp(value) {
|
|
1244
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1245
|
-
}
|
|
1246
1250
|
function sanitizeText(value, secrets) {
|
|
1247
1251
|
let sanitized = value.replace(/(authorization\s*:\s*bearer\s+)[^\s,"'}]+/gi, "$1***").replace(/((?:developer|access)[_-]?token\s*[:=]\s*["']?)[^\s,"'}]+/gi, "$1***");
|
|
1248
1252
|
for (const secret of secrets) {
|
|
@@ -8045,7 +8049,7 @@ function nonBlank(value) {
|
|
|
8045
8049
|
return trimmed ? trimmed : void 0;
|
|
8046
8050
|
}
|
|
8047
8051
|
function resolveBuildCommit(env = process.env) {
|
|
8048
|
-
const embedded = true ? "
|
|
8052
|
+
const embedded = true ? "3bf6bc30c036bb0dbbed1d8ed17141d1ad2a387f" : void 0;
|
|
8049
8053
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
8050
8054
|
}
|
|
8051
8055
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -12143,10 +12147,14 @@ import { eq as eq9 } from "drizzle-orm";
|
|
|
12143
12147
|
var log7 = createLogger("SitemapParser");
|
|
12144
12148
|
var LOC_REGEX = /<loc>([^<]+)<\/loc>/gi;
|
|
12145
12149
|
var SITEMAP_TAG_REGEX = /<sitemap>[\s\S]*?<\/sitemap>/gi;
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
+
var sitemapFetch = createGuardedFetch({ allowLoopback: true });
|
|
12151
|
+
async function fetchSitemap(url) {
|
|
12152
|
+
try {
|
|
12153
|
+
return await sitemapFetch(url);
|
|
12154
|
+
} catch (err) {
|
|
12155
|
+
if (err instanceof EgressRefusedError) throw new Error(`Sitemap ${url} rejected: ${err.message}`);
|
|
12156
|
+
if (err instanceof EgressFailedError) throw new Error(`Failed to fetch sitemap at ${url}: ${err.message}`);
|
|
12157
|
+
throw err;
|
|
12150
12158
|
}
|
|
12151
12159
|
}
|
|
12152
12160
|
async function readSitemapBody(res) {
|
|
@@ -12178,8 +12186,7 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
|
|
|
12178
12186
|
visited.add(url);
|
|
12179
12187
|
let res;
|
|
12180
12188
|
try {
|
|
12181
|
-
await
|
|
12182
|
-
res = await fetch(url);
|
|
12189
|
+
res = await fetchSitemap(url);
|
|
12183
12190
|
} catch (err) {
|
|
12184
12191
|
if (!isChild) throw err;
|
|
12185
12192
|
log7.warn("child-sitemap.fetch-failed", {
|
|
@@ -12864,7 +12871,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
12864
12871
|
const rowsByProject = groupByProject(rows, projectsByDomain);
|
|
12865
12872
|
for (const p of allProjects) {
|
|
12866
12873
|
const projectRows = rowsByProject.get(p.id) ?? [];
|
|
12867
|
-
const summary =
|
|
12874
|
+
const summary = computeBacklinkSummaryMetrics(projectRows);
|
|
12868
12875
|
tx.insert(backlinkSummaries).values({
|
|
12869
12876
|
id: crypto17.randomUUID(),
|
|
12870
12877
|
projectId: p.id,
|
|
@@ -12947,20 +12954,6 @@ function groupByProject(rows, projectsByDomain) {
|
|
|
12947
12954
|
}
|
|
12948
12955
|
return out;
|
|
12949
12956
|
}
|
|
12950
|
-
function computeSummary(rows) {
|
|
12951
|
-
if (rows.length === 0) {
|
|
12952
|
-
return { totalLinkingDomains: 0, totalHosts: 0, top10HostsShare: "0" };
|
|
12953
|
-
}
|
|
12954
|
-
const sorted = [...rows].sort((a, b) => b.numHosts - a.numHosts);
|
|
12955
|
-
const totalHosts = sorted.reduce((acc, r) => acc + r.numHosts, 0);
|
|
12956
|
-
const top10Hosts = sorted.slice(0, 10).reduce((acc, r) => acc + r.numHosts, 0);
|
|
12957
|
-
const share = totalHosts > 0 ? top10Hosts / totalHosts : 0;
|
|
12958
|
-
return {
|
|
12959
|
-
totalLinkingDomains: rows.length,
|
|
12960
|
-
totalHosts,
|
|
12961
|
-
top10HostsShare: share.toFixed(6)
|
|
12962
|
-
};
|
|
12963
|
-
}
|
|
12964
12957
|
|
|
12965
12958
|
// src/backlink-extract.ts
|
|
12966
12959
|
import crypto18 from "crypto";
|
|
@@ -13031,7 +13024,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
13031
13024
|
}));
|
|
13032
13025
|
tx.insert(backlinkDomains).values(values).run();
|
|
13033
13026
|
}
|
|
13034
|
-
const summary =
|
|
13027
|
+
const summary = computeSummary(rows);
|
|
13035
13028
|
tx.insert(backlinkSummaries).values({
|
|
13036
13029
|
id: crypto18.randomUUID(),
|
|
13037
13030
|
projectId,
|
|
@@ -13071,7 +13064,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
13071
13064
|
throw err;
|
|
13072
13065
|
}
|
|
13073
13066
|
}
|
|
13074
|
-
function
|
|
13067
|
+
function computeSummary(rows) {
|
|
13075
13068
|
return computeBacklinkSummaryMetrics(rows);
|
|
13076
13069
|
}
|
|
13077
13070
|
|
|
@@ -15526,11 +15519,17 @@ var SITE_LIVENESS_MAX_PASS_GAP_MS = 45 * 6e4;
|
|
|
15526
15519
|
var Notifier = class {
|
|
15527
15520
|
db;
|
|
15528
15521
|
serverUrl;
|
|
15522
|
+
/**
|
|
15523
|
+
* The server's `allowLoopbackWebhooks`, so delivery admits exactly the
|
|
15524
|
+
* destinations that creating and testing a webhook admitted.
|
|
15525
|
+
*/
|
|
15526
|
+
allowLoopbackWebhooks;
|
|
15529
15527
|
/** Projects with a review dispatch in progress; see {@link dispatchReviewAlerts}. */
|
|
15530
15528
|
reviewDispatchInFlight = /* @__PURE__ */ new Set();
|
|
15531
|
-
constructor(db, serverUrl) {
|
|
15529
|
+
constructor(db, serverUrl, opts = {}) {
|
|
15532
15530
|
this.db = db;
|
|
15533
15531
|
this.serverUrl = serverUrl;
|
|
15532
|
+
this.allowLoopbackWebhooks = opts.allowLoopbackWebhooks === true;
|
|
15534
15533
|
}
|
|
15535
15534
|
/** Called after a run completes (success, partial, or failed). */
|
|
15536
15535
|
async onRunCompleted(runId, projectId, opts) {
|
|
@@ -16096,7 +16095,7 @@ var Notifier = class {
|
|
|
16096
16095
|
const body = destination.render ? destination.render(toAlertView(payload)) : payload;
|
|
16097
16096
|
const signingSecret = destination.signed ? webhookSecret : null;
|
|
16098
16097
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
16099
|
-
const targetCheck = await resolveWebhookTarget(url);
|
|
16098
|
+
const targetCheck = await resolveWebhookTarget(url, { allowLoopback: this.allowLoopbackWebhooks });
|
|
16100
16099
|
if (!targetCheck.ok) {
|
|
16101
16100
|
log20.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
16102
16101
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
@@ -21526,6 +21525,10 @@ function waitForServerRuntimeStartup(app) {
|
|
|
21526
21525
|
const startup = runtimeStartupByServer.get(app);
|
|
21527
21526
|
return startup ?? Promise.reject(new Error("Server runtime startup was not registered"));
|
|
21528
21527
|
}
|
|
21528
|
+
var keylessFirstRunSetupByServer = /* @__PURE__ */ new WeakMap();
|
|
21529
|
+
function allowsKeylessFirstRunSetup(app) {
|
|
21530
|
+
return keylessFirstRunSetupByServer.get(app) ?? false;
|
|
21531
|
+
}
|
|
21529
21532
|
var DEFAULT_QUOTA = {
|
|
21530
21533
|
maxConcurrency: 2,
|
|
21531
21534
|
maxRequestsPerMinute: 10,
|
|
@@ -21618,9 +21621,6 @@ function summarizeProviderConfig(config) {
|
|
|
21618
21621
|
quota: { ...config?.quota ?? DEFAULT_QUOTA }
|
|
21619
21622
|
};
|
|
21620
21623
|
}
|
|
21621
|
-
function hashApiKey2(key) {
|
|
21622
|
-
return crypto29.createHash("sha256").update(key).digest("hex");
|
|
21623
|
-
}
|
|
21624
21624
|
var DASHBOARD_SCRYPT_KEYLEN = 64;
|
|
21625
21625
|
var DASHBOARD_SCRYPT_COST = 1 << 15;
|
|
21626
21626
|
var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
@@ -21659,7 +21659,7 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
21659
21659
|
};
|
|
21660
21660
|
}
|
|
21661
21661
|
if (/^[a-f0-9]{64}$/i.test(storedHash)) {
|
|
21662
|
-
const candidate = Buffer.from(
|
|
21662
|
+
const candidate = Buffer.from(hashApiKey(password), "hex");
|
|
21663
21663
|
const expected = Buffer.from(storedHash, "hex");
|
|
21664
21664
|
if (candidate.length !== expected.length)
|
|
21665
21665
|
return { ok: false, needsRehash: false };
|
|
@@ -21668,22 +21668,6 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
21668
21668
|
}
|
|
21669
21669
|
return { ok: false, needsRehash: false };
|
|
21670
21670
|
}
|
|
21671
|
-
function parseCookies(header) {
|
|
21672
|
-
if (!header) return {};
|
|
21673
|
-
return header.split(";").map((part) => part.trim()).filter(Boolean).reduce((cookies, part) => {
|
|
21674
|
-
const eqIdx = part.indexOf("=");
|
|
21675
|
-
if (eqIdx <= 0) return cookies;
|
|
21676
|
-
const name = part.slice(0, eqIdx).trim();
|
|
21677
|
-
const value = part.slice(eqIdx + 1).trim();
|
|
21678
|
-
if (!name) return cookies;
|
|
21679
|
-
try {
|
|
21680
|
-
cookies[name] = decodeURIComponent(value);
|
|
21681
|
-
} catch {
|
|
21682
|
-
cookies[name] = value;
|
|
21683
|
-
}
|
|
21684
|
-
return cookies;
|
|
21685
|
-
}, {});
|
|
21686
|
-
}
|
|
21687
21671
|
function serializeSessionCookie(opts) {
|
|
21688
21672
|
const parts = [
|
|
21689
21673
|
`${opts.name}=${opts.value ? encodeURIComponent(opts.value) : ""}`,
|
|
@@ -21754,6 +21738,38 @@ function isLoopbackBindHost(host) {
|
|
|
21754
21738
|
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) return true;
|
|
21755
21739
|
return false;
|
|
21756
21740
|
}
|
|
21741
|
+
function isLoopbackPeerAddress(address) {
|
|
21742
|
+
if (!address) return false;
|
|
21743
|
+
const normalized = address.toLowerCase().replace(/^::ffff:(?=\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)/, "");
|
|
21744
|
+
return isIP(normalized) !== 0 && isLoopbackBindHost(normalized);
|
|
21745
|
+
}
|
|
21746
|
+
function isDirectLocalRequest(request) {
|
|
21747
|
+
if (request.raw.httpVersion !== "1.1") return false;
|
|
21748
|
+
if (!isLoopbackPeerAddress(request.raw.socket.remoteAddress)) return false;
|
|
21749
|
+
if (hasProxyHeaders(request)) return false;
|
|
21750
|
+
const host = request.headers.host;
|
|
21751
|
+
if (!host) return false;
|
|
21752
|
+
let hostname;
|
|
21753
|
+
try {
|
|
21754
|
+
hostname = new URL(`http://${host}`).hostname;
|
|
21755
|
+
} catch {
|
|
21756
|
+
return false;
|
|
21757
|
+
}
|
|
21758
|
+
return hostname !== "" && isLoopbackBindHost(hostname);
|
|
21759
|
+
}
|
|
21760
|
+
function configuresExternalAccess(trustProxy, basePath, ...configuredUrls) {
|
|
21761
|
+
if (trustProxy !== false || basePath !== void 0) return true;
|
|
21762
|
+
return configuredUrls.some((configuredUrl) => {
|
|
21763
|
+
const value = configuredUrl?.trim();
|
|
21764
|
+
if (!value) return false;
|
|
21765
|
+
try {
|
|
21766
|
+
const { hostname } = new URL(value);
|
|
21767
|
+
return hostname === "" || !isLoopbackBindHost(hostname);
|
|
21768
|
+
} catch {
|
|
21769
|
+
return true;
|
|
21770
|
+
}
|
|
21771
|
+
});
|
|
21772
|
+
}
|
|
21757
21773
|
function isAllowedHost(host, allowedHosts) {
|
|
21758
21774
|
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
21759
21775
|
return allowedHosts.has(normalized) || isIP(normalized) !== 0;
|
|
@@ -22028,7 +22044,8 @@ async function createServer(opts) {
|
|
|
22028
22044
|
onFirstActivation: () => maybeShowActivationNotice()
|
|
22029
22045
|
});
|
|
22030
22046
|
jobRunner.recoverStaleRuns();
|
|
22031
|
-
const
|
|
22047
|
+
const allowLoopbackWebhooks = process.env.CANONRY_ALLOW_LOOPBACK_WEBHOOKS !== "0";
|
|
22048
|
+
const notifier = new Notifier(opts.db, serverUrl, { allowLoopbackWebhooks });
|
|
22032
22049
|
const intelligenceService = new IntelligenceService(opts.db);
|
|
22033
22050
|
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
22034
22051
|
skipProbe: true,
|
|
@@ -22819,7 +22836,7 @@ async function createServer(opts) {
|
|
|
22819
22836
|
}
|
|
22820
22837
|
})();
|
|
22821
22838
|
if (opts.config.apiKey) {
|
|
22822
|
-
const keyHash =
|
|
22839
|
+
const keyHash = hashApiKey(opts.config.apiKey);
|
|
22823
22840
|
const existing = opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash, keyHash)).get();
|
|
22824
22841
|
if (!existing) {
|
|
22825
22842
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
@@ -22845,11 +22862,11 @@ async function createServer(opts) {
|
|
|
22845
22862
|
ne3(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
|
|
22846
22863
|
)
|
|
22847
22864
|
)).run();
|
|
22848
|
-
const createSession = (key, passwordBased = false) => {
|
|
22865
|
+
const createSession = (key, passwordBased = false, db = opts.db) => {
|
|
22849
22866
|
const now = /* @__PURE__ */ new Date();
|
|
22850
|
-
|
|
22867
|
+
db.delete(dashboardSessions).where(lte2(dashboardSessions.expiresAt, now.toISOString())).run();
|
|
22851
22868
|
const sessionId = crypto29.randomBytes(32).toString("hex");
|
|
22852
|
-
|
|
22869
|
+
db.insert(dashboardSessions).values({
|
|
22853
22870
|
tokenHash: hashSessionToken(sessionId),
|
|
22854
22871
|
apiKeyId: key.id,
|
|
22855
22872
|
apiKeyHash: key.keyHash,
|
|
@@ -22883,12 +22900,9 @@ async function createServer(opts) {
|
|
|
22883
22900
|
};
|
|
22884
22901
|
const getDefaultApiKey = () => {
|
|
22885
22902
|
if (!opts.config.apiKey) return void 0;
|
|
22886
|
-
return opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash,
|
|
22903
|
+
return opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
22887
22904
|
};
|
|
22888
|
-
const
|
|
22889
|
-
const key = getDefaultApiKey();
|
|
22890
|
-
if (!key || key.revokedAt) return false;
|
|
22891
|
-
const sessionId = createSession(key, true);
|
|
22905
|
+
const setSessionCookie = (reply, sessionId) => {
|
|
22892
22906
|
reply.header(
|
|
22893
22907
|
"set-cookie",
|
|
22894
22908
|
serializeSessionCookie({
|
|
@@ -22899,19 +22913,27 @@ async function createServer(opts) {
|
|
|
22899
22913
|
ttlMs: SESSION_TTL_MS
|
|
22900
22914
|
})
|
|
22901
22915
|
);
|
|
22916
|
+
};
|
|
22917
|
+
const createPasswordSession = (reply) => {
|
|
22918
|
+
const key = getDefaultApiKey();
|
|
22919
|
+
if (!key || key.revokedAt) return false;
|
|
22920
|
+
setSessionCookie(reply, createSession(key, true));
|
|
22902
22921
|
return true;
|
|
22903
22922
|
};
|
|
22904
|
-
const
|
|
22905
|
-
|
|
22923
|
+
const keylessLocalSetup = isLoopbackBindHost(opts.host) && !configuresExternalAccess(trustProxy, basePath, opts.config.apiUrl, opts.config.publicUrl);
|
|
22924
|
+
keylessFirstRunSetupByServer.set(app, keylessLocalSetup);
|
|
22925
|
+
const requestPresentsKey = (request, key) => {
|
|
22906
22926
|
const header = request.headers.authorization;
|
|
22907
22927
|
if (!header) return false;
|
|
22908
22928
|
const parts = header.split(" ");
|
|
22909
|
-
if (parts.length !== 2 || parts[0] !== "Bearer") return false;
|
|
22910
|
-
const
|
|
22911
|
-
|
|
22929
|
+
if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
|
|
22930
|
+
const presented = Buffer.from(hashApiKey(parts[1]), "hex");
|
|
22931
|
+
const expected = Buffer.from(key.keyHash, "hex");
|
|
22932
|
+
return presented.length === expected.length && crypto29.timingSafeEqual(presented, expected);
|
|
22912
22933
|
};
|
|
22913
22934
|
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
22914
22935
|
const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
|
|
22936
|
+
const serverApiKeyMissing = () => new AppError("AUTH_INVALID", "Server API key not found \u2014 run canonry bootstrap", 401);
|
|
22915
22937
|
app.get(apiPrefix + "/session", async (request, reply) => {
|
|
22916
22938
|
if (namedAccountsInUse()) {
|
|
22917
22939
|
return reply.send({ authenticated: false, setupRequired: false });
|
|
@@ -22919,7 +22941,7 @@ async function createServer(opts) {
|
|
|
22919
22941
|
if (!dashboardRequirePassword) {
|
|
22920
22942
|
return reply.send({ authenticated: true, setupRequired: false });
|
|
22921
22943
|
}
|
|
22922
|
-
const sessionId =
|
|
22944
|
+
const sessionId = parseCookieHeader(request.headers.cookie)[SESSION_COOKIE_NAME];
|
|
22923
22945
|
return reply.send({
|
|
22924
22946
|
authenticated: Boolean(sessionId && resolveSessionApiKeyId(sessionId)),
|
|
22925
22947
|
setupRequired: !opts.config.dashboardPasswordHash
|
|
@@ -22933,9 +22955,13 @@ async function createServer(opts) {
|
|
|
22933
22955
|
if (!dashboardRequirePassword) {
|
|
22934
22956
|
return reply.send({ authenticated: true, setupRequired: false });
|
|
22935
22957
|
}
|
|
22936
|
-
|
|
22937
|
-
|
|
22938
|
-
|
|
22958
|
+
const found = getDefaultApiKey();
|
|
22959
|
+
const defaultKey = found && !found.revokedAt ? found : void 0;
|
|
22960
|
+
const presentsConfiguredKey = opts.config.apiKey ? requestPresentsKey(request, { keyHash: hashApiKey(opts.config.apiKey) }) : false;
|
|
22961
|
+
const presentedRootKey = defaultKey !== void 0 && presentsConfiguredKey;
|
|
22962
|
+
if (!presentedRootKey && !(keylessLocalSetup && isDirectLocalRequest(request))) {
|
|
22963
|
+
const err = presentsConfiguredKey ? serverApiKeyMissing() : authRequired(
|
|
22964
|
+
"Setting the dashboard password requires the root API key (apiKey in config.yaml), unless the request comes directly from this machine to a loopback-bound server that has no external URL, base path, or trusted proxy configured."
|
|
22939
22965
|
);
|
|
22940
22966
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
22941
22967
|
}
|
|
@@ -22948,12 +22974,31 @@ async function createServer(opts) {
|
|
|
22948
22974
|
const err = validationError("Password must be at least 8 characters");
|
|
22949
22975
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
22950
22976
|
}
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
if (!createPasswordSession(reply)) {
|
|
22954
|
-
const err = authInvalid();
|
|
22977
|
+
if (!defaultKey) {
|
|
22978
|
+
const err = serverApiKeyMissing();
|
|
22955
22979
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
22956
22980
|
}
|
|
22981
|
+
opts.config.dashboardPasswordHash = hashDashboardPassword(password);
|
|
22982
|
+
let sessionId;
|
|
22983
|
+
try {
|
|
22984
|
+
sessionId = opts.db.transaction((tx) => {
|
|
22985
|
+
writeAuditLog(tx, auditFromRequest(request, {
|
|
22986
|
+
actor: `api-key:${defaultKey.id}`,
|
|
22987
|
+
credentialId: presentedRootKey ? defaultKey.id : null,
|
|
22988
|
+
requestId: request.id,
|
|
22989
|
+
action: "dashboard-password.created",
|
|
22990
|
+
entityType: "dashboard-password",
|
|
22991
|
+
diff: { authorizedBy: presentedRootKey ? "root-api-key" : "local-request" }
|
|
22992
|
+
}));
|
|
22993
|
+
const created = createSession(defaultKey, true, tx);
|
|
22994
|
+
saveConfigPatch(opts.config);
|
|
22995
|
+
return created;
|
|
22996
|
+
});
|
|
22997
|
+
} catch (err) {
|
|
22998
|
+
opts.config.dashboardPasswordHash = void 0;
|
|
22999
|
+
throw err;
|
|
23000
|
+
}
|
|
23001
|
+
setSessionCookie(reply, sessionId);
|
|
22957
23002
|
return reply.send({ authenticated: true });
|
|
22958
23003
|
});
|
|
22959
23004
|
app.post(apiPrefix + "/session", async (request, reply) => {
|
|
@@ -22992,40 +23037,26 @@ async function createServer(opts) {
|
|
|
22992
23037
|
opts.config.dashboardPasswordHash = migratedHash;
|
|
22993
23038
|
}
|
|
22994
23039
|
if (!createPasswordSession(reply)) {
|
|
22995
|
-
|
|
22996
|
-
|
|
22997
|
-
code: "AUTH_INVALID",
|
|
22998
|
-
message: "Server API key not found \u2014 run canonry bootstrap"
|
|
22999
|
-
}
|
|
23000
|
-
});
|
|
23040
|
+
const err2 = serverApiKeyMissing();
|
|
23041
|
+
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
23001
23042
|
}
|
|
23002
23043
|
return reply.send({ authenticated: true });
|
|
23003
23044
|
}
|
|
23004
23045
|
if (apiKey) {
|
|
23005
|
-
const key = opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash,
|
|
23046
|
+
const key = opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
23006
23047
|
if (!key || key.revokedAt) {
|
|
23007
23048
|
const err2 = authInvalid();
|
|
23008
23049
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
23009
23050
|
}
|
|
23010
23051
|
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq29(apiKeys.id, key.id)).run();
|
|
23011
|
-
|
|
23012
|
-
reply.header(
|
|
23013
|
-
"set-cookie",
|
|
23014
|
-
serializeSessionCookie({
|
|
23015
|
-
name: SESSION_COOKIE_NAME,
|
|
23016
|
-
value: sessionId,
|
|
23017
|
-
path: sessionCookiePath,
|
|
23018
|
-
secure: sessionCookieSecure,
|
|
23019
|
-
ttlMs: SESSION_TTL_MS
|
|
23020
|
-
})
|
|
23021
|
-
);
|
|
23052
|
+
setSessionCookie(reply, createSession(key));
|
|
23022
23053
|
return reply.send({ authenticated: true });
|
|
23023
23054
|
}
|
|
23024
23055
|
const err = validationError("Either password or apiKey is required");
|
|
23025
23056
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
23026
23057
|
});
|
|
23027
23058
|
app.delete(apiPrefix + "/session", async (request, reply) => {
|
|
23028
|
-
const sessionId =
|
|
23059
|
+
const sessionId = parseCookieHeader(request.headers.cookie)[SESSION_COOKIE_NAME];
|
|
23029
23060
|
clearSession(sessionId);
|
|
23030
23061
|
reply.header(
|
|
23031
23062
|
"set-cookie",
|
|
@@ -23277,12 +23308,7 @@ async function createServer(opts) {
|
|
|
23277
23308
|
getAgentPluginState: opts.getAgentPluginState,
|
|
23278
23309
|
// Powers the `canonry.version.current` doctor check. Non-blocking.
|
|
23279
23310
|
getUpdateStatus: () => getServerUpdateStatus(),
|
|
23280
|
-
|
|
23281
|
-
// webhook at localhost (Discord test container, Pipedream-mock dev server,
|
|
23282
|
-
// etc.) is a legitimate workflow. Default to allowing it for the local
|
|
23283
|
-
// installer; cloud deployments inherit the secure default of `false` by
|
|
23284
|
-
// not passing this option. Override with CANONRY_ALLOW_LOOPBACK_WEBHOOKS=0.
|
|
23285
|
-
allowLoopbackWebhooks: process.env.CANONRY_ALLOW_LOOPBACK_WEBHOOKS !== "0",
|
|
23311
|
+
allowLoopbackWebhooks,
|
|
23286
23312
|
// Wall-clock budget for one incremental Vercel drain. This is the only lever
|
|
23287
23313
|
// that decides whether a source catches up or falls further behind, and it
|
|
23288
23314
|
// was previously reachable only from tests, so a source losing ground could
|
|
@@ -24164,6 +24190,7 @@ export {
|
|
|
24164
24190
|
createGoogleMarketingCredentialStore,
|
|
24165
24191
|
createGoogleMarketingRuntime,
|
|
24166
24192
|
waitForServerRuntimeStartup,
|
|
24193
|
+
allowsKeylessFirstRunSetup,
|
|
24167
24194
|
isLoopbackBindHost,
|
|
24168
24195
|
createServer
|
|
24169
24196
|
};
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
installSkills,
|
|
5
5
|
loadConfigRaw,
|
|
6
6
|
saveConfigPatch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-P2XY5YX7.js";
|
|
8
8
|
import {
|
|
9
9
|
PACKAGE_VERSION
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-QAIJH74T.js";
|
|
11
11
|
import {
|
|
12
12
|
SKILL_MANIFEST_FILENAME,
|
|
13
13
|
SkillsClients
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-MIRAKDRW.js";
|
|
15
15
|
|
|
16
16
|
// src/skills-autosync.ts
|
|
17
17
|
import fs from "fs";
|