From e988fd354cebfd8bd94bd6bc70fcc105d0427ba9 Mon Sep 17 00:00:00 2001 From: Predrag Radenkovic Date: Wed, 8 Jul 2026 13:42:19 +0200 Subject: [PATCH 1/3] Verify API key on paste during install (#27) The installer accepted whatever the user pasted and always printed "Sign in successful", even for a wrong or whitespace-padded key. - Trim the pasted key (a tester copied it with newlines/extra spaces). - Verify the key via POST /status (200 = valid, 401 = invalid), which checks only the API key and nothing else about the install. - Re-prompt in a loop until a valid key is entered (or empty to skip); show a clear "Invalid API key" message, and a distinct message when the API cannot be reached. - Only print "Sign in successful" when a verified key is configured. Applied to both install.sh and install.ps1. --- install/bash/install.sh | 63 +++++++++++++++++++++++++++++-- install/powershell/install.ps1 | 68 ++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 8 deletions(-) diff --git a/install/bash/install.sh b/install/bash/install.sh index d66b753..95011f8 100755 --- a/install/bash/install.sh +++ b/install/bash/install.sh @@ -5,6 +5,9 @@ set -euo pipefail # Base URL for additional scripts CODEPLAIN_SCRIPTS_BASE_URL="${CODEPLAIN_SCRIPTS_BASE_URL:-https://codeplain.ai}" +# Base URL for the Codeplain API (used to verify the API key) +CODEPLAIN_API_URL="${CODEPLAIN_API_URL:-https://api.codeplain.ai}" + # Brand Colors (True Color / 24-bit) YELLOW='\033[38;2;224;255;110m' # #E0FF6E GREEN='\033[38;2;121;252;150m' # #79FC96 @@ -39,6 +42,30 @@ install_uv() { export PATH="$HOME/.local/bin:$PATH" } +# Trim leading/trailing whitespace (spaces, tabs, newlines, carriage returns). +# Users often copy the API key with surrounding whitespace or newlines. +trim_whitespace() { + local value="$*" + value="${value#"${value%%[![:space:]]*}"}" # strip leading whitespace + value="${value%"${value##*[![:space:]]}"}" # strip trailing whitespace + printf '%s' "$value" +} + +# Verify an API key against the Codeplain API's /status endpoint. +# Sets the global VALIDATION_HTTP_CODE and returns 0 only when the key is valid +# (HTTP 200). This checks only the API key, nothing else about the install. +validate_api_key() { + local key="$1" + local http_code + http_code=$(curl -s -o /dev/null -w "%{http_code}" \ + --max-time 30 \ + -X POST "${CODEPLAIN_API_URL}/status" \ + -H "Content-Type: application/json" \ + --data "{\"api_key\":\"${key}\"}" 2>/dev/null || true) + VALIDATION_HTTP_CODE="${http_code:-000}" + [ "$VALIDATION_HTTP_CODE" = "200" ] +} + # Check if uv is installed if ! command -v uv &> /dev/null; then echo -e "${GRAY}uv is not installed.${NC}" @@ -100,8 +127,33 @@ if [ "$SKIP_API_KEY_SETUP" = false ]; then else echo -e "Go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key." echo "" - read -r -p "Paste your API key here: " API_KEY < /dev/tty - echo "" + # Keep prompting until we get a valid API key (or the user submits an + # empty value to skip). The pasted key is trimmed and verified against + # the Codeplain API before we accept it. + while true; do + read -r -p "Paste your API key here: " API_KEY < /dev/tty + echo "" + API_KEY="$(trim_whitespace "$API_KEY")" + + # Empty input: let the user skip key setup for now. + if [ -z "$API_KEY" ]; then + break + fi + + echo -e "${GRAY}Verifying your API key...${NC}" + if validate_api_key "$API_KEY"; then + echo -e "${GREEN}✓${NC} API key verified." + echo "" + break + elif [ "$VALIDATION_HTTP_CODE" = "401" ]; then + echo -e "${RED}Invalid API key. Please make sure the full key was copied.${NC}" + echo "" + else + echo -e "${RED}Could not verify the API key (could not reach ${CODEPLAIN_API_URL}).${NC}" + echo -e "${GRAY}Check your internet connection and try again.${NC}" + echo "" + fi + done fi fi @@ -159,8 +211,11 @@ cat << 'EOF' |_| EOF echo "" -echo -e "${GREEN}✓ Sign in successful.${NC}" -echo "" +# Only claim success when a verified API key is actually configured. +if [ -n "${CODEPLAIN_API_KEY:-}" ]; then + echo -e "${GREEN}✓ Sign in successful.${NC}" + echo "" +fi echo -e " ${WHITE}Welcome to *codeplain!${NC}" echo "" echo -e " ${GRAY}Spec-driven, production-ready code generation${NC}" diff --git a/install/powershell/install.ps1 b/install/powershell/install.ps1 index 75a4b25..0c48300 100644 --- a/install/powershell/install.ps1 +++ b/install/powershell/install.ps1 @@ -12,6 +12,11 @@ if (-not $env:CODEPLAIN_SCRIPTS_BASE_URL) { $env:CODEPLAIN_SCRIPTS_BASE_URL = "https://codeplain.ai" } +# Base URL for the Codeplain API (used to verify the API key) +if (-not $env:CODEPLAIN_API_URL) { + $env:CODEPLAIN_API_URL = "https://api.codeplain.ai" +} + # Brand Colors (True Color / 24-bit) $ESC = [char]27 $YELLOW = "$ESC[38;2;224;255;110m" # #E0FF6E @@ -56,6 +61,33 @@ function Install-Uv { } } +# Verify an API key against the Codeplain API's /status endpoint. +# Returns a status string: "valid" (HTTP 200), "invalid" (HTTP 401), or +# "error" (could not reach the API). This checks only the API key. +function Test-ApiKey { + param([string]$Key) + + $body = @{ api_key = $Key } | ConvertTo-Json -Compress + try { + $response = Invoke-WebRequest -Uri "$($env:CODEPLAIN_API_URL)/status" ` + -Method Post ` + -ContentType "application/json" ` + -Body $body ` + -TimeoutSec 30 ` + -UseBasicParsing ` + -ErrorAction Stop + if ($response.StatusCode -eq 200) { return "valid" } + return "error" + } catch { + $statusCode = $null + if ($_.Exception.Response) { + $statusCode = [int]$_.Exception.Response.StatusCode + } + if ($statusCode -eq 401) { return "invalid" } + return "error" + } +} + # Check if uv is installed if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { Write-Host "${GRAY}uv is not installed.${NC}" @@ -151,8 +183,33 @@ if (-not $skipApiKeySetup) { } else { Write-Host "Go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key." Write-Host "" - $apiKey = Read-Host "Paste your API key here" - Write-Host "" + # Keep prompting until we get a valid API key (or the user submits an + # empty value to skip). The pasted key is trimmed and verified against + # the Codeplain API before we accept it. + while ($true) { + $apiKey = (Read-Host "Paste your API key here").Trim() + Write-Host "" + + # Empty input: let the user skip key setup for now. + if (-not $apiKey) { + break + } + + Write-Host "${GRAY}Verifying your API key...${NC}" + $result = Test-ApiKey $apiKey + if ($result -eq "valid") { + Write-Host "${GREEN}✓${NC} API key verified." + Write-Host "" + break + } elseif ($result -eq "invalid") { + Write-Host "${RED}Invalid API key. Please make sure the full key was copied.${NC}" + Write-Host "" + } else { + Write-Host "${RED}Could not verify the API key (could not reach $($env:CODEPLAIN_API_URL)).${NC}" + Write-Host "${GRAY}Check your internet connection and try again.${NC}" + Write-Host "" + } + } } } @@ -181,8 +238,11 @@ Write-Host @' |_| '@ Write-Host "" -Write-Host "${GREEN}✓ Sign in successful.${NC}" -Write-Host "" +# Only claim success when a verified API key is actually configured. +if ($env:CODEPLAIN_API_KEY) { + Write-Host "${GREEN}✓ Sign in successful.${NC}" + Write-Host "" +} Write-Host " ${WHITE}Welcome to *codeplain!${NC}" Write-Host "" Write-Host " ${GRAY}Spec-driven, production-ready code generation${NC}" From f0518102e8db26aa1e8de88f56d91de490fa70a4 Mon Sep 17 00:00:00 2001 From: Predrag Radenkovic Date: Wed, 8 Jul 2026 13:46:29 +0200 Subject: [PATCH 2/3] Verify existing API key during install too (#27) Previously an already-configured CODEPLAIN_API_KEY that the user chose to keep was trusted without checking, so a stale/expired key still printed "Sign in successful". - Verify the existing key against /status when the user keeps it (and in non-interactive mode). - If invalid: interactive falls through to enter a new key; non-interactive warns and does not claim success. - If the API is unreachable, keep the existing key rather than locking the user out over a transient network issue. - Track verification with API_KEY_VERIFIED and gate the "Sign in successful" banner on it (instead of mere key presence). Applied to both install.sh and install.ps1. --- install/bash/install.sh | 33 ++++++++++++++++++++++++++++++--- install/powershell/install.ps1 | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/install/bash/install.sh b/install/bash/install.sh index 95011f8..1e5de4c 100755 --- a/install/bash/install.sh +++ b/install/bash/install.sh @@ -102,10 +102,11 @@ fi # Check if API key already exists SKIP_API_KEY_SETUP=false +API_KEY_VERIFIED=false if [ -n "${CODEPLAIN_API_KEY:-}" ]; then + USE_EXISTING=false if [ "$NONINTERACTIVE" = "1" ]; then - echo -e "${GREEN}✓${NC} Using existing CODEPLAIN_API_KEY (non-interactive mode)." - SKIP_API_KEY_SETUP=true + USE_EXISTING=true else echo -e " You already have an API key configured." echo "" @@ -115,8 +116,33 @@ if [ -n "${CODEPLAIN_API_KEY:-}" ]; then echo "" if [[ ! "$GET_NEW_KEY" =~ ^[Yy]$ ]]; then + USE_EXISTING=true + fi + fi + + # Verify the existing key before trusting it, so a stale/expired key + # doesn't slip through with a false "Sign in successful". + if [ "$USE_EXISTING" = true ]; then + echo -e "${GRAY}Verifying your existing API key...${NC}" + if validate_api_key "$CODEPLAIN_API_KEY"; then echo -e "${GREEN}✓${NC} Using existing API key." SKIP_API_KEY_SETUP=true + API_KEY_VERIFIED=true + elif [ "$VALIDATION_HTTP_CODE" = "401" ]; then + if [ "$NONINTERACTIVE" = "1" ]; then + echo -e "${RED}Existing CODEPLAIN_API_KEY is invalid.${NC}" + echo -e "${GRAY}Set a valid CODEPLAIN_API_KEY and re-run the installer.${NC}" + SKIP_API_KEY_SETUP=true + else + echo -e "${RED}Your existing API key is invalid. Please enter a new one.${NC}" + echo "" + # SKIP_API_KEY_SETUP stays false: fall through to the paste flow. + fi + else + # Could not reach the API. Keep the existing key rather than + # blocking the user over a transient network issue. + echo -e "${GRAY}Could not verify the existing API key (could not reach ${CODEPLAIN_API_URL}). Keeping it.${NC}" + SKIP_API_KEY_SETUP=true fi fi fi @@ -144,6 +170,7 @@ if [ "$SKIP_API_KEY_SETUP" = false ]; then if validate_api_key "$API_KEY"; then echo -e "${GREEN}✓${NC} API key verified." echo "" + API_KEY_VERIFIED=true break elif [ "$VALIDATION_HTTP_CODE" = "401" ]; then echo -e "${RED}Invalid API key. Please make sure the full key was copied.${NC}" @@ -212,7 +239,7 @@ cat << 'EOF' EOF echo "" # Only claim success when a verified API key is actually configured. -if [ -n "${CODEPLAIN_API_KEY:-}" ]; then +if [ "$API_KEY_VERIFIED" = true ]; then echo -e "${GREEN}✓ Sign in successful.${NC}" echo "" fi diff --git a/install/powershell/install.ps1 b/install/powershell/install.ps1 index 0c48300..5ea262a 100644 --- a/install/powershell/install.ps1 +++ b/install/powershell/install.ps1 @@ -157,10 +157,11 @@ Write-Host "" # Check if API key already exists $skipApiKeySetup = $false +$apiKeyVerified = $false if ($env:CODEPLAIN_API_KEY) { + $useExisting = $false if ($nonInteractive) { - Write-Host "${GREEN}✓${NC} Using existing CODEPLAIN_API_KEY (non-interactive mode)." - $skipApiKeySetup = $true + $useExisting = $true } else { Write-Host " You already have an API key configured." Write-Host "" @@ -170,8 +171,34 @@ if ($env:CODEPLAIN_API_KEY) { Write-Host "" if ($getNewKey -notmatch '^[Yy]$') { + $useExisting = $true + } + } + + # Verify the existing key before trusting it, so a stale/expired key + # doesn't slip through with a false "Sign in successful". + if ($useExisting) { + Write-Host "${GRAY}Verifying your existing API key...${NC}" + $existingResult = Test-ApiKey $env:CODEPLAIN_API_KEY + if ($existingResult -eq "valid") { Write-Host "${GREEN}✓${NC} Using existing API key." $skipApiKeySetup = $true + $apiKeyVerified = $true + } elseif ($existingResult -eq "invalid") { + if ($nonInteractive) { + Write-Host "${RED}Existing CODEPLAIN_API_KEY is invalid.${NC}" + Write-Host "${GRAY}Set a valid CODEPLAIN_API_KEY and re-run the installer.${NC}" + $skipApiKeySetup = $true + } else { + Write-Host "${RED}Your existing API key is invalid. Please enter a new one.${NC}" + Write-Host "" + # $skipApiKeySetup stays false: fall through to the paste flow. + } + } else { + # Could not reach the API. Keep the existing key rather than + # blocking the user over a transient network issue. + Write-Host "${GRAY}Could not verify the existing API key (could not reach $($env:CODEPLAIN_API_URL)). Keeping it.${NC}" + $skipApiKeySetup = $true } } } @@ -200,6 +227,7 @@ if (-not $skipApiKeySetup) { if ($result -eq "valid") { Write-Host "${GREEN}✓${NC} API key verified." Write-Host "" + $apiKeyVerified = $true break } elseif ($result -eq "invalid") { Write-Host "${RED}Invalid API key. Please make sure the full key was copied.${NC}" @@ -239,7 +267,7 @@ Write-Host @' '@ Write-Host "" # Only claim success when a verified API key is actually configured. -if ($env:CODEPLAIN_API_KEY) { +if ($apiKeyVerified) { Write-Host "${GREEN}✓ Sign in successful.${NC}" Write-Host "" } From 348d37723f384181524993b8acd20ee61e79b821 Mon Sep 17 00:00:00 2001 From: Predrag Radenkovic Date: Wed, 8 Jul 2026 15:20:08 +0200 Subject: [PATCH 3/3] Verify the installed tool works at the end of install (#27) After installing, run `codeplain --status` as a final end-to-end check that the tool launches, is on PATH, and can reach the API. Output is suppressed; the result is mapped to a simple pass/fail. On failure the installer stops with a generic "something went wrong" message instead of declaring success. To make this signal reliable, `codeplain --status` now exits non-zero on failure (missing key or a failed status fetch); previously it always exited 0. Human-visible output is unchanged. The check runs only when an API key is configured, so users who skip key setup are not falsely told something went wrong. Applied to both install.sh and install.ps1. --- install/bash/install.sh | 17 +++++++++++++++++ install/powershell/install.ps1 | 22 ++++++++++++++++++++++ plain2code.py | 3 ++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/install/bash/install.sh b/install/bash/install.sh index 1e5de4c..35bab15 100755 --- a/install/bash/install.sh +++ b/install/bash/install.sh @@ -389,6 +389,23 @@ if [[ ! "${DOWNLOAD_EXAMPLES:-}" =~ ^[Nn]$ ]]; then run_script "examples.sh" fi +# Final verification: make sure the installed codeplain can actually run and +# reach the API. Only meaningful when an API key is configured, so users who +# skipped the key are not falsely told something went wrong. +if [ -n "${CODEPLAIN_API_KEY:-}" ]; then + # Ensure the freshly installed tool is findable in this session. + export PATH="$HOME/.local/bin:$PATH" + echo -e "${GRAY}Verifying your installation...${NC}" + if codeplain --status > /dev/null 2>&1; then + echo -e "${GREEN}✓${NC} Installation verified." + else + echo -e "${RED}Something went wrong during installation.${NC}" + echo -e "${GRAY}Please restart your terminal and try again, or reinstall with:${NC}" + echo -e " uv tool install --force codeplain" + exit 1 + fi +fi + # Final message if [ "$NONINTERACTIVE" != "1" ]; then clear diff --git a/install/powershell/install.ps1 b/install/powershell/install.ps1 index 5ea262a..a7418da 100644 --- a/install/powershell/install.ps1 +++ b/install/powershell/install.ps1 @@ -420,6 +420,28 @@ if ($downloadExamples -notmatch '^[Nn]$') { Invoke-SubScript "examples.ps1" } +# Final verification: make sure the installed codeplain can actually run and +# reach the API. Only meaningful when an API key is configured, so users who +# skipped the key are not falsely told something went wrong. +if ($env:CODEPLAIN_API_KEY) { + Write-Host "${GRAY}Verifying your installation...${NC}" + $verifyOk = $false + try { + & codeplain --status *> $null + $verifyOk = ($LASTEXITCODE -eq 0) + } catch { + $verifyOk = $false + } + if ($verifyOk) { + Write-Host "${GREEN}✓${NC} Installation verified." + } else { + Write-Host "${RED}Something went wrong during installation.${NC}" + Write-Host "${GRAY}Please restart your terminal and try again, or reinstall with:${NC}" + Write-Host " uv tool install --force codeplain" + exit 1 + } +} + # Final message if (-not $nonInteractive) { Clear-Host } Write-Host "" diff --git a/plain2code.py b/plain2code.py index 37a20f3..d0d3d9e 100644 --- a/plain2code.py +++ b/plain2code.py @@ -327,7 +327,7 @@ def main(): # noqa: C901 console.error( "Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n" ) - return + sys.exit(1) if not args.api: args.api = "https://api.codeplain.ai" @@ -336,6 +336,7 @@ def main(): # noqa: C901 print_status(args.api_key, args.api, system_config.client_version) except Exception as e: console.error(f"Error fetching status: {str(e)}") + sys.exit(1) return template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)