<# .SYNOPSIS Gets a valid Bearer token for the Ka-Note production API. PREFERRED (fully automatic): Add the redirect URI once in Azure Portal: App registrations → Ka-Note (1aba7af7-...) → Authentication → Add platform "Mobile and desktop" → URI: https://login.microsoftonline.com/common/oauth2/nativeclient Then uncomment the MSAL section below. CURRENT FALLBACK: reads cached token from %USERPROFILE%\.ka-note\token.txt Store a fresh token there with: .\set-token.ps1 "eyJ..." .OUTPUTS Writes the access token string to stdout (no trailing newline). #> param() $ErrorActionPreference = 'Stop' $tokenFile = Join-Path $env:USERPROFILE '.ka-note\token.txt' # ── Option A: MSAL.PS (uncomment after adding redirect URI in Azure Portal) ─── # Requires one-time Azure Portal step: # App registrations → 1aba7af7-eec1-4e49-b87e-9f941c0e8630 → Authentication # → Add platform "Mobile and desktop applications" # → Redirect URI: https://login.microsoftonline.com/common/oauth2/nativeclient # → Save # # if (-not (Get-Module -ListAvailable MSAL.PS)) { # Install-Module -Name MSAL.PS -Scope CurrentUser -Force -AllowClobber # } # Import-Module MSAL.PS # $p = @{ # ClientId = '1aba7af7-eec1-4e49-b87e-9f941c0e8630' # TenantId = '94cf90d7-e9ff-49a1-bc3b-a5b94d3cc8ca' # Scopes = 'api://1aba7af7-eec1-4e49-b87e-9f941c0e8630/access' # } # try { $r = Get-MsalToken @p -Silent 2>$null } # catch { $r = Get-MsalToken @p -Interactive } # Write-Output $r.AccessToken # return # ── Option B: cached token file (current default) ──────────────────────────── if (-not (Test-Path $tokenFile)) { Write-Error "No cached token found. Run: .\set-token.ps1 `"eyJ...`"" exit 1 } $token = (Get-Content $tokenFile -Raw -Encoding UTF8).Trim() # Check JWT expiry (payload is base64url, second segment) try { $parts = $token.Split('.') $payload = $parts[1] # Pad base64url to multiple of 4 $pad = 4 - ($payload.Length % 4); if ($pad -ne 4) { $payload += '=' * $pad } $payload = $payload.Replace('-', '+').Replace('_', '/') $claims = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($payload)) | ConvertFrom-Json $expUtc = [System.DateTimeOffset]::FromUnixTimeSeconds($claims.exp).UtcDateTime $remaining = ($expUtc - [System.DateTime]::UtcNow).TotalMinutes if ($remaining -lt 2) { Write-Warning "Token expired $(if ($remaining -lt 0) { [math]::Round(-$remaining) + ' min ago' } else { 'in < 2 min' }). Run: .\set-token.ps1 `"eyJ...`"" exit 1 } Write-Host " Token valid for $([math]::Round($remaining)) min." -ForegroundColor DarkGray } catch { Write-Warning "Could not parse token expiry - using as-is." } Write-Output $token