@kutrion/cli 1.2.9 → 1.2.10
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 +971 -732
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,8 +42,10 @@ var init_define_KT_HUB_FILES = __esm({
|
|
|
42
42
|
# ensure-hub.sh compare A B | verify-file FILE SHA512_B64
|
|
43
43
|
#
|
|
44
44
|
# Exit: 0 present, installed or skipped by policy; 10 refused; 11 cannot install here; 12 fetch failed.
|
|
45
|
+
# Every run that gets past argument parsing ends with one "ensure-hub: finished exit=N" line, so a caller can wait on it.
|
|
45
46
|
# Environment: KT_NO_HUB=1 skips; KT_HUB_FEED_BASE (https or file) replaces the feed;
|
|
46
|
-
# KT_HUB_ALLOW_UNSIGNED=1 accepts a build with no publisher identity; KT_HUB_APPS_DIR replaces /Applications
|
|
47
|
+
# KT_HUB_ALLOW_UNSIGNED=1 accepts a build with no publisher identity; KT_HUB_APPS_DIR replaces /Applications;
|
|
48
|
+
# KT_HUB_FETCH_BUDGET seconds bounds one download across its resumed attempts (default 1200).
|
|
47
49
|
set -u
|
|
48
50
|
|
|
49
51
|
SELF_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
@@ -51,7 +53,7 @@ CONF=\${KT_HUB_CONF:-$SELF_DIR/hub.conf}
|
|
|
51
53
|
APP_ID=com.kutrion.desktop
|
|
52
54
|
PKG_NAME=kutrion
|
|
53
55
|
|
|
54
|
-
log() { printf 'ensure-hub: %s\\n' "$*"; }
|
|
56
|
+
log() { printf 'ensure-hub: %s %s\\n' "$(date -u +%H:%M:%S)" "$*"; }
|
|
55
57
|
refuse() { log "refused, nothing installed: $*"; exit 10; }
|
|
56
58
|
cannot() { log "skipped: $*"; exit 11; }
|
|
57
59
|
|
|
@@ -99,15 +101,49 @@ verify_file() {
|
|
|
99
101
|
[ "$got" = "$2" ] || return 10
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
file_size() { wc -c < "$1" 2>/dev/null | tr -d ' ' || echo 0; }
|
|
105
|
+
|
|
106
|
+
# A download that stalls or drops resumes from the bytes already on disk instead of starting over; the whole
|
|
107
|
+
# download shares one budget so a dead link ends the run rather than holding the install lock.
|
|
102
108
|
fetch() {
|
|
103
109
|
case "$1" in
|
|
104
|
-
file://*) command cp "\${1#file://}" "$2" 2>/dev/null ;;
|
|
105
|
-
https://*)
|
|
106
|
-
if command -v curl >/dev/null 2>&1; then curl -fsSL --proto '=https' --tlsv1.2 --retry 2 --connect-timeout 15 --max-time 900 -o "$2" "$1"
|
|
107
|
-
elif command -v wget >/dev/null 2>&1; then wget -q --https-only -O "$2" "$1"
|
|
108
|
-
else return 1; fi ;;
|
|
110
|
+
file://*) command cp "\${1#file://}" "$2" 2>/dev/null; return ;;
|
|
111
|
+
https://*) ;;
|
|
109
112
|
*) return 1 ;;
|
|
110
113
|
esac
|
|
114
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
115
|
+
command -v wget >/dev/null 2>&1 || return 1
|
|
116
|
+
wget -q --https-only -c -O "$2" "$1"; return
|
|
117
|
+
fi
|
|
118
|
+
budget=\${KT_HUB_FETCH_BUDGET:-1200}; case "$budget" in ''|*[!0-9]*) budget=1200 ;; esac
|
|
119
|
+
t0=$(date +%s); n=0
|
|
120
|
+
while :; do
|
|
121
|
+
left=$((budget - ($(date +%s) - t0)))
|
|
122
|
+
[ "$left" -gt 0 ] || { log "fetch budget of \${budget}s used up at $(file_size "$2") bytes"; return 1; }
|
|
123
|
+
n=$((n + 1))
|
|
124
|
+
curl -fsS --proto '=https' --tlsv1.2 -C - --connect-timeout 15 --max-time "$left" --speed-time 60 --speed-limit 1024 -o "$2" "$1" && return 0
|
|
125
|
+
rc=$?
|
|
126
|
+
log "fetch attempt $n stopped (curl exit $rc) at $(file_size "$2") bytes"
|
|
127
|
+
[ "$n" -lt 20 ] || return 1
|
|
128
|
+
sleep 5
|
|
129
|
+
done
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# fetch with a begin line, a byte count every 30 s while it runs, and an end line.
|
|
133
|
+
fetch_logged() {
|
|
134
|
+
log "fetch begin $1"
|
|
135
|
+
t=$(date +%s)
|
|
136
|
+
case "$1" in
|
|
137
|
+
https://*)
|
|
138
|
+
parent=$$
|
|
139
|
+
( while sleep 30; do kill -0 "$parent" 2>/dev/null || exit 0; log "fetching, $(file_size "$2") bytes so far"; done ) &
|
|
140
|
+
tick=$!
|
|
141
|
+
fetch "$1" "$2"; rc=$?
|
|
142
|
+
kill "$tick" 2>/dev/null; wait "$tick" 2>/dev/null ;;
|
|
143
|
+
*) fetch "$1" "$2"; rc=$? ;;
|
|
144
|
+
esac
|
|
145
|
+
log "fetch end rc=$rc bytes=$(file_size "$2") seconds=$(( $(date +%s) - t ))"
|
|
146
|
+
return $rc
|
|
111
147
|
}
|
|
112
148
|
|
|
113
149
|
# A feed file name is a bare name; anything with a path, scheme or whitespace could point off the feed origin.
|
|
@@ -194,7 +230,7 @@ retry() {
|
|
|
194
230
|
while :; do
|
|
195
231
|
"$@" && return 0
|
|
196
232
|
[ "$DEFER" -eq 1 ] && [ "$n" -lt 36 ] || return 1
|
|
197
|
-
n=$((n + 1)); sleep 5
|
|
233
|
+
n=$((n + 1)); log "install attempt $n failed, retrying in 5s"; sleep 5
|
|
198
234
|
done
|
|
199
235
|
}
|
|
200
236
|
|
|
@@ -266,6 +302,8 @@ verify_linux_package() {
|
|
|
266
302
|
}
|
|
267
303
|
|
|
268
304
|
cleanup() {
|
|
305
|
+
rc=$?
|
|
306
|
+
[ "$STARTED" -eq 1 ] && log "finished exit=$rc"
|
|
269
307
|
[ "$LOCK_HELD" -eq 1 ] && rm -r -f "$LOCK"
|
|
270
308
|
# A run staged by the app lives in its own temp directory; nothing else is ever removed.
|
|
271
309
|
case "$(basename "$SELF_DIR")" in kt-hub-run-*) rm -r -f "$SELF_DIR" ;; esac
|
|
@@ -275,7 +313,7 @@ cleanup() {
|
|
|
275
313
|
}
|
|
276
314
|
|
|
277
315
|
main() {
|
|
278
|
-
FIRST_RUN=0; DEFER=0; FORCE=0; VERIFY_ONLY=0; OS_ARG=""; WORK=""; MOUNT=""; LOCK=""; LOCK_HELD=0
|
|
316
|
+
FIRST_RUN=0; DEFER=0; FORCE=0; VERIFY_ONLY=0; OS_ARG=""; WORK=""; MOUNT=""; LOCK=""; LOCK_HELD=0; STARTED=0
|
|
279
317
|
trap cleanup EXIT
|
|
280
318
|
while [ $# -gt 0 ]; do
|
|
281
319
|
case "$1" in
|
|
@@ -291,6 +329,7 @@ main() {
|
|
|
291
329
|
shift
|
|
292
330
|
done
|
|
293
331
|
|
|
332
|
+
STARTED=1
|
|
294
333
|
[ "\${KT_NO_HUB:-}" = 1 ] && { log "skipped: KT_NO_HUB=1"; exit 0; }
|
|
295
334
|
MIN=$(conf_get MIN_HUB_VERSION)
|
|
296
335
|
[ -n "$MIN" ] || refuse "no MIN_HUB_VERSION in $CONF"
|
|
@@ -337,6 +376,7 @@ main() {
|
|
|
337
376
|
WORK=$(mktemp -d "\${TMPDIR:-/tmp}/kt-hub.XXXXXX") || cannot "no temporary directory"
|
|
338
377
|
|
|
339
378
|
case "$OS" in mac) chan=latest-mac.yml ;; *) chan=latest-linux.yml ;; esac
|
|
379
|
+
log "start, minimum $MIN, kind $KIND"
|
|
340
380
|
fetch "$FEED/$chan" "$WORK/feed.yml" || { log "could not fetch $FEED/$chan"; exit 12; }
|
|
341
381
|
fv=$(feed_version "$WORK/feed.yml")
|
|
342
382
|
c=$(ver_cmp "\${fv:-0}" "$MIN"); [ "$c" = 0 ] || [ "$c" = 1 ] || refuse "the feed offers \${fv:-no version}, below the minimum $MIN"
|
|
@@ -344,7 +384,8 @@ main() {
|
|
|
344
384
|
[ -n "$pair" ] || refuse "the feed lists no $KIND package for $(uname -m)"
|
|
345
385
|
name=\${pair% *}; want=\${pair##* }
|
|
346
386
|
bare_name "$name" || refuse "the feed names '$name', which is not a plain file name"
|
|
347
|
-
|
|
387
|
+
fetch_logged "$FEED/$name" "$WORK/$name" || { log "could not fetch $FEED/$name"; exit 12; }
|
|
388
|
+
log "verify sha512"
|
|
348
389
|
verify_file "$WORK/$name" "$want" || refuse "sha512 of $name does not match the feed"
|
|
349
390
|
log "sha512 of $name matches the feed"
|
|
350
391
|
|
|
@@ -369,6 +410,7 @@ main() {
|
|
|
369
410
|
[ "$VERIFY_ONLY" -eq 1 ] && { log "verified $name, not installing (--verify-only)"; exit 0; }
|
|
370
411
|
# The package manager's sandbox user must be able to read the file.
|
|
371
412
|
chmod 644 "$WORK/$name"; chmod 755 "$WORK"
|
|
413
|
+
log "install $KIND"
|
|
372
414
|
if [ "$KIND" = deb ]; then retry install_deb "$WORK/$name"; else retry install_rpm "$WORK/$name"; fi || { log "the package manager failed"; exit 12; }
|
|
373
415
|
log "installed Kutrion $fv" ;;
|
|
374
416
|
esac
|
|
@@ -382,8 +424,11 @@ main "$@"
|
|
|
382
424
|
#
|
|
383
425
|
# ensure-hub.ps1 [-FirstRun] [-Force] [-VerifyOnly]
|
|
384
426
|
# Exit: 0 present, installed or skipped by policy; 10 refused; 11 cannot install here; 12 fetch or install failed.
|
|
427
|
+
# Every run that gets past the dot-source guard ends with one "ensure-hub: HH:mm:ss finished exit=N" line, written to the output and
|
|
428
|
+
# to the log file (%LOCALAPPDATA%\\Kutrion\\ensure-hub.log, or KT_HUB_LOG), so a caller that started it detached can wait on it.
|
|
385
429
|
# Environment: KT_NO_HUB=1 skips; KT_HUB_FEED_BASE (https or file) replaces the feed;
|
|
386
|
-
# KT_HUB_ALLOW_UNSIGNED=1 accepts a build with no publisher identity
|
|
430
|
+
# KT_HUB_ALLOW_UNSIGNED=1 accepts a build with no publisher identity; KT_HUB_FETCH_BUDGET seconds bounds one
|
|
431
|
+
# download across its resumed attempts (default 1200).
|
|
387
432
|
param(
|
|
388
433
|
[switch]$FirstRun,
|
|
389
434
|
[switch]$Force,
|
|
@@ -395,9 +440,23 @@ $ErrorActionPreference = 'Stop'
|
|
|
395
440
|
$ProgressPreference = 'SilentlyContinue'
|
|
396
441
|
$AppId = 'com.kutrion.desktop'
|
|
397
442
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
443
|
+
$script:ExitCode = 1
|
|
444
|
+
$script:LogFile = if ($env:KT_HUB_LOG) { $env:KT_HUB_LOG } elseif ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA 'Kutrion\\ensure-hub.log' } else { '' }
|
|
445
|
+
|
|
446
|
+
# Host and file, not the pipeline: functions that return a value must not carry log lines with it.
|
|
447
|
+
function Write-Hub([string]$m) {
|
|
448
|
+
$line = 'ensure-hub: {0} {1}' -f (Get-Date -Format 'HH:mm:ss'), $m
|
|
449
|
+
Write-Host $line
|
|
450
|
+
if ($script:LogFile) {
|
|
451
|
+
try {
|
|
452
|
+
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $script:LogFile) | Out-Null
|
|
453
|
+
Add-Content -LiteralPath $script:LogFile -Value $line -Encoding ASCII
|
|
454
|
+
} catch { $script:LogFile = '' }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function Exit-Hub([int]$Code) { $script:ExitCode = $Code; exit $Code }
|
|
458
|
+
function Stop-Refused([string]$m) { Write-Hub "refused, nothing installed: $m"; Exit-Hub 10 }
|
|
459
|
+
function Stop-Cannot([string]$m) { Write-Hub "skipped: $m"; Exit-Hub 11 }
|
|
401
460
|
|
|
402
461
|
function Get-ConfValue([string]$Name) {
|
|
403
462
|
if (-not (Test-Path -LiteralPath $Conf)) { return '' }
|
|
@@ -482,17 +541,94 @@ function Get-InstalledHub {
|
|
|
482
541
|
return $null
|
|
483
542
|
}
|
|
484
543
|
|
|
544
|
+
function Get-FileLength([string]$Path) { if (Test-Path -LiteralPath $Path) { return (Get-Item -LiteralPath $Path).Length } else { return 0L } }
|
|
545
|
+
|
|
546
|
+
# One HTTP attempt. Asks for the bytes after what is on disk; a server that answers 200 anyway sends the whole file, so the
|
|
547
|
+
# file is rewritten from the start. Stops when no byte arrives for 60 s or the shared deadline passes.
|
|
548
|
+
function Receive-Range([string]$Url, [string]$Dest, [datetime]$Deadline) {
|
|
549
|
+
$have = Get-FileLength $Dest
|
|
550
|
+
$req = [Net.HttpWebRequest]::Create($Url)
|
|
551
|
+
$req.Timeout = 30000
|
|
552
|
+
$req.ReadWriteTimeout = 60000
|
|
553
|
+
if ($have -gt 0) { $req.AddRange([long]$have) }
|
|
554
|
+
try { $resp = $req.GetResponse() }
|
|
555
|
+
catch [Net.WebException] {
|
|
556
|
+
# 416: the bytes on disk are not a prefix of what the server holds; start over rather than loop on it.
|
|
557
|
+
if ($_.Exception.Response -and [int]$_.Exception.Response.StatusCode -eq 416 -and $have -gt 0) { Remove-Item -LiteralPath $Dest -Force; $_.Exception.Response.Close(); return $false }
|
|
558
|
+
throw
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
if ($resp.ResponseUri.Scheme -ne 'https') { throw "the feed redirected to a non-https address" }
|
|
562
|
+
$partial = ([int]$resp.StatusCode -eq 206)
|
|
563
|
+
$total = -1L
|
|
564
|
+
if ($partial) { if ($resp.Headers['Content-Range'] -match '/(\\d+)$') { $total = [long]$Matches[1] } }
|
|
565
|
+
elseif ($resp.ContentLength -ge 0) { $total = $resp.ContentLength }
|
|
566
|
+
$mode = if ($partial) { [IO.FileMode]::Append } else { [IO.FileMode]::Create }
|
|
567
|
+
$in = $resp.GetResponseStream()
|
|
568
|
+
$out = New-Object IO.FileStream($Dest, $mode, [IO.FileAccess]::Write)
|
|
569
|
+
try {
|
|
570
|
+
$buf = New-Object byte[] 65536
|
|
571
|
+
$clock = [Diagnostics.Stopwatch]::StartNew()
|
|
572
|
+
$lastTick = 0; $lastData = 0
|
|
573
|
+
while ($true) {
|
|
574
|
+
$task = $in.ReadAsync($buf, 0, $buf.Length)
|
|
575
|
+
while (-not $task.Wait(1000)) {
|
|
576
|
+
if ($clock.Elapsed.TotalSeconds - $lastData -gt 60) { throw 'no data for 60 s' }
|
|
577
|
+
if ((Get-Date) -gt $Deadline) { throw 'the time budget ended' }
|
|
578
|
+
if ($clock.Elapsed.TotalSeconds - $lastTick -ge 30) { $lastTick = $clock.Elapsed.TotalSeconds; Write-Hub "fetching, $(Get-FileLength $Dest) bytes so far" }
|
|
579
|
+
}
|
|
580
|
+
$n = $task.Result
|
|
581
|
+
if ($n -le 0) { break }
|
|
582
|
+
$out.Write($buf, 0, $n)
|
|
583
|
+
$lastData = $clock.Elapsed.TotalSeconds
|
|
584
|
+
if ((Get-Date) -gt $Deadline) { throw 'the time budget ended' }
|
|
585
|
+
if ($lastData - $lastTick -ge 30) { $lastTick = $lastData; $out.Flush(); Write-Hub "fetching, $(Get-FileLength $Dest) bytes so far" }
|
|
586
|
+
}
|
|
587
|
+
} finally { $out.Dispose(); $in.Dispose() }
|
|
588
|
+
$now = Get-FileLength $Dest
|
|
589
|
+
if ($total -ge 0 -and $now -ne $total) { throw "the download stopped at $now of $total bytes" }
|
|
590
|
+
return $true
|
|
591
|
+
} finally { $resp.Close() }
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
# A download that stalls or drops resumes from the bytes already on disk instead of starting over; the whole
|
|
595
|
+
# download shares one budget so a dead link ends the run rather than holding the install lock.
|
|
485
596
|
function Get-Fetched([string]$Url, [string]$Dest) {
|
|
486
597
|
if ($Url -like 'file://*') { Copy-Item -LiteralPath ([Uri]$Url).LocalPath -Destination $Dest -Force; return }
|
|
487
598
|
if ($Url -notlike 'https://*') { throw "only https and file feeds are used: $Url" }
|
|
488
599
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
489
|
-
|
|
600
|
+
$budget = 1200
|
|
601
|
+
[void][int]::TryParse($env:KT_HUB_FETCH_BUDGET, [ref]$budget)
|
|
602
|
+
if ($budget -le 0) { $budget = 1200 }
|
|
603
|
+
$deadline = (Get-Date).AddSeconds($budget)
|
|
604
|
+
$attempt = 0
|
|
605
|
+
while ($true) {
|
|
606
|
+
$attempt++
|
|
607
|
+
try { if (Receive-Range $Url $Dest $deadline) { return } }
|
|
608
|
+
catch {
|
|
609
|
+
Write-Hub "fetch attempt $attempt stopped ($($_.Exception.Message)) at $(Get-FileLength $Dest) bytes"
|
|
610
|
+
if ((Get-Date) -gt $deadline) { throw "fetch budget of \${budget}s used up" }
|
|
611
|
+
if ($attempt -ge 20) { throw 'too many failed attempts' }
|
|
612
|
+
Start-Sleep -Seconds 5
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
# Get-Fetched with a begin line and an end line; the byte counts every 30 s come from the transfer itself.
|
|
618
|
+
function Get-FetchedLogged([string]$Url, [string]$Dest) {
|
|
619
|
+
Write-Hub "fetch begin $Url"
|
|
620
|
+
$t = Get-Date; $ok = $false
|
|
621
|
+
try { Get-Fetched $Url $Dest; $ok = $true } finally {
|
|
622
|
+
Write-Hub ('fetch end rc={0} bytes={1} seconds={2}' -f $(if ($ok) { 0 } else { 1 }), (Get-FileLength $Dest), [int]((Get-Date) - $t).TotalSeconds)
|
|
623
|
+
}
|
|
490
624
|
}
|
|
491
625
|
|
|
492
626
|
# Dot-sourcing loads the functions above and runs nothing, so the checks can be driven one by one.
|
|
493
627
|
if ($MyInvocation.InvocationName -eq '.') { return }
|
|
494
628
|
|
|
495
|
-
|
|
629
|
+
try {
|
|
630
|
+
Write-Hub 'start'
|
|
631
|
+
if ($env:KT_NO_HUB -eq '1') { Write-Hub 'skipped: KT_NO_HUB=1'; Exit-Hub 0 }
|
|
496
632
|
|
|
497
633
|
$min = Get-ConfValue 'MIN_HUB_VERSION'
|
|
498
634
|
if (-not $min) { Stop-Refused "no MIN_HUB_VERSION in $Conf" }
|
|
@@ -500,14 +636,15 @@ $feed = if ($env:KT_HUB_FEED_BASE) { $env:KT_HUB_FEED_BASE } else { Get-ConfValu
|
|
|
500
636
|
if (-not $feed) { Stop-Refused 'no feed configured' }
|
|
501
637
|
$publisher = Get-ConfValue 'HUB_WIN_PUBLISHER'
|
|
502
638
|
$allowUnsigned = ($env:KT_HUB_ALLOW_UNSIGNED -eq '1') -or ((Get-ConfValue 'HUB_ALLOW_UNSIGNED') -eq '1')
|
|
639
|
+
Write-Hub "minimum $min"
|
|
503
640
|
|
|
504
641
|
if (-not $Force) {
|
|
505
642
|
$cur = Get-InstalledHub
|
|
506
643
|
if ($null -ne $cur) {
|
|
507
|
-
if ($cur -eq 'unknown') { Write-Hub 'present, version unreadable, left alone';
|
|
644
|
+
if ($cur -eq 'unknown') { Write-Hub 'present, version unreadable, left alone'; Exit-Hub 0 }
|
|
508
645
|
$c = Compare-HubVersion $cur $min
|
|
509
|
-
if ($null -eq $c) { Write-Hub "present, version '$cur' not comparable, left alone";
|
|
510
|
-
if ($c -ge 0) { Write-Hub "present at $cur (minimum $min), nothing to do";
|
|
646
|
+
if ($null -eq $c) { Write-Hub "present, version '$cur' not comparable, left alone"; Exit-Hub 0 }
|
|
647
|
+
if ($c -ge 0) { Write-Hub "present at $cur (minimum $min), nothing to do"; Exit-Hub 0 }
|
|
511
648
|
Write-Hub "present at $cur, below $min"
|
|
512
649
|
} else {
|
|
513
650
|
Write-Hub 'absent'
|
|
@@ -531,13 +668,13 @@ $mutex = New-Object Threading.Mutex($false, 'Local\\KutrionEnsureHub')
|
|
|
531
668
|
$held = $false
|
|
532
669
|
if (-not $VerifyOnly) {
|
|
533
670
|
try { $held = $mutex.WaitOne(0) } catch [Threading.AbandonedMutexException] { $held = $true }
|
|
534
|
-
if (-not $held) { Write-Hub 'another install is running';
|
|
671
|
+
if (-not $held) { Write-Hub 'another install is running'; Exit-Hub 0 }
|
|
535
672
|
}
|
|
536
673
|
|
|
537
674
|
$work = Join-Path ([IO.Path]::GetTempPath()) ('kt-hub-' + [guid]::NewGuid().ToString('N'))
|
|
538
675
|
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
|
539
676
|
try {
|
|
540
|
-
try { Get-
|
|
677
|
+
try { Get-FetchedLogged "$feed/latest.yml" (Join-Path $work 'feed.yml') } catch { Write-Hub "could not fetch $feed/latest.yml"; Exit-Hub 12 }
|
|
541
678
|
$text = Get-Content -LiteralPath (Join-Path $work 'feed.yml') -Raw
|
|
542
679
|
$fv = Get-FeedVersion $text
|
|
543
680
|
$c = if ($fv) { Compare-HubVersion $fv $min } else { $null }
|
|
@@ -546,7 +683,8 @@ try {
|
|
|
546
683
|
if (-not $entry) { Stop-Refused 'the feed lists no installer' }
|
|
547
684
|
if (-not (Test-BareName $entry.Name)) { Stop-Refused "the feed names '$($entry.Name)', which is not a plain file name" }
|
|
548
685
|
$exe = Join-Path $work $entry.Name
|
|
549
|
-
try { Get-
|
|
686
|
+
try { Get-FetchedLogged "$feed/$($entry.Name)" $exe } catch { Write-Hub "could not fetch $feed/$($entry.Name)"; Exit-Hub 12 }
|
|
687
|
+
Write-Hub 'verify sha512'
|
|
550
688
|
if (-not (Test-FileHash $exe $entry.Sha512)) { Stop-Refused "sha512 of $($entry.Name) does not match the feed" }
|
|
551
689
|
Write-Hub "sha512 of $($entry.Name) matches the feed"
|
|
552
690
|
|
|
@@ -561,23 +699,28 @@ try {
|
|
|
561
699
|
Stop-Refused 'no publisher identity is configured and unsigned builds are not allowed'
|
|
562
700
|
}
|
|
563
701
|
|
|
564
|
-
if ($VerifyOnly) { Write-Hub "verified $($entry.Name), not installing (-VerifyOnly)";
|
|
702
|
+
if ($VerifyOnly) { Write-Hub "verified $($entry.Name), not installing (-VerifyOnly)"; Exit-Hub 0 }
|
|
565
703
|
|
|
566
704
|
# Waits on this pid only: Start-Process -Wait in Windows PowerShell 5.1 waits on the whole process tree, so an
|
|
567
705
|
# installer that starts the app never returns.
|
|
706
|
+
Write-Hub 'install'
|
|
568
707
|
$p = [Diagnostics.Process]::Start((New-Object Diagnostics.ProcessStartInfo -Property @{ FileName = $exe; Arguments = '/S /currentuser'; UseShellExecute = $false }))
|
|
569
|
-
if (-not $p.WaitForExit(600000)) { & taskkill.exe /PID $p.Id /T /F | Out-Null; Write-Hub 'the installer did not finish in 10 minutes';
|
|
570
|
-
if ($p.ExitCode -ne 0) { Write-Hub "the installer exited $($p.ExitCode)";
|
|
708
|
+
if (-not $p.WaitForExit(600000)) { & taskkill.exe /PID $p.Id /T /F | Out-Null; Write-Hub 'the installer did not finish in 10 minutes'; Exit-Hub 12 }
|
|
709
|
+
if ($p.ExitCode -ne 0) { Write-Hub "the installer exited $($p.ExitCode)"; Exit-Hub 12 }
|
|
571
710
|
$after = Get-InstalledHub
|
|
572
|
-
if ($null -eq $after) { Write-Hub 'the installer finished but no Hub is registered';
|
|
711
|
+
if ($null -eq $after) { Write-Hub 'the installer finished but no Hub is registered'; Exit-Hub 12 }
|
|
573
712
|
Write-Hub "installed Kutrion $after"
|
|
574
|
-
|
|
713
|
+
Exit-Hub 0
|
|
575
714
|
} finally {
|
|
576
715
|
Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue
|
|
577
716
|
if ($held) { $mutex.ReleaseMutex() }
|
|
578
717
|
# A run staged by the app lives in its own temp directory; nothing else is ever removed.
|
|
579
718
|
if ((Split-Path -Leaf $PSScriptRoot) -like 'kt-hub-run-*') { Remove-Item -LiteralPath $PSScriptRoot -Recurse -Force -ErrorAction SilentlyContinue }
|
|
580
719
|
}
|
|
720
|
+
} finally {
|
|
721
|
+
Write-Hub "finished exit=$($script:ExitCode)"
|
|
722
|
+
}
|
|
723
|
+
exit $script:ExitCode
|
|
581
724
|
`, "hub.conf": "# Read by ensure-hub.sh and ensure-hub.ps1, and shipped beside them in every app build.\nMIN_HUB_VERSION=1.2.9\nFEED_BASE=https://kutrion.com/updates\n# Authenticode subject common name the Hub installer must carry. Empty until a certificate exists (#973).\nHUB_WIN_PUBLISHER=\n# Apple Team ID the Hub bundle must be signed with. Empty until the Developer ID exists (#973).\nHUB_MAC_TEAM_ID=\n# 0 installs nothing that cannot be tied to a publisher identity above. 1 until code signing lands (#973, #3295). On Windows this equals the NSIS updater trust: sha512 over TLS from our own HTTPS feed. On macOS and Linux it is sha512 over TLS only, weaker than the macOS updater. Owner accepted this on #3283. KT_HUB_ALLOW_UNSIGNED=1 overrides per run.\nHUB_ALLOW_UNSIGNED=1\n" };
|
|
582
725
|
}
|
|
583
726
|
});
|
|
@@ -2840,7 +2983,7 @@ var require_websocket = __commonJS({
|
|
|
2840
2983
|
var http = require("http");
|
|
2841
2984
|
var net = require("net");
|
|
2842
2985
|
var tls = require("tls");
|
|
2843
|
-
var { randomBytes:
|
|
2986
|
+
var { randomBytes: randomBytes6, createHash: createHash2 } = require("crypto");
|
|
2844
2987
|
var { Duplex, Readable } = require("stream");
|
|
2845
2988
|
var { URL: URL2 } = require("url");
|
|
2846
2989
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -3378,7 +3521,7 @@ var require_websocket = __commonJS({
|
|
|
3378
3521
|
}
|
|
3379
3522
|
}
|
|
3380
3523
|
const defaultPort = isSecure ? 443 : 80;
|
|
3381
|
-
const key =
|
|
3524
|
+
const key = randomBytes6(16).toString("base64");
|
|
3382
3525
|
const request = isSecure ? https.request : http.request;
|
|
3383
3526
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
3384
3527
|
let perMessageDeflate;
|
|
@@ -3508,7 +3651,7 @@ var require_websocket = __commonJS({
|
|
|
3508
3651
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
3509
3652
|
return;
|
|
3510
3653
|
}
|
|
3511
|
-
const digest =
|
|
3654
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
3512
3655
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
3513
3656
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
3514
3657
|
return;
|
|
@@ -3880,7 +4023,7 @@ var require_websocket_server = __commonJS({
|
|
|
3880
4023
|
var EventEmitter = require("events");
|
|
3881
4024
|
var http = require("http");
|
|
3882
4025
|
var { Duplex } = require("stream");
|
|
3883
|
-
var { createHash:
|
|
4026
|
+
var { createHash: createHash2 } = require("crypto");
|
|
3884
4027
|
var extension2 = require_extension();
|
|
3885
4028
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3886
4029
|
var subprotocol2 = require_subprotocol();
|
|
@@ -4187,7 +4330,7 @@ var require_websocket_server = __commonJS({
|
|
|
4187
4330
|
);
|
|
4188
4331
|
}
|
|
4189
4332
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
4190
|
-
const digest =
|
|
4333
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
4191
4334
|
const headers = [
|
|
4192
4335
|
"HTTP/1.1 101 Switching Protocols",
|
|
4193
4336
|
"Upgrade: websocket",
|
|
@@ -8777,25 +8920,25 @@ var require_data_url = __commonJS({
|
|
|
8777
8920
|
function isHTTPWhiteSpace(char) {
|
|
8778
8921
|
return char === 13 || char === 10 || char === 9 || char === 32;
|
|
8779
8922
|
}
|
|
8780
|
-
function removeHTTPWhitespace(
|
|
8781
|
-
return removeChars(
|
|
8923
|
+
function removeHTTPWhitespace(str8, leading = true, trailing = true) {
|
|
8924
|
+
return removeChars(str8, leading, trailing, isHTTPWhiteSpace);
|
|
8782
8925
|
}
|
|
8783
8926
|
function isASCIIWhitespace(char) {
|
|
8784
8927
|
return char === 13 || char === 10 || char === 9 || char === 12 || char === 32;
|
|
8785
8928
|
}
|
|
8786
|
-
function removeASCIIWhitespace(
|
|
8787
|
-
return removeChars(
|
|
8929
|
+
function removeASCIIWhitespace(str8, leading = true, trailing = true) {
|
|
8930
|
+
return removeChars(str8, leading, trailing, isASCIIWhitespace);
|
|
8788
8931
|
}
|
|
8789
|
-
function removeChars(
|
|
8932
|
+
function removeChars(str8, leading, trailing, predicate) {
|
|
8790
8933
|
let lead = 0;
|
|
8791
|
-
let trail =
|
|
8934
|
+
let trail = str8.length - 1;
|
|
8792
8935
|
if (leading) {
|
|
8793
|
-
while (lead <
|
|
8936
|
+
while (lead < str8.length && predicate(str8.charCodeAt(lead))) lead++;
|
|
8794
8937
|
}
|
|
8795
8938
|
if (trailing) {
|
|
8796
|
-
while (trail > 0 && predicate(
|
|
8939
|
+
while (trail > 0 && predicate(str8.charCodeAt(trail))) trail--;
|
|
8797
8940
|
}
|
|
8798
|
-
return lead === 0 && trail ===
|
|
8941
|
+
return lead === 0 && trail === str8.length - 1 ? str8 : str8.slice(lead, trail + 1);
|
|
8799
8942
|
}
|
|
8800
8943
|
function isomorphicDecode(input) {
|
|
8801
8944
|
const length = input.length;
|
|
@@ -10714,7 +10857,7 @@ var require_body = __commonJS({
|
|
|
10714
10857
|
const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, "0")}`;
|
|
10715
10858
|
const prefix = `--${boundary}\r
|
|
10716
10859
|
Content-Disposition: form-data`;
|
|
10717
|
-
const escape = (
|
|
10860
|
+
const escape = (str8) => str8.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22");
|
|
10718
10861
|
const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, "\r\n");
|
|
10719
10862
|
const blobParts = [];
|
|
10720
10863
|
const rn = new Uint8Array([13, 10]);
|
|
@@ -17294,13 +17437,13 @@ var require_stringifyComment = __commonJS({
|
|
|
17294
17437
|
"node_modules/yaml/dist/stringify/stringifyComment.js"(exports2) {
|
|
17295
17438
|
"use strict";
|
|
17296
17439
|
init_define_KT_HUB_FILES();
|
|
17297
|
-
var stringifyComment = (
|
|
17440
|
+
var stringifyComment = (str8) => str8.replace(/^(?!$)(?: $)?/gm, "#");
|
|
17298
17441
|
function indentComment(comment, indent) {
|
|
17299
17442
|
if (/^\n+$/.test(comment))
|
|
17300
17443
|
return comment.substring(1);
|
|
17301
17444
|
return indent ? comment.replace(/^(?! *$)/gm, indent) : comment;
|
|
17302
17445
|
}
|
|
17303
|
-
var lineComment = (
|
|
17446
|
+
var lineComment = (str8, indent, comment) => str8.endsWith("\n") ? indentComment(comment, indent) : comment.includes("\n") ? "\n" + indentComment(comment, indent) : (str8.endsWith(" ") ? "" : " ") + comment;
|
|
17304
17447
|
exports2.indentComment = indentComment;
|
|
17305
17448
|
exports2.lineComment = lineComment;
|
|
17306
17449
|
exports2.stringifyComment = stringifyComment;
|
|
@@ -17456,16 +17599,16 @@ var require_stringifyString = __commonJS({
|
|
|
17456
17599
|
lineWidth: ctx.options.lineWidth,
|
|
17457
17600
|
minContentWidth: ctx.options.minContentWidth
|
|
17458
17601
|
});
|
|
17459
|
-
var containsDocumentMarker = (
|
|
17460
|
-
function lineLengthOverLimit(
|
|
17602
|
+
var containsDocumentMarker = (str8) => /^(%|---|\.\.\.)/m.test(str8);
|
|
17603
|
+
function lineLengthOverLimit(str8, lineWidth, indentLength) {
|
|
17461
17604
|
if (!lineWidth || lineWidth < 0)
|
|
17462
17605
|
return false;
|
|
17463
17606
|
const limit = lineWidth - indentLength;
|
|
17464
|
-
const strLen =
|
|
17607
|
+
const strLen = str8.length;
|
|
17465
17608
|
if (strLen <= limit)
|
|
17466
17609
|
return false;
|
|
17467
17610
|
for (let i = 0, start = 0; i < strLen; ++i) {
|
|
17468
|
-
if (
|
|
17611
|
+
if (str8[i] === "\n") {
|
|
17469
17612
|
if (i - start > limit)
|
|
17470
17613
|
return true;
|
|
17471
17614
|
start = i + 1;
|
|
@@ -17482,11 +17625,11 @@ var require_stringifyString = __commonJS({
|
|
|
17482
17625
|
const { implicitKey } = ctx;
|
|
17483
17626
|
const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength;
|
|
17484
17627
|
const indent = ctx.indent || (containsDocumentMarker(value) ? " " : "");
|
|
17485
|
-
let
|
|
17628
|
+
let str8 = "";
|
|
17486
17629
|
let start = 0;
|
|
17487
17630
|
for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
|
|
17488
17631
|
if (ch === " " && json[i + 1] === "\\" && json[i + 2] === "n") {
|
|
17489
|
-
|
|
17632
|
+
str8 += json.slice(start, i) + "\\ ";
|
|
17490
17633
|
i += 1;
|
|
17491
17634
|
start = i;
|
|
17492
17635
|
ch = "\\";
|
|
@@ -17495,38 +17638,38 @@ var require_stringifyString = __commonJS({
|
|
|
17495
17638
|
switch (json[i + 1]) {
|
|
17496
17639
|
case "u":
|
|
17497
17640
|
{
|
|
17498
|
-
|
|
17641
|
+
str8 += json.slice(start, i);
|
|
17499
17642
|
const code = json.substr(i + 2, 4);
|
|
17500
17643
|
switch (code) {
|
|
17501
17644
|
case "0000":
|
|
17502
|
-
|
|
17645
|
+
str8 += "\\0";
|
|
17503
17646
|
break;
|
|
17504
17647
|
case "0007":
|
|
17505
|
-
|
|
17648
|
+
str8 += "\\a";
|
|
17506
17649
|
break;
|
|
17507
17650
|
case "000b":
|
|
17508
|
-
|
|
17651
|
+
str8 += "\\v";
|
|
17509
17652
|
break;
|
|
17510
17653
|
case "001b":
|
|
17511
|
-
|
|
17654
|
+
str8 += "\\e";
|
|
17512
17655
|
break;
|
|
17513
17656
|
case "0085":
|
|
17514
|
-
|
|
17657
|
+
str8 += "\\N";
|
|
17515
17658
|
break;
|
|
17516
17659
|
case "00a0":
|
|
17517
|
-
|
|
17660
|
+
str8 += "\\_";
|
|
17518
17661
|
break;
|
|
17519
17662
|
case "2028":
|
|
17520
|
-
|
|
17663
|
+
str8 += "\\L";
|
|
17521
17664
|
break;
|
|
17522
17665
|
case "2029":
|
|
17523
|
-
|
|
17666
|
+
str8 += "\\P";
|
|
17524
17667
|
break;
|
|
17525
17668
|
default:
|
|
17526
17669
|
if (code.substr(0, 2) === "00")
|
|
17527
|
-
|
|
17670
|
+
str8 += "\\x" + code.substr(2);
|
|
17528
17671
|
else
|
|
17529
|
-
|
|
17672
|
+
str8 += json.substr(i, 6);
|
|
17530
17673
|
}
|
|
17531
17674
|
i += 5;
|
|
17532
17675
|
start = i + 1;
|
|
@@ -17536,14 +17679,14 @@ var require_stringifyString = __commonJS({
|
|
|
17536
17679
|
if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
|
|
17537
17680
|
i += 1;
|
|
17538
17681
|
} else {
|
|
17539
|
-
|
|
17682
|
+
str8 += json.slice(start, i) + "\n\n";
|
|
17540
17683
|
while (json[i + 2] === "\\" && json[i + 3] === "n" && json[i + 4] !== '"') {
|
|
17541
|
-
|
|
17684
|
+
str8 += "\n";
|
|
17542
17685
|
i += 2;
|
|
17543
17686
|
}
|
|
17544
|
-
|
|
17687
|
+
str8 += indent;
|
|
17545
17688
|
if (json[i + 2] === " ")
|
|
17546
|
-
|
|
17689
|
+
str8 += "\\";
|
|
17547
17690
|
i += 1;
|
|
17548
17691
|
start = i + 1;
|
|
17549
17692
|
}
|
|
@@ -17552,8 +17695,8 @@ var require_stringifyString = __commonJS({
|
|
|
17552
17695
|
i += 1;
|
|
17553
17696
|
}
|
|
17554
17697
|
}
|
|
17555
|
-
|
|
17556
|
-
return implicitKey ?
|
|
17698
|
+
str8 = start ? str8 + json.slice(start) : json;
|
|
17699
|
+
return implicitKey ? str8 : foldFlowLines.foldFlowLines(str8, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false));
|
|
17557
17700
|
}
|
|
17558
17701
|
function singleQuotedString(value, ctx) {
|
|
17559
17702
|
if (ctx.options.singleQuote === false || ctx.implicitKey && value.includes("\n") || /[ \t]\n|\n[ \t]/.test(value))
|
|
@@ -17681,15 +17824,15 @@ ${indent}${start}${value}${end}`;
|
|
|
17681
17824
|
return quotedString(value, ctx);
|
|
17682
17825
|
}
|
|
17683
17826
|
}
|
|
17684
|
-
const
|
|
17827
|
+
const str8 = value.replace(/\n+/g, `$&
|
|
17685
17828
|
${indent}`);
|
|
17686
17829
|
if (actualString) {
|
|
17687
|
-
const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(
|
|
17830
|
+
const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str8);
|
|
17688
17831
|
const { compat, tags } = ctx.doc.schema;
|
|
17689
17832
|
if (tags.some(test) || compat?.some(test))
|
|
17690
17833
|
return quotedString(value, ctx);
|
|
17691
17834
|
}
|
|
17692
|
-
return implicitKey ?
|
|
17835
|
+
return implicitKey ? str8 : foldFlowLines.foldFlowLines(str8, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false));
|
|
17693
17836
|
}
|
|
17694
17837
|
function stringifyString(item, ctx, onComment, onChompKeep) {
|
|
17695
17838
|
const { implicitKey, inFlow } = ctx;
|
|
@@ -17842,11 +17985,11 @@ var require_stringify = __commonJS({
|
|
|
17842
17985
|
const props = stringifyProps(node, tagObj, ctx);
|
|
17843
17986
|
if (props.length > 0)
|
|
17844
17987
|
ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
|
|
17845
|
-
const
|
|
17988
|
+
const str8 = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep);
|
|
17846
17989
|
if (!props)
|
|
17847
|
-
return
|
|
17848
|
-
return identity.isScalar(node) ||
|
|
17849
|
-
${ctx.indent}${
|
|
17990
|
+
return str8;
|
|
17991
|
+
return identity.isScalar(node) || str8[0] === "{" || str8[0] === "[" ? `${props} ${str8}` : `${props}
|
|
17992
|
+
${ctx.indent}${str8}`;
|
|
17850
17993
|
}
|
|
17851
17994
|
exports2.createStringifyContext = createStringifyContext;
|
|
17852
17995
|
exports2.stringify = stringify;
|
|
@@ -17882,8 +18025,8 @@ var require_stringifyPair = __commonJS({
|
|
|
17882
18025
|
});
|
|
17883
18026
|
let keyCommentDone = false;
|
|
17884
18027
|
let chompKeep = false;
|
|
17885
|
-
let
|
|
17886
|
-
if (!explicitKey && !ctx.inFlow &&
|
|
18028
|
+
let str8 = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
|
|
18029
|
+
if (!explicitKey && !ctx.inFlow && str8.length > 1024) {
|
|
17887
18030
|
if (simpleKeys)
|
|
17888
18031
|
throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
|
|
17889
18032
|
explicitKey = true;
|
|
@@ -17892,27 +18035,27 @@ var require_stringifyPair = __commonJS({
|
|
|
17892
18035
|
if (allNullValues || value == null) {
|
|
17893
18036
|
if (keyCommentDone && onComment)
|
|
17894
18037
|
onComment();
|
|
17895
|
-
return
|
|
18038
|
+
return str8 === "" ? "?" : explicitKey ? `? ${str8}` : str8;
|
|
17896
18039
|
}
|
|
17897
18040
|
} else if (allNullValues && !simpleKeys || value == null && explicitKey) {
|
|
17898
|
-
|
|
18041
|
+
str8 = `? ${str8}`;
|
|
17899
18042
|
if (keyComment && !keyCommentDone) {
|
|
17900
|
-
|
|
18043
|
+
str8 += stringifyComment.lineComment(str8, ctx.indent, commentString(keyComment));
|
|
17901
18044
|
} else if (chompKeep && onChompKeep)
|
|
17902
18045
|
onChompKeep();
|
|
17903
|
-
return
|
|
18046
|
+
return str8;
|
|
17904
18047
|
}
|
|
17905
18048
|
if (keyCommentDone)
|
|
17906
18049
|
keyComment = null;
|
|
17907
18050
|
if (explicitKey) {
|
|
17908
18051
|
if (keyComment)
|
|
17909
|
-
|
|
17910
|
-
|
|
18052
|
+
str8 += stringifyComment.lineComment(str8, ctx.indent, commentString(keyComment));
|
|
18053
|
+
str8 = `? ${str8}
|
|
17911
18054
|
${indent}:`;
|
|
17912
18055
|
} else {
|
|
17913
|
-
|
|
18056
|
+
str8 = `${str8}:`;
|
|
17914
18057
|
if (keyComment)
|
|
17915
|
-
|
|
18058
|
+
str8 += stringifyComment.lineComment(str8, ctx.indent, commentString(keyComment));
|
|
17916
18059
|
}
|
|
17917
18060
|
let vsb, vcb, valueComment;
|
|
17918
18061
|
if (identity.isNode(value)) {
|
|
@@ -17928,7 +18071,7 @@ ${indent}:`;
|
|
|
17928
18071
|
}
|
|
17929
18072
|
ctx.implicitKey = false;
|
|
17930
18073
|
if (!explicitKey && !keyComment && identity.isScalar(value))
|
|
17931
|
-
ctx.indentAtStart =
|
|
18074
|
+
ctx.indentAtStart = str8.length + 1;
|
|
17932
18075
|
chompKeep = false;
|
|
17933
18076
|
if (!indentSeq && indentStep.length >= 2 && !ctx.inFlow && !explicitKey && identity.isSeq(value) && !value.flow && !value.tag && !value.anchor) {
|
|
17934
18077
|
ctx.indent = ctx.indent.substring(2);
|
|
@@ -17972,16 +18115,16 @@ ${ctx.indent}`;
|
|
|
17972
18115
|
} else if (valueStr === "" || valueStr[0] === "\n") {
|
|
17973
18116
|
ws = "";
|
|
17974
18117
|
}
|
|
17975
|
-
|
|
18118
|
+
str8 += ws + valueStr;
|
|
17976
18119
|
if (ctx.inFlow) {
|
|
17977
18120
|
if (valueCommentDone && onComment)
|
|
17978
18121
|
onComment();
|
|
17979
18122
|
} else if (valueComment && !valueCommentDone) {
|
|
17980
|
-
|
|
18123
|
+
str8 += stringifyComment.lineComment(str8, ctx.indent, commentString(valueComment));
|
|
17981
18124
|
} else if (chompKeep && onChompKeep) {
|
|
17982
18125
|
onChompKeep();
|
|
17983
18126
|
}
|
|
17984
|
-
return
|
|
18127
|
+
return str8;
|
|
17985
18128
|
}
|
|
17986
18129
|
exports2.stringifyPair = stringifyPair;
|
|
17987
18130
|
}
|
|
@@ -18213,31 +18356,31 @@ var require_stringifyCollection = __commonJS({
|
|
|
18213
18356
|
}
|
|
18214
18357
|
}
|
|
18215
18358
|
chompKeep = false;
|
|
18216
|
-
let
|
|
18359
|
+
let str9 = stringify.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
|
|
18217
18360
|
if (comment2)
|
|
18218
|
-
|
|
18361
|
+
str9 += stringifyComment.lineComment(str9, itemIndent, commentString(comment2));
|
|
18219
18362
|
if (chompKeep && comment2)
|
|
18220
18363
|
chompKeep = false;
|
|
18221
|
-
lines.push(blockItemPrefix +
|
|
18364
|
+
lines.push(blockItemPrefix + str9);
|
|
18222
18365
|
}
|
|
18223
|
-
let
|
|
18366
|
+
let str8;
|
|
18224
18367
|
if (lines.length === 0) {
|
|
18225
|
-
|
|
18368
|
+
str8 = flowChars.start + flowChars.end;
|
|
18226
18369
|
} else {
|
|
18227
|
-
|
|
18370
|
+
str8 = lines[0];
|
|
18228
18371
|
for (let i = 1; i < lines.length; ++i) {
|
|
18229
18372
|
const line = lines[i];
|
|
18230
|
-
|
|
18373
|
+
str8 += line ? `
|
|
18231
18374
|
${indent}${line}` : "\n";
|
|
18232
18375
|
}
|
|
18233
18376
|
}
|
|
18234
18377
|
if (comment) {
|
|
18235
|
-
|
|
18378
|
+
str8 += "\n" + stringifyComment.indentComment(commentString(comment), indent);
|
|
18236
18379
|
if (onComment)
|
|
18237
18380
|
onComment();
|
|
18238
18381
|
} else if (chompKeep && onChompKeep)
|
|
18239
18382
|
onChompKeep();
|
|
18240
|
-
return
|
|
18383
|
+
return str8;
|
|
18241
18384
|
}
|
|
18242
18385
|
function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
|
|
18243
18386
|
const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx;
|
|
@@ -18280,21 +18423,21 @@ ${indent}${line}` : "\n";
|
|
|
18280
18423
|
}
|
|
18281
18424
|
if (comment)
|
|
18282
18425
|
reqNewline = true;
|
|
18283
|
-
let
|
|
18284
|
-
reqNewline || (reqNewline = lines.length > linesAtValue ||
|
|
18426
|
+
let str8 = stringify.stringify(item, itemCtx, () => comment = null);
|
|
18427
|
+
reqNewline || (reqNewline = lines.length > linesAtValue || str8.includes("\n"));
|
|
18285
18428
|
if (i < items.length - 1) {
|
|
18286
|
-
|
|
18429
|
+
str8 += ",";
|
|
18287
18430
|
} else if (ctx.options.trailingComma) {
|
|
18288
18431
|
if (ctx.options.lineWidth > 0) {
|
|
18289
|
-
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (
|
|
18432
|
+
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str8.length + 2) > ctx.options.lineWidth);
|
|
18290
18433
|
}
|
|
18291
18434
|
if (reqNewline) {
|
|
18292
|
-
|
|
18435
|
+
str8 += ",";
|
|
18293
18436
|
}
|
|
18294
18437
|
}
|
|
18295
18438
|
if (comment)
|
|
18296
|
-
|
|
18297
|
-
lines.push(
|
|
18439
|
+
str8 += stringifyComment.lineComment(str8, itemIndent, commentString(comment));
|
|
18440
|
+
lines.push(str8);
|
|
18298
18441
|
linesAtValue = lines.length;
|
|
18299
18442
|
}
|
|
18300
18443
|
const { start, end } = flowChars;
|
|
@@ -18306,11 +18449,11 @@ ${indent}${line}` : "\n";
|
|
|
18306
18449
|
reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth;
|
|
18307
18450
|
}
|
|
18308
18451
|
if (reqNewline) {
|
|
18309
|
-
let
|
|
18452
|
+
let str8 = start;
|
|
18310
18453
|
for (const line of lines)
|
|
18311
|
-
|
|
18454
|
+
str8 += line ? `
|
|
18312
18455
|
${indentStep}${indent}${line}` : "\n";
|
|
18313
|
-
return `${
|
|
18456
|
+
return `${str8}
|
|
18314
18457
|
${indent}${end}`;
|
|
18315
18458
|
} else {
|
|
18316
18459
|
return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`;
|
|
@@ -18647,7 +18790,7 @@ var require_string = __commonJS({
|
|
|
18647
18790
|
identify: (value) => typeof value === "string",
|
|
18648
18791
|
default: true,
|
|
18649
18792
|
tag: "tag:yaml.org,2002:str",
|
|
18650
|
-
resolve: (
|
|
18793
|
+
resolve: (str8) => str8,
|
|
18651
18794
|
stringify(item, ctx, onComment, onChompKeep) {
|
|
18652
18795
|
ctx = Object.assign({ actualString: true }, ctx);
|
|
18653
18796
|
return stringifyString.stringifyString(item, ctx, onComment, onChompKeep);
|
|
@@ -18687,7 +18830,7 @@ var require_bool = __commonJS({
|
|
|
18687
18830
|
default: true,
|
|
18688
18831
|
tag: "tag:yaml.org,2002:bool",
|
|
18689
18832
|
test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,
|
|
18690
|
-
resolve: (
|
|
18833
|
+
resolve: (str8) => new Scalar.Scalar(str8[0] === "t" || str8[0] === "T"),
|
|
18691
18834
|
stringify({ source, value }, ctx) {
|
|
18692
18835
|
if (source && boolTag.test.test(source)) {
|
|
18693
18836
|
const sv = source[0] === "t" || source[0] === "T";
|
|
@@ -18741,7 +18884,7 @@ var require_float = __commonJS({
|
|
|
18741
18884
|
default: true,
|
|
18742
18885
|
tag: "tag:yaml.org,2002:float",
|
|
18743
18886
|
test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
|
|
18744
|
-
resolve: (
|
|
18887
|
+
resolve: (str8) => str8.slice(-3).toLowerCase() === "nan" ? NaN : str8[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
|
|
18745
18888
|
stringify: stringifyNumber.stringifyNumber
|
|
18746
18889
|
};
|
|
18747
18890
|
var floatExp = {
|
|
@@ -18750,7 +18893,7 @@ var require_float = __commonJS({
|
|
|
18750
18893
|
tag: "tag:yaml.org,2002:float",
|
|
18751
18894
|
format: "EXP",
|
|
18752
18895
|
test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
|
|
18753
|
-
resolve: (
|
|
18896
|
+
resolve: (str8) => parseFloat(str8),
|
|
18754
18897
|
stringify(node) {
|
|
18755
18898
|
const num2 = Number(node.value);
|
|
18756
18899
|
return isFinite(num2) ? num2.toExponential() : stringifyNumber.stringifyNumber(node);
|
|
@@ -18761,11 +18904,11 @@ var require_float = __commonJS({
|
|
|
18761
18904
|
default: true,
|
|
18762
18905
|
tag: "tag:yaml.org,2002:float",
|
|
18763
18906
|
test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/,
|
|
18764
|
-
resolve(
|
|
18765
|
-
const node = new Scalar.Scalar(parseFloat(
|
|
18766
|
-
const dot =
|
|
18767
|
-
if (dot !== -1 &&
|
|
18768
|
-
node.minFractionDigits =
|
|
18907
|
+
resolve(str8) {
|
|
18908
|
+
const node = new Scalar.Scalar(parseFloat(str8));
|
|
18909
|
+
const dot = str8.indexOf(".");
|
|
18910
|
+
if (dot !== -1 && str8[str8.length - 1] === "0")
|
|
18911
|
+
node.minFractionDigits = str8.length - dot - 1;
|
|
18769
18912
|
return node;
|
|
18770
18913
|
},
|
|
18771
18914
|
stringify: stringifyNumber.stringifyNumber
|
|
@@ -18783,7 +18926,7 @@ var require_int = __commonJS({
|
|
|
18783
18926
|
init_define_KT_HUB_FILES();
|
|
18784
18927
|
var stringifyNumber = require_stringifyNumber();
|
|
18785
18928
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
18786
|
-
var intResolve = (
|
|
18929
|
+
var intResolve = (str8, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str8) : parseInt(str8.substring(offset), radix);
|
|
18787
18930
|
function intStringify(node, radix, prefix) {
|
|
18788
18931
|
const { value } = node;
|
|
18789
18932
|
if (intIdentify(value) && value >= 0)
|
|
@@ -18796,7 +18939,7 @@ var require_int = __commonJS({
|
|
|
18796
18939
|
tag: "tag:yaml.org,2002:int",
|
|
18797
18940
|
format: "OCT",
|
|
18798
18941
|
test: /^0o[0-7]+$/,
|
|
18799
|
-
resolve: (
|
|
18942
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 2, 8, opt),
|
|
18800
18943
|
stringify: (node) => intStringify(node, 8, "0o")
|
|
18801
18944
|
};
|
|
18802
18945
|
var int = {
|
|
@@ -18804,7 +18947,7 @@ var require_int = __commonJS({
|
|
|
18804
18947
|
default: true,
|
|
18805
18948
|
tag: "tag:yaml.org,2002:int",
|
|
18806
18949
|
test: /^[-+]?[0-9]+$/,
|
|
18807
|
-
resolve: (
|
|
18950
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 0, 10, opt),
|
|
18808
18951
|
stringify: stringifyNumber.stringifyNumber
|
|
18809
18952
|
};
|
|
18810
18953
|
var intHex = {
|
|
@@ -18813,7 +18956,7 @@ var require_int = __commonJS({
|
|
|
18813
18956
|
tag: "tag:yaml.org,2002:int",
|
|
18814
18957
|
format: "HEX",
|
|
18815
18958
|
test: /^0x[0-9a-fA-F]+$/,
|
|
18816
|
-
resolve: (
|
|
18959
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 2, 16, opt),
|
|
18817
18960
|
stringify: (node) => intStringify(node, 16, "0x")
|
|
18818
18961
|
};
|
|
18819
18962
|
exports2.int = int;
|
|
@@ -18868,7 +19011,7 @@ var require_schema2 = __commonJS({
|
|
|
18868
19011
|
identify: (value) => typeof value === "string",
|
|
18869
19012
|
default: true,
|
|
18870
19013
|
tag: "tag:yaml.org,2002:str",
|
|
18871
|
-
resolve: (
|
|
19014
|
+
resolve: (str8) => str8,
|
|
18872
19015
|
stringify: stringifyJSON
|
|
18873
19016
|
},
|
|
18874
19017
|
{
|
|
@@ -18885,7 +19028,7 @@ var require_schema2 = __commonJS({
|
|
|
18885
19028
|
default: true,
|
|
18886
19029
|
tag: "tag:yaml.org,2002:bool",
|
|
18887
19030
|
test: /^true$|^false$/,
|
|
18888
|
-
resolve: (
|
|
19031
|
+
resolve: (str8) => str8 === "true",
|
|
18889
19032
|
stringify: stringifyJSON
|
|
18890
19033
|
},
|
|
18891
19034
|
{
|
|
@@ -18893,7 +19036,7 @@ var require_schema2 = __commonJS({
|
|
|
18893
19036
|
default: true,
|
|
18894
19037
|
tag: "tag:yaml.org,2002:int",
|
|
18895
19038
|
test: /^-?(?:0|[1-9][0-9]*)$/,
|
|
18896
|
-
resolve: (
|
|
19039
|
+
resolve: (str8, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str8) : parseInt(str8, 10),
|
|
18897
19040
|
stringify: ({ value }) => intIdentify(value) ? value.toString() : JSON.stringify(value)
|
|
18898
19041
|
},
|
|
18899
19042
|
{
|
|
@@ -18901,7 +19044,7 @@ var require_schema2 = __commonJS({
|
|
|
18901
19044
|
default: true,
|
|
18902
19045
|
tag: "tag:yaml.org,2002:float",
|
|
18903
19046
|
test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,
|
|
18904
|
-
resolve: (
|
|
19047
|
+
resolve: (str8) => parseFloat(str8),
|
|
18905
19048
|
stringify: stringifyJSON
|
|
18906
19049
|
}
|
|
18907
19050
|
];
|
|
@@ -18909,9 +19052,9 @@ var require_schema2 = __commonJS({
|
|
|
18909
19052
|
default: true,
|
|
18910
19053
|
tag: "",
|
|
18911
19054
|
test: /^/,
|
|
18912
|
-
resolve(
|
|
18913
|
-
onError(`Unresolved plain scalar ${JSON.stringify(
|
|
18914
|
-
return
|
|
19055
|
+
resolve(str8, onError) {
|
|
19056
|
+
onError(`Unresolved plain scalar ${JSON.stringify(str8)}`);
|
|
19057
|
+
return str8;
|
|
18915
19058
|
}
|
|
18916
19059
|
};
|
|
18917
19060
|
var schema = [map.map, seq.seq].concat(jsonScalars, jsonError);
|
|
@@ -18944,10 +19087,10 @@ var require_binary = __commonJS({
|
|
|
18944
19087
|
if (typeof node_buffer.Buffer === "function") {
|
|
18945
19088
|
return node_buffer.Buffer.from(src, "base64");
|
|
18946
19089
|
} else if (typeof atob === "function") {
|
|
18947
|
-
const
|
|
18948
|
-
const buffer = new Uint8Array(
|
|
18949
|
-
for (let i = 0; i <
|
|
18950
|
-
buffer[i] =
|
|
19090
|
+
const str8 = atob(src.replace(/[\n\r]/g, ""));
|
|
19091
|
+
const buffer = new Uint8Array(str8.length);
|
|
19092
|
+
for (let i = 0; i < str8.length; ++i)
|
|
19093
|
+
buffer[i] = str8.charCodeAt(i);
|
|
18951
19094
|
return buffer;
|
|
18952
19095
|
} else {
|
|
18953
19096
|
onError("This environment does not support reading binary tags; either Buffer or atob is required");
|
|
@@ -18958,28 +19101,28 @@ var require_binary = __commonJS({
|
|
|
18958
19101
|
if (!value)
|
|
18959
19102
|
return "";
|
|
18960
19103
|
const buf = value;
|
|
18961
|
-
let
|
|
19104
|
+
let str8;
|
|
18962
19105
|
if (typeof node_buffer.Buffer === "function") {
|
|
18963
|
-
|
|
19106
|
+
str8 = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64");
|
|
18964
19107
|
} else if (typeof btoa === "function") {
|
|
18965
19108
|
let s = "";
|
|
18966
19109
|
for (let i = 0; i < buf.length; ++i)
|
|
18967
19110
|
s += String.fromCharCode(buf[i]);
|
|
18968
|
-
|
|
19111
|
+
str8 = btoa(s);
|
|
18969
19112
|
} else {
|
|
18970
19113
|
throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required");
|
|
18971
19114
|
}
|
|
18972
19115
|
type ?? (type = Scalar.Scalar.BLOCK_LITERAL);
|
|
18973
19116
|
if (type !== Scalar.Scalar.QUOTE_DOUBLE) {
|
|
18974
19117
|
const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth);
|
|
18975
|
-
const n = Math.ceil(
|
|
19118
|
+
const n = Math.ceil(str8.length / lineWidth);
|
|
18976
19119
|
const lines = new Array(n);
|
|
18977
19120
|
for (let i = 0, o = 0; i < n; ++i, o += lineWidth) {
|
|
18978
|
-
lines[i] =
|
|
19121
|
+
lines[i] = str8.substr(o, lineWidth);
|
|
18979
19122
|
}
|
|
18980
|
-
|
|
19123
|
+
str8 = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? "\n" : " ");
|
|
18981
19124
|
}
|
|
18982
|
-
return stringifyString.stringifyString({ comment, type, value:
|
|
19125
|
+
return stringifyString.stringifyString({ comment, type, value: str8 }, ctx, onComment, onChompKeep);
|
|
18983
19126
|
}
|
|
18984
19127
|
};
|
|
18985
19128
|
exports2.binary = binary;
|
|
@@ -19189,7 +19332,7 @@ var require_float2 = __commonJS({
|
|
|
19189
19332
|
default: true,
|
|
19190
19333
|
tag: "tag:yaml.org,2002:float",
|
|
19191
19334
|
test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
|
|
19192
|
-
resolve: (
|
|
19335
|
+
resolve: (str8) => str8.slice(-3).toLowerCase() === "nan" ? NaN : str8[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
|
|
19193
19336
|
stringify: stringifyNumber.stringifyNumber
|
|
19194
19337
|
};
|
|
19195
19338
|
var floatExp = {
|
|
@@ -19198,7 +19341,7 @@ var require_float2 = __commonJS({
|
|
|
19198
19341
|
tag: "tag:yaml.org,2002:float",
|
|
19199
19342
|
format: "EXP",
|
|
19200
19343
|
test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,
|
|
19201
|
-
resolve: (
|
|
19344
|
+
resolve: (str8) => parseFloat(str8.replace(/_/g, "")),
|
|
19202
19345
|
stringify(node) {
|
|
19203
19346
|
const num2 = Number(node.value);
|
|
19204
19347
|
return isFinite(num2) ? num2.toExponential() : stringifyNumber.stringifyNumber(node);
|
|
@@ -19209,11 +19352,11 @@ var require_float2 = __commonJS({
|
|
|
19209
19352
|
default: true,
|
|
19210
19353
|
tag: "tag:yaml.org,2002:float",
|
|
19211
19354
|
test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/,
|
|
19212
|
-
resolve(
|
|
19213
|
-
const node = new Scalar.Scalar(parseFloat(
|
|
19214
|
-
const dot =
|
|
19355
|
+
resolve(str8) {
|
|
19356
|
+
const node = new Scalar.Scalar(parseFloat(str8.replace(/_/g, "")));
|
|
19357
|
+
const dot = str8.indexOf(".");
|
|
19215
19358
|
if (dot !== -1) {
|
|
19216
|
-
const f =
|
|
19359
|
+
const f = str8.substring(dot + 1).replace(/_/g, "");
|
|
19217
19360
|
if (f[f.length - 1] === "0")
|
|
19218
19361
|
node.minFractionDigits = f.length;
|
|
19219
19362
|
}
|
|
@@ -19234,34 +19377,34 @@ var require_int2 = __commonJS({
|
|
|
19234
19377
|
init_define_KT_HUB_FILES();
|
|
19235
19378
|
var stringifyNumber = require_stringifyNumber();
|
|
19236
19379
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
19237
|
-
function intResolve(
|
|
19238
|
-
const
|
|
19239
|
-
if (
|
|
19380
|
+
function intResolve(str8, offset, radix, { intAsBigInt }) {
|
|
19381
|
+
const sign2 = str8[0];
|
|
19382
|
+
if (sign2 === "-" || sign2 === "+")
|
|
19240
19383
|
offset += 1;
|
|
19241
|
-
|
|
19384
|
+
str8 = str8.substring(offset).replace(/_/g, "");
|
|
19242
19385
|
if (intAsBigInt) {
|
|
19243
19386
|
switch (radix) {
|
|
19244
19387
|
case 2:
|
|
19245
|
-
|
|
19388
|
+
str8 = `0b${str8}`;
|
|
19246
19389
|
break;
|
|
19247
19390
|
case 8:
|
|
19248
|
-
|
|
19391
|
+
str8 = `0o${str8}`;
|
|
19249
19392
|
break;
|
|
19250
19393
|
case 16:
|
|
19251
|
-
|
|
19394
|
+
str8 = `0x${str8}`;
|
|
19252
19395
|
break;
|
|
19253
19396
|
}
|
|
19254
|
-
const n2 = BigInt(
|
|
19255
|
-
return
|
|
19397
|
+
const n2 = BigInt(str8);
|
|
19398
|
+
return sign2 === "-" ? BigInt(-1) * n2 : n2;
|
|
19256
19399
|
}
|
|
19257
|
-
const n = parseInt(
|
|
19258
|
-
return
|
|
19400
|
+
const n = parseInt(str8, radix);
|
|
19401
|
+
return sign2 === "-" ? -1 * n : n;
|
|
19259
19402
|
}
|
|
19260
19403
|
function intStringify(node, radix, prefix) {
|
|
19261
19404
|
const { value } = node;
|
|
19262
19405
|
if (intIdentify(value)) {
|
|
19263
|
-
const
|
|
19264
|
-
return value < 0 ? "-" + prefix +
|
|
19406
|
+
const str8 = value.toString(radix);
|
|
19407
|
+
return value < 0 ? "-" + prefix + str8.substr(1) : prefix + str8;
|
|
19265
19408
|
}
|
|
19266
19409
|
return stringifyNumber.stringifyNumber(node);
|
|
19267
19410
|
}
|
|
@@ -19271,7 +19414,7 @@ var require_int2 = __commonJS({
|
|
|
19271
19414
|
tag: "tag:yaml.org,2002:int",
|
|
19272
19415
|
format: "BIN",
|
|
19273
19416
|
test: /^[-+]?0b[0-1_]+$/,
|
|
19274
|
-
resolve: (
|
|
19417
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 2, 2, opt),
|
|
19275
19418
|
stringify: (node) => intStringify(node, 2, "0b")
|
|
19276
19419
|
};
|
|
19277
19420
|
var intOct = {
|
|
@@ -19280,7 +19423,7 @@ var require_int2 = __commonJS({
|
|
|
19280
19423
|
tag: "tag:yaml.org,2002:int",
|
|
19281
19424
|
format: "OCT",
|
|
19282
19425
|
test: /^[-+]?0[0-7_]+$/,
|
|
19283
|
-
resolve: (
|
|
19426
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 1, 8, opt),
|
|
19284
19427
|
stringify: (node) => intStringify(node, 8, "0")
|
|
19285
19428
|
};
|
|
19286
19429
|
var int = {
|
|
@@ -19288,7 +19431,7 @@ var require_int2 = __commonJS({
|
|
|
19288
19431
|
default: true,
|
|
19289
19432
|
tag: "tag:yaml.org,2002:int",
|
|
19290
19433
|
test: /^[-+]?[0-9][0-9_]*$/,
|
|
19291
|
-
resolve: (
|
|
19434
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 0, 10, opt),
|
|
19292
19435
|
stringify: stringifyNumber.stringifyNumber
|
|
19293
19436
|
};
|
|
19294
19437
|
var intHex = {
|
|
@@ -19297,7 +19440,7 @@ var require_int2 = __commonJS({
|
|
|
19297
19440
|
tag: "tag:yaml.org,2002:int",
|
|
19298
19441
|
format: "HEX",
|
|
19299
19442
|
test: /^[-+]?0x[0-9a-fA-F_]+$/,
|
|
19300
|
-
resolve: (
|
|
19443
|
+
resolve: (str8, _onError, opt) => intResolve(str8, 2, 16, opt),
|
|
19301
19444
|
stringify: (node) => intStringify(node, 16, "0x")
|
|
19302
19445
|
};
|
|
19303
19446
|
exports2.int = int;
|
|
@@ -19403,12 +19546,12 @@ var require_timestamp = __commonJS({
|
|
|
19403
19546
|
"use strict";
|
|
19404
19547
|
init_define_KT_HUB_FILES();
|
|
19405
19548
|
var stringifyNumber = require_stringifyNumber();
|
|
19406
|
-
function parseSexagesimal(
|
|
19407
|
-
const
|
|
19408
|
-
const parts =
|
|
19549
|
+
function parseSexagesimal(str8, asBigInt) {
|
|
19550
|
+
const sign2 = str8[0];
|
|
19551
|
+
const parts = sign2 === "-" || sign2 === "+" ? str8.substring(1) : str8;
|
|
19409
19552
|
const num2 = (n) => asBigInt ? BigInt(n) : Number(n);
|
|
19410
19553
|
const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num2(60) + num2(p), num2(0));
|
|
19411
|
-
return
|
|
19554
|
+
return sign2 === "-" ? num2(-1) * res : res;
|
|
19412
19555
|
}
|
|
19413
19556
|
function stringifySexagesimal(node) {
|
|
19414
19557
|
let { value } = node;
|
|
@@ -19417,9 +19560,9 @@ var require_timestamp = __commonJS({
|
|
|
19417
19560
|
num2 = (n) => BigInt(n);
|
|
19418
19561
|
else if (isNaN(value) || !isFinite(value))
|
|
19419
19562
|
return stringifyNumber.stringifyNumber(node);
|
|
19420
|
-
let
|
|
19563
|
+
let sign2 = "";
|
|
19421
19564
|
if (value < 0) {
|
|
19422
|
-
|
|
19565
|
+
sign2 = "-";
|
|
19423
19566
|
value *= num2(-1);
|
|
19424
19567
|
}
|
|
19425
19568
|
const _60 = num2(60);
|
|
@@ -19434,7 +19577,7 @@ var require_timestamp = __commonJS({
|
|
|
19434
19577
|
parts.unshift(value);
|
|
19435
19578
|
}
|
|
19436
19579
|
}
|
|
19437
|
-
return
|
|
19580
|
+
return sign2 + parts.map((n) => String(n).padStart(2, "0")).join(":").replace(/000000\d*$/, "");
|
|
19438
19581
|
}
|
|
19439
19582
|
var intTime = {
|
|
19440
19583
|
identify: (value) => typeof value === "bigint" || Number.isInteger(value),
|
|
@@ -19442,7 +19585,7 @@ var require_timestamp = __commonJS({
|
|
|
19442
19585
|
tag: "tag:yaml.org,2002:int",
|
|
19443
19586
|
format: "TIME",
|
|
19444
19587
|
test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/,
|
|
19445
|
-
resolve: (
|
|
19588
|
+
resolve: (str8, _onError, { intAsBigInt }) => parseSexagesimal(str8, intAsBigInt),
|
|
19446
19589
|
stringify: stringifySexagesimal
|
|
19447
19590
|
};
|
|
19448
19591
|
var floatTime = {
|
|
@@ -19451,7 +19594,7 @@ var require_timestamp = __commonJS({
|
|
|
19451
19594
|
tag: "tag:yaml.org,2002:float",
|
|
19452
19595
|
format: "TIME",
|
|
19453
19596
|
test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/,
|
|
19454
|
-
resolve: (
|
|
19597
|
+
resolve: (str8) => parseSexagesimal(str8, false),
|
|
19455
19598
|
stringify: stringifySexagesimal
|
|
19456
19599
|
};
|
|
19457
19600
|
var timestamp = {
|
|
@@ -19462,8 +19605,8 @@ var require_timestamp = __commonJS({
|
|
|
19462
19605
|
// may be omitted altogether, resulting in a date format. In such a case, the time part is
|
|
19463
19606
|
// assumed to be 00:00:00Z (start of day, UTC).
|
|
19464
19607
|
test: RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?$"),
|
|
19465
|
-
resolve(
|
|
19466
|
-
const match =
|
|
19608
|
+
resolve(str8) {
|
|
19609
|
+
const match = str8.match(timestamp.test);
|
|
19467
19610
|
if (!match)
|
|
19468
19611
|
throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
|
|
19469
19612
|
const [, year, month, day, hour, minute, second] = match.map(Number);
|
|
@@ -24060,10 +24203,10 @@ function hexToBytes(hex) {
|
|
|
24060
24203
|
}
|
|
24061
24204
|
var nextTick = async () => {
|
|
24062
24205
|
};
|
|
24063
|
-
function utf8ToBytes(
|
|
24064
|
-
if (typeof
|
|
24206
|
+
function utf8ToBytes(str8) {
|
|
24207
|
+
if (typeof str8 !== "string")
|
|
24065
24208
|
throw new TypeError("string expected");
|
|
24066
|
-
return new Uint8Array(new TextEncoder().encode(
|
|
24209
|
+
return new Uint8Array(new TextEncoder().encode(str8));
|
|
24067
24210
|
}
|
|
24068
24211
|
function kdfInputToBytes(data, errorTitle = "") {
|
|
24069
24212
|
if (typeof data === "string")
|
|
@@ -25676,7 +25819,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
25676
25819
|
bits2int_modN: "function"
|
|
25677
25820
|
});
|
|
25678
25821
|
ecdsaOpts = Object.assign({}, ecdsaOpts);
|
|
25679
|
-
const
|
|
25822
|
+
const randomBytes6 = ecdsaOpts.randomBytes === void 0 ? randomBytes2 : ecdsaOpts.randomBytes;
|
|
25680
25823
|
const hmac2 = ecdsaOpts.hmac === void 0 ? (key, msg2) => hmac(hash_, key, msg2) : ecdsaOpts.hmac;
|
|
25681
25824
|
const { Fp, Fn } = Point;
|
|
25682
25825
|
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
|
|
@@ -25822,7 +25965,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
25822
25965
|
throw new Error("invalid private key");
|
|
25823
25966
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
25824
25967
|
if (extraEntropy != null && extraEntropy !== false) {
|
|
25825
|
-
const e = extraEntropy === true ?
|
|
25968
|
+
const e = extraEntropy === true ? randomBytes6(lengths.secretKey) : extraEntropy;
|
|
25826
25969
|
seedArgs.push(abytes2(e, void 0, "extraEntropy"));
|
|
25827
25970
|
}
|
|
25828
25971
|
const seed = concatBytes2(...seedArgs);
|
|
@@ -25849,7 +25992,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
25849
25992
|
}
|
|
25850
25993
|
return { seed, k2sig };
|
|
25851
25994
|
}
|
|
25852
|
-
function
|
|
25995
|
+
function sign2(message, secretKey, opts2 = {}) {
|
|
25853
25996
|
const { seed, k2sig } = prepSig(message, secretKey, opts2);
|
|
25854
25997
|
const drbg = createHmacDrbg(hash_.outputLen, Fn.BYTES, hmac2);
|
|
25855
25998
|
const sig = drbg(seed, k2sig);
|
|
@@ -25895,7 +26038,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
25895
26038
|
utils,
|
|
25896
26039
|
lengths,
|
|
25897
26040
|
Point,
|
|
25898
|
-
sign:
|
|
26041
|
+
sign: sign2,
|
|
25899
26042
|
verify,
|
|
25900
26043
|
recoverPublicKey,
|
|
25901
26044
|
Signature,
|
|
@@ -28317,7 +28460,15 @@ var KutrionClient = class {
|
|
|
28317
28460
|
}
|
|
28318
28461
|
// Rotate tokens explicitly (req() also does this transparently on 401).
|
|
28319
28462
|
async refreshTokens(refreshToken) {
|
|
28320
|
-
|
|
28463
|
+
const { body, signerThrew } = await this.signedRefreshBody(refreshToken);
|
|
28464
|
+
try {
|
|
28465
|
+
return await this.req("POST", "/auth/refresh", body, false);
|
|
28466
|
+
} catch (e) {
|
|
28467
|
+
if (signerThrew && !body.signature && e instanceof ApiError && e.status === 401 && isRefreshSignatureRefusal(e.body)) {
|
|
28468
|
+
throw new ApiError(0, "signer_unavailable");
|
|
28469
|
+
}
|
|
28470
|
+
throw e;
|
|
28471
|
+
}
|
|
28321
28472
|
}
|
|
28322
28473
|
refreshBody(refreshToken) {
|
|
28323
28474
|
return this.refreshClaim ? { refresh_token: refreshToken, client: this.refreshClaim } : { refresh_token: refreshToken };
|
|
@@ -28462,8 +28613,8 @@ var KutrionClient = class {
|
|
|
28462
28613
|
// --- step-up (MT-3 #117): lift a social-login session's vault gate by proving a 2nd factor.
|
|
28463
28614
|
// Returns a fresh, unlocked token (swapped in via onToken). Passkey holders can instead complete
|
|
28464
28615
|
// a passkey login, which also mints an unlocked token.
|
|
28465
|
-
async stepUpTotp(code) {
|
|
28466
|
-
const r = await this.req("POST", "/auth/step-up/totp", { code });
|
|
28616
|
+
async stepUpTotp(code, approvalId) {
|
|
28617
|
+
const r = await this.req("POST", "/auth/step-up/totp", approvalId ? { code, approval_id: approvalId } : { code });
|
|
28467
28618
|
this.onToken?.(r.token);
|
|
28468
28619
|
return r;
|
|
28469
28620
|
}
|
|
@@ -28471,11 +28622,23 @@ var KutrionClient = class {
|
|
|
28471
28622
|
webauthnStepUpOptions() {
|
|
28472
28623
|
return this.req("POST", "/auth/webauthn/step-up/options");
|
|
28473
28624
|
}
|
|
28474
|
-
async webauthnStepUpVerify(response) {
|
|
28475
|
-
const r = await this.req("POST", "/auth/webauthn/step-up/verify", { response });
|
|
28625
|
+
async webauthnStepUpVerify(response, approvalId) {
|
|
28626
|
+
const r = await this.req("POST", "/auth/webauthn/step-up/verify", approvalId ? { response, approval_id: approvalId } : { response });
|
|
28476
28627
|
this.onToken?.(r.token);
|
|
28477
28628
|
return r;
|
|
28478
28629
|
}
|
|
28630
|
+
// #3301: the new-Hub approval page (server/src/modules/hub-keys.ts). The read returns only what the
|
|
28631
|
+
// approval's own session may see; an unknown, spent or foreign id is a 404 or `{status:"expired"}`.
|
|
28632
|
+
hubApprovalRead(id) {
|
|
28633
|
+
return this.req("GET", `/auth/hub-channel-keys/approvals/${encodeURIComponent(id)}`);
|
|
28634
|
+
}
|
|
28635
|
+
hubApprovalPassword(id, password) {
|
|
28636
|
+
return this.req("POST", `/auth/hub-channel-keys/approvals/${encodeURIComponent(id)}/password`, { password });
|
|
28637
|
+
}
|
|
28638
|
+
// Idempotent for a confirmed approval of the same session, so the page calls it straight after confirm.
|
|
28639
|
+
hubApprovalComplete(id) {
|
|
28640
|
+
return this.req("POST", `/auth/hub-channel-keys/approvals/${encodeURIComponent(id)}/complete`);
|
|
28641
|
+
}
|
|
28479
28642
|
// #1723: the browser half of the desktop step-up handoff. The ceremony above has already run and
|
|
28480
28643
|
// recorded the proof; this hands the request id back and gets the app's custom-scheme return URL.
|
|
28481
28644
|
// The proof must be newer than the request, so it is only ever called after a verify.
|
|
@@ -28708,6 +28871,9 @@ var KutrionClient = class {
|
|
|
28708
28871
|
credentialHealth() {
|
|
28709
28872
|
return this.req("GET", "/vault/health");
|
|
28710
28873
|
}
|
|
28874
|
+
credentialHealthSummary() {
|
|
28875
|
+
return this.req("GET", "/vault/health/summary");
|
|
28876
|
+
}
|
|
28711
28877
|
// #745: duplicate credentials, found server-side across every vault the caller can reach. No secret
|
|
28712
28878
|
// is returned — members carry an opaque secretGroup label so a client can show which ones agree.
|
|
28713
28879
|
credentialDuplicates() {
|
|
@@ -28801,7 +28967,7 @@ var KutrionClient = class {
|
|
|
28801
28967
|
}
|
|
28802
28968
|
/** enroll: SSH rows with a username and no password are created with no credential, for a bulk enrollment (#2635). */
|
|
28803
28969
|
commitConnImport(vaultId, connections, opts2 = {}) {
|
|
28804
|
-
return this.req("POST", "/connection-import/commit", { vault_id: vaultId, connections, ...opts2.enroll ? { enroll: true } : {} });
|
|
28970
|
+
return this.req("POST", "/connection-import/commit", { vault_id: vaultId, connections, ...opts2.enroll ? { enroll: true } : {}, ...opts2.reach_via ? { reach_via: opts2.reach_via } : {} });
|
|
28805
28971
|
}
|
|
28806
28972
|
// Raw text download of a provider's sample/template file (carries the auth token).
|
|
28807
28973
|
async importTemplate(provider) {
|
|
@@ -29492,6 +29658,54 @@ var KutrionClient = class {
|
|
|
29492
29658
|
{ scope, resource_id: resourceId }
|
|
29493
29659
|
);
|
|
29494
29660
|
}
|
|
29661
|
+
// --- #3264: port-forward allow-list. Owners and admins only. Opening the policy and adding a target
|
|
29662
|
+
// answer 403 step_up_required until a fresh check; 409 port_forward_target_exists / port_forward_target_limit. ---
|
|
29663
|
+
getPortForwardPolicy() {
|
|
29664
|
+
return this.req("GET", "/org/port-forward-policy");
|
|
29665
|
+
}
|
|
29666
|
+
setPortForwardPolicy(policy) {
|
|
29667
|
+
return this.req("PUT", "/org/port-forward-policy", { policy });
|
|
29668
|
+
}
|
|
29669
|
+
listPortForwardTargets() {
|
|
29670
|
+
return this.req("GET", "/org/port-forward-targets");
|
|
29671
|
+
}
|
|
29672
|
+
addPortForwardTarget(input) {
|
|
29673
|
+
return this.req("POST", "/org/port-forward-targets", input);
|
|
29674
|
+
}
|
|
29675
|
+
removePortForwardTarget(id) {
|
|
29676
|
+
return this.req("DELETE", `/org/port-forward-targets/${encodeURIComponent(id)}`);
|
|
29677
|
+
}
|
|
29678
|
+
// --- #3311: connectors. Org owners and admins only; every route is a 404 where the deployment has the
|
|
29679
|
+
// tunnel off, a 402 `feature: "connectors"` at the plan's cap. ---
|
|
29680
|
+
listConnectors() {
|
|
29681
|
+
return this.req("GET", "/connectors");
|
|
29682
|
+
}
|
|
29683
|
+
getConnector(id) {
|
|
29684
|
+
return this.req("GET", `/connectors/${encodeURIComponent(id)}`);
|
|
29685
|
+
}
|
|
29686
|
+
createConnector(name) {
|
|
29687
|
+
return this.req("POST", "/connectors", { name });
|
|
29688
|
+
}
|
|
29689
|
+
/** 403 step_up_required until a fresh check; 402 when reviving a revoked connector would pass the cap. */
|
|
29690
|
+
issueConnectorEnrolmentToken(id) {
|
|
29691
|
+
return this.req("POST", `/connectors/${encodeURIComponent(id)}/enrolment-token`);
|
|
29692
|
+
}
|
|
29693
|
+
revokeConnector(id) {
|
|
29694
|
+
return this.req("POST", `/connectors/${encodeURIComponent(id)}/revoke`);
|
|
29695
|
+
}
|
|
29696
|
+
/** 400 invalid_route lists problems by index; 409 wide_cidr_acknowledgement_required lists `wide` (resend with acknowledge_wide); 409 connector_revoked. */
|
|
29697
|
+
getConnectorRoutes(id) {
|
|
29698
|
+
return this.req("GET", `/connectors/${encodeURIComponent(id)}/routes`);
|
|
29699
|
+
}
|
|
29700
|
+
/** Replaces the whole set. */
|
|
29701
|
+
setConnectorRoutes(id, routes, acknowledgeWide = false) {
|
|
29702
|
+
return this.req("PUT", `/connectors/${encodeURIComponent(id)}/routes`, { routes, acknowledge_wide: acknowledgeWide });
|
|
29703
|
+
}
|
|
29704
|
+
/** Dry run by default. `confirm: true` writes the pins and needs a fresh step-up (403 step_up_required).
|
|
29705
|
+
* Fingerprints come only from the caller's input; nothing observed through the connector is returned. */
|
|
29706
|
+
pinConnectorHostKeys(id, input) {
|
|
29707
|
+
return this.req("POST", `/connectors/${encodeURIComponent(id)}/host-keys`, input);
|
|
29708
|
+
}
|
|
29495
29709
|
// --- SSH host-key pins (#951 records them, #959 makes them answerable) ---
|
|
29496
29710
|
// Read and discard only, org admin/owner. There is deliberately no create or update: nothing in the
|
|
29497
29711
|
// product accepts a fingerprint from a human, because a pin means "this is what the host actually
|
|
@@ -29504,8 +29718,9 @@ var KutrionClient = class {
|
|
|
29504
29718
|
return this.req("DELETE", `/ssh/host-keys/${id}`);
|
|
29505
29719
|
}
|
|
29506
29720
|
/**
|
|
29507
|
-
* #2743: pins the key a host_key_unconfirmed refusal showed this caller.
|
|
29508
|
-
* fingerprint it observed for this caller
|
|
29721
|
+
* #2743: pins the key a host_key_unconfirmed refusal showed this caller. For a first-use refusal the server accepts only the
|
|
29722
|
+
* fingerprint it observed for this caller; for a host behind a jump chain (#3336) it holds no observed
|
|
29723
|
+
* key, so an organization admin passes the fingerprint read from the host itself and the server checks it. 403 step_up_required
|
|
29509
29724
|
* or host_key_confirm_forbidden; 409 host_key_not_observed, host_key_fingerprint_mismatch or
|
|
29510
29725
|
* host_key_mismatch.
|
|
29511
29726
|
*/
|
|
@@ -30917,6 +31132,8 @@ var SSH_REFUSAL_CLASSIFICATION = {
|
|
|
30917
31132
|
unsupported_credential: { family: "refused", retry: "pointless" },
|
|
30918
31133
|
// The connection's port is not one the broker will dial.
|
|
30919
31134
|
bad_port: { family: "refused", retry: "pointless" },
|
|
31135
|
+
// The org's port-forward allow-list does not list the target; it stays unlisted until an admin adds it.
|
|
31136
|
+
forward_target_refused: { family: "refused", retry: "pointless" },
|
|
30920
31137
|
// The client presented none; reconnecting presents none again.
|
|
30921
31138
|
missing_join_token: { family: "refused", retry: "pointless" },
|
|
30922
31139
|
// The same token is presented again and rejected again.
|
|
@@ -30932,6 +31149,8 @@ var SSH_REFUSAL_CLASSIFICATION = {
|
|
|
30932
31149
|
connection_unavailable: { family: "refused", retry: "pointless" },
|
|
30933
31150
|
// Same as not_found, for the credential the connection names.
|
|
30934
31151
|
credential_not_found: { family: "refused", retry: "pointless" },
|
|
31152
|
+
// #3386: a temporary credential past its expiry; the same attempt earns the same refusal.
|
|
31153
|
+
credential_expired: { family: "refused", retry: "pointless" },
|
|
30935
31154
|
// #1949: this app does not own the surface and the user holds no live grant for it. "pointless" —
|
|
30936
31155
|
// the same token from the same app earns the identical refusal, and the remedy is elsewhere: open
|
|
30937
31156
|
// the app that owns the surface, or have an administrator grant access from this one.
|
|
@@ -31040,10 +31259,48 @@ var HOST_KEY_CONFIRM_COPY = {
|
|
|
31040
31259
|
forbidden: "Only an organization admin, or an editor of the vault that holds this connection, can trust a new host key. Ask one of them to confirm it; they should check the fingerprint above against the host itself first.",
|
|
31041
31260
|
notThisConnection: "This key is not for the host this connection opens. It belongs to a jump host on the way, or Kutrion could not tell which host it belongs to, so it cannot be trusted from this terminal. Open that host's own connection and confirm its key there; that needs an organization admin, or an editor of the vault that holds that connection."
|
|
31042
31261
|
};
|
|
31262
|
+
var str3 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
31043
31263
|
var pinHost = (h) => h.trim().toLowerCase().replace(/\.$/, "");
|
|
31044
31264
|
function isOwnHost(pending, own) {
|
|
31045
31265
|
return !!own?.host && own.port !== void 0 && pending.port !== void 0 && pending.port !== null && pending.port === Number(own.port) && pinHost(pending.host) === pinHost(own.host);
|
|
31046
31266
|
}
|
|
31267
|
+
var HOST_KEY_CHAINED_COPY = {
|
|
31268
|
+
heading: "Confirm this host's identity before connecting",
|
|
31269
|
+
body: "This host is reached through a jump host, and its key has not been confirmed yet, so Kutrion paused before signing in and did not send your credential. Confirming records this key for the whole organization; every later connection must match it.",
|
|
31270
|
+
help: "Read the fingerprint on the host itself, not through the jump host. Run this command on the host (it shows the Ed25519 key; use the matching file if the host uses another key type):",
|
|
31271
|
+
command: "ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub",
|
|
31272
|
+
fingerprintLabel: "Fingerprint from the host",
|
|
31273
|
+
fingerprintHint: "Enter the value that starts with SHA256:",
|
|
31274
|
+
invalid: "That is not a fingerprint. A fingerprint starts with SHA256: followed by 43 characters.",
|
|
31275
|
+
confirm: "Confirm and trust this host",
|
|
31276
|
+
confirming: "Confirming",
|
|
31277
|
+
nonAdmin: "Only an organization admin can confirm this host's key. Ask one to read the fingerprint on the host and confirm it.",
|
|
31278
|
+
mismatch: "That fingerprint does not match. Read it again on the host itself and try again.",
|
|
31279
|
+
expired: "The time to confirm has run out. Connect again, then confirm.",
|
|
31280
|
+
failed: "Could not confirm this host key.",
|
|
31281
|
+
checkingRole: "Checking your role\u2026",
|
|
31282
|
+
notThisConnection: "Open this host's own connection to confirm its key.",
|
|
31283
|
+
banner: "Connection paused: this host's key has not been confirmed."
|
|
31284
|
+
};
|
|
31285
|
+
var HOST_KEY_FINGERPRINT_PATTERN = /SHA256:[A-Za-z0-9+/]{43}(?![A-Za-z0-9+/=])/;
|
|
31286
|
+
var HOST_KEY_FINGERPRINT_EXACT = /^SHA256:[A-Za-z0-9+/]{43}$/;
|
|
31287
|
+
function extractHostKeyFingerprint(input) {
|
|
31288
|
+
return input.match(HOST_KEY_FINGERPRINT_PATTERN)?.[0] ?? null;
|
|
31289
|
+
}
|
|
31290
|
+
function hostKeyChainedFromFrame(frame) {
|
|
31291
|
+
if (frame?.code !== "host_key_unconfirmed" || !frame.detail || typeof frame.detail !== "object") return null;
|
|
31292
|
+
const d = frame.detail;
|
|
31293
|
+
const host2 = str3(d.host);
|
|
31294
|
+
if (!host2 || str3(d.presented_fingerprint) || str3(d.connector_id)) return null;
|
|
31295
|
+
const port = typeof d.port === "number" ? d.port : void 0;
|
|
31296
|
+
return { target: port === void 0 ? host2 : `${host2}:${port}`, host: host2, port };
|
|
31297
|
+
}
|
|
31298
|
+
function hostKeyConfirmFailure(code) {
|
|
31299
|
+
if (code === "host_key_confirm_forbidden" || code === "forbidden") return "forbidden";
|
|
31300
|
+
if (code === "host_key_fingerprint_mismatch" || code === "host_key_mismatch") return "mismatch";
|
|
31301
|
+
if (code === "host_key_not_observed") return "expired";
|
|
31302
|
+
return "other";
|
|
31303
|
+
}
|
|
31047
31304
|
|
|
31048
31305
|
// ../packages/shared/src/conn-import-plan.ts
|
|
31049
31306
|
init_define_KT_HUB_FILES();
|
|
@@ -31051,6 +31308,18 @@ function connImportNeedsCredential(row) {
|
|
|
31051
31308
|
return !row.username || !row.password && !row.credential_id && !(row.enroll && row.protocol === "SFTP");
|
|
31052
31309
|
}
|
|
31053
31310
|
var CONN_IMPORT_NO_CREDENTIAL_HINT = "Will not import: this export carries no password. Enter a username and password to import it.";
|
|
31311
|
+
var CONN_IMPORT_BINDING_COPY = {
|
|
31312
|
+
title: "Import result",
|
|
31313
|
+
boundHeading: "Credentials now limited to these hosts",
|
|
31314
|
+
bound: (credential, n) => `${credential} is now limited to these ${n} host${n === 1 ? "" : "s"}:`,
|
|
31315
|
+
needsHeading: "Needs a credential binding",
|
|
31316
|
+
needsIntro: (n) => `${n} host${n === 1 ? "" : "s"} will be refused until a vault editor adds ${n === 1 ? "it" : "them"} to the credential.`,
|
|
31317
|
+
needsRow: (name, dest) => `${name} (${dest}) will be refused until a vault editor adds this host to the credential.`,
|
|
31318
|
+
needsWhere: "A vault editor can add the host to the credential's destinations in the Kutrion web app.",
|
|
31319
|
+
done: "Done",
|
|
31320
|
+
next: "Continue"
|
|
31321
|
+
};
|
|
31322
|
+
var connImportDestLabel = (d) => `${d.protocol} ${d.host}:${d.port}`;
|
|
31054
31323
|
|
|
31055
31324
|
// ../packages/shared/src/jump-refusal.ts
|
|
31056
31325
|
init_define_KT_HUB_FILES();
|
|
@@ -31067,11 +31336,11 @@ function isJumpRefusalCode(code) {
|
|
|
31067
31336
|
return !!code && JUMP_REFUSAL_CODES.includes(code);
|
|
31068
31337
|
}
|
|
31069
31338
|
var MAX_JUMP_HOPS = 4;
|
|
31070
|
-
var
|
|
31339
|
+
var str4 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
31071
31340
|
function parseJumpRefusal(body) {
|
|
31072
31341
|
if (!body || typeof body !== "object") return null;
|
|
31073
31342
|
const b = body;
|
|
31074
|
-
const code =
|
|
31343
|
+
const code = str4(b.code) ?? str4(b.error);
|
|
31075
31344
|
if (!isJumpRefusalCode(code)) return null;
|
|
31076
31345
|
const nested = b.detail && typeof b.detail === "object" ? b.detail : {};
|
|
31077
31346
|
const pick2 = (k) => b[k] !== void 0 ? b[k] : nested[k];
|
|
@@ -31079,14 +31348,14 @@ function parseJumpRefusal(body) {
|
|
|
31079
31348
|
const allowed = pick2("allowed_destinations");
|
|
31080
31349
|
return {
|
|
31081
31350
|
code,
|
|
31082
|
-
hopConnectionId:
|
|
31351
|
+
hopConnectionId: str4(pick2("hop_connection_id")),
|
|
31083
31352
|
hopIndex: typeof idx === "number" && Number.isFinite(idx) ? idx : null,
|
|
31084
|
-
detail: typeof b.detail === "string" ?
|
|
31353
|
+
detail: typeof b.detail === "string" ? str4(b.detail) : str4(nested.detail) ?? str4(b.error_description),
|
|
31085
31354
|
allowedDestinations: Array.isArray(allowed) ? allowed.filter((x) => typeof x === "string" && x.trim() !== "") : null
|
|
31086
31355
|
};
|
|
31087
31356
|
}
|
|
31088
31357
|
function hopName(r, names) {
|
|
31089
|
-
return r.hopConnectionId ?
|
|
31358
|
+
return r.hopConnectionId ? str4(names(r.hopConnectionId)) : null;
|
|
31090
31359
|
}
|
|
31091
31360
|
function jumpHopLabel(r, names) {
|
|
31092
31361
|
const name = hopName(r, names);
|
|
@@ -31098,7 +31367,7 @@ function jumpHopPossessive(r, names) {
|
|
|
31098
31367
|
}
|
|
31099
31368
|
function jumpRefusalSummary(r, names, openedName) {
|
|
31100
31369
|
const name = hopName(r, names);
|
|
31101
|
-
const opened =
|
|
31370
|
+
const opened = str4(openedName);
|
|
31102
31371
|
const target = opened ? `\u201C${opened}\u201D` : "this connection";
|
|
31103
31372
|
const where = r.hopIndex !== null && r.hopIndex > 0 ? ` at jump host ${r.hopIndex + 1} of the chain,` : "";
|
|
31104
31373
|
const opening = `Kutrion did not reach ${target}. It was refused${where} one hop earlier,`;
|
|
@@ -31130,14 +31399,14 @@ function jumpAllowedDestinationsLabel(r, names) {
|
|
|
31130
31399
|
function parseJumpHostInUse(body) {
|
|
31131
31400
|
if (!body || typeof body !== "object") return null;
|
|
31132
31401
|
const b = body;
|
|
31133
|
-
if (
|
|
31402
|
+
if (str4(b.error) !== "jump_host_in_use") return null;
|
|
31134
31403
|
const rows = Array.isArray(b.used_by) ? b.used_by : [];
|
|
31135
|
-
const usedBy = rows.filter((x) => !!x && typeof x === "object").map((x) => ({ id:
|
|
31404
|
+
const usedBy = rows.filter((x) => !!x && typeof x === "object").map((x) => ({ id: str4(x.id) ?? "", name: str4(x.name) ?? "" })).filter((x) => x.id && x.name);
|
|
31136
31405
|
return { usedBy };
|
|
31137
31406
|
}
|
|
31138
31407
|
var JUMP_HOST_IN_USE_TITLE = "Other connections are reached through this one";
|
|
31139
31408
|
function jumpHostInUseSummary(r, deletingName) {
|
|
31140
|
-
const name =
|
|
31409
|
+
const name = str4(deletingName);
|
|
31141
31410
|
const subject2 = name ? `\u201C${name}\u201D` : "this connection";
|
|
31142
31411
|
const n = r.usedBy.length;
|
|
31143
31412
|
if (!n) {
|
|
@@ -33163,11 +33432,9 @@ function osKeychainAvailable() {
|
|
|
33163
33432
|
}
|
|
33164
33433
|
}
|
|
33165
33434
|
|
|
33166
|
-
// src/
|
|
33435
|
+
// src/hub-seed-login.ts
|
|
33167
33436
|
init_define_KT_HUB_FILES();
|
|
33168
|
-
var
|
|
33169
|
-
var import_node_child_process = require("node:child_process");
|
|
33170
|
-
var import_node_http = require("node:http");
|
|
33437
|
+
var import_os3 = require("os");
|
|
33171
33438
|
|
|
33172
33439
|
// src/device-identity.ts
|
|
33173
33440
|
init_define_KT_HUB_FILES();
|
|
@@ -33206,90 +33473,6 @@ async function bindRefreshKey(deviceId, devicePkcs8B64, certMessage, transport)
|
|
|
33206
33473
|
return transport.unlock({ device_id: deviceId, signature, refresh_key: { spki, certification, hardware: false } });
|
|
33207
33474
|
}
|
|
33208
33475
|
|
|
33209
|
-
// src/hub/borrow-confirm.ts
|
|
33210
|
-
init_define_KT_HUB_FILES();
|
|
33211
|
-
var import_node_url = require("node:url");
|
|
33212
|
-
var text = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
33213
|
-
var SCRIPTS = [
|
|
33214
|
-
["latin", new RegExp("\\p{Script=Latin}", "u")],
|
|
33215
|
-
["cyrillic", new RegExp("\\p{Script=Cyrillic}", "u")],
|
|
33216
|
-
["greek", new RegExp("\\p{Script=Greek}", "u")],
|
|
33217
|
-
["armenian", new RegExp("\\p{Script=Armenian}", "u")],
|
|
33218
|
-
["cjk", /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u],
|
|
33219
|
-
["hangul", new RegExp("\\p{Script=Hangul}", "u")],
|
|
33220
|
-
["arabic", new RegExp("\\p{Script=Arabic}", "u")],
|
|
33221
|
-
["hebrew", new RegExp("\\p{Script=Hebrew}", "u")]
|
|
33222
|
-
];
|
|
33223
|
-
function mixesScripts(s) {
|
|
33224
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33225
|
-
for (const ch of s) {
|
|
33226
|
-
if (!new RegExp("\\p{L}", "u").test(ch)) continue;
|
|
33227
|
-
seen.add(SCRIPTS.find(([, re]) => re.test(ch))?.[0] ?? "other");
|
|
33228
|
-
}
|
|
33229
|
-
return seen.size > 1;
|
|
33230
|
-
}
|
|
33231
|
-
function joinedAccount(user, lastAccount) {
|
|
33232
|
-
const at = user.email.lastIndexOf("@");
|
|
33233
|
-
const localPart = at >= 0 ? user.email.slice(0, at) : user.email;
|
|
33234
|
-
const raw = at >= 0 ? user.email.slice(at + 1) : "";
|
|
33235
|
-
const ascii = raw ? (0, import_node_url.domainToASCII)(raw.toLowerCase()) || raw : "";
|
|
33236
|
-
return {
|
|
33237
|
-
email: user.email,
|
|
33238
|
-
localPart,
|
|
33239
|
-
domain: ascii,
|
|
33240
|
-
internationalDomain: ascii !== raw.toLowerCase(),
|
|
33241
|
-
mixedScript: mixesScripts(localPart),
|
|
33242
|
-
displayName: text(user.display_name),
|
|
33243
|
-
orgName: text(user.org_name),
|
|
33244
|
-
accountChanged: lastAccount !== null && lastAccount !== user.id
|
|
33245
|
-
};
|
|
33246
|
-
}
|
|
33247
|
-
function createBorrowConfirm(deps) {
|
|
33248
|
-
let account = null;
|
|
33249
|
-
let heldId = null;
|
|
33250
|
-
let heldName = null;
|
|
33251
|
-
const remember = (user) => {
|
|
33252
|
-
if (deps.lastAccount.get() !== user.id) deps.lastAccount.set(user.id);
|
|
33253
|
-
};
|
|
33254
|
-
const reject = async () => {
|
|
33255
|
-
account = null;
|
|
33256
|
-
heldId = null;
|
|
33257
|
-
heldName = null;
|
|
33258
|
-
const held = deps.borrow.discard();
|
|
33259
|
-
if (!held) return;
|
|
33260
|
-
deps.pin.clear();
|
|
33261
|
-
await deps.revoke(held.deviceId, held.bearer).catch(() => void 0);
|
|
33262
|
-
};
|
|
33263
|
-
return {
|
|
33264
|
-
hold(user, deviceId, deviceName) {
|
|
33265
|
-
account = user;
|
|
33266
|
-
heldId = deviceId;
|
|
33267
|
-
heldName = deviceName ?? null;
|
|
33268
|
-
},
|
|
33269
|
-
pending: () => account ? { ...joinedAccount(account, deps.lastAccount.get()), deviceName: heldName } : null,
|
|
33270
|
-
seen(user) {
|
|
33271
|
-
if (!account && user) remember(user);
|
|
33272
|
-
},
|
|
33273
|
-
async confirm() {
|
|
33274
|
-
const user = account;
|
|
33275
|
-
const id = heldId;
|
|
33276
|
-
if (!user || !id) return false;
|
|
33277
|
-
if (!deps.borrow.commit(id)) {
|
|
33278
|
-
await reject();
|
|
33279
|
-
return false;
|
|
33280
|
-
}
|
|
33281
|
-
account = null;
|
|
33282
|
-
heldId = null;
|
|
33283
|
-
remember(user);
|
|
33284
|
-
return true;
|
|
33285
|
-
},
|
|
33286
|
-
reject,
|
|
33287
|
-
forgetHub() {
|
|
33288
|
-
deps.pin.clear();
|
|
33289
|
-
}
|
|
33290
|
-
};
|
|
33291
|
-
}
|
|
33292
|
-
|
|
33293
33476
|
// src/hub-login.ts
|
|
33294
33477
|
init_define_KT_HUB_FILES();
|
|
33295
33478
|
var import_os2 = require("os");
|
|
@@ -33772,16 +33955,19 @@ async function redeemEnrolment(p) {
|
|
|
33772
33955
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
33773
33956
|
// #2975: this build asks the user to confirm a first-contact account, and says so, so a server
|
|
33774
33957
|
// that refuses builds without that step can tell the two apart.
|
|
33775
|
-
body: JSON.stringify({ code: p.code, signature, audience, capabilities: ["i1"] }),
|
|
33958
|
+
body: JSON.stringify({ code: p.code, signature, audience, capabilities: ["i1"], hub_pk: p.hubPk }),
|
|
33776
33959
|
signal: AbortSignal.timeout(15e3)
|
|
33777
33960
|
});
|
|
33961
|
+
p.latch?.observe(audience, res.headers);
|
|
33962
|
+
const parsed = await res.json().catch(() => null);
|
|
33778
33963
|
if (!res.ok) {
|
|
33779
33964
|
if (res.status !== 403) return null;
|
|
33780
|
-
const refusal =
|
|
33781
|
-
return refusal?.error === "device_clients_disabled" || refusal?.error === "satellite_update_required" ? refusal.error : null;
|
|
33965
|
+
const refusal = parsed;
|
|
33966
|
+
return refusal?.error === "device_clients_disabled" || refusal?.error === "satellite_update_required" || refusal?.error === "hub_not_bound" ? refusal.error : null;
|
|
33782
33967
|
}
|
|
33783
|
-
const body =
|
|
33968
|
+
const body = parsed ?? {};
|
|
33784
33969
|
if (!body.device?.id || !body.access_token || !body.refresh_token) return null;
|
|
33970
|
+
p.latch?.tokenIssued(audience, res, body);
|
|
33785
33971
|
return {
|
|
33786
33972
|
deviceId: body.device.id,
|
|
33787
33973
|
deviceName: typeof body.device.name === "string" ? body.device.name : null,
|
|
@@ -33836,9 +34022,11 @@ async function borrowAndEnrol(deps) {
|
|
|
33836
34022
|
apiBase: deps.apiBase,
|
|
33837
34023
|
identity: deps.identity,
|
|
33838
34024
|
code: borrowed.code,
|
|
34025
|
+
hubPk: borrowed.hubPublicKey,
|
|
34026
|
+
latch: deps.latch,
|
|
33839
34027
|
fetchImpl: deps.fetchImpl
|
|
33840
34028
|
});
|
|
33841
|
-
if (enrolment === "device_clients_disabled" || enrolment === "satellite_update_required") return { ok: false, reason: enrolment };
|
|
34029
|
+
if (enrolment === "device_clients_disabled" || enrolment === "satellite_update_required" || enrolment === "hub_not_bound") return { ok: false, reason: enrolment };
|
|
33842
34030
|
return enrolment ? { ok: true, enrolment, hubPublicKey: borrowed.hubPublicKey } : { ok: false, reason: "enrol_refused" };
|
|
33843
34031
|
}
|
|
33844
34032
|
function createBorrowSession(store, client, resolveDeps) {
|
|
@@ -33907,6 +34095,191 @@ function createBorrowSession(store, client, resolveDeps) {
|
|
|
33907
34095
|
};
|
|
33908
34096
|
}
|
|
33909
34097
|
|
|
34098
|
+
// src/hub/hub-key.ts
|
|
34099
|
+
init_define_KT_HUB_FILES();
|
|
34100
|
+
var FEATURES_HEADER = "kutrion-features";
|
|
34101
|
+
var HUB_KEY_FEATURE = "hub-key-check";
|
|
34102
|
+
var HUB_KEY_COPY = {
|
|
34103
|
+
notVouched: "Kutrion could not be confirmed for your account. Sign in to the Kutrion app directly.",
|
|
34104
|
+
notBound: "Sign in to the Kutrion app again."
|
|
34105
|
+
};
|
|
34106
|
+
function advertisesHubKeyCheck(headers) {
|
|
34107
|
+
let raw;
|
|
34108
|
+
try {
|
|
34109
|
+
raw = headers?.get(FEATURES_HEADER);
|
|
34110
|
+
} catch {
|
|
34111
|
+
return false;
|
|
34112
|
+
}
|
|
34113
|
+
return !!raw && raw.split(",").some((v) => v.trim().toLowerCase() === HUB_KEY_FEATURE);
|
|
34114
|
+
}
|
|
34115
|
+
function isApiNotFound(status, body) {
|
|
34116
|
+
return status === 404 && body?.error === "not_found";
|
|
34117
|
+
}
|
|
34118
|
+
var str5 = (v) => typeof v === "string" && v.length > 0;
|
|
34119
|
+
function issuesTokens(body) {
|
|
34120
|
+
const o = body && typeof body === "object" ? body : null;
|
|
34121
|
+
const device = o?.device && typeof o.device === "object" ? o.device : null;
|
|
34122
|
+
return !!o && str5(device?.id) && str5(o.access_token) && str5(o.refresh_token);
|
|
34123
|
+
}
|
|
34124
|
+
function createFeatureLatch(store, log3) {
|
|
34125
|
+
const read2 = () => {
|
|
34126
|
+
try {
|
|
34127
|
+
return store.read();
|
|
34128
|
+
} catch {
|
|
34129
|
+
return null;
|
|
34130
|
+
}
|
|
34131
|
+
};
|
|
34132
|
+
const write2 = (origin) => {
|
|
34133
|
+
try {
|
|
34134
|
+
store.write(origin);
|
|
34135
|
+
} catch {
|
|
34136
|
+
log3("[hub-key] could not persist the feature latch");
|
|
34137
|
+
}
|
|
34138
|
+
};
|
|
34139
|
+
const observe = (origin, headers) => {
|
|
34140
|
+
if (advertisesHubKeyCheck(headers) && read2() !== origin) write2(origin);
|
|
34141
|
+
};
|
|
34142
|
+
return {
|
|
34143
|
+
// An unreadable store reads as latched: not knowing is never a reason to accept a legacy answer.
|
|
34144
|
+
latched(origin) {
|
|
34145
|
+
try {
|
|
34146
|
+
return store.read() === origin;
|
|
34147
|
+
} catch {
|
|
34148
|
+
return true;
|
|
34149
|
+
}
|
|
34150
|
+
},
|
|
34151
|
+
observe,
|
|
34152
|
+
verdictSeen(origin) {
|
|
34153
|
+
if (read2() !== origin) write2(origin);
|
|
34154
|
+
},
|
|
34155
|
+
tokenIssued(origin, res, body) {
|
|
34156
|
+
if (res.status < 200 || res.status > 299 || res.redirected !== false) return;
|
|
34157
|
+
let type = "";
|
|
34158
|
+
try {
|
|
34159
|
+
type = res.headers?.get("content-type") ?? "";
|
|
34160
|
+
} catch {
|
|
34161
|
+
return;
|
|
34162
|
+
}
|
|
34163
|
+
if (!type.toLowerCase().includes("application/json") || !issuesTokens(body)) return;
|
|
34164
|
+
if (advertisesHubKeyCheck(res.headers)) {
|
|
34165
|
+
observe(origin, res.headers);
|
|
34166
|
+
return;
|
|
34167
|
+
}
|
|
34168
|
+
if (read2() !== origin) return;
|
|
34169
|
+
write2(null);
|
|
34170
|
+
log3(`[hub-key] unlatched ${origin}: its token-issuing response carried no ${FEATURES_HEADER}`);
|
|
34171
|
+
}
|
|
34172
|
+
};
|
|
34173
|
+
}
|
|
34174
|
+
async function checkHubKey(p) {
|
|
34175
|
+
const origin = apiOrigin(p.apiBase);
|
|
34176
|
+
if (!origin || !p.bearer) return "unavailable";
|
|
34177
|
+
let res;
|
|
34178
|
+
try {
|
|
34179
|
+
res = await (p.fetchImpl ?? fetch)(`${p.apiBase}/auth/hub-channel-keys/check`, {
|
|
34180
|
+
method: "POST",
|
|
34181
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${p.bearer}`, ...p.headers },
|
|
34182
|
+
body: JSON.stringify({ channel_key: p.hubPk }),
|
|
34183
|
+
redirect: "error",
|
|
34184
|
+
signal: AbortSignal.timeout(p.timeoutMs ?? 1e4)
|
|
34185
|
+
});
|
|
34186
|
+
} catch {
|
|
34187
|
+
return "unavailable";
|
|
34188
|
+
}
|
|
34189
|
+
p.latch.observe(origin, res.headers);
|
|
34190
|
+
const body = await res.json().catch(() => null);
|
|
34191
|
+
if (isApiNotFound(res.status, body) && !p.latch.latched(origin)) return "legacy";
|
|
34192
|
+
if (!res.ok) return "unavailable";
|
|
34193
|
+
const v = body?.verdict;
|
|
34194
|
+
if (v !== "vouched" && v !== "first_contact" && v !== "new_hub" && v !== "refused") return "unavailable";
|
|
34195
|
+
p.latch.verdictSeen(origin);
|
|
34196
|
+
return v;
|
|
34197
|
+
}
|
|
34198
|
+
|
|
34199
|
+
// src/hub/borrow-confirm.ts
|
|
34200
|
+
init_define_KT_HUB_FILES();
|
|
34201
|
+
var import_node_url = require("node:url");
|
|
34202
|
+
var text = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
34203
|
+
var SCRIPTS = [
|
|
34204
|
+
["latin", new RegExp("\\p{Script=Latin}", "u")],
|
|
34205
|
+
["cyrillic", new RegExp("\\p{Script=Cyrillic}", "u")],
|
|
34206
|
+
["greek", new RegExp("\\p{Script=Greek}", "u")],
|
|
34207
|
+
["armenian", new RegExp("\\p{Script=Armenian}", "u")],
|
|
34208
|
+
["cjk", /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u],
|
|
34209
|
+
["hangul", new RegExp("\\p{Script=Hangul}", "u")],
|
|
34210
|
+
["arabic", new RegExp("\\p{Script=Arabic}", "u")],
|
|
34211
|
+
["hebrew", new RegExp("\\p{Script=Hebrew}", "u")]
|
|
34212
|
+
];
|
|
34213
|
+
function mixesScripts(s) {
|
|
34214
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34215
|
+
for (const ch of s) {
|
|
34216
|
+
if (!new RegExp("\\p{L}", "u").test(ch)) continue;
|
|
34217
|
+
seen.add(SCRIPTS.find(([, re]) => re.test(ch))?.[0] ?? "other");
|
|
34218
|
+
}
|
|
34219
|
+
return seen.size > 1;
|
|
34220
|
+
}
|
|
34221
|
+
function joinedAccount(user, lastAccount) {
|
|
34222
|
+
const at = user.email.lastIndexOf("@");
|
|
34223
|
+
const localPart = at >= 0 ? user.email.slice(0, at) : user.email;
|
|
34224
|
+
const raw = at >= 0 ? user.email.slice(at + 1) : "";
|
|
34225
|
+
const ascii = raw ? (0, import_node_url.domainToASCII)(raw.toLowerCase()) || raw : "";
|
|
34226
|
+
return {
|
|
34227
|
+
email: user.email,
|
|
34228
|
+
localPart,
|
|
34229
|
+
domain: ascii,
|
|
34230
|
+
internationalDomain: ascii !== raw.toLowerCase(),
|
|
34231
|
+
mixedScript: mixesScripts(localPart),
|
|
34232
|
+
displayName: text(user.display_name),
|
|
34233
|
+
orgName: text(user.org_name),
|
|
34234
|
+
accountChanged: lastAccount !== null && lastAccount !== user.id
|
|
34235
|
+
};
|
|
34236
|
+
}
|
|
34237
|
+
function createBorrowConfirm(deps) {
|
|
34238
|
+
let account = null;
|
|
34239
|
+
let heldId = null;
|
|
34240
|
+
let heldName = null;
|
|
34241
|
+
const remember = (user) => {
|
|
34242
|
+
if (deps.lastAccount.get() !== user.id) deps.lastAccount.set(user.id);
|
|
34243
|
+
};
|
|
34244
|
+
const reject = async () => {
|
|
34245
|
+
account = null;
|
|
34246
|
+
heldId = null;
|
|
34247
|
+
heldName = null;
|
|
34248
|
+
const held = deps.borrow.discard();
|
|
34249
|
+
if (!held) return;
|
|
34250
|
+
deps.pin.clear();
|
|
34251
|
+
await deps.revoke(held.deviceId, held.bearer).catch(() => void 0);
|
|
34252
|
+
};
|
|
34253
|
+
return {
|
|
34254
|
+
hold(user, deviceId, deviceName) {
|
|
34255
|
+
account = user;
|
|
34256
|
+
heldId = deviceId;
|
|
34257
|
+
heldName = deviceName ?? null;
|
|
34258
|
+
},
|
|
34259
|
+
pending: () => account ? { ...joinedAccount(account, deps.lastAccount.get()), deviceName: heldName } : null,
|
|
34260
|
+
seen(user) {
|
|
34261
|
+
if (!account && user) remember(user);
|
|
34262
|
+
},
|
|
34263
|
+
async confirm() {
|
|
34264
|
+
const user = account;
|
|
34265
|
+
const id = heldId;
|
|
34266
|
+
if (!user || !id) return false;
|
|
34267
|
+
if (!deps.borrow.commit(id)) {
|
|
34268
|
+
await reject();
|
|
34269
|
+
return false;
|
|
34270
|
+
}
|
|
34271
|
+
account = null;
|
|
34272
|
+
heldId = null;
|
|
34273
|
+
remember(user);
|
|
34274
|
+
return true;
|
|
34275
|
+
},
|
|
34276
|
+
reject,
|
|
34277
|
+
forgetHub() {
|
|
34278
|
+
deps.pin.clear();
|
|
34279
|
+
}
|
|
34280
|
+
};
|
|
34281
|
+
}
|
|
34282
|
+
|
|
33910
34283
|
// src/hub/last-account.ts
|
|
33911
34284
|
init_define_KT_HUB_FILES();
|
|
33912
34285
|
var import_node_fs2 = require("node:fs");
|
|
@@ -34004,6 +34377,8 @@ var slot = {
|
|
|
34004
34377
|
refresh: (base) => `${base}#refresh`,
|
|
34005
34378
|
device: (base) => `${base}#device`,
|
|
34006
34379
|
hubPin: (base) => `${base}#hub-pin`,
|
|
34380
|
+
// #3301: the API origin that has advertised the Hub key check; a fact about the server, kept across sign-out.
|
|
34381
|
+
featuresLatch: (base) => `${base}#hub-key-latch`,
|
|
34007
34382
|
lastAccount: (base) => `${base}#last-account`,
|
|
34008
34383
|
// #3051: the device id whose refresh-key bind is settled (bound, or refused for good), so it runs once.
|
|
34009
34384
|
refreshBound: (base) => `${base}#refresh-bound`
|
|
@@ -34079,13 +34454,20 @@ function cliHubStores(base, kc, keychainAvailable) {
|
|
|
34079
34454
|
drop(kc, slot.hubPin(base));
|
|
34080
34455
|
}
|
|
34081
34456
|
};
|
|
34457
|
+
const latch = {
|
|
34458
|
+
read: () => kc.get(slot.featuresLatch(base)) ?? null,
|
|
34459
|
+
write: (origin) => {
|
|
34460
|
+
if (origin === null) kc.clear(slot.featuresLatch(base));
|
|
34461
|
+
else kc.set(slot.featuresLatch(base), origin);
|
|
34462
|
+
}
|
|
34463
|
+
};
|
|
34082
34464
|
const lastAccount = {
|
|
34083
34465
|
get: () => read(kc, slot.lastAccount(base)),
|
|
34084
34466
|
set: (userId) => {
|
|
34085
34467
|
write(kc, slot.lastAccount(base), userId);
|
|
34086
34468
|
}
|
|
34087
34469
|
};
|
|
34088
|
-
return { store, pin, lastAccount, failedWrite: () => failed2 ? { deviceId, bearer } : null };
|
|
34470
|
+
return { store, pin, latch, lastAccount, failedWrite: () => failed2 ? { deviceId, bearer } : null };
|
|
34089
34471
|
}
|
|
34090
34472
|
function borrowedDeviceKey(base, kc) {
|
|
34091
34473
|
return readDevice(kc, base).key;
|
|
@@ -34147,6 +34529,7 @@ var HUB_COPY = {
|
|
|
34147
34529
|
storeFailed: "The sign-in from the Kutrion app could not be saved in this computer's keychain, so that device was signed out and removed. Nothing was kept.",
|
|
34148
34530
|
failure: {
|
|
34149
34531
|
hub_not_running: "The Kutrion app is not running.",
|
|
34532
|
+
hub_timed_out: "The Kutrion app, or you, did not answer in time. Run the login again to retry.",
|
|
34150
34533
|
hub_identity_changed: "The program answering for the Kutrion app is not the one this CLI paired with, so it was not used.",
|
|
34151
34534
|
grant_not_from_hub: "The answer did not come from the paired Kutrion app, so it was not used.",
|
|
34152
34535
|
protocol: "The Kutrion app answered in a way this CLI does not understand.",
|
|
@@ -34160,7 +34543,8 @@ var HUB_COPY = {
|
|
|
34160
34543
|
unavailable: "The Kutrion app could not get a sign-in code from the server.",
|
|
34161
34544
|
enrol_refused: "The server did not accept the sign-in from the Kutrion app.",
|
|
34162
34545
|
device_clients_disabled: "Signing in through the Kutrion app is turned off on this server.",
|
|
34163
|
-
satellite_update_required: "The server needs a newer CLI to sign in through the Kutrion app. Update the CLI, then start a new sign-in from the Kutrion app."
|
|
34546
|
+
satellite_update_required: "The server needs a newer CLI to sign in through the Kutrion app. Update the CLI, then start a new sign-in from the Kutrion app.",
|
|
34547
|
+
hub_not_bound: "Sign in to the Kutrion app again."
|
|
34164
34548
|
}
|
|
34165
34549
|
};
|
|
34166
34550
|
async function presentJoined(confirm, out, ask, lead = HUB_COPY.joinedLead) {
|
|
@@ -34184,13 +34568,14 @@ var hubAbsent = (reason) => reason !== void 0 && ["hub_not_running", "hub_signed
|
|
|
34184
34568
|
async function hubLogin(deps) {
|
|
34185
34569
|
if (!deps.keychainAvailable()) return { kind: "fallback", note: null, reason: "no_keychain" };
|
|
34186
34570
|
if (locationFault(deps.location)) return { kind: "fallback", note: null, reason: "hub_not_running" };
|
|
34187
|
-
const { store, pin, lastAccount, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
34571
|
+
const { store, pin, latch, lastAccount, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
34188
34572
|
const revoke = (deviceId, bearer) => endServerSession(deps.call, deviceId, bearer);
|
|
34189
34573
|
const session = createBorrowSession(store, "cli", () => ({
|
|
34190
34574
|
location: deps.location,
|
|
34191
34575
|
apiBase: deps.base,
|
|
34192
34576
|
pin,
|
|
34193
|
-
revokeReplaced: revoke
|
|
34577
|
+
revokeReplaced: revoke,
|
|
34578
|
+
latch: createFeatureLatch(latch, deps.log ?? ((line) => console.error(line)))
|
|
34194
34579
|
}));
|
|
34195
34580
|
const confirm = createBorrowConfirm({
|
|
34196
34581
|
borrow: { commit: (id) => session.commit(id), discard: () => session.discard() },
|
|
@@ -34241,315 +34626,16 @@ async function hubLogin(deps) {
|
|
|
34241
34626
|
return { kind: "signed_in", user, firstContact: true };
|
|
34242
34627
|
}
|
|
34243
34628
|
|
|
34244
|
-
// src/app-signin.ts
|
|
34245
|
-
var SIGNIN_FLOW_DEADLINE_MS = 3e5;
|
|
34246
|
-
var REQUEST_URI_PREFIX = "urn:ietf:params:oauth:request_uri:";
|
|
34247
|
-
var ID_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
34248
|
-
var HANDLE_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
34249
|
-
var MAX_CALLBACK_BYTES = 2048;
|
|
34250
|
-
var BROWSER_SIGNIN_COPY = {
|
|
34251
|
-
lead: "Opening your web browser to sign in with Kutrion.",
|
|
34252
|
-
waiting: "Waiting for you to approve this CLI in the browser. This request expires in a few minutes.",
|
|
34253
|
-
openIt: (url) => `If the page did not open, go to this address in a browser on this computer:
|
|
34254
|
-
${url}`,
|
|
34255
|
-
signedIn: "Sign in with Kutrion approved.",
|
|
34256
|
-
notYou: "Not kept: that device was signed out and removed.",
|
|
34257
|
-
notKept: HUB_COPY.notKept,
|
|
34258
|
-
unconfirmed: "This CLI could not check which account the sign-in belongs to, so nothing from it was kept. Try again.",
|
|
34259
|
-
storeFailed: "The sign-in could not be saved in this computer's keychain, so that device was signed out and removed. Nothing was kept.",
|
|
34260
|
-
failure: {
|
|
34261
|
-
unavailable: "This Kutrion server does not offer sign-in through the browser.",
|
|
34262
|
-
already_enrolled: "This CLI is already registered on this computer. Run kt logout first.",
|
|
34263
|
-
no_keychain: "There is no OS keychain to keep this sign-in in.",
|
|
34264
|
-
rate_limited: "Too many sign-in attempts. Wait a few minutes and try again.",
|
|
34265
|
-
unreachable: "Kutrion could not be reached. Check the connection and try again.",
|
|
34266
|
-
request_failed: "Kutrion did not accept the sign-in request. Try again.",
|
|
34267
|
-
no_approval_url: "The server did not return an approval page to open.",
|
|
34268
|
-
approval_url_refused: "The approval page the server named is not a secure web address, so it was not used.",
|
|
34269
|
-
listener_failed: "This CLI could not listen on this computer for the approval.",
|
|
34270
|
-
timeout: "The approval did not arrive in time. Start again when you are ready.",
|
|
34271
|
-
access_denied: "The sign-in was denied in the browser. Nothing was kept.",
|
|
34272
|
-
origin_mismatch: "The approval came from a different network than this CLI, so it was refused. Sign in with your password instead.",
|
|
34273
|
-
device_limit: "Your account has reached its device limit. Remove a device you no longer use in the web portal, then try again.",
|
|
34274
|
-
redeem_failed: "The approval could not be completed on this computer. Start again."
|
|
34275
|
-
}
|
|
34276
|
-
};
|
|
34277
|
-
function postJson(base, fetchImpl) {
|
|
34278
|
-
return async (path, body) => {
|
|
34279
|
-
try {
|
|
34280
|
-
const res = await fetchImpl(base + path, {
|
|
34281
|
-
method: "POST",
|
|
34282
|
-
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
34283
|
-
body: JSON.stringify(body),
|
|
34284
|
-
signal: AbortSignal.timeout(15e3)
|
|
34285
|
-
});
|
|
34286
|
-
const parsed = await res.json().catch(() => null);
|
|
34287
|
-
return res.ok ? { ok: true, data: parsed } : { ok: false, status: res.status, body: parsed };
|
|
34288
|
-
} catch {
|
|
34289
|
-
return { ok: false, status: null, body: null };
|
|
34290
|
-
}
|
|
34291
|
-
};
|
|
34292
|
-
}
|
|
34293
|
-
var errorOf = (body) => body && typeof body === "object" ? body.error : void 0;
|
|
34294
|
-
function isUnavailable(status, body) {
|
|
34295
|
-
const e = errorOf(body);
|
|
34296
|
-
if (status === 403) return e === "kutrion_sign_in_disabled" || e === "device_clients_disabled";
|
|
34297
|
-
if (status === 400) return e === "unknown_client";
|
|
34298
|
-
return status === 404;
|
|
34299
|
-
}
|
|
34300
|
-
function requestIdOf(requestUri) {
|
|
34301
|
-
if (typeof requestUri !== "string" || !requestUri.startsWith(REQUEST_URI_PREFIX)) return null;
|
|
34302
|
-
const id = requestUri.slice(REQUEST_URI_PREFIX.length);
|
|
34303
|
-
return ID_RE.test(id) ? id : null;
|
|
34304
|
-
}
|
|
34305
|
-
function approvalUrlRefusal(url, allowLoopbackHttp) {
|
|
34306
|
-
if (typeof url !== "string") return null;
|
|
34307
|
-
let u;
|
|
34308
|
-
try {
|
|
34309
|
-
u = new URL(url);
|
|
34310
|
-
} catch {
|
|
34311
|
-
return null;
|
|
34312
|
-
}
|
|
34313
|
-
const loopback = u.hostname === "localhost" || u.hostname === "127.0.0.1";
|
|
34314
|
-
if (u.protocol !== "https:" && !(allowLoopbackHttp && loopback && u.protocol === "http:")) return null;
|
|
34315
|
-
if (u.username || u.password) return null;
|
|
34316
|
-
return u.href;
|
|
34317
|
-
}
|
|
34318
|
-
function parseCallbackForm(body) {
|
|
34319
|
-
const p = new URLSearchParams(body);
|
|
34320
|
-
const requestId = p.get("request_id");
|
|
34321
|
-
if (!requestId || !ID_RE.test(requestId)) return null;
|
|
34322
|
-
const handle = p.get("handle");
|
|
34323
|
-
const error = p.get("error");
|
|
34324
|
-
if (handle !== null && error === null && HANDLE_RE.test(handle)) return { requestId, handle };
|
|
34325
|
-
if (error !== null && handle === null && (error === "access_denied" || error === "origin_mismatch")) return { requestId, error };
|
|
34326
|
-
return null;
|
|
34327
|
-
}
|
|
34328
|
-
var PAGE = (title, line) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font-family:system-ui;margin:3rem"><h1>${title}</h1><p>${line}</p></body>`;
|
|
34329
|
-
function listenForCallback() {
|
|
34330
|
-
return new Promise((resolve2) => {
|
|
34331
|
-
let expected = null;
|
|
34332
|
-
let origin = null;
|
|
34333
|
-
const queue = [];
|
|
34334
|
-
let waiter = null;
|
|
34335
|
-
const server = (0, import_node_http.createServer)((req, res) => {
|
|
34336
|
-
const headers = { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" };
|
|
34337
|
-
if (req.method !== "POST" || req.url !== "/signin/callback") {
|
|
34338
|
-
res.writeHead(404, headers).end();
|
|
34339
|
-
return;
|
|
34340
|
-
}
|
|
34341
|
-
const sent = req.headers.origin;
|
|
34342
|
-
if (origin && sent !== void 0 && sent !== "null" && sent !== origin) {
|
|
34343
|
-
res.writeHead(403, headers).end();
|
|
34344
|
-
return;
|
|
34345
|
-
}
|
|
34346
|
-
const chunks = [];
|
|
34347
|
-
let size = 0;
|
|
34348
|
-
req.on("data", (c) => {
|
|
34349
|
-
size += c.length;
|
|
34350
|
-
if (size > MAX_CALLBACK_BYTES) {
|
|
34351
|
-
res.writeHead(413, headers).end();
|
|
34352
|
-
req.destroy();
|
|
34353
|
-
return;
|
|
34354
|
-
}
|
|
34355
|
-
chunks.push(c);
|
|
34356
|
-
});
|
|
34357
|
-
req.on("end", () => {
|
|
34358
|
-
if (size > MAX_CALLBACK_BYTES) return;
|
|
34359
|
-
const cb = parseCallbackForm(Buffer.concat(chunks).toString("utf8"));
|
|
34360
|
-
if (!cb || cb.requestId !== expected || queue.length >= 8) {
|
|
34361
|
-
res.writeHead(400, headers).end(PAGE("Not recognized", "This sign-in request is not the one this CLI is waiting for."));
|
|
34362
|
-
return;
|
|
34363
|
-
}
|
|
34364
|
-
res.writeHead(200, headers).end(PAGE("Return to your terminal", "You can close this tab. The CLI is finishing the sign-in."));
|
|
34365
|
-
const w = waiter;
|
|
34366
|
-
waiter = null;
|
|
34367
|
-
if (w) w(cb);
|
|
34368
|
-
else queue.push(cb);
|
|
34369
|
-
});
|
|
34370
|
-
});
|
|
34371
|
-
server.once("error", () => resolve2(null));
|
|
34372
|
-
server.listen(0, "127.0.0.1", () => {
|
|
34373
|
-
const port = server.address().port;
|
|
34374
|
-
resolve2({
|
|
34375
|
-
port,
|
|
34376
|
-
expect: (requestId, approvalOrigin) => {
|
|
34377
|
-
expected = requestId;
|
|
34378
|
-
origin = approvalOrigin ?? null;
|
|
34379
|
-
},
|
|
34380
|
-
next: (until) => new Promise((done) => {
|
|
34381
|
-
const queued = queue.shift();
|
|
34382
|
-
if (queued) {
|
|
34383
|
-
done(queued);
|
|
34384
|
-
return;
|
|
34385
|
-
}
|
|
34386
|
-
const timer = setTimeout(() => {
|
|
34387
|
-
waiter = null;
|
|
34388
|
-
done("timeout");
|
|
34389
|
-
}, Math.max(0, until - Date.now()));
|
|
34390
|
-
waiter = (c) => {
|
|
34391
|
-
clearTimeout(timer);
|
|
34392
|
-
done(c);
|
|
34393
|
-
};
|
|
34394
|
-
}),
|
|
34395
|
-
close: () => {
|
|
34396
|
-
waiter = null;
|
|
34397
|
-
server.close();
|
|
34398
|
-
server.closeAllConnections();
|
|
34399
|
-
}
|
|
34400
|
-
});
|
|
34401
|
-
});
|
|
34402
|
-
});
|
|
34403
|
-
}
|
|
34404
|
-
function openInBrowser(url, platform3 = process.platform, env = process.env) {
|
|
34405
|
-
if (platform3 !== "darwin" && platform3 !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) return Promise.resolve(false);
|
|
34406
|
-
const [cmd, args] = platform3 === "darwin" ? ["open", [url]] : platform3 === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
34407
|
-
return new Promise((resolve2) => {
|
|
34408
|
-
try {
|
|
34409
|
-
const child = (0, import_node_child_process.spawn)(cmd, args, { stdio: "ignore", detached: true });
|
|
34410
|
-
child.once("error", () => resolve2(false));
|
|
34411
|
-
child.once("spawn", () => {
|
|
34412
|
-
child.unref();
|
|
34413
|
-
resolve2(true);
|
|
34414
|
-
});
|
|
34415
|
-
} catch {
|
|
34416
|
-
resolve2(false);
|
|
34417
|
-
}
|
|
34418
|
-
});
|
|
34419
|
-
}
|
|
34420
|
-
async function redeem(post, handle, verifier, key) {
|
|
34421
|
-
const priv = (0, import_node_crypto4.createPrivateKey)({ key: Buffer.from(key.privatePkcs8B64, "base64"), format: "der", type: "pkcs8" });
|
|
34422
|
-
const signature = (0, import_node_crypto4.sign)(null, Buffer.from(handle, "utf8"), priv).toString("base64");
|
|
34423
|
-
const res = await post(
|
|
34424
|
-
"/auth/app-signin/redeem",
|
|
34425
|
-
{ handle, code_verifier: verifier, signature }
|
|
34426
|
-
);
|
|
34427
|
-
if (!res.ok) {
|
|
34428
|
-
if (res.status === 400 && errorOf(res.body) === "invalid_grant") return "invalid_grant";
|
|
34429
|
-
if (res.status === 409 && errorOf(res.body) === "device_limit") return "device_limit";
|
|
34430
|
-
return "failed";
|
|
34431
|
-
}
|
|
34432
|
-
const { device, access_token: access, refresh_token: refresh } = res.data;
|
|
34433
|
-
if (typeof device?.id !== "string" || typeof access !== "string" || typeof refresh !== "string") return "failed";
|
|
34434
|
-
return { deviceId: device.id, privatePkcs8B64: key.privatePkcs8B64, accessToken: access, refreshToken: refresh };
|
|
34435
|
-
}
|
|
34436
|
-
async function runBrowserSignin(deps) {
|
|
34437
|
-
const fail = (reason) => ({ ok: false, reason });
|
|
34438
|
-
if (deps.store.deviceId()) return fail("already_enrolled");
|
|
34439
|
-
if (!deps.store.keychainAvailable()) return fail("no_keychain");
|
|
34440
|
-
const listener = await (deps.listen ?? listenForCallback)();
|
|
34441
|
-
if (!listener) return fail("listener_failed");
|
|
34442
|
-
try {
|
|
34443
|
-
const key = resolveDeviceKey(deps.store, deps.generateKey);
|
|
34444
|
-
const verifier = (0, import_node_crypto4.randomBytes)(32).toString("base64url");
|
|
34445
|
-
const res = await deps.post("/auth/app-signin/request", {
|
|
34446
|
-
client_id: "cli",
|
|
34447
|
-
public_key: key.publicSpkiB64,
|
|
34448
|
-
code_challenge: (0, import_node_crypto4.createHash)("sha256").update(verifier).digest("base64url"),
|
|
34449
|
-
code_challenge_method: "S256",
|
|
34450
|
-
device_name: deps.deviceName().slice(0, 100),
|
|
34451
|
-
loopback_port: listener.port
|
|
34452
|
-
});
|
|
34453
|
-
if (!res.ok) {
|
|
34454
|
-
if (isUnavailable(res.status, res.body)) return fail("unavailable");
|
|
34455
|
-
return fail(res.status === 429 ? "rate_limited" : res.status === null ? "unreachable" : "request_failed");
|
|
34456
|
-
}
|
|
34457
|
-
const requestId = requestIdOf(res.data.request_uri);
|
|
34458
|
-
if (!requestId) return fail("request_failed");
|
|
34459
|
-
if (typeof res.data.approval_url !== "string" || !res.data.approval_url) return fail("no_approval_url");
|
|
34460
|
-
const href = approvalUrlRefusal(res.data.approval_url, deps.allowLoopbackHttp);
|
|
34461
|
-
if (!href) return fail("approval_url_refused");
|
|
34462
|
-
const until = Date.now() + (deps.deadlineMs ?? SIGNIN_FLOW_DEADLINE_MS);
|
|
34463
|
-
deps.out(BROWSER_SIGNIN_COPY.lead);
|
|
34464
|
-
listener.expect(requestId, new URL(href).origin);
|
|
34465
|
-
let waited = listener.next(until);
|
|
34466
|
-
await deps.open(href);
|
|
34467
|
-
deps.out(BROWSER_SIGNIN_COPY.openIt(href));
|
|
34468
|
-
deps.out(BROWSER_SIGNIN_COPY.waiting);
|
|
34469
|
-
for (; ; ) {
|
|
34470
|
-
const cb = await waited;
|
|
34471
|
-
if (cb === "timeout") return fail("timeout");
|
|
34472
|
-
if ("error" in cb) return fail(cb.error);
|
|
34473
|
-
const out = await redeem(deps.post, cb.handle, verifier, key);
|
|
34474
|
-
if (out === "invalid_grant") {
|
|
34475
|
-
if (Date.now() >= until) return fail("timeout");
|
|
34476
|
-
waited = listener.next(until);
|
|
34477
|
-
continue;
|
|
34478
|
-
}
|
|
34479
|
-
if (out === "device_limit") return fail("device_limit");
|
|
34480
|
-
if (out === "failed") return fail("redeem_failed");
|
|
34481
|
-
return { ok: true, held: out };
|
|
34482
|
-
}
|
|
34483
|
-
} finally {
|
|
34484
|
-
listener.close();
|
|
34485
|
-
}
|
|
34486
|
-
}
|
|
34487
|
-
async function browserLogin(deps) {
|
|
34488
|
-
const { store, lastAccount, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
34489
|
-
const outcome = await runBrowserSignin({ ...deps, store });
|
|
34490
|
-
if (!outcome.ok) return { kind: "failed", note: BROWSER_SIGNIN_COPY.failure[outcome.reason], reason: outcome.reason };
|
|
34491
|
-
const held = outcome.held;
|
|
34492
|
-
deps.out(BROWSER_SIGNIN_COPY.signedIn);
|
|
34493
|
-
let current = held;
|
|
34494
|
-
const revoke = (deviceId, bearer) => endServerSession(deps.call, deviceId, bearer);
|
|
34495
|
-
const confirm = createBorrowConfirm({
|
|
34496
|
-
borrow: {
|
|
34497
|
-
commit: (id) => {
|
|
34498
|
-
const h = current;
|
|
34499
|
-
if (!h || h.deviceId !== id || store.deviceId()) return false;
|
|
34500
|
-
current = null;
|
|
34501
|
-
store.setRefresh(h.refreshToken);
|
|
34502
|
-
store.setDevice(h.deviceId, h.privatePkcs8B64);
|
|
34503
|
-
store.setAccess(h.accessToken);
|
|
34504
|
-
return true;
|
|
34505
|
-
},
|
|
34506
|
-
discard: () => {
|
|
34507
|
-
const h = current;
|
|
34508
|
-
current = null;
|
|
34509
|
-
return h ? { deviceId: h.deviceId, bearer: h.accessToken } : null;
|
|
34510
|
-
}
|
|
34511
|
-
},
|
|
34512
|
-
pin: { hubPublicKey: null, pin() {
|
|
34513
|
-
}, clear() {
|
|
34514
|
-
} },
|
|
34515
|
-
lastAccount,
|
|
34516
|
-
revoke
|
|
34517
|
-
});
|
|
34518
|
-
const me = await deps.call("GET", "/auth/me", void 0, held.accessToken).catch(() => null);
|
|
34519
|
-
const user = me?.ok && me.data?.user ? me.data.user : null;
|
|
34520
|
-
if (!user) {
|
|
34521
|
-
await confirm.reject();
|
|
34522
|
-
return { kind: "failed", note: BROWSER_SIGNIN_COPY.unconfirmed };
|
|
34523
|
-
}
|
|
34524
|
-
confirm.hold(user, held.deviceId);
|
|
34525
|
-
const verdict = await presentJoined(confirm, deps.out, deps.ask, "Sign in with Kutrion approved this CLI for this account:");
|
|
34526
|
-
if (verdict === "not_you") return { kind: "failed", note: BROWSER_SIGNIN_COPY.notYou };
|
|
34527
|
-
if (verdict === "not_kept" || !await confirm.confirm()) return { kind: "failed", note: BROWSER_SIGNIN_COPY.notKept };
|
|
34528
|
-
const f = failedWrite();
|
|
34529
|
-
if (f) {
|
|
34530
|
-
await endServerSession(deps.call, f.deviceId, f.bearer);
|
|
34531
|
-
dropBorrowed(deps.base, deps.kc);
|
|
34532
|
-
try {
|
|
34533
|
-
if (f.bearer && deps.kc.get(deps.base) === f.bearer) deps.kc.clear(deps.base);
|
|
34534
|
-
} catch {
|
|
34535
|
-
}
|
|
34536
|
-
return { kind: "store_failed", note: BROWSER_SIGNIN_COPY.storeFailed };
|
|
34537
|
-
}
|
|
34538
|
-
return { kind: "signed_in", user };
|
|
34539
|
-
}
|
|
34540
|
-
|
|
34541
|
-
// src/hub-seed-login.ts
|
|
34542
|
-
init_define_KT_HUB_FILES();
|
|
34543
|
-
var import_os3 = require("os");
|
|
34544
|
-
|
|
34545
34629
|
// src/hub/hub-seed.ts
|
|
34546
34630
|
init_define_KT_HUB_FILES();
|
|
34547
34631
|
var import_node_net2 = require("node:net");
|
|
34548
|
-
var
|
|
34632
|
+
var import_node_child_process = require("node:child_process");
|
|
34549
34633
|
var import_node_fs3 = require("node:fs");
|
|
34550
34634
|
var import_node_os2 = require("node:os");
|
|
34551
34635
|
var import_node_path3 = require("node:path");
|
|
34552
34636
|
var REPLY_DEADLINE_MS2 = 13e4;
|
|
34637
|
+
var CONFIRM_DEADLINE_MS = 12e4;
|
|
34638
|
+
var SHOW_CODES_AFTER_MS = 1500;
|
|
34553
34639
|
var CODE_RE2 = /^[A-Za-z0-9_-]{43}$/;
|
|
34554
34640
|
var CLOSED = Symbol("closed");
|
|
34555
34641
|
var BAD = Symbol("bad");
|
|
@@ -34599,35 +34685,80 @@ function lineReader(sock) {
|
|
|
34599
34685
|
};
|
|
34600
34686
|
}
|
|
34601
34687
|
var failed = (reason) => ({ ok: false, reason });
|
|
34602
|
-
function
|
|
34603
|
-
const
|
|
34604
|
-
if (resolved === null) return Promise.resolve(failed("hub_not_running"));
|
|
34605
|
-
const origin = apiOrigin(deps.apiBase);
|
|
34606
|
-
if (origin === null) return Promise.resolve(failed("protocol"));
|
|
34607
|
-
const { identity } = deps;
|
|
34608
|
-
const sock = (deps.connectTo ?? ((e) => (0, import_node_net2.connect)(e)))(resolved.endpoint);
|
|
34688
|
+
function openConn(deps, endpoint) {
|
|
34689
|
+
const sock = (deps.connectTo ?? ((e) => (0, import_node_net2.connect)(e)))(endpoint);
|
|
34609
34690
|
const rd = lineReader(sock);
|
|
34610
34691
|
const timer = setTimeout(() => sock.destroy(), REPLY_DEADLINE_MS2);
|
|
34611
34692
|
timer.unref?.();
|
|
34612
|
-
|
|
34613
|
-
|
|
34614
|
-
|
|
34615
|
-
|
|
34616
|
-
|
|
34617
|
-
|
|
34618
|
-
|
|
34619
|
-
|
|
34620
|
-
const refusedFirst = parseRefuse(first);
|
|
34621
|
-
if (refusedFirst) return failed(refusedFirst.reason);
|
|
34622
|
-
const offer = parseOfferSeed(first);
|
|
34623
|
-
if (!offer) return failed("protocol");
|
|
34624
|
-
const pinned = deps.pin.hubPublicKey;
|
|
34625
|
-
if (pinned && !sameKey(pinned, offer.hub_pk)) return failed("hub_identity_changed");
|
|
34626
|
-
if (resolved.expectedHubPk && !sameKey(resolved.expectedHubPk, offer.hub_pk)) return failed("hub_identity_changed");
|
|
34627
|
-
if (!pinned) {
|
|
34628
|
-
if (!await deps.confirmHub(keyFingerprint(identity.client, offer.hub_pk))) return failed("hub_unconfirmed");
|
|
34629
|
-
deps.pin.pin(offer.hub_pk);
|
|
34693
|
+
return {
|
|
34694
|
+
sock,
|
|
34695
|
+
rd,
|
|
34696
|
+
send: (frame) => sock.write(`${JSON.stringify(frame)}
|
|
34697
|
+
`),
|
|
34698
|
+
close: () => {
|
|
34699
|
+
clearTimeout(timer);
|
|
34700
|
+
sock.destroy();
|
|
34630
34701
|
}
|
|
34702
|
+
};
|
|
34703
|
+
}
|
|
34704
|
+
async function helloFor(conn, identity, origin) {
|
|
34705
|
+
const cnonce = newNonce();
|
|
34706
|
+
conn.send({ v: CHANNEL_VERSION, t: "hello-seed", client: identity.client, pk: identity.publicSpkiB64, cnonce, origin });
|
|
34707
|
+
const first = await conn.rd.next();
|
|
34708
|
+
if (first === CLOSED) return failed("hub_not_running");
|
|
34709
|
+
if (first === BAD) return failed("protocol");
|
|
34710
|
+
const refused = parseRefuse(first);
|
|
34711
|
+
if (refused) return failed(refused.reason);
|
|
34712
|
+
const offer = parseOfferSeed(first);
|
|
34713
|
+
return offer ? { cnonce, offer } : failed("protocol");
|
|
34714
|
+
}
|
|
34715
|
+
async function confirmWithin(deps, codes) {
|
|
34716
|
+
const ac = new AbortController();
|
|
34717
|
+
const t = setTimeout(() => ac.abort(), deps.timing?.confirmMs ?? CONFIRM_DEADLINE_MS);
|
|
34718
|
+
t.unref?.();
|
|
34719
|
+
try {
|
|
34720
|
+
const yes = await deps.confirmHub(codes, ac.signal);
|
|
34721
|
+
return ac.signal.aborted ? "timed_out" : yes;
|
|
34722
|
+
} finally {
|
|
34723
|
+
clearTimeout(t);
|
|
34724
|
+
}
|
|
34725
|
+
}
|
|
34726
|
+
async function seedOnce(deps) {
|
|
34727
|
+
const resolved = resolveEndpoint(deps.location, { platform: deps.platform });
|
|
34728
|
+
if (resolved === null) return failed("hub_not_running");
|
|
34729
|
+
const origin = apiOrigin(deps.apiBase);
|
|
34730
|
+
if (origin === null) return failed("protocol");
|
|
34731
|
+
const { identity } = deps;
|
|
34732
|
+
const appCode = keyFingerprint(identity.client, identity.publicSpkiB64);
|
|
34733
|
+
const wrongHub = (hubPk2, expect) => expect !== null && !sameKey(expect, hubPk2) || resolved.expectedHubPk != null && !sameKey(resolved.expectedHubPk, hubPk2);
|
|
34734
|
+
const pinned = deps.pin.hubPublicKey ?? null;
|
|
34735
|
+
const probe = openConn(deps, resolved.endpoint);
|
|
34736
|
+
let met;
|
|
34737
|
+
try {
|
|
34738
|
+
met = await helloFor(probe, identity, origin);
|
|
34739
|
+
} finally {
|
|
34740
|
+
probe.close();
|
|
34741
|
+
}
|
|
34742
|
+
if (!("offer" in met)) return met;
|
|
34743
|
+
const hubPk = met.offer.hub_pk;
|
|
34744
|
+
if (wrongHub(hubPk, pinned)) return failed("hub_identity_changed");
|
|
34745
|
+
const verdict = await deps.check(hubPk);
|
|
34746
|
+
if (verdict === "unavailable") return failed("hub_check_unavailable");
|
|
34747
|
+
if (verdict === "refused" || verdict === "new_hub") return failed("hub_not_vouched");
|
|
34748
|
+
if (pinned === null || verdict === "first_contact") {
|
|
34749
|
+
const answer = await confirmWithin(deps, { app: appCode, hub: keyFingerprint(identity.client, hubPk) });
|
|
34750
|
+
if (answer === "timed_out") return failed("hub_timed_out");
|
|
34751
|
+
if (!answer) return failed("hub_unconfirmed");
|
|
34752
|
+
if (pinned === null) deps.pin.pin(hubPk);
|
|
34753
|
+
}
|
|
34754
|
+
const expectHub = hubPk;
|
|
34755
|
+
const conn = openConn(deps, resolved.endpoint);
|
|
34756
|
+
const ui = new AbortController();
|
|
34757
|
+
try {
|
|
34758
|
+
const met2 = await helloFor(conn, identity, origin);
|
|
34759
|
+
if (!("offer" in met2)) return met2;
|
|
34760
|
+
const { cnonce, offer } = met2;
|
|
34761
|
+
if (wrongHub(offer.hub_pk, expectHub)) return failed("hub_identity_changed");
|
|
34631
34762
|
const parts = {
|
|
34632
34763
|
client: identity.client,
|
|
34633
34764
|
satPk: identity.publicSpkiB64,
|
|
@@ -34637,48 +34768,67 @@ function seedOnce(deps) {
|
|
|
34637
34768
|
snonce: offer.snonce,
|
|
34638
34769
|
origin
|
|
34639
34770
|
};
|
|
34640
|
-
send({ v: CHANNEL_VERSION, t: "prove", sig: signTranscript(identity.privatePkcs8B64, seedRequestTranscript(parts)) });
|
|
34641
|
-
|
|
34642
|
-
|
|
34771
|
+
conn.send({ v: CHANNEL_VERSION, t: "prove", sig: signTranscript(identity.privatePkcs8B64, seedRequestTranscript(parts)) });
|
|
34772
|
+
let stopped = false;
|
|
34773
|
+
const show = deps.showCodes;
|
|
34774
|
+
const showTimer = show ? setTimeout(() => {
|
|
34775
|
+
void show({ app: appCode, hub: keyFingerprint(identity.client, offer.hub_pk) }, ui.signal).then((stop) => {
|
|
34776
|
+
if (stop && !ui.signal.aborted) {
|
|
34777
|
+
stopped = true;
|
|
34778
|
+
conn.sock.destroy();
|
|
34779
|
+
}
|
|
34780
|
+
}, () => void 0);
|
|
34781
|
+
}, deps.timing?.showCodesAfterMs ?? SHOW_CODES_AFTER_MS) : null;
|
|
34782
|
+
const second = await conn.rd.next();
|
|
34783
|
+
if (showTimer) clearTimeout(showTimer);
|
|
34784
|
+
ui.abort();
|
|
34785
|
+
if (stopped) return failed("hub_unconfirmed");
|
|
34786
|
+
if (second === CLOSED) return failed("hub_timed_out");
|
|
34643
34787
|
if (second === BAD) return failed("protocol");
|
|
34644
34788
|
const refusedSecond = parseRefuse(second);
|
|
34645
34789
|
if (refusedSecond) return failed(refusedSecond.reason);
|
|
34646
34790
|
const accept = parseAcceptSeed(second);
|
|
34647
34791
|
if (!accept) return failed("protocol");
|
|
34648
34792
|
if (!verifyTranscript(offer.hub_pk, seedAcceptTranscript(parts), accept.sig)) return failed("hub_not_from_hub");
|
|
34649
|
-
const issued = await deps.issue(offer.device_pk);
|
|
34793
|
+
const issued = await deps.issue(offer.device_pk, offer.hub_pk);
|
|
34650
34794
|
if (!issued.ok) return failed(issued.reason);
|
|
34651
|
-
send({
|
|
34795
|
+
conn.send({
|
|
34652
34796
|
v: CHANNEL_VERSION,
|
|
34653
34797
|
t: "seed",
|
|
34654
34798
|
code: issued.code,
|
|
34655
34799
|
sig: signTranscript(identity.privatePkcs8B64, seedTranscript({ ...parts, code: issued.code }))
|
|
34656
34800
|
});
|
|
34657
|
-
const third = await rd.next();
|
|
34658
|
-
if (third === CLOSED) return failed("
|
|
34801
|
+
const third = await conn.rd.next();
|
|
34802
|
+
if (third === CLOSED) return failed("hub_timed_out");
|
|
34659
34803
|
if (third === BAD) return failed("protocol");
|
|
34660
34804
|
const refusedThird = parseRefuse(third);
|
|
34661
34805
|
if (refusedThird) return failed(refusedThird.reason);
|
|
34662
34806
|
const seeded = parseSeeded(third);
|
|
34663
34807
|
return seeded ? { ok: true, result: seeded.result, hubPublicKey: offer.hub_pk } : failed("protocol");
|
|
34664
|
-
}
|
|
34665
|
-
|
|
34666
|
-
|
|
34667
|
-
}
|
|
34808
|
+
} finally {
|
|
34809
|
+
ui.abort();
|
|
34810
|
+
conn.close();
|
|
34811
|
+
}
|
|
34668
34812
|
}
|
|
34669
34813
|
function hubCodeIssuer(p) {
|
|
34670
|
-
return async (devicePk) => {
|
|
34814
|
+
return async (devicePk, hubPk, approvalId) => {
|
|
34671
34815
|
const bearer = p.bearer();
|
|
34672
34816
|
const deviceId = p.deviceId();
|
|
34673
34817
|
if (!bearer || !deviceId) return { ok: false, reason: "issue_refused" };
|
|
34674
34818
|
const res = await p.post(
|
|
34675
34819
|
"/auth/handoff/issue",
|
|
34676
|
-
{
|
|
34820
|
+
{
|
|
34821
|
+
purpose: "enrolment",
|
|
34822
|
+
device_id: deviceId,
|
|
34823
|
+
client: { id: "hub", name: p.hubName, platform: "desktop", public_key: devicePk, channel_key: hubPk },
|
|
34824
|
+
...approvalId ? { approval_id: approvalId } : {}
|
|
34825
|
+
},
|
|
34677
34826
|
bearer
|
|
34678
34827
|
);
|
|
34679
34828
|
if (!res.ok) {
|
|
34680
34829
|
const err = res.body?.error;
|
|
34681
|
-
|
|
34830
|
+
if (res.status === 403 && err === "step_up_required") return { ok: false, reason: "step_up_required" };
|
|
34831
|
+
return { ok: false, reason: res.status === 403 && err === "hub_not_vouched" ? "hub_not_vouched" : "issue_refused" };
|
|
34682
34832
|
}
|
|
34683
34833
|
const code = res.data?.code;
|
|
34684
34834
|
return typeof code === "string" && CODE_RE2.test(code) ? { ok: true, code } : { ok: false, reason: "issue_refused" };
|
|
@@ -34707,11 +34857,11 @@ function hubLauncher(env = {}) {
|
|
|
34707
34857
|
const home = env.home ?? (0, import_node_os2.homedir)();
|
|
34708
34858
|
const exists = env.exists ?? import_node_fs3.existsSync;
|
|
34709
34859
|
const exec2 = env.exec ?? ((file, args) => new Promise((resolve2, reject) => {
|
|
34710
|
-
(0,
|
|
34860
|
+
(0, import_node_child_process.execFile)(file, args, { timeout: 5e3, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (e, out) => e ? reject(e) : resolve2(out));
|
|
34711
34861
|
}));
|
|
34712
34862
|
const startDetached2 = env.startDetached ?? ((file, args) => {
|
|
34713
34863
|
try {
|
|
34714
|
-
const child = (0,
|
|
34864
|
+
const child = (0, import_node_child_process.spawn)(file, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
34715
34865
|
child.on("error", () => void 0);
|
|
34716
34866
|
child.unref();
|
|
34717
34867
|
return true;
|
|
@@ -34785,14 +34935,16 @@ async function seedHubAfterSignIn(deps) {
|
|
|
34785
34935
|
if (!reachable()) return { kind: "retry" };
|
|
34786
34936
|
}
|
|
34787
34937
|
const launcher = deps.launcher;
|
|
34788
|
-
const confirmHub = launcher?.foreground ? async (
|
|
34938
|
+
const confirmHub = launcher?.foreground ? async (codes, signal) => {
|
|
34789
34939
|
await Promise.resolve(launcher.foreground?.()).catch(() => void 0);
|
|
34790
|
-
return deps.confirmHub(
|
|
34940
|
+
return deps.confirmHub(codes, signal);
|
|
34791
34941
|
} : deps.confirmHub;
|
|
34792
34942
|
const out = await seedOnce({ ...deps, confirmHub });
|
|
34793
34943
|
if (out.ok) return { kind: "done", result: out.result };
|
|
34794
34944
|
if (out.reason === "hub_other_account") return { kind: "other_account" };
|
|
34795
34945
|
if (out.reason === "hub_not_running") return { kind: "retry" };
|
|
34946
|
+
if (out.reason === "hub_timed_out") return { kind: "timed_out" };
|
|
34947
|
+
if (out.reason === "hub_check_unavailable") return { kind: "retry" };
|
|
34796
34948
|
return { kind: "stopped", reason: out.reason };
|
|
34797
34949
|
}
|
|
34798
34950
|
|
|
@@ -34804,13 +34956,17 @@ var SEED_COPY = {
|
|
|
34804
34956
|
failedNotRemoved: (name) => `This CLI could not register itself as a device, and the device it created could not be removed. You are still logged in. Remove "${name}" from Active devices in the web portal.`,
|
|
34805
34957
|
joined: "The Kutrion app on this computer is now signed in to this account.",
|
|
34806
34958
|
otherAccount: "The Kutrion app on this computer is signed in to a different account. Sign it out there first. This CLI keeps its own sign-in.",
|
|
34959
|
+
retryLater: "The Kutrion app was not signed in because the account service could not be reached to check it. It will try again at the next sign-in.",
|
|
34807
34960
|
declined: "Not shared: the Kutrion app was not signed in.",
|
|
34808
34961
|
stopped: (reason) => `The Kutrion app was not signed in (${printable(reason)}).`,
|
|
34809
|
-
firstContact: (
|
|
34810
|
-
`The Kutrion app on this computer answered. Its code is ${
|
|
34962
|
+
firstContact: (codes) => [
|
|
34963
|
+
`The Kutrion app on this computer answered. Its code is ${codes.hub}.`,
|
|
34964
|
+
`The Kutrion app will then ask you to allow this CLI, and show this CLI's code: ${codes.app}`,
|
|
34811
34965
|
"If you did not just open the Kutrion app yourself, answer no.",
|
|
34812
34966
|
"Sharing gives the Kutrion app its own sign-in to your account. Your password is not sent, and you can sign it out again from Active devices."
|
|
34813
34967
|
],
|
|
34968
|
+
allowCodes: (codes) => `The Kutrion app is asking whether to allow this CLI. Check it shows these codes before you allow it: this CLI ${codes.app}, Kutrion ${codes.hub}. If they differ, choose Don't allow in the Kutrion app.`,
|
|
34969
|
+
timedOut: "Sharing your sign-in with the Kutrion app timed out, so it was not signed in. You are still logged in. Run kt login again to retry.",
|
|
34814
34970
|
ask: "\x1B[0mSign the Kutrion app in too? Type yes to share, or anything else to skip: "
|
|
34815
34971
|
};
|
|
34816
34972
|
function seedSkipReason(env, platform3, tty) {
|
|
@@ -34888,7 +35044,8 @@ async function seedHubFromLogin(deps) {
|
|
|
34888
35044
|
if (skip) return { kind: "skipped", reason: skip };
|
|
34889
35045
|
if (!deps.location || !deps.keychainAvailable()) return { kind: "skipped", reason: "no_channel" };
|
|
34890
35046
|
try {
|
|
34891
|
-
const { store, pin } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
35047
|
+
const { store, pin, latch: latchStore } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
35048
|
+
const latch = createFeatureLatch(latchStore, deps.log ?? ((line) => console.error(line)));
|
|
34892
35049
|
const deviceId = store.deviceId();
|
|
34893
35050
|
const keyB64 = store.devicePrivateKeyPkcs8();
|
|
34894
35051
|
const bearer = () => {
|
|
@@ -34904,18 +35061,29 @@ async function seedHubFromLogin(deps) {
|
|
|
34904
35061
|
if (!mode?.ok || mode.data?.vault_mode !== "audited") return { kind: "skipped", reason: "private_vault" };
|
|
34905
35062
|
const key = resolveDeviceKey(store);
|
|
34906
35063
|
const issuer = hubCodeIssuer({ post: deps.post, bearer, deviceId: () => store.deviceId(), hubName: `Kutrion on ${host()}`.slice(0, 255) });
|
|
35064
|
+
let checkUnavailable = false;
|
|
34907
35065
|
const status = await seedHubAfterSignIn({
|
|
34908
35066
|
location: deps.location,
|
|
34909
35067
|
identity: { client: "cli", ...key },
|
|
34910
35068
|
pin,
|
|
34911
35069
|
apiBase: deps.base,
|
|
34912
|
-
confirmHub: async (
|
|
34913
|
-
for (const line of SEED_COPY.firstContact(
|
|
35070
|
+
confirmHub: async (codes) => {
|
|
35071
|
+
for (const line of SEED_COPY.firstContact(codes)) deps.out(line);
|
|
34914
35072
|
return (await deps.ask(SEED_COPY.ask).catch(() => "")).trim().toLowerCase() === "yes";
|
|
34915
35073
|
},
|
|
34916
|
-
|
|
35074
|
+
// Printed once; a terminal cannot retract a line, so the prompt never asks to stop.
|
|
35075
|
+
showCodes: async (codes) => {
|
|
35076
|
+
deps.out(SEED_COPY.allowCodes(codes));
|
|
35077
|
+
return false;
|
|
35078
|
+
},
|
|
35079
|
+
check: async (hubPk) => {
|
|
35080
|
+
const verdict = await checkHubKey({ apiBase: deps.base, bearer: bearer(), hubPk, latch, fetchImpl: deps.fetchImpl });
|
|
35081
|
+
if (verdict === "unavailable") checkUnavailable = true;
|
|
35082
|
+
return verdict;
|
|
35083
|
+
},
|
|
35084
|
+
issue: async (devicePk, hubPk) => {
|
|
34917
35085
|
await deps.touch().catch(() => void 0);
|
|
34918
|
-
return issuer(devicePk);
|
|
35086
|
+
return issuer(devicePk, hubPk);
|
|
34919
35087
|
},
|
|
34920
35088
|
launcher: withForeground(deps.launcher ?? hubLauncher({ platform: platform3, localAppData: env.LOCALAPPDATA, programFiles: env.ProgramFiles }), tty),
|
|
34921
35089
|
env,
|
|
@@ -34923,7 +35091,10 @@ async function seedHubFromLogin(deps) {
|
|
|
34923
35091
|
});
|
|
34924
35092
|
if (status.kind === "done" && status.result === "joined") deps.out(SEED_COPY.joined);
|
|
34925
35093
|
else if (status.kind === "other_account") deps.out(SEED_COPY.otherAccount);
|
|
35094
|
+
else if (status.kind === "timed_out") deps.out(SEED_COPY.timedOut);
|
|
35095
|
+
else if (status.kind === "retry" && checkUnavailable) deps.out(SEED_COPY.retryLater);
|
|
34926
35096
|
else if (status.kind === "stopped" && status.reason === "hub_unconfirmed") deps.out(SEED_COPY.declined);
|
|
35097
|
+
else if (status.kind === "stopped" && status.reason === "hub_not_vouched") deps.out(HUB_KEY_COPY.notVouched);
|
|
34927
35098
|
else if (status.kind === "stopped" && status.reason !== "disabled") deps.out(SEED_COPY.stopped(status.reason));
|
|
34928
35099
|
return status;
|
|
34929
35100
|
} catch {
|
|
@@ -34933,7 +35104,7 @@ async function seedHubFromLogin(deps) {
|
|
|
34933
35104
|
|
|
34934
35105
|
// src/hub/ensure-hub.ts
|
|
34935
35106
|
init_define_KT_HUB_FILES();
|
|
34936
|
-
var
|
|
35107
|
+
var import_node_child_process2 = require("node:child_process");
|
|
34937
35108
|
var import_node_fs4 = require("node:fs");
|
|
34938
35109
|
var import_node_os3 = require("node:os");
|
|
34939
35110
|
var import_node_path4 = require("node:path");
|
|
@@ -34956,7 +35127,7 @@ function stageFiles(files) {
|
|
|
34956
35127
|
return dir;
|
|
34957
35128
|
}
|
|
34958
35129
|
function startDetached(command, args) {
|
|
34959
|
-
const child = (0,
|
|
35130
|
+
const child = (0, import_node_child_process2.spawn)(command, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
34960
35131
|
child.on("error", () => void 0);
|
|
34961
35132
|
child.unref();
|
|
34962
35133
|
}
|
|
@@ -35215,7 +35386,7 @@ var HOST_KEY_CONFIRM_COPY2 = {
|
|
|
35215
35386
|
notThisConnection: "This key is not for the host this connection opens. It belongs to a jump host on the way, or Kutrion could not tell which host it belongs to, so it cannot be trusted from here. Open that host's own connection and confirm its key there; that needs an organization admin, or an editor of the vault that holds that connection.",
|
|
35216
35387
|
connectAgain: "Connect again:"
|
|
35217
35388
|
};
|
|
35218
|
-
var HOST_KEY_FINGERPRINT =
|
|
35389
|
+
var HOST_KEY_FINGERPRINT = HOST_KEY_FINGERPRINT_EXACT;
|
|
35219
35390
|
function parseHostKeyUnconfirmed(d) {
|
|
35220
35391
|
if (!d || typeof d !== "object") return null;
|
|
35221
35392
|
const o = d;
|
|
@@ -35269,6 +35440,74 @@ function hostKeyConfirmErrorLines(e, connectionId, indent = " ") {
|
|
|
35269
35440
|
}
|
|
35270
35441
|
return null;
|
|
35271
35442
|
}
|
|
35443
|
+
var CHAINED_PROMPT = "Fingerprint from the host: ";
|
|
35444
|
+
function parseHostKeyChained(d) {
|
|
35445
|
+
return hostKeyChainedFromFrame({ code: "host_key_unconfirmed", detail: d && typeof d === "object" ? d : void 0 });
|
|
35446
|
+
}
|
|
35447
|
+
function hostKeyChainedFromError(e) {
|
|
35448
|
+
if (!(e instanceof ApiError) || e.status !== 409) return null;
|
|
35449
|
+
const b = e.body;
|
|
35450
|
+
return b?.error === "host_key_unconfirmed" ? parseHostKeyChained(b.host_key) : null;
|
|
35451
|
+
}
|
|
35452
|
+
function hostKeyChainedLines(k, own, indent = " ") {
|
|
35453
|
+
const lines = [`${HOST_KEY_CHAINED_COPY.heading} (host_key_unconfirmed)`, "", HOST_KEY_CHAINED_COPY.body, "", `${indent}host: ${k.target}`, ""];
|
|
35454
|
+
if (own && isOwnHost(k, own)) {
|
|
35455
|
+
lines.push(
|
|
35456
|
+
HOST_KEY_CHAINED_COPY.help,
|
|
35457
|
+
`${indent}${HOST_KEY_CHAINED_COPY.command}`,
|
|
35458
|
+
"",
|
|
35459
|
+
"An organization admin then trusts it with:",
|
|
35460
|
+
`${indent}kt ssh trust ${own.id} --fingerprint <fingerprint from the host>`,
|
|
35461
|
+
"",
|
|
35462
|
+
HOST_KEY_CHAINED_COPY.nonAdmin
|
|
35463
|
+
);
|
|
35464
|
+
} else {
|
|
35465
|
+
lines.push(HOST_KEY_CHAINED_COPY.notThisConnection);
|
|
35466
|
+
}
|
|
35467
|
+
return lines;
|
|
35468
|
+
}
|
|
35469
|
+
async function confirmChainedHostKey(deps, connId, k, own) {
|
|
35470
|
+
deps.out("");
|
|
35471
|
+
deps.out(`${HOST_KEY_CHAINED_COPY.heading} (host_key_unconfirmed)`);
|
|
35472
|
+
deps.out("");
|
|
35473
|
+
deps.out(HOST_KEY_CHAINED_COPY.body);
|
|
35474
|
+
deps.out("");
|
|
35475
|
+
deps.out(` host: ${k.target}`);
|
|
35476
|
+
deps.out("");
|
|
35477
|
+
if (!own || !isOwnHost(k, own)) {
|
|
35478
|
+
deps.out(HOST_KEY_CHAINED_COPY.notThisConnection);
|
|
35479
|
+
return false;
|
|
35480
|
+
}
|
|
35481
|
+
if (!await deps.isAdmin()) {
|
|
35482
|
+
deps.out(HOST_KEY_CHAINED_COPY.nonAdmin);
|
|
35483
|
+
return false;
|
|
35484
|
+
}
|
|
35485
|
+
deps.out(HOST_KEY_CHAINED_COPY.help);
|
|
35486
|
+
deps.out(` ${HOST_KEY_CHAINED_COPY.command}`);
|
|
35487
|
+
let fingerprint = null;
|
|
35488
|
+
for (let attempt = 0; attempt < 3 && !fingerprint; attempt++) {
|
|
35489
|
+
const answer = await deps.ask(CHAINED_PROMPT);
|
|
35490
|
+
if (!answer) return false;
|
|
35491
|
+
fingerprint = extractHostKeyFingerprint(answer);
|
|
35492
|
+
if (!fingerprint) deps.out(HOST_KEY_CHAINED_COPY.invalid);
|
|
35493
|
+
}
|
|
35494
|
+
if (!fingerprint) return false;
|
|
35495
|
+
try {
|
|
35496
|
+
const r = await deps.confirm(connId, fingerprint);
|
|
35497
|
+
deps.out(`${r.created ? "trusted" : "already trusted"}: ${r.pinned.host}:${r.pinned.port} ${r.pinned.key_type} ${r.pinned.fingerprint}`);
|
|
35498
|
+
deps.out(`connect with: kt ssh ${connId}`);
|
|
35499
|
+
return true;
|
|
35500
|
+
} catch (e) {
|
|
35501
|
+
if (!(e instanceof ApiError)) throw e;
|
|
35502
|
+
const code = e.body?.error;
|
|
35503
|
+
const failure = hostKeyConfirmFailure(typeof code === "string" ? code : null);
|
|
35504
|
+
if (failure === "forbidden") deps.out(HOST_KEY_CHAINED_COPY.nonAdmin);
|
|
35505
|
+
else if (failure === "mismatch") deps.out(HOST_KEY_CHAINED_COPY.mismatch);
|
|
35506
|
+
else if (failure === "expired") deps.out(`${HOST_KEY_CHAINED_COPY.expired} kt ssh ${connId}`);
|
|
35507
|
+
else throw e;
|
|
35508
|
+
return false;
|
|
35509
|
+
}
|
|
35510
|
+
}
|
|
35272
35511
|
|
|
35273
35512
|
// src/refusal.ts
|
|
35274
35513
|
var REFUSALS = {
|
|
@@ -35293,7 +35532,7 @@ var REFUSALS = {
|
|
|
35293
35532
|
action: "Run `kt conns` and pass an id from the first column to --jump."
|
|
35294
35533
|
},
|
|
35295
35534
|
// auth-guard.ts:109 (social-session gate) and :167 (adaptive), plus rotation.ts:159, jit.ts:226,
|
|
35296
|
-
// fleet.ts:190, agents.ts:146, modules/ssh.ts:727 and :
|
|
35535
|
+
// fleet.ts:190, agents.ts:146, modules/ssh.ts:727 and :1222. The remedy is mechanical and worth
|
|
35297
35536
|
// stating exactly, because it is not obvious that it can be satisfied anywhere but here: step-up
|
|
35298
35537
|
// freshness is a per-USER Redis proof with a ~5 minute TTL (server/src/lib/step-up.ts:16), not a
|
|
35299
35538
|
// claim baked into this CLI's token. Stepping up in the web app on the same account therefore
|
|
@@ -35403,7 +35642,7 @@ var REFUSALS = {
|
|
|
35403
35642
|
why: "Promoting now would leave those hosts refusing every session, because they do not yet accept a certificate from the next CA.",
|
|
35404
35643
|
action: "Install or verify each host listed by `kt broker-ca status`, or revoke its enrollment, then promote again."
|
|
35405
35644
|
},
|
|
35406
|
-
// authz.ts:69, connection-import.ts:
|
|
35645
|
+
// authz.ts:69, connection-import.ts:142. Overage freeze after a downgrade — read stays, write stops.
|
|
35407
35646
|
vault_frozen: {
|
|
35408
35647
|
title: "Vault is read-only",
|
|
35409
35648
|
why: "This vault is frozen for writes because the plan's vault limit was exceeded after a downgrade.",
|
|
@@ -35493,7 +35732,7 @@ var REFUSALS = {
|
|
|
35493
35732
|
why: "Kutrion does not display, copy, export or share this credential's value. It is still used to authenticate connections and integrations.",
|
|
35494
35733
|
action: "Use the credential rather than reading it \u2014 `kt ssh <connId>` or `kt exec <connId> <command>` broker a session with it without the value passing through this terminal."
|
|
35495
35734
|
},
|
|
35496
|
-
// modules/ssh.ts:
|
|
35735
|
+
// modules/ssh.ts:470, modules/rdp.ts:151.
|
|
35497
35736
|
inject_only_requires_write: {
|
|
35498
35737
|
title: "Write access required for a No-reveal credential",
|
|
35499
35738
|
why: "Pointing a connection at a No-reveal credential needs write access to the vault holding it, because the person doing it decides where the credential gets presented.",
|
|
@@ -35625,15 +35864,15 @@ var REFUSALS = {
|
|
|
35625
35864
|
action: "Ask whoever created it for a new one. `kt share <credId>` mints a fresh one-time link if the credential is yours."
|
|
35626
35865
|
}
|
|
35627
35866
|
};
|
|
35628
|
-
var
|
|
35867
|
+
var str6 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
35629
35868
|
var asRecord = (body) => body && typeof body === "object" ? body : null;
|
|
35630
35869
|
function serverText(body) {
|
|
35631
35870
|
const b = asRecord(body);
|
|
35632
35871
|
if (!b) return null;
|
|
35633
|
-
return
|
|
35872
|
+
return str6(b.hint) ?? str6(b.detail) ?? str6(b.remedy) ?? str6(b.message) ?? str6(b.reason);
|
|
35634
35873
|
}
|
|
35635
35874
|
function refusalCode(e) {
|
|
35636
|
-
return
|
|
35875
|
+
return str6(asRecord(e.body)?.error) ?? str6(e.message) ?? "unknown";
|
|
35637
35876
|
}
|
|
35638
35877
|
var CONTEXT_FIELDS = [
|
|
35639
35878
|
"feature",
|
|
@@ -35758,7 +35997,7 @@ function jumpRefusalReport(frame, code, head, ctx, indent) {
|
|
|
35758
35997
|
if (!jump) return null;
|
|
35759
35998
|
const names = ctx.names ?? noNames;
|
|
35760
35999
|
const lines = [code ? `${head} (${code})` : head, "", jumpRefusalSummary(jump, names, ctx.openedName)];
|
|
35761
|
-
const message =
|
|
36000
|
+
const message = str6(frame.error);
|
|
35762
36001
|
if (jump.detail && jump.detail !== message) lines.push("", jump.detail);
|
|
35763
36002
|
if (jump.allowedDestinations?.length) {
|
|
35764
36003
|
lines.push("", jumpAllowedDestinationsLabel(jump, names), ...jump.allowedDestinations.map((a) => `${indent}${a}`));
|
|
@@ -35767,14 +36006,16 @@ function jumpRefusalReport(frame, code, head, ctx, indent) {
|
|
|
35767
36006
|
return lines;
|
|
35768
36007
|
}
|
|
35769
36008
|
function wsRefusalLines(frame, indent = " ", jumpCtx = {}) {
|
|
35770
|
-
const code =
|
|
36009
|
+
const code = str6(frame.code);
|
|
35771
36010
|
const d = frame.detail ?? {};
|
|
35772
|
-
const host2 =
|
|
36011
|
+
const host2 = str6(d.host);
|
|
35773
36012
|
const port = typeof d.port === "number" ? d.port : null;
|
|
35774
36013
|
const allowed = Array.isArray(d.allowed_destinations) ? d.allowed_destinations.filter((x) => typeof x === "string" && x.trim() !== "") : [];
|
|
35775
36014
|
if (code === "host_key_unconfirmed") {
|
|
35776
36015
|
const k = parseHostKeyUnconfirmed(frame.detail);
|
|
35777
36016
|
if (k) return hostKeyUnconfirmedLines(k, jumpCtx.own, indent);
|
|
36017
|
+
const chained = parseHostKeyChained(frame.detail);
|
|
36018
|
+
if (chained) return hostKeyChainedLines(chained, jumpCtx.own, indent);
|
|
35778
36019
|
}
|
|
35779
36020
|
const family = refusalFamily(code);
|
|
35780
36021
|
const dest = parseDestinationRefusal({ error: code, reason: d.reason, allowed_destinations: allowed }) ?? { credential: null, destination: "", allowedDestinations: allowed, detail: null, side: null, reason: null };
|
|
@@ -35790,20 +36031,20 @@ function wsRefusalLines(frame, indent = " ", jumpCtx = {}) {
|
|
|
35790
36031
|
const jump = jumpRefusalReport(frame, code, head, jumpCtx, indent);
|
|
35791
36032
|
if (jump) return jump;
|
|
35792
36033
|
const lines = [code && !ourFault ? `${head} (${code})` : head, ""];
|
|
35793
|
-
lines.push(
|
|
36034
|
+
lines.push(str6(frame.error) ?? "The server sent no explanation with this refusal.");
|
|
35794
36035
|
if (host2) lines.push("", `${indent}endpoint: ${port === null ? host2 : `${host2}:${port}`}`);
|
|
35795
36036
|
if (allowed.length) {
|
|
35796
36037
|
lines.push("", destinationRefusalAllowedLabel(dest));
|
|
35797
36038
|
lines.push(...allowed.map((a) => `${indent}${a}`));
|
|
35798
36039
|
}
|
|
35799
|
-
const remedy = !isDest ?
|
|
36040
|
+
const remedy = !isDest ? str6(d.remedy) : ourFault ? destinationRefusalAction(dest) : `${destinationRefusalAction(dest)} ${DESTINATION_REFUSAL_NOT_A_REMEDY} ${DESTINATION_REFUSAL_CLI_ROUTE}`;
|
|
35800
36041
|
if (remedy) lines.push("", remedy);
|
|
35801
36042
|
else if (!code) lines.push("", "The server sent no code and no remedy with this refusal.");
|
|
35802
36043
|
return lines;
|
|
35803
36044
|
}
|
|
35804
36045
|
|
|
35805
36046
|
// src/shell.ts
|
|
35806
|
-
function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession) {
|
|
36047
|
+
function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onChainedHostKey) {
|
|
35807
36048
|
const wsUrl = shellUrl(base, connId);
|
|
35808
36049
|
const ws = new wrapper_default(wsUrl, { headers: { Authorization: `Bearer ${token}` }, agent: wsAgentFor(wsUrl) });
|
|
35809
36050
|
const stdin = process.stdin;
|
|
@@ -35861,6 +36102,14 @@ function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession) {
|
|
|
35861
36102
|
const frame = parseWsRefusal(text2);
|
|
35862
36103
|
if (frame) {
|
|
35863
36104
|
refused = true;
|
|
36105
|
+
const chained = frame.code === "host_key_unconfirmed" ? parseHostKeyChained(frame.detail) : null;
|
|
36106
|
+
if (chained && onChainedHostKey && isTty) {
|
|
36107
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
36108
|
+
stdin.removeAllListeners("data");
|
|
36109
|
+
void onChainedHostKey(chained).catch(() => {
|
|
36110
|
+
}).then(() => cleanup(1));
|
|
36111
|
+
return;
|
|
36112
|
+
}
|
|
35864
36113
|
process.stderr.write(`\r
|
|
35865
36114
|
${wsRefusalLines(frame, " ", jumpCtx).join("\r\n")}\r
|
|
35866
36115
|
`);
|
|
@@ -36459,16 +36708,16 @@ function rejectLiteralSecrets(node, path) {
|
|
|
36459
36708
|
}
|
|
36460
36709
|
}
|
|
36461
36710
|
function execCommand(v, where) {
|
|
36462
|
-
const command =
|
|
36711
|
+
const command = str7(v, where);
|
|
36463
36712
|
rejectVaultReferenceInCommand(command, where);
|
|
36464
36713
|
return command;
|
|
36465
36714
|
}
|
|
36466
|
-
function
|
|
36715
|
+
function str7(v, where) {
|
|
36467
36716
|
if (typeof v !== "string" || v.trim() === "") throw new JobFileError(`${where} must be a non-empty string`);
|
|
36468
36717
|
return v;
|
|
36469
36718
|
}
|
|
36470
36719
|
function connectionAlias(v, where, declared) {
|
|
36471
|
-
const alias =
|
|
36720
|
+
const alias = str7(v, where);
|
|
36472
36721
|
if (!(alias in declared)) throw new JobFileError(`${where}: undeclared connection "${alias}"`);
|
|
36473
36722
|
return alias;
|
|
36474
36723
|
}
|
|
@@ -36485,10 +36734,10 @@ function parseExpectations(raw, where) {
|
|
|
36485
36734
|
case "stdout_contains":
|
|
36486
36735
|
case "stderr_contains":
|
|
36487
36736
|
case "stdout_not_contains":
|
|
36488
|
-
out[k] =
|
|
36737
|
+
out[k] = str7(v, `${where}.${k}`);
|
|
36489
36738
|
break;
|
|
36490
36739
|
case "stdout_matches": {
|
|
36491
|
-
const pattern =
|
|
36740
|
+
const pattern = str7(v, `${where}.stdout_matches`);
|
|
36492
36741
|
try {
|
|
36493
36742
|
new RegExp(pattern);
|
|
36494
36743
|
} catch {
|
|
@@ -36505,7 +36754,7 @@ function parseExpectations(raw, where) {
|
|
|
36505
36754
|
}
|
|
36506
36755
|
function parseSyncSide(raw, where, declared) {
|
|
36507
36756
|
if (!isRecord(raw)) throw new JobFileError(`${where} must be a mapping with connection and path`);
|
|
36508
|
-
return { connection: connectionAlias(raw.connection, `${where}.connection`, declared), path:
|
|
36757
|
+
return { connection: connectionAlias(raw.connection, `${where}.connection`, declared), path: str7(raw.path, `${where}.path`) };
|
|
36509
36758
|
}
|
|
36510
36759
|
function parseStep(raw, index, declared) {
|
|
36511
36760
|
const where = `steps[${index}]`;
|
|
@@ -36543,16 +36792,16 @@ function parseStep(raw, index, declared) {
|
|
|
36543
36792
|
...common,
|
|
36544
36793
|
action,
|
|
36545
36794
|
connection: connectionAlias(body.connection, `${where}.get.connection`, declared),
|
|
36546
|
-
remote:
|
|
36547
|
-
local:
|
|
36795
|
+
remote: str7(body.remote, `${where}.get.remote`),
|
|
36796
|
+
local: str7(body.local, `${where}.get.local`)
|
|
36548
36797
|
};
|
|
36549
36798
|
case "put":
|
|
36550
36799
|
return {
|
|
36551
36800
|
...common,
|
|
36552
36801
|
action,
|
|
36553
36802
|
connection: connectionAlias(body.connection, `${where}.put.connection`, declared),
|
|
36554
|
-
local:
|
|
36555
|
-
remote:
|
|
36803
|
+
local: str7(body.local, `${where}.put.local`),
|
|
36804
|
+
remote: str7(body.remote, `${where}.put.remote`)
|
|
36556
36805
|
};
|
|
36557
36806
|
case "sync":
|
|
36558
36807
|
return {
|
|
@@ -36579,7 +36828,7 @@ function parseJobFile(source) {
|
|
|
36579
36828
|
if (doc.connections !== void 0 && doc.connections !== null) {
|
|
36580
36829
|
if (!isRecord(doc.connections)) throw new JobFileError("connections must be a mapping of alias to connection name or id");
|
|
36581
36830
|
for (const [alias, target] of Object.entries(doc.connections)) {
|
|
36582
|
-
connections[alias] =
|
|
36831
|
+
connections[alias] = str7(target, `connections.${alias}`);
|
|
36583
36832
|
}
|
|
36584
36833
|
}
|
|
36585
36834
|
if (!Array.isArray(doc.steps) || doc.steps.length === 0) {
|
|
@@ -37899,13 +38148,13 @@ var ROLES = ["OWNER", "ADMIN", "EDITOR", "VIEWER"];
|
|
|
37899
38148
|
var TOKEN_SCOPES = ["scim", "events"];
|
|
37900
38149
|
var ORG_ROLES = ["owner", "admin", "member"];
|
|
37901
38150
|
var ROTATION_SERVICES = ["linux_password", "postgres_password", "mysql_password"];
|
|
37902
|
-
var VERSION = true ? "1.2.
|
|
38151
|
+
var VERSION = true ? "1.2.10" : "dev";
|
|
37903
38152
|
var HELP = `kt \u2014 Kutrion CLI
|
|
37904
38153
|
kt login [--email <e>] [--password-stdin] [--code <2fa>] prompts for the password, or reads it from stdin;
|
|
37905
38154
|
at a terminal with no --email, first asks the Kutrion desktop app,
|
|
37906
|
-
then
|
|
38155
|
+
then falls back to your email and password;
|
|
37907
38156
|
at the emailed-code prompt, a blank entry or r sends a new code
|
|
37908
|
-
kt login --browser
|
|
38157
|
+
kt login --browser not available; exits with an error
|
|
37909
38158
|
kt whoami
|
|
37910
38159
|
kt logout
|
|
37911
38160
|
|
|
@@ -38107,7 +38356,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38107
38356
|
kt integrations [catalog] alert/SIEM integrations (or the event catalog)
|
|
38108
38357
|
kt integration test <id> send a test delivery
|
|
38109
38358
|
kt hcv [sync <connId>] HashiCorp Vault connections (or sync one)
|
|
38110
|
-
kt compliance [report]
|
|
38359
|
+
kt compliance [report] control mapping to SOC 2 and PCI-DSS (or a printable report)
|
|
38111
38360
|
kt analytics usage overview
|
|
38112
38361
|
kt emergency emergency-access contacts
|
|
38113
38362
|
|
|
@@ -38424,66 +38673,29 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38424
38673
|
const fromStdin = flags["password-stdin"] === true;
|
|
38425
38674
|
if (fromStdin && typeof flags.email !== "string") die("--password-stdin needs --email, because stdin carries the password");
|
|
38426
38675
|
const accountRoot = appDataDir();
|
|
38427
|
-
|
|
38428
|
-
|
|
38429
|
-
die("--browser signs in through the web browser; it cannot be combined with --email, --password-stdin or --code");
|
|
38430
|
-
}
|
|
38431
|
-
if (viaBrowser && !process.stdin.isTTY) {
|
|
38432
|
-
console.error("kt login --browser needs an interactive terminal to confirm the account. In a script, use `kt login --email <email> --password-stdin`.");
|
|
38676
|
+
if (flags.browser === true) {
|
|
38677
|
+
console.error("kt login --browser is not available: browser sign-in is switched off. Sign in with the Kutrion app (`kt login`), or use `kt login --email <email> --password-stdin`.");
|
|
38433
38678
|
process.exit(EXIT_ERROR);
|
|
38434
38679
|
}
|
|
38435
|
-
|
|
38436
|
-
const
|
|
38437
|
-
if (hint) console.error(hint);
|
|
38438
|
-
const r = await browserLogin({
|
|
38680
|
+
if (process.stdin.isTTY && flags.email === void 0 && !fromStdin && flags.code === void 0 && accountRoot) {
|
|
38681
|
+
const hub = await hubLogin({
|
|
38439
38682
|
base: cfg.base,
|
|
38683
|
+
location: hubLocation(accountRoot),
|
|
38440
38684
|
kc,
|
|
38441
38685
|
keychainAvailable: osKeychainAvailable,
|
|
38442
38686
|
call: hubCall,
|
|
38443
|
-
post: postJson(cfg.base, refuseBodyRedirects()),
|
|
38444
|
-
open: openInBrowser,
|
|
38445
38687
|
out: (l) => console.error(l),
|
|
38446
|
-
ask: (q) => promptLine(q)
|
|
38447
|
-
deviceName: () => (0, import_os5.hostname)() || "Kutrion CLI",
|
|
38448
|
-
allowLoopbackHttp: isLoopback(u.hostname)
|
|
38688
|
+
ask: (q) => promptLine(q)
|
|
38449
38689
|
});
|
|
38450
|
-
if (
|
|
38451
|
-
if (
|
|
38452
|
-
|
|
38453
|
-
|
|
38454
|
-
|
|
38455
|
-
|
|
38456
|
-
recordSignIn(accountRoot, r.user);
|
|
38457
|
-
console.log(`logged in with Kutrion as ${printable(r.user.email)} (token stored in ${backendName()})`);
|
|
38458
|
-
if (rememberBase(cfg.base)) console.log(`server saved: ${cfg.base} (later commands need no --base)`);
|
|
38459
|
-
return true;
|
|
38460
|
-
};
|
|
38461
|
-
if (viaBrowser) {
|
|
38462
|
-
if (await browserSignIn()) break;
|
|
38463
|
-
}
|
|
38464
|
-
if (process.stdin.isTTY && flags.email === void 0 && !fromStdin && flags.code === void 0) {
|
|
38465
|
-
if (!accountRoot) {
|
|
38466
|
-
if (await browserSignIn()) break;
|
|
38467
|
-
} else {
|
|
38468
|
-
const hub = await hubLogin({
|
|
38469
|
-
base: cfg.base,
|
|
38470
|
-
location: hubLocation(accountRoot),
|
|
38471
|
-
kc,
|
|
38472
|
-
keychainAvailable: osKeychainAvailable,
|
|
38473
|
-
call: hubCall,
|
|
38474
|
-
out: (l) => console.error(l),
|
|
38475
|
-
ask: (q) => promptLine(q)
|
|
38476
|
-
});
|
|
38477
|
-
if (hub.kind === "store_failed") die(hub.note);
|
|
38478
|
-
if (hub.kind === "signed_in") {
|
|
38479
|
-
recordSignIn(accountRoot, hub.user);
|
|
38480
|
-
console.log(`logged in with the Kutrion app${hub.user ? ` as ${printable(hub.user.email)}` : ""} (token stored in ${backendName()})`);
|
|
38481
|
-
if (rememberBase(cfg.base)) console.log(`server saved: ${cfg.base} (later commands need no --base)`);
|
|
38482
|
-
break;
|
|
38483
|
-
}
|
|
38484
|
-
if (hub.note) console.error(hub.note);
|
|
38485
|
-
if (hubAbsent(hub.reason) && await browserSignIn()) break;
|
|
38690
|
+
if (hub.kind === "store_failed") die(hub.note);
|
|
38691
|
+
if (hub.kind === "signed_in") {
|
|
38692
|
+
recordSignIn(accountRoot, hub.user);
|
|
38693
|
+
console.log(`logged in with the Kutrion app${hub.user ? ` as ${printable(hub.user.email)}` : ""} (token stored in ${backendName()})`);
|
|
38694
|
+
if (rememberBase(cfg.base)) console.log(`server saved: ${cfg.base} (later commands need no --base)`);
|
|
38695
|
+
break;
|
|
38486
38696
|
}
|
|
38697
|
+
if (hub.note) console.error(hub.note);
|
|
38698
|
+
if (hubAbsent(hub.reason)) console.error("Installing the Kutrion app... Run `kt login` again once it is open, or sign in with your email and password below.");
|
|
38487
38699
|
}
|
|
38488
38700
|
if (flags.email === void 0 && !fromStdin) {
|
|
38489
38701
|
const hint = otherAppsHint(accountRoot);
|
|
@@ -38714,7 +38926,16 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38714
38926
|
break;
|
|
38715
38927
|
}
|
|
38716
38928
|
if (!positionals[0]) return die("usage: kt ssh <connId>");
|
|
38717
|
-
|
|
38929
|
+
const sshCtx = await connectionContext(positionals[0]);
|
|
38930
|
+
const sshId = positionals[0];
|
|
38931
|
+
runShell(cfg.base, token, sshId, void 0, sshCtx, void 0, async (k) => {
|
|
38932
|
+
await confirmChainedHostKey({
|
|
38933
|
+
isAdmin: () => client.me().then((r) => r.user.org_role === "owner" || r.user.org_role === "admin", () => false),
|
|
38934
|
+
ask: (q) => promptLine(q),
|
|
38935
|
+
confirm: (id, fp) => withStepUp(client, () => client.confirmSshHostKey(id, fp)),
|
|
38936
|
+
out: (l) => console.error(l)
|
|
38937
|
+
}, sshId, k, sshCtx.own);
|
|
38938
|
+
});
|
|
38718
38939
|
return;
|
|
38719
38940
|
}
|
|
38720
38941
|
case "exec": {
|
|
@@ -38722,8 +38943,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38722
38943
|
if (!positionals[0] || !positionals[1]) return die("usage: kt exec <connId> <command...>");
|
|
38723
38944
|
const r = await client.exec(positionals[0], positionals.slice(1).join(" ")).catch(async (e) => {
|
|
38724
38945
|
const k = hostKeyUnconfirmedFromError(e);
|
|
38725
|
-
|
|
38726
|
-
|
|
38946
|
+
const chained = k ? null : hostKeyChainedFromError(e);
|
|
38947
|
+
if (!k && !chained) throw e;
|
|
38948
|
+
const own = (await connectionContext(positionals[0])).own;
|
|
38949
|
+
for (const line of k ? hostKeyUnconfirmedLines(k, own) : hostKeyChainedLines(chained, own)) console.error(line);
|
|
38727
38950
|
return process.exit(1);
|
|
38728
38951
|
});
|
|
38729
38952
|
if (asJson) console.log(JSON.stringify(r, null, 2));
|
|
@@ -38811,8 +39034,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38811
39034
|
}
|
|
38812
39035
|
} catch (e) {
|
|
38813
39036
|
const k = hostKeyUnconfirmedFromError(e);
|
|
38814
|
-
|
|
38815
|
-
|
|
39037
|
+
const chained = k ? null : hostKeyChainedFromError(e);
|
|
39038
|
+
if (!k && !chained) throw e;
|
|
39039
|
+
const own = (await connectionContext(connId)).own;
|
|
39040
|
+
for (const line of k ? hostKeyUnconfirmedLines(k, own) : hostKeyChainedLines(chained, own)) console.error(line);
|
|
38816
39041
|
process.exit(1);
|
|
38817
39042
|
}
|
|
38818
39043
|
break;
|
|
@@ -39628,14 +39853,19 @@ Re-run with --fill to type a username and password for each, or create them with
|
|
|
39628
39853
|
}
|
|
39629
39854
|
let imported = 0, updated = 0;
|
|
39630
39855
|
const skipped = [];
|
|
39856
|
+
const needsBinding = [];
|
|
39857
|
+
const boundByCred = /* @__PURE__ */ new Map();
|
|
39631
39858
|
for (let i = 0; i < ready.length; i += 200) {
|
|
39632
39859
|
const r = await client.commitConnImport(vaultId, ready.slice(i, i + 200));
|
|
39633
39860
|
imported += r.imported;
|
|
39634
39861
|
updated += r.updated;
|
|
39635
39862
|
skipped.push(...r.skipped);
|
|
39863
|
+
needsBinding.push(...r.needs_binding ?? []);
|
|
39864
|
+
for (const b of r.bound_destinations ?? []) boundByCred.set(b.credential_id, b);
|
|
39636
39865
|
}
|
|
39866
|
+
const bound = [...boundByCred.values()];
|
|
39637
39867
|
if (asJson) {
|
|
39638
|
-
show({ provider: parsed.provider, imported, updated, skipped, held_back: blocked.length });
|
|
39868
|
+
show({ provider: parsed.provider, imported, updated, skipped, held_back: blocked.length, needs_binding: needsBinding, bound_destinations: bound });
|
|
39639
39869
|
break;
|
|
39640
39870
|
}
|
|
39641
39871
|
console.log(`imported ${imported} from ${parsed.provider}${updated ? `, updated ${updated}` : ""}`);
|
|
@@ -39644,6 +39874,15 @@ Re-run with --fill to type a username and password for each, or create them with
|
|
|
39644
39874
|
for (const [reason, names] of byReason) {
|
|
39645
39875
|
console.log(`skipped ${names.length} \u2014 ${CONN_SKIP_REASON[reason] ?? reason}: ${names.join(", ")}`);
|
|
39646
39876
|
}
|
|
39877
|
+
for (const b of bound) {
|
|
39878
|
+
console.log(CONN_IMPORT_BINDING_COPY.bound(b.credential, b.destinations.length));
|
|
39879
|
+
for (const d of b.destinations) console.log(` ${connImportDestLabel(d)}`);
|
|
39880
|
+
}
|
|
39881
|
+
if (needsBinding.length) {
|
|
39882
|
+
console.log(`${CONN_IMPORT_BINDING_COPY.needsHeading}: ${CONN_IMPORT_BINDING_COPY.needsIntro(needsBinding.length)}`);
|
|
39883
|
+
for (const n of needsBinding) console.log(` ${n.name} (${connImportDestLabel(n)})`);
|
|
39884
|
+
console.log(CONN_IMPORT_BINDING_COPY.needsWhere);
|
|
39885
|
+
}
|
|
39647
39886
|
} else die("usage: kt conn add --name <n> --host <h> --username <u> --cred <credId> | kt conn set <connId> [--name --host --port --username --cred] | kt conn rm <connId> | kt conn providers | kt conn import <vaultId> <file>");
|
|
39648
39887
|
break;
|
|
39649
39888
|
}
|