<# AZA – Step 12: Authorized Smoke-Test (PowerShell) What it does: - Loads MEDWORK_API_TOKENS (preferred) or MEDWORK_API_TOKEN (legacy) from deploy/.env - Picks the first token (NEW) from a comma-separated list - Calls GET /license/status with header: X-API-Token: - Optionally sends X-Device-Id (stable per run unless you override) Run: powershell -ExecutionPolicy Bypass -File .\deploy\authorized_test.ps1 Optional: powershell -ExecutionPolicy Bypass -File .\deploy\authorized_test.ps1 -BaseUrl http://127.0.0.1:8000 powershell -ExecutionPolicy Bypass -File .\deploy\authorized_test.ps1 -DeviceId "test-device-123" Expected: 200 + JSON: {"valid": true/false, "valid_until": 1774553596 or null} #> [CmdletBinding()] param( [string]$BaseUrl = "http://127.0.0.1:8000", # default: .env next to this script (deploy\.env) [string]$EnvFile = "", [string]$DeviceId = "" ) function Get-ScriptDir { # Prefer PSScriptRoot when available, otherwise fall back to MyInvocation (works in Windows PowerShell). if ($PSScriptRoot -and $PSScriptRoot.Trim().Length -gt 0) { return $PSScriptRoot } $p = $MyInvocation.MyCommand.Path if (-not $p) { throw "Cannot determine script directory (PSScriptRoot/MyInvocation empty)." } return (Split-Path -Parent $p) } function Load-DotEnv([string]$Path) { if (-not (Test-Path -LiteralPath $Path)) { throw "Missing .env file at: $Path" } $map = @{} Get-Content -LiteralPath $Path | ForEach-Object { $line = $_.Trim() if ($line.Length -eq 0) { return } if ($line.StartsWith("#")) { return } $idx = $line.IndexOf("=") if ($idx -lt 1) { return } $k = $line.Substring(0, $idx).Trim() $v = $line.Substring($idx + 1).Trim() # strip optional wrapping quotes if (($v.StartsWith('"') -and $v.EndsWith('"')) -or ($v.StartsWith("'") -and $v.EndsWith("'"))) { $v = $v.Substring(1, $v.Length - 2) } $map[$k] = $v } return $map } function First-TokenFromValue([string]$value) { if (-not $value) { return "" } # supports "NEW,OLD" and multi-line/space separated variants $value = $value.Trim() $parts = $value -split "[,\r\n]+" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } if ($parts.Count -ge 1) { return $parts[0] } return "" } function All-TokensFromValue([string]$value) { if (-not $value) { return @() } $value = $value.Trim() return ($value -split "[,\r\n]+" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }) } try { if (-not $EnvFile -or $EnvFile.Trim().Length -eq 0) { $scriptDir = Get-ScriptDir $EnvFile = Join-Path $scriptDir ".env" } $envMap = Load-DotEnv -Path $EnvFile } catch { Write-Host "❌ $($_.Exception.Message)" exit 1 } $tokenValue = "" $tokenSource = "" if ($envMap.ContainsKey("MEDWORK_API_TOKENS")) { $tokenValue = $envMap["MEDWORK_API_TOKENS"] } elseif ($envMap.ContainsKey("MEDWORK_API_TOKEN")) { $tokenValue = $envMap["MEDWORK_API_TOKEN"] } $tokens = @() if ($envMap.ContainsKey("MEDWORK_API_TOKENS")) { $tokenSource = "MEDWORK_API_TOKENS" $tokens = All-TokensFromValue -value $envMap["MEDWORK_API_TOKENS"] } elseif ($envMap.ContainsKey("MEDWORK_API_TOKEN")) { $tokenSource = "MEDWORK_API_TOKEN" $tokens = All-TokensFromValue -value $envMap["MEDWORK_API_TOKEN"] } if (-not $tokens -or $tokens.Count -lt 1) { Write-Host "❌ No token found in $EnvFile (expected MEDWORK_API_TOKENS or MEDWORK_API_TOKEN)." exit 1 } if (-not $DeviceId) { # deterministic-ish per machine/user; override via -DeviceId when needed $DeviceId = "ps-" + $env:COMPUTERNAME + "-" + $env:USERNAME } $base = $BaseUrl.TrimEnd("/") $url = "$base/license/status" Write-Host "[AZA] Authorized Smoke-Test" Write-Host " BaseUrl: $base" Write-Host " Endpoint: /license/status" Write-Host " TokenSrc: $tokenSource" Write-Host " Tokens: $($tokens.Count) (will try in order)" Write-Host " TokenLen: $($tokens[0].Length) (first token length only)" Write-Host " DeviceId: $DeviceId" Write-Host "" function Invoke-GetJson([string]$u, [hashtable]$h) { try { $resp = Invoke-RestMethod -Method GET -Uri $u -Headers $h -TimeoutSec 15 return @{ ok=$true; status=200; body=$resp } } catch { $e = $_.Exception $status = $null $body = "" if ($e.Response -and $e.Response.StatusCode) { $status = [int]$e.Response.StatusCode try { $stream = $e.Response.GetResponseStream() if ($stream) { $reader = New-Object System.IO.StreamReader($stream) $body = $reader.ReadToEnd() } } catch { } } return @{ ok=$false; status=$status; body=$body; message=$e.Message } } } $base = $BaseUrl.TrimEnd("/") function Invoke-GetJsonNoAuth([string]$u) { try { $resp = Invoke-RestMethod -Method GET -Uri $u -TimeoutSec 15 return @{ ok=$true; status=200; body=$resp } } catch { $e = $_.Exception $status = $null if ($e.Response -and $e.Response.StatusCode) { $status = [int]$e.Response.StatusCode } return @{ ok=$false; status=$status; message=$e.Message } } } function Discover-LicenseStatusPath([string]$baseUrl) { $openapiUrl = "$baseUrl/openapi.json" $r = Invoke-GetJsonNoAuth $openapiUrl if (-not $r.ok) { return @{ ok=$false; reason="openapi_unavailable"; path=$null } } try { $paths = $r.body.paths.PSObject.Properties.Name $hits = @() foreach ($p in $paths) { $pl = $p.ToLowerInvariant() if ($pl -like "*license*" -and $pl -like "*status*") { $hits += $p } } if ($hits.Count -ge 1) { return @{ ok=$true; reason="openapi_match"; path=$hits[0]; all=$hits } } return @{ ok=$false; reason="openapi_no_match"; path=$null } } catch { return @{ ok=$false; reason="openapi_parse_failed"; path=$null } } } $lastStatus = $null # First: sanity check health so we know we're talking to the correct server $health = Invoke-GetJsonNoAuth "$base/health" if ($health.ok) { Write-Host "Health: OK (GET /health)" } else { Write-Host "Health: not OK (GET /health) status=$($health.status)" } Write-Host "" # Discover correct license/status path via OpenAPI if available $disc = Discover-LicenseStatusPath $base $pathsToTry = @() if ($disc.ok -and $disc.path) { $pathsToTry += $disc.path if ($disc.all -and $disc.all.Count -gt 1) { foreach ($p in $disc.all) { if ($p -ne $disc.path) { $pathsToTry += $p } } } Write-Host "Discovered path(s) via /openapi.json:" $pathsToTry | ForEach-Object { Write-Host " - $_" } } else { # Fallback candidates (do not change server behavior, just try likely paths) $pathsToTry = @("/license/status", "/api/license/status", "/v1/license/status", "/license/status/") Write-Host "OpenAPI discovery not usable ($($disc.reason)). Trying common candidates:" $pathsToTry | ForEach-Object { Write-Host " - $_" } } Write-Host "" foreach ($path in $pathsToTry) { $testUrl = "$base$path" foreach ($t in $tokens) { $headers = @{ "X-API-Token" = $t "X-Device-Id" = $DeviceId "Accept" = "application/json" } $res = Invoke-GetJson -u $testUrl -h $headers if ($res.ok) { Write-Host "OK HTTP 200 ($path)" $json = $res.body | ConvertTo-Json -Depth 10 -Compress Write-Host $json exit 0 } $lastStatus = $res.status if ($res.status -eq 404) { # try next path break } if ($res.status -eq 401 -or $res.status -eq 403) { # try next token continue } Write-Host "FAIL HTTP $($res.status) ($path)" if ($res.body) { Write-Host $res.body } else { Write-Host $res.message } exit 1 } } Write-Host "FAIL (no matching license/status endpoint found, or all tokens unauthorized)" Write-Host "LastStatus: $lastStatus" exit 1