codebase-onboarder 0.5.0 → 0.7.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/README.md +196 -47
- package/bin/build-if-source.js +23 -0
- package/cli/commands.d.ts +26 -0
- package/cli/commands.js +836 -811
- package/cli/commands.js.map +1 -0
- package/cli/explorer/advanced.d.ts +14 -0
- package/cli/explorer/advanced.js +449 -0
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +396 -229
- package/cli/explorer/app.js.map +1 -0
- package/cli/explorer/assistant.d.ts +9 -0
- package/cli/explorer/assistant.js +58 -0
- package/cli/explorer/assistant.js.map +1 -0
- package/cli/explorer/commands.d.ts +17 -0
- package/cli/explorer/commands.js +400 -162
- package/cli/explorer/commands.js.map +1 -0
- package/cli/explorer/featureViews.d.ts +124 -0
- package/cli/explorer/featureViews.js +179 -0
- package/cli/explorer/featureViews.js.map +1 -0
- package/cli/explorer/github.d.ts +21 -0
- package/cli/explorer/github.js +73 -0
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +173 -0
- package/cli/explorer/graphs.js.map +1 -0
- package/cli/explorer/picker.d.ts +22 -0
- package/cli/explorer/picker.js +150 -0
- package/cli/explorer/picker.js.map +1 -0
- package/cli/explorer/report.d.ts +85 -0
- package/cli/explorer/report.js +60 -0
- package/cli/explorer/report.js.map +1 -0
- package/cli/explorer/session.d.ts +449 -0
- package/cli/explorer/session.js +184 -95
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +496 -412
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +33 -0
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +235 -138
- package/cli/main.js.map +1 -0
- package/cli/prompt.d.ts +5 -0
- package/cli/prompt.js +84 -80
- package/cli/prompt.js.map +1 -0
- package/cli/scanOptions.d.ts +2 -0
- package/cli/scanOptions.js +11 -0
- package/cli/scanOptions.js.map +1 -0
- package/cli/ui.d.ts +33 -0
- package/cli/ui.js +55 -35
- package/cli/ui.js.map +1 -0
- package/cli/wizard.d.ts +187 -0
- package/cli/wizard.js +304 -284
- package/cli/wizard.js.map +1 -0
- package/package.json +15 -4
- package/public/app.d.ts +1 -0
- package/public/app.js +1057 -1009
- package/public/app.js.map +1 -0
- package/public/browser.d.ts +68 -0
- package/public/browser.js +2 -0
- package/public/browser.js.map +1 -0
- package/public/index.html +30 -13
- package/public/js/about.d.ts +6 -0
- package/public/js/about.js +165 -172
- package/public/js/about.js.map +1 -0
- package/public/js/aiDraft.d.ts +3 -0
- package/public/js/aiDraft.js +116 -124
- package/public/js/aiDraft.js.map +1 -0
- package/public/js/analysisPanel.d.ts +7 -0
- package/public/js/analysisPanel.js +166 -166
- package/public/js/analysisPanel.js.map +1 -0
- package/public/js/analysisReport.d.ts +38 -0
- package/public/js/analysisReport.js +105 -110
- package/public/js/analysisReport.js.map +1 -0
- package/public/js/api.d.ts +26 -0
- package/public/js/api.js +176 -167
- package/public/js/api.js.map +1 -0
- package/public/js/atlas.d.ts +6 -0
- package/public/js/atlas.js +50 -58
- package/public/js/atlas.js.map +1 -0
- package/public/js/blameView.d.ts +2 -0
- package/public/js/blameView.js +36 -37
- package/public/js/blameView.js.map +1 -0
- package/public/js/codeTab.d.ts +3 -0
- package/public/js/codeTab.js +175 -180
- package/public/js/codeTab.js.map +1 -0
- package/public/js/codeViewer.d.ts +5 -0
- package/public/js/codeViewer.js +162 -150
- package/public/js/codeViewer.js.map +1 -0
- package/public/js/components/FileChip.d.ts +9 -0
- package/public/js/components/FileChip.js +45 -49
- package/public/js/components/FileChip.js.map +1 -0
- package/public/js/components/MetricSparkline.d.ts +7 -0
- package/public/js/components/MetricSparkline.js +35 -44
- package/public/js/components/MetricSparkline.js.map +1 -0
- package/public/js/components/RiskBadge.d.ts +9 -0
- package/public/js/components/RiskBadge.js +55 -63
- package/public/js/components/RiskBadge.js.map +1 -0
- package/public/js/deepAnalysisView.d.ts +99 -0
- package/public/js/deepAnalysisView.js +282 -302
- package/public/js/deepAnalysisView.js.map +1 -0
- package/public/js/diagramPane.d.ts +8 -0
- package/public/js/diagramPane.js +133 -135
- package/public/js/diagramPane.js.map +1 -0
- package/public/js/diffView.d.ts +6 -0
- package/public/js/diffView.js +222 -209
- package/public/js/diffView.js.map +1 -0
- package/public/js/docsView.d.ts +2 -0
- package/public/js/docsView.js +441 -442
- package/public/js/docsView.js.map +1 -0
- package/public/js/dom.d.ts +189 -0
- package/public/js/dom.js +5 -0
- package/public/js/dom.js.map +1 -0
- package/public/js/fileSourceBrowser.d.ts +5 -0
- package/public/js/fileSourceBrowser.js +33 -41
- package/public/js/fileSourceBrowser.js.map +1 -0
- package/public/js/flameGraph.d.ts +5 -0
- package/public/js/flameGraph.js +62 -72
- package/public/js/flameGraph.js.map +1 -0
- package/public/js/forceGraph.d.ts +10 -0
- package/public/js/forceGraph.js +581 -556
- package/public/js/forceGraph.js.map +1 -0
- package/public/js/heatmap.d.ts +4 -0
- package/public/js/heatmap.js +142 -164
- package/public/js/heatmap.js.map +1 -0
- package/public/js/highlight.d.ts +2 -0
- package/public/js/highlight.js +116 -107
- package/public/js/highlight.js.map +1 -0
- package/public/js/html.d.ts +2 -0
- package/public/js/html.js +14 -44
- package/public/js/html.js.map +1 -0
- package/public/js/insightsView.d.ts +5 -0
- package/public/js/insightsView.js +93 -111
- package/public/js/insightsView.js.map +1 -0
- package/public/js/inspector.d.ts +37 -0
- package/public/js/inspector.js +416 -445
- package/public/js/inspector.js.map +1 -0
- package/public/js/llm.d.ts +113 -0
- package/public/js/llm.js +233 -272
- package/public/js/llm.js.map +1 -0
- package/public/js/mapView.d.ts +3 -0
- package/public/js/mapView.js +121 -120
- package/public/js/mapView.js.map +1 -0
- package/public/js/markdown.d.ts +3 -0
- package/public/js/markdown.js +84 -91
- package/public/js/markdown.js.map +1 -0
- package/public/js/mindmap.d.ts +32 -0
- package/public/js/mindmap.js +244 -259
- package/public/js/mindmap.js.map +1 -0
- package/public/js/mobilePanels.d.ts +5 -0
- package/public/js/mobilePanels.js +50 -0
- package/public/js/mobilePanels.js.map +1 -0
- package/public/js/repoFiles.d.ts +12 -0
- package/public/js/repoFiles.js +28 -27
- package/public/js/repoFiles.js.map +1 -0
- package/public/js/reviewView.d.ts +28 -0
- package/public/js/reviewView.js +256 -0
- package/public/js/reviewView.js.map +1 -0
- package/public/js/sbomView.d.ts +3 -0
- package/public/js/sbomView.js +59 -67
- package/public/js/sbomView.js.map +1 -0
- package/public/js/scanCache.d.ts +16 -0
- package/public/js/scanCache.js +67 -65
- package/public/js/scanCache.js.map +1 -0
- package/public/js/search.d.ts +7 -0
- package/public/js/search.js +868 -800
- package/public/js/search.js.map +1 -0
- package/public/js/searchLocal.d.ts +42 -0
- package/public/js/searchLocal.js +87 -0
- package/public/js/searchLocal.js.map +1 -0
- package/public/js/serverSettings.d.ts +5 -0
- package/public/js/serverSettings.js +153 -164
- package/public/js/serverSettings.js.map +1 -0
- package/public/js/state.d.ts +798 -0
- package/public/js/state.js +114 -122
- package/public/js/state.js.map +1 -0
- package/public/js/tour.d.ts +6 -0
- package/public/js/tour.js +5 -7
- package/public/js/tour.js.map +1 -0
- package/public/js/transitions.d.ts +2 -0
- package/public/js/transitions.js +9 -10
- package/public/js/transitions.js.map +1 -0
- package/public/js/tree.d.ts +12 -0
- package/public/js/tree.js +157 -154
- package/public/js/tree.js.map +1 -0
- package/public/js/workflowsView.d.ts +3 -0
- package/public/js/workflowsView.js +15 -22
- package/public/js/workflowsView.js.map +1 -0
- package/public/search.css +495 -0
- package/public/styles.css +163 -346
- package/public/workbench.css +166 -0
- package/server/apiAuth.d.ts +3 -0
- package/server/apiAuth.js +18 -20
- package/server/apiAuth.js.map +1 -0
- package/server/apiDiff.d.ts +2 -0
- package/server/apiDiff.js +20 -18
- package/server/apiDiff.js.map +1 -0
- package/server/apiDocs.d.ts +2 -0
- package/server/apiDocs.js +38 -40
- package/server/apiDocs.js.map +1 -0
- package/server/apiFile.d.ts +1 -0
- package/server/apiFile.js +14 -17
- package/server/apiFile.js.map +1 -0
- package/server/apiGitBlame.d.ts +2 -0
- package/server/apiGitBlame.js +50 -51
- package/server/apiGitBlame.js.map +1 -0
- package/server/apiMcp.d.ts +4 -0
- package/server/apiMcp.js +64 -66
- package/server/apiMcp.js.map +1 -0
- package/server/apiReview.d.ts +2 -0
- package/server/apiReview.js +45 -0
- package/server/apiReview.js.map +1 -0
- package/server/apiScan.d.ts +5 -0
- package/server/apiScan.js +90 -71
- package/server/apiScan.js.map +1 -0
- package/server/apiSearch.d.ts +33 -0
- package/server/apiSearch.js +184 -200
- package/server/apiSearch.js.map +1 -0
- package/server/apiSettings.d.ts +4 -0
- package/server/apiSettings.js +84 -85
- package/server/apiSettings.js.map +1 -0
- package/server/apiTools.d.ts +3 -0
- package/server/apiTools.js +55 -56
- package/server/apiTools.js.map +1 -0
- package/server/auth.d.ts +5 -0
- package/server/auth.js +54 -51
- package/server/auth.js.map +1 -0
- package/server/config.d.ts +42 -0
- package/server/config.js +189 -194
- package/server/config.js.map +1 -0
- package/server/daemon.d.ts +12 -0
- package/server/daemon.js +124 -114
- package/server/daemon.js.map +1 -0
- package/server/fileSourceNode.d.ts +2 -0
- package/server/fileSourceNode.js +34 -36
- package/server/fileSourceNode.js.map +1 -0
- package/server/gitClone.d.ts +9 -0
- package/server/gitClone.js +59 -61
- package/server/gitClone.js.map +1 -0
- package/server/gitDiff.d.ts +36 -0
- package/server/gitDiff.js +271 -169
- package/server/gitDiff.js.map +1 -0
- package/server/gitHistory.d.ts +12 -0
- package/server/gitHistory.js +80 -80
- package/server/gitHistory.js.map +1 -0
- package/server/gitRemote.d.ts +12 -0
- package/server/gitRemote.js +60 -0
- package/server/gitRemote.js.map +1 -0
- package/server/htmlText.d.ts +4 -0
- package/server/htmlText.js +16 -23
- package/server/htmlText.js.map +1 -0
- package/server/http.d.ts +5 -0
- package/server/http.js +27 -30
- package/server/http.js.map +1 -0
- package/server/httpGuards.d.ts +2 -0
- package/server/httpGuards.js +47 -43
- package/server/httpGuards.js.map +1 -0
- package/server/https.d.ts +34 -0
- package/server/https.js +121 -111
- package/server/https.js.map +1 -0
- package/server/index.d.ts +40 -0
- package/server/index.js +139 -147
- package/server/index.js.map +1 -0
- package/server/layout.d.ts +26 -0
- package/server/layout.js +59 -37
- package/server/layout.js.map +1 -0
- package/server/llmProxy.d.ts +3 -0
- package/server/llmProxy.js +137 -135
- package/server/llmProxy.js.map +1 -0
- package/server/logger.d.ts +38 -0
- package/server/logger.js +198 -211
- package/server/logger.js.map +1 -0
- package/server/mcp/analysis.d.ts +29 -0
- package/server/mcp/analysis.js +99 -120
- package/server/mcp/analysis.js.map +1 -0
- package/server/mcp/http.d.ts +9 -0
- package/server/mcp/http.js +128 -137
- package/server/mcp/http.js.map +1 -0
- package/server/mcp/runner.d.ts +68 -0
- package/server/mcp/runner.js +208 -218
- package/server/mcp/runner.js.map +1 -0
- package/server/mcp/server.d.ts +29 -0
- package/server/mcp/server.js +166 -179
- package/server/mcp/server.js.map +1 -0
- package/server/mcp/standalone.d.ts +1 -0
- package/server/mcp/standalone.js +8 -10
- package/server/mcp/standalone.js.map +1 -0
- package/server/mcp/tools.d.ts +2020 -0
- package/server/mcp/tools.js +523 -574
- package/server/mcp/tools.js.map +1 -0
- package/server/paths.d.ts +3 -0
- package/server/paths.js +17 -15
- package/server/paths.js.map +1 -0
- package/server/pidfile.d.ts +9 -0
- package/server/pidfile.js +43 -45
- package/server/pidfile.js.map +1 -0
- package/server/review.d.ts +5 -0
- package/server/review.js +35 -0
- package/server/review.js.map +1 -0
- package/server/router.d.ts +33 -0
- package/server/router.js +191 -197
- package/server/router.js.map +1 -0
- package/server/scanRequest.d.ts +3 -0
- package/server/scanRequest.js +36 -0
- package/server/scanRequest.js.map +1 -0
- package/server/searchIndex.d.ts +17 -0
- package/server/searchIndex.js +83 -80
- package/server/searchIndex.js.map +1 -0
- package/server/sessions.d.ts +14 -0
- package/server/sessions.js +62 -70
- package/server/sessions.js.map +1 -0
- package/server/startup.d.ts +118 -0
- package/server/startup.js +143 -148
- package/server/startup.js.map +1 -0
- package/server/static.d.ts +6 -0
- package/server/static.js +28 -31
- package/server/static.js.map +1 -0
- package/server/tools/install.d.ts +4 -0
- package/server/tools/install.js +176 -185
- package/server/tools/install.js.map +1 -0
- package/server/tools/parse.d.ts +8 -0
- package/server/tools/parse.js +112 -114
- package/server/tools/parse.js.map +1 -0
- package/server/tools/platform.d.ts +9 -0
- package/server/tools/platform.js +49 -47
- package/server/tools/platform.js.map +1 -0
- package/server/tools/registry.d.ts +137 -0
- package/server/tools/registry.js +173 -172
- package/server/tools/registry.js.map +1 -0
- package/server/tools/scan.d.ts +34 -0
- package/server/tools/scan.js +89 -98
- package/server/tools/scan.js.map +1 -0
- package/server/tools.d.ts +34 -0
- package/server/tools.js +94 -107
- package/server/tools.js.map +1 -0
- package/server/tunnel.d.ts +29 -0
- package/server/tunnel.js +61 -66
- package/server/tunnel.js.map +1 -0
- package/shared/analyzer/docs.d.ts +16 -0
- package/shared/analyzer/docs.js +99 -93
- package/shared/analyzer/docs.js.map +1 -0
- package/shared/analyzer/explainLocal.d.ts +4 -0
- package/shared/analyzer/explainLocal.js +122 -142
- package/shared/analyzer/explainLocal.js.map +1 -0
- package/shared/analyzer/github.d.ts +37 -0
- package/shared/analyzer/github.js +79 -0
- package/shared/analyzer/github.js.map +1 -0
- package/shared/analyzer/graph.d.ts +41 -0
- package/shared/analyzer/graph.js +393 -400
- package/shared/analyzer/graph.js.map +1 -0
- package/shared/analyzer/health.d.ts +33 -0
- package/shared/analyzer/health.js +197 -193
- package/shared/analyzer/health.js.map +1 -0
- package/shared/analyzer/history.d.ts +29 -0
- package/shared/analyzer/history.js +110 -111
- package/shared/analyzer/history.js.map +1 -0
- package/shared/analyzer/jsonConfig.d.ts +2 -0
- package/shared/analyzer/jsonConfig.js +61 -0
- package/shared/analyzer/jsonConfig.js.map +1 -0
- package/shared/analyzer/languages/csharp.d.ts +26 -0
- package/shared/analyzer/languages/csharp.js +83 -88
- package/shared/analyzer/languages/csharp.js.map +1 -0
- package/shared/analyzer/languages/generic.d.ts +23 -0
- package/shared/analyzer/languages/generic.js +94 -105
- package/shared/analyzer/languages/generic.js.map +1 -0
- package/shared/analyzer/languages/go.d.ts +24 -0
- package/shared/analyzer/languages/go.js +45 -54
- package/shared/analyzer/languages/go.js.map +1 -0
- package/shared/analyzer/languages/index.d.ts +6 -0
- package/shared/analyzer/languages/index.js +19 -20
- package/shared/analyzer/languages/index.js.map +1 -0
- package/shared/analyzer/languages/java.d.ts +20 -0
- package/shared/analyzer/languages/java.js +73 -78
- package/shared/analyzer/languages/java.js.map +1 -0
- package/shared/analyzer/languages/javascript.d.ts +12 -0
- package/shared/analyzer/languages/javascript.js +258 -209
- package/shared/analyzer/languages/javascript.js.map +1 -0
- package/shared/analyzer/languages/python.d.ts +23 -0
- package/shared/analyzer/languages/python.js +92 -100
- package/shared/analyzer/languages/python.js.map +1 -0
- package/shared/analyzer/languages/rust.d.ts +17 -0
- package/shared/analyzer/languages/rust.js +96 -98
- package/shared/analyzer/languages/rust.js.map +1 -0
- package/shared/analyzer/languages/typescript.d.ts +11 -0
- package/shared/analyzer/languages/typescript.js +117 -116
- package/shared/analyzer/languages/typescript.js.map +1 -0
- package/shared/analyzer/licenses.d.ts +18 -0
- package/shared/analyzer/licenses.js +130 -133
- package/shared/analyzer/licenses.js.map +1 -0
- package/shared/analyzer/metrics.d.ts +20 -0
- package/shared/analyzer/metrics.js +112 -104
- package/shared/analyzer/metrics.js.map +1 -0
- package/shared/analyzer/pathUtil.d.ts +10 -0
- package/shared/analyzer/pathUtil.js +29 -35
- package/shared/analyzer/pathUtil.js.map +1 -0
- package/shared/analyzer/patterns.d.ts +11 -0
- package/shared/analyzer/patterns.js +231 -253
- package/shared/analyzer/patterns.js.map +1 -0
- package/shared/analyzer/scan.d.ts +62 -0
- package/shared/analyzer/scan.js +451 -442
- package/shared/analyzer/scan.js.map +1 -0
- package/shared/analyzer/scanControl.d.ts +11 -0
- package/shared/analyzer/scanControl.js +62 -0
- package/shared/analyzer/scanControl.js.map +1 -0
- package/shared/analyzer/security.d.ts +18 -0
- package/shared/analyzer/security.js +159 -159
- package/shared/analyzer/security.js.map +1 -0
- package/shared/analyzer/services.d.ts +5 -0
- package/shared/analyzer/services.js +141 -138
- package/shared/analyzer/services.js.map +1 -0
- package/shared/analyzer/stack.d.ts +6 -0
- package/shared/analyzer/stack.js +134 -141
- package/shared/analyzer/stack.js.map +1 -0
- package/shared/analyzer/tour.d.ts +2 -0
- package/shared/analyzer/tour.js +45 -53
- package/shared/analyzer/tour.js.map +1 -0
- package/shared/analyzer/util.d.ts +8 -0
- package/shared/analyzer/util.js +130 -76
- package/shared/analyzer/util.js.map +1 -0
- package/shared/analyzer/workflows.d.ts +8 -0
- package/shared/analyzer/workflows.js +139 -134
- package/shared/analyzer/workflows.js.map +1 -0
- package/shared/contracts.d.ts +250 -0
- package/shared/contracts.js +2 -0
- package/shared/contracts.js.map +1 -0
- package/shared/diagram/aiFacts.d.ts +35 -0
- package/shared/diagram/aiFacts.js +102 -105
- package/shared/diagram/aiFacts.js.map +1 -0
- package/shared/diagram/aiMermaid.d.ts +5 -0
- package/shared/diagram/aiMermaid.js +46 -45
- package/shared/diagram/aiMermaid.js.map +1 -0
- package/shared/diagram/atlas.d.ts +16 -0
- package/shared/diagram/atlas.js +51 -60
- package/shared/diagram/atlas.js.map +1 -0
- package/shared/diagram/mermaid.d.ts +43 -0
- package/shared/diagram/mermaid.js +348 -370
- package/shared/diagram/mermaid.js.map +1 -0
- package/shared/review/contracts.d.ts +95 -0
- package/shared/review/contracts.js +2 -0
- package/shared/review/contracts.js.map +1 -0
- package/shared/review/review.d.ts +10 -0
- package/shared/review/review.js +184 -0
- package/shared/review/review.js.map +1 -0
- package/shared/search/query.d.ts +32 -0
- package/shared/search/query.js +395 -388
- package/shared/search/query.js.map +1 -0
package/server/apiSettings.js
CHANGED
|
@@ -6,121 +6,120 @@
|
|
|
6
6
|
// status codes, the secret-handling rules (never read the key back out, show a
|
|
7
7
|
// new one exactly once), and the restart hint — the running server bound its
|
|
8
8
|
// socket at boot, so a host/port change is real but not yet in effect.
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
DEFAULT_SETTINGS, generateAccessKey, normalizeSettings, publicSettings,
|
|
12
|
-
readSettings, updateSettings, writeSettings, configPath,
|
|
13
|
-
} from './config.js';
|
|
9
|
+
import { DEFAULT_SETTINGS, generateAccessKey, normalizeSettings, publicSettings, readSettings, updateSettings, writeSettings, configPath, } from './config.js';
|
|
14
10
|
import { sendError, sendJSON } from './http.js';
|
|
15
11
|
import { sessionCookie } from './auth.js';
|
|
16
12
|
import { tunnelStatus } from './tunnel.js';
|
|
17
13
|
import { httpsStatus } from './https.js';
|
|
18
|
-
|
|
19
14
|
// Where this request's settings live. The router's config object carries the
|
|
20
15
|
// path when the server was booted with one; tests and a bare `createServer()`
|
|
21
16
|
// get the defaults-in-memory view instead of somebody's home directory.
|
|
22
17
|
function settingsFile(config) {
|
|
23
|
-
|
|
18
|
+
return config?.configPath || null;
|
|
24
19
|
}
|
|
25
|
-
|
|
26
20
|
async function currentSettings(config) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if (typeof config?.getSettings === 'function')
|
|
22
|
+
return config.getSettings();
|
|
23
|
+
const file = settingsFile(config);
|
|
24
|
+
if (file)
|
|
25
|
+
return readSettings(file);
|
|
26
|
+
return { ...DEFAULT_SETTINGS, account: { ...DEFAULT_SETTINGS.account }, tunnel: { ...DEFAULT_SETTINGS.tunnel } };
|
|
31
27
|
}
|
|
32
|
-
|
|
33
28
|
// The public body every read returns: settings with the secret masked, plus
|
|
34
29
|
// the connection picture (URLs, tunnel status) the drawer and CLI both render.
|
|
35
30
|
async function publicBody(config) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
const settings = await currentSettings(config);
|
|
32
|
+
return {
|
|
33
|
+
settings: publicSettings(settings),
|
|
34
|
+
configFile: settingsFile(config) || configPath(),
|
|
35
|
+
node: process.version,
|
|
36
|
+
tunnels: tunnelStatus(settings),
|
|
37
|
+
https: httpsStatus(settings, settingsFile(config) || configPath()),
|
|
38
|
+
};
|
|
44
39
|
}
|
|
45
|
-
|
|
46
40
|
export async function handleGetSettings(req, res, config) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
try {
|
|
42
|
+
sendJSON(res, 200, await publicBody(config));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
sendError(res, 500, err.message || 'The settings could not be read.');
|
|
46
|
+
}
|
|
52
47
|
}
|
|
53
|
-
|
|
54
48
|
// Which changes the running server cannot absorb. Access-key and tunnel flags
|
|
55
49
|
// are read live per request. Host/port are bound sockets. Domain/HTTPS change
|
|
56
50
|
// the managed Caddyfile and certificate, so Caddy must validate/reload too.
|
|
57
51
|
function restartRequired(config, next) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
const boot = config?.boot;
|
|
53
|
+
if (!boot)
|
|
54
|
+
return [];
|
|
55
|
+
const changed = [];
|
|
56
|
+
if (next.host !== boot.host)
|
|
57
|
+
changed.push('host');
|
|
58
|
+
if (next.port !== boot.port)
|
|
59
|
+
changed.push('port');
|
|
60
|
+
if (next.domain !== boot.domain)
|
|
61
|
+
changed.push('domain');
|
|
62
|
+
if (next.https !== boot.https)
|
|
63
|
+
changed.push('https');
|
|
64
|
+
return changed;
|
|
66
65
|
}
|
|
67
|
-
|
|
68
66
|
export async function handleUpdateSettings(res, body, config) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
const file = settingsFile(config);
|
|
68
|
+
if (!file) {
|
|
69
|
+
return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');
|
|
70
|
+
}
|
|
71
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
72
|
+
return sendError(res, 400, 'Send a JSON object of settings to change.');
|
|
73
|
+
}
|
|
74
|
+
// Only known keys may be patched — a typo like "ports" must be a loud 400,
|
|
75
|
+
// not a silently ignored write that the UI then claims it saved.
|
|
76
|
+
const allowed = new Set(['mode', 'host', 'port', 'domain', 'https', 'autoOpen', 'account', 'tunnel']);
|
|
77
|
+
const unknown = Object.keys(body).filter((k) => k !== 'accessKey' && !allowed.has(k));
|
|
78
|
+
if (unknown.length) {
|
|
79
|
+
return sendError(res, 400, `Unknown setting${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}.`);
|
|
80
|
+
}
|
|
81
|
+
if ('accessKey' in body) {
|
|
82
|
+
return sendError(res, 400, 'The access key is rotated, not patched — POST /api/settings/access-key.');
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const next = await updateSettings(body, file);
|
|
86
|
+
sendJSON(res, 200, {
|
|
87
|
+
settings: publicSettings(next),
|
|
88
|
+
restartRequired: restartRequired(config, next),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
sendError(res, 400, err.message || 'Those settings did not validate.');
|
|
93
|
+
}
|
|
95
94
|
}
|
|
96
|
-
|
|
97
95
|
// Rotation returns the new key in the clear, once. It is never in a GET after
|
|
98
96
|
// that — `hasAccessKey`/`accessKeyMasked` are all the readback there is. The
|
|
99
97
|
// person copies it from this response into the devices that need it.
|
|
100
98
|
export async function handleRotateAccessKey(req, res, config) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
99
|
+
const file = settingsFile(config);
|
|
100
|
+
if (!file) {
|
|
101
|
+
return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const accessKey = generateAccessKey();
|
|
105
|
+
const current = await currentSettings(config);
|
|
106
|
+
const next = await writeSettings({ ...current, accessKey }, file);
|
|
107
|
+
sendJSON(res, 200, {
|
|
108
|
+
accessKey,
|
|
109
|
+
settings: publicSettings(next),
|
|
110
|
+
note: 'Shown once. This browser is signed in with the new key; update every other device.',
|
|
111
|
+
}, {
|
|
112
|
+
'cache-control': 'no-store',
|
|
113
|
+
'set-cookie': sessionCookie(req, next),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
sendError(res, 400, err.message || 'The key could not be rotated.');
|
|
118
|
+
}
|
|
120
119
|
}
|
|
121
|
-
|
|
122
120
|
// Kept exported for the tests: a patch that survives normalizeSettings is the
|
|
123
121
|
// same patch the wizard and the API would both accept.
|
|
124
122
|
export function validatePatch(patch) {
|
|
125
|
-
|
|
123
|
+
return normalizeSettings({ ...DEFAULT_SETTINGS, ...patch });
|
|
126
124
|
}
|
|
125
|
+
//# sourceMappingURL=apiSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiSettings.js","sourceRoot":"","sources":["../src/server/apiSettings.ts"],"names":[],"mappings":"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","sourcesContent":["// The settings endpoints: read the public shape, apply a patch, rotate the key.\n//\n// All three go through `server/config.js` for validation and the atomic 0600\n// write, so the CLI wizard, `onboarder config set`, and this API can never save\n// three dialects of the same file. What lives here is only what HTTP adds:\n// status codes, the secret-handling rules (never read the key back out, show a\n// new one exactly once), and the restart hint — the running server bound its\n// socket at boot, so a host/port change is real but not yet in effect.\n\nimport {\n DEFAULT_SETTINGS, generateAccessKey, normalizeSettings, publicSettings,\n readSettings, updateSettings, writeSettings, configPath,\n} from './config.js';\nimport { sendError, sendJSON } from './http.js';\nimport { sessionCookie } from './auth.js';\nimport { tunnelStatus } from './tunnel.js';\nimport { httpsStatus } from './https.js';\n\n// Where this request's settings live. The router's config object carries the\n// path when the server was booted with one; tests and a bare `createServer()`\n// get the defaults-in-memory view instead of somebody's home directory.\nfunction settingsFile(config) {\n return config?.configPath || null;\n}\n\nasync function currentSettings(config) {\n if (typeof config?.getSettings === 'function') return config.getSettings();\n const file = settingsFile(config);\n if (file) return readSettings(file);\n return { ...DEFAULT_SETTINGS, account: { ...DEFAULT_SETTINGS.account }, tunnel: { ...DEFAULT_SETTINGS.tunnel } };\n}\n\n// The public body every read returns: settings with the secret masked, plus\n// the connection picture (URLs, tunnel status) the drawer and CLI both render.\nasync function publicBody(config) {\n const settings = await currentSettings(config);\n return {\n settings: publicSettings(settings),\n configFile: settingsFile(config) || configPath(),\n node: process.version,\n tunnels: tunnelStatus(settings),\n https: httpsStatus(settings, settingsFile(config) || configPath()),\n };\n}\n\nexport async function handleGetSettings(req, res, config) {\n try {\n sendJSON(res, 200, await publicBody(config));\n } catch (err) {\n sendError(res, 500, err.message || 'The settings could not be read.');\n }\n}\n\n// Which changes the running server cannot absorb. Access-key and tunnel flags\n// are read live per request. Host/port are bound sockets. Domain/HTTPS change\n// the managed Caddyfile and certificate, so Caddy must validate/reload too.\nfunction restartRequired(config, next) {\n const boot = config?.boot;\n if (!boot) return [];\n const changed = [];\n if (next.host !== boot.host) changed.push('host');\n if (next.port !== boot.port) changed.push('port');\n if (next.domain !== boot.domain) changed.push('domain');\n if (next.https !== boot.https) changed.push('https');\n return changed;\n}\n\nexport async function handleUpdateSettings(res, body, config) {\n const file = settingsFile(config);\n if (!file) {\n return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');\n }\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return sendError(res, 400, 'Send a JSON object of settings to change.');\n }\n // Only known keys may be patched — a typo like \"ports\" must be a loud 400,\n // not a silently ignored write that the UI then claims it saved.\n const allowed = new Set(['mode', 'host', 'port', 'domain', 'https', 'autoOpen', 'account', 'tunnel']);\n const unknown = Object.keys(body).filter((k) => k !== 'accessKey' && !allowed.has(k));\n if (unknown.length) {\n return sendError(res, 400, `Unknown setting${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}.`);\n }\n if ('accessKey' in body) {\n return sendError(res, 400, 'The access key is rotated, not patched — POST /api/settings/access-key.');\n }\n try {\n const next = await updateSettings(body, file);\n sendJSON(res, 200, {\n settings: publicSettings(next),\n restartRequired: restartRequired(config, next),\n });\n } catch (err) {\n sendError(res, 400, err.message || 'Those settings did not validate.');\n }\n}\n\n// Rotation returns the new key in the clear, once. It is never in a GET after\n// that — `hasAccessKey`/`accessKeyMasked` are all the readback there is. The\n// person copies it from this response into the devices that need it.\nexport async function handleRotateAccessKey(req, res, config) {\n const file = settingsFile(config);\n if (!file) {\n return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');\n }\n try {\n const accessKey = generateAccessKey();\n const current = await currentSettings(config);\n const next = await writeSettings({ ...current, accessKey }, file);\n sendJSON(res, 200, {\n accessKey,\n settings: publicSettings(next),\n note: 'Shown once. This browser is signed in with the new key; update every other device.',\n }, {\n 'cache-control': 'no-store',\n 'set-cookie': sessionCookie(req, next),\n });\n } catch (err) {\n sendError(res, 400, err.message || 'The key could not be rotated.');\n }\n}\n\n// Kept exported for the tests: a patch that survives normalizeSettings is the\n// same patch the wizard and the API would both accept.\nexport function validatePatch(patch) {\n return normalizeSettings({ ...DEFAULT_SETTINGS, ...patch });\n}\n"]}
|
package/server/apiTools.js
CHANGED
|
@@ -7,46 +7,42 @@
|
|
|
7
7
|
// else. `tools.js` spawns with argument arrays and hard timeouts, so a crafted
|
|
8
8
|
// path becomes an argument and a hung tool becomes a failed pass, not a stuck
|
|
9
9
|
// server.
|
|
10
|
-
|
|
11
10
|
import { sendError, sendJSON } from './http.js';
|
|
12
11
|
import { getSession } from './sessions.js';
|
|
13
12
|
import { runExternalAnalysis, toolsStatus } from './tools/scan.js';
|
|
14
13
|
import { installTool, isInstalling, plansFor } from './tools/install.js';
|
|
15
14
|
import { TOOL_DEFS } from './tools/registry.js';
|
|
16
|
-
|
|
17
15
|
const KNOWN_TOOLS = new Set(TOOL_DEFS.map((d) => d.id));
|
|
18
|
-
|
|
19
16
|
export function handleToolsStatus(res) {
|
|
20
|
-
|
|
17
|
+
sendJSON(res, 200, { tools: toolsStatus() });
|
|
21
18
|
}
|
|
22
|
-
|
|
23
19
|
export async function handleToolsRun(res, body) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
toolFilter =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
20
|
+
const { scanId, kinds, tools, options } = body || {};
|
|
21
|
+
if (!scanId)
|
|
22
|
+
return sendError(res, 400, 'Missing scanId.');
|
|
23
|
+
const session = getSession(scanId);
|
|
24
|
+
if (!session)
|
|
25
|
+
return sendError(res, 404, 'That scan is gone. Rescan the repo.');
|
|
26
|
+
// `kinds` narrows by purpose, `tools` to named engines. An unknown value is a
|
|
27
|
+
// typo the caller would rather hear about than have silently ignored.
|
|
28
|
+
let kindFilter = null;
|
|
29
|
+
if (Array.isArray(kinds) && kinds.length) {
|
|
30
|
+
const ok = kinds.filter((k) => k === 'security' || k === 'dead-code');
|
|
31
|
+
if (!ok.length)
|
|
32
|
+
return sendError(res, 400, 'kinds must be "security" and/or "dead-code".');
|
|
33
|
+
kindFilter = ok;
|
|
34
|
+
}
|
|
35
|
+
let toolFilter = null;
|
|
36
|
+
if (Array.isArray(tools) && tools.length) {
|
|
37
|
+
toolFilter = tools.map((t) => String(t));
|
|
38
|
+
}
|
|
39
|
+
// `options` is the per-engine GUI form. Validation happens against the
|
|
40
|
+
// registry schema inside the run — the handler just passes it along.
|
|
41
|
+
const report = await runExternalAnalysis(session.root, {
|
|
42
|
+
kinds: kindFilter, tools: toolFilter, options,
|
|
43
|
+
});
|
|
44
|
+
return sendJSON(res, 200, report);
|
|
48
45
|
}
|
|
49
|
-
|
|
50
46
|
// POST /api/tools/install — install a missing engine, streaming progress.
|
|
51
47
|
//
|
|
52
48
|
// This is the one endpoint that changes the machine, so it is worth spelling
|
|
@@ -61,30 +57,33 @@ export async function handleToolsRun(res, body) {
|
|
|
61
57
|
// * The stream is SSE in the same `data:`-framed shape the explain proxy
|
|
62
58
|
// uses, so the front end reads install progress exactly like AI output.
|
|
63
59
|
export async function handleToolsInstall(res, body) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
60
|
+
const tool = String(body?.tool || '');
|
|
61
|
+
if (!KNOWN_TOOLS.has(tool)) {
|
|
62
|
+
return sendError(res, 400, `Unknown tool "${tool}". Known: ${[...KNOWN_TOOLS].join(', ')}.`);
|
|
63
|
+
}
|
|
64
|
+
if (!plansFor(tool).length) {
|
|
65
|
+
return sendError(res, 400, `Onboarder has no install plan for ${tool} on ${process.platform}.`);
|
|
66
|
+
}
|
|
67
|
+
if (isInstalling(tool)) {
|
|
68
|
+
return sendError(res, 409, `${tool} is already being installed.`);
|
|
69
|
+
}
|
|
70
|
+
res.writeHead(200, {
|
|
71
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
72
|
+
'cache-control': 'no-cache',
|
|
73
|
+
connection: 'keep-alive',
|
|
74
|
+
});
|
|
75
|
+
const send = (event) => {
|
|
76
|
+
try {
|
|
77
|
+
res.write('data: ' + JSON.stringify(event) + '\n\n');
|
|
78
|
+
}
|
|
79
|
+
catch { /* client went away */ }
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await installTool(tool, send);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
send({ type: 'done', ok: false, error: err.message || 'The install failed.' });
|
|
86
|
+
}
|
|
87
|
+
res.end();
|
|
90
88
|
}
|
|
89
|
+
//# sourceMappingURL=apiTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiTools.js","sourceRoot":"","sources":["../src/server/apiTools.ts"],"names":[],"mappings":"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","sourcesContent":["// GET /api/tools — which analyzers this machine can run, for the engines panel.\n// POST /api/tools/run — run the available analyzers against a live scan and\n// return the normalized findings.\n//\n// The scan id is a capability the same way it is for `/api/file`: it names a\n// root the person chose, and the analyzers are run inside that root and nowhere\n// else. `tools.js` spawns with argument arrays and hard timeouts, so a crafted\n// path becomes an argument and a hung tool becomes a failed pass, not a stuck\n// server.\n\nimport { sendError, sendJSON } from './http.js';\nimport { getSession } from './sessions.js';\nimport { runExternalAnalysis, toolsStatus } from './tools/scan.js';\nimport { installTool, isInstalling, plansFor } from './tools/install.js';\nimport { TOOL_DEFS } from './tools/registry.js';\n\nconst KNOWN_TOOLS = new Set(TOOL_DEFS.map((d) => d.id));\n\nexport function handleToolsStatus(res) {\n sendJSON(res, 200, { tools: toolsStatus() });\n}\n\nexport async function handleToolsRun(res, body) {\n const { scanId, kinds, tools, options } = body || {};\n if (!scanId) return sendError(res, 400, 'Missing scanId.');\n const session = getSession(scanId);\n if (!session) return sendError(res, 404, 'That scan is gone. Rescan the repo.');\n\n // `kinds` narrows by purpose, `tools` to named engines. An unknown value is a\n // typo the caller would rather hear about than have silently ignored.\n let kindFilter = null;\n if (Array.isArray(kinds) && kinds.length) {\n const ok = kinds.filter((k) => k === 'security' || k === 'dead-code');\n if (!ok.length) return sendError(res, 400, 'kinds must be \"security\" and/or \"dead-code\".');\n kindFilter = ok;\n }\n let toolFilter = null;\n if (Array.isArray(tools) && tools.length) {\n toolFilter = tools.map((t) => String(t));\n }\n\n // `options` is the per-engine GUI form. Validation happens against the\n // registry schema inside the run — the handler just passes it along.\n const report = await runExternalAnalysis(session.root, {\n kinds: kindFilter, tools: toolFilter, options,\n });\n return sendJSON(res, 200, report);\n}\n\n// POST /api/tools/install — install a missing engine, streaming progress.\n//\n// This is the one endpoint that changes the machine, so it is worth spelling\n// out why that is safe enough for a local, self-hosted app:\n//\n// * The route is a POST, which the router's same-origin gate already\n// refuses for any page that is not Onboarder's own — a foreign tab cannot\n// drive it.\n// * `tool` must name a registry entry; the install plans are fixed data in\n// `tools/install.js`, spawned as argument arrays. Nothing from the\n// request body ever becomes part of a command line.\n// * The stream is SSE in the same `data:`-framed shape the explain proxy\n// uses, so the front end reads install progress exactly like AI output.\nexport async function handleToolsInstall(res, body) {\n const tool = String(body?.tool || '');\n if (!KNOWN_TOOLS.has(tool)) {\n return sendError(res, 400, `Unknown tool \"${tool}\". Known: ${[...KNOWN_TOOLS].join(', ')}.`);\n }\n if (!plansFor(tool).length) {\n return sendError(res, 400, `Onboarder has no install plan for ${tool} on ${process.platform}.`);\n }\n if (isInstalling(tool)) {\n return sendError(res, 409, `${tool} is already being installed.`);\n }\n\n res.writeHead(200, {\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n });\n const send = (event) => {\n try { res.write('data: ' + JSON.stringify(event) + '\\n\\n'); } catch { /* client went away */ }\n };\n\n try {\n await installTool(tool, send);\n } catch (err) {\n send({ type: 'done', ok: false, error: err.message || 'The install failed.' });\n }\n res.end();\n}\n"]}
|
package/server/auth.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const SESSION_COOKIE = "onboarder_session";
|
|
2
|
+
export declare const SESSION_TTL_SECONDS: number;
|
|
3
|
+
export declare function createSession(settings: any, now?: number): string;
|
|
4
|
+
export declare function hasValidSession(req: any, settings: any, now?: number): boolean;
|
|
5
|
+
export declare function sessionCookie(req: any, settings: any, { clear }?: Record<string, any>): string;
|
package/server/auth.js
CHANGED
|
@@ -4,69 +4,72 @@
|
|
|
4
4
|
// opaque timestamp + HMAC using the configured key as the signing secret. The
|
|
5
5
|
// browser gets an HttpOnly cookie, JavaScript cannot read it, and rotating the
|
|
6
6
|
// access key invalidates every old session immediately.
|
|
7
|
-
|
|
8
7
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
9
|
-
|
|
10
8
|
export const SESSION_COOKIE = 'onboarder_session';
|
|
11
9
|
export const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
12
|
-
|
|
13
10
|
function signature(expires, accessKey) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
return createHmac('sha256', accessKey)
|
|
12
|
+
.update(`onboarder-session-v1:${expires}`)
|
|
13
|
+
.digest('base64url');
|
|
17
14
|
}
|
|
18
|
-
|
|
19
15
|
export function createSession(settings, now = Date.now()) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const key = String(settings?.accessKey || '');
|
|
17
|
+
if (!key)
|
|
18
|
+
return '';
|
|
19
|
+
const expires = Math.floor(now / 1000) + SESSION_TTL_SECONDS;
|
|
20
|
+
return `${expires}.${signature(expires, key)}`;
|
|
24
21
|
}
|
|
25
|
-
|
|
26
22
|
function cookieValue(req, name) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
const header = String(req.headers?.cookie || '');
|
|
24
|
+
for (const part of header.split(';')) {
|
|
25
|
+
const at = part.indexOf('=');
|
|
26
|
+
if (at === -1)
|
|
27
|
+
continue;
|
|
28
|
+
if (part.slice(0, at).trim() !== name)
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
return decodeURIComponent(part.slice(at + 1).trim());
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return '';
|
|
35
38
|
}
|
|
36
|
-
|
|
37
39
|
export function hasValidSession(req, settings, now = Date.now()) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
const key = String(settings?.accessKey || '');
|
|
41
|
+
const token = cookieValue(req, SESSION_COOKIE);
|
|
42
|
+
const dot = token.indexOf('.');
|
|
43
|
+
if (!key || dot < 1)
|
|
44
|
+
return false;
|
|
45
|
+
const expires = Number(token.slice(0, dot));
|
|
46
|
+
const supplied = token.slice(dot + 1);
|
|
47
|
+
if (!Number.isSafeInteger(expires) || expires <= Math.floor(now / 1000) || !supplied)
|
|
48
|
+
return false;
|
|
49
|
+
const expected = signature(expires, key);
|
|
50
|
+
const a = Buffer.from(supplied);
|
|
51
|
+
const b = Buffer.from(expected);
|
|
52
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
51
53
|
}
|
|
52
|
-
|
|
53
54
|
function requestIsSecure(req) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if (req.socket?.encrypted)
|
|
56
|
+
return true;
|
|
57
|
+
const forwarded = String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
|
|
58
|
+
return forwarded === 'https';
|
|
57
59
|
}
|
|
58
|
-
|
|
59
60
|
export function sessionCookie(req, settings, { clear = false } = {}) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
const parts = [
|
|
62
|
+
`${SESSION_COOKIE}=${clear ? '' : encodeURIComponent(createSession(settings))}`,
|
|
63
|
+
'Path=/',
|
|
64
|
+
'HttpOnly',
|
|
65
|
+
'SameSite=Strict',
|
|
66
|
+
`Max-Age=${clear ? 0 : SESSION_TTL_SECONDS}`,
|
|
67
|
+
];
|
|
68
|
+
// A direct bare-IP deployment may still be plain HTTP, where Secure would make
|
|
69
|
+
// the cookie unusable. Caddy forwards the original scheme, so domain HTTPS
|
|
70
|
+
// gets the production-safe flag automatically.
|
|
71
|
+
if (requestIsSecure(req))
|
|
72
|
+
parts.push('Secure');
|
|
73
|
+
return parts.join('; ');
|
|
72
74
|
}
|
|
75
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/server/auth.ts"],"names":[],"mappings":"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","sourcesContent":["// Browser sessions for the self-hosted access-key gate.\n//\n// The access key itself never becomes a cookie. A successful login mints an\n// opaque timestamp + HMAC using the configured key as the signing secret. The\n// browser gets an HttpOnly cookie, JavaScript cannot read it, and rotating the\n// access key invalidates every old session immediately.\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport const SESSION_COOKIE = 'onboarder_session';\nexport const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60;\n\nfunction signature(expires, accessKey) {\n return createHmac('sha256', accessKey)\n .update(`onboarder-session-v1:${expires}`)\n .digest('base64url');\n}\n\nexport function createSession(settings, now = Date.now()) {\n const key = String(settings?.accessKey || '');\n if (!key) return '';\n const expires = Math.floor(now / 1000) + SESSION_TTL_SECONDS;\n return `${expires}.${signature(expires, key)}`;\n}\n\nfunction cookieValue(req, name) {\n const header = String(req.headers?.cookie || '');\n for (const part of header.split(';')) {\n const at = part.indexOf('=');\n if (at === -1) continue;\n if (part.slice(0, at).trim() !== name) continue;\n try { return decodeURIComponent(part.slice(at + 1).trim()); } catch { return ''; }\n }\n return '';\n}\n\nexport function hasValidSession(req, settings, now = Date.now()) {\n const key = String(settings?.accessKey || '');\n const token = cookieValue(req, SESSION_COOKIE);\n const dot = token.indexOf('.');\n if (!key || dot < 1) return false;\n\n const expires = Number(token.slice(0, dot));\n const supplied = token.slice(dot + 1);\n if (!Number.isSafeInteger(expires) || expires <= Math.floor(now / 1000) || !supplied) return false;\n\n const expected = signature(expires, key);\n const a = Buffer.from(supplied);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nfunction requestIsSecure(req) {\n if (req.socket?.encrypted) return true;\n const forwarded = String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();\n return forwarded === 'https';\n}\n\nexport function sessionCookie(req, settings, { clear = false }: Record<string, any> = {}) {\n const parts = [\n `${SESSION_COOKIE}=${clear ? '' : encodeURIComponent(createSession(settings))}`,\n 'Path=/',\n 'HttpOnly',\n 'SameSite=Strict',\n `Max-Age=${clear ? 0 : SESSION_TTL_SECONDS}`,\n ];\n // A direct bare-IP deployment may still be plain HTTP, where Secure would make\n // the cookie unusable. Caddy forwards the original scheme, so domain HTTPS\n // gets the production-safe flag automatically.\n if (requestIsSecure(req)) parts.push('Secure');\n return parts.join('; ');\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Settings, SettingsInput, ServerUrls } from '../shared/contracts.js';
|
|
2
|
+
export declare const CONFIG_VERSION = 2;
|
|
3
|
+
export declare const LOOPBACK_HOSTS: Set<string>;
|
|
4
|
+
export declare const DEFAULT_SETTINGS: Readonly<Settings>;
|
|
5
|
+
export declare function configHome(env?: NodeJS.ProcessEnv): string;
|
|
6
|
+
export declare function configPath(env?: NodeJS.ProcessEnv): string;
|
|
7
|
+
export declare function isLoopbackHost(host: any): boolean;
|
|
8
|
+
export declare function normalizeSettings(value?: SettingsInput): Settings;
|
|
9
|
+
export declare function generateAccessKey(): string;
|
|
10
|
+
export declare function serverUrls(value?: SettingsInput): ServerUrls;
|
|
11
|
+
export declare function maskAccessKey(key: any): string;
|
|
12
|
+
export declare function browserUrl(settings: any): string;
|
|
13
|
+
export declare function publicSettings(value?: SettingsInput): {
|
|
14
|
+
version: number;
|
|
15
|
+
mode: "local" | "self-hosted";
|
|
16
|
+
host: string;
|
|
17
|
+
port: number;
|
|
18
|
+
domain: string;
|
|
19
|
+
https: boolean;
|
|
20
|
+
autoOpen: boolean;
|
|
21
|
+
hasAccessKey: boolean;
|
|
22
|
+
accessKeyMasked: string;
|
|
23
|
+
account: import("../shared/contracts.js").AccountSettings;
|
|
24
|
+
tunnel: {
|
|
25
|
+
cloudflare: boolean;
|
|
26
|
+
tailscale: boolean;
|
|
27
|
+
};
|
|
28
|
+
urls: ServerUrls;
|
|
29
|
+
security: {
|
|
30
|
+
remoteAuth: boolean;
|
|
31
|
+
loopback: boolean;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export declare function allowedHosts(value?: SettingsInput): unknown[];
|
|
35
|
+
export declare function accessKeysMatch(expected: any, actual: any): boolean;
|
|
36
|
+
export declare function bearerToken(req: any): string;
|
|
37
|
+
export declare function requestIsLoopback(req: any): boolean;
|
|
38
|
+
export declare function authReason(req: any, settings?: SettingsInput): "A valid access key is required." | "Self-hosted mode has no access key configured.";
|
|
39
|
+
export declare function configExists(file?: string): Promise<boolean>;
|
|
40
|
+
export declare function readSettings(file?: string): Promise<Settings>;
|
|
41
|
+
export declare function writeSettings(value: any, file?: string): Promise<Settings>;
|
|
42
|
+
export declare function updateSettings(patch?: SettingsInput, file?: string): Promise<Settings>;
|