Connect-PveServer -ApiToken was a plain string, so the token landed verbatim
in PSReadLine history and any transcript, and PveSession published ApiToken,
Ticket and CsrfToken as public getters, so Format-List *, ConvertTo-Json and
Export-Clixml of a session printed them.
The parameter is now a SecureString, extracted at the cmdlet boundary with the
Marshal/ZeroFree pattern ADR 0002 established for passwords. A plain string
still binds for one minor release through an argument transformation, and the
cmdlet warns that the string form goes away in the next major; the marker
lives in a ConditionalWeakTable keyed on the converted instance, so an
abandoned binding neither retains the secret nor mislabels a later call.
The three session getters become internal. PveHttpClient is in the same
assembly and the xUnit project already has InternalsVisibleTo, so the header
construction and its tests are unchanged.
Refs ADR 0028, issue #147.
Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
* fix: renew ticket sessions inside PveHttpClient before expiry and once on a 401
A ticket session died two hours after Connect-PveServer, and any -Wait that
crossed that line surfaced as a raw 401 from the status poll. PveHttpClient
now renews a ticket past half its lifetime by posting it as the password to
/access/ticket, and after a 401 on a ticket-mode request renews once and
retries once; a failed renewal is PveSessionExpiredException with the 401
inner. Renewals are single-flighted per session through an in-flight task
so concurrent callers share one POST and its outcome, the credential is one
immutable snapshot so no request mixes two tickets, and the renewal POST is
bounded by the session timeout rather than the calling client's. API-token
sessions never renew. Implements ADR 0027.
* fix: keep the cluster-join password re-auth fallback across the client's ticket renewal
A cluster join rotates the auth key, so the 401 the status poll gets back
now reaches Add-PveClusterMember as PveSessionExpiredException after the
client's own renewal fails. Widen the fallback's catch so the password
re-authentication, the only path that survives a key rotation, still runs.
---------
Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
PveHttpClient was constructed without setting HttpClient.Timeout, so
.NET's 100s default applied to every request. Multi-GB ISO uploads via
Send-PveFile on a real LAN reliably tripped this with TaskCanceledException
after 100 seconds, and there was no way to override it.
- PveSession gains a Timeout (TimeSpan) property, defaulting to 100s.
- PveHttpClient accepts an optional per-instance timeout override that
takes precedence over the session timeout.
- Connect-PveServer exposes -TimeoutSeconds to set the session default.
- Send-PveFile and Invoke-PveStorageDownload expose -TimeoutSeconds with
a 30-minute implicit default so large uploads/downloads do not trip
the 100s default. -TimeoutSeconds 0 means Timeout.InfiniteTimeSpan.
Tracked as F087. Closes#59.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 tests covering authentication (version parsing, session expiry, token
format validation), model deserialization from real PVE 8.x/9.x API JSON
fixtures, and service layer behavior. Includes TestHelper with mock
HttpMessageHandler factory and 21 JSON fixture files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>