#Requires -Version 5.1 <# .SYNOPSIS Generates a fresh OAuth access token from the local Stalwart dev container and writes it to .env.development.local (gitignored). .DESCRIPTION Local development only. Requires the dev container from docker-compose.yml (`docker compose up -d`) to be running. Tokens expire after 1 hour; re-run this script and restart "npm run dev" when the UI starts returning 401s. The credentials below belong to the disposable local Stalwart container. See DEVELOPMENT.md for the full workflow. Non-Windows shells (and AI agents without PowerShell) can use scripts/dev-token.sh instead. #> param( [string]$ApiBaseUrl = "http://localhost:8080", [string]$AccountName = "admin@example.org", [string]$AccountSecret = "c8321iEscHDy0GWV" ) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot # PKCE pair (S256) $chars = (48..57) + (65..90) + (97..122) $verifier = -join ($chars | Get-Random -Count 64 | ForEach-Object { [char]$_ }) $sha = [System.Security.Cryptography.SHA256]::Create() $challenge = [Convert]::ToBase64String($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($verifier))).Replace('+', '-').Replace('/', '_').TrimEnd('=') $redirectUri = "http://localhost:3005/oauth/callback" $authPayload = @{ type = "authCode" accountName = $AccountName accountSecret = $AccountSecret clientId = "stalwart-webui" redirectUri = $redirectUri scope = "openid email profile offline_access" state = [guid]::NewGuid().ToString("N") codeChallenge = $challenge codeChallengeMethod = "S256" } | ConvertTo-Json -Compress $auth = Invoke-RestMethod -Uri "$ApiBaseUrl/api/auth" -Method Post -ContentType "application/json" -Body $authPayload -TimeoutSec 15 if ($auth.type -ne "authenticated" -or -not $auth.client_code) { throw "Unexpected /api/auth response: $($auth | ConvertTo-Json -Compress)" } $tokenBody = "grant_type=authorization_code&code=$($auth.client_code)&code_verifier=$verifier&client_id=stalwart-webui&redirect_uri=$([uri]::EscapeDataString($redirectUri))" $token = Invoke-RestMethod -Uri "$ApiBaseUrl/auth/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -TimeoutSec 15 $envPath = Join-Path $root ".env.development.local" @" # Generated by scripts/dev-token.ps1 - gitignored, do not commit. # Empty base URL: API calls stay same-origin and go through the Vite proxy. VITE_API_BASE_URL= VITE_ACCESS_TOKEN=$($token.access_token) "@ | Set-Content -Path $envPath -Encoding ascii Write-Host "Token written to $envPath (expires in $($token.expires_in)s). Restart 'npm run dev' to pick it up."