Files
Stalwart-webui/scripts/dev-token.ps1
T
Steven RYDELL f4c8f8f21c feat(dev): add one-command local Stalwart test server + workflow docs
Add docker-compose.yml: a disposable Stalwart instance with a fixed dev
admin account (STALWART_RECOVERY_ADMIN), matching the credentials
scripts/dev-token.ps1 already expected. Wired up via new npm scripts
(dev:server, dev:server:down, dev:server:logs).

scripts/dev-token.ps1 was previously untracked (the whole scripts/
directory was gitignored) even though it's part of the documented dev
workflow — un-ignored it, and added scripts/dev-token.sh, a POSIX
equivalent for non-Windows shells and AI agents without PowerShell.

New DEVELOPMENT.md documents the full loop end-to-end (start server,
get a token, run the dev server, verify), written so it's actionable by
both humans and AI coding agents without needing a browser. Linked from
AGENTS.md (Commands) and README.md (Getting started).

Verified manually: docker compose up brings the server to a healthy
state, /api/auth + /auth/token issue a working bearer token, and
/jmap/session returns 200 with it end-to-end.
2026-08-01 18:05:43 +02:00

61 lines
2.6 KiB
PowerShell

#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."