Private/New-PSUApiKey.ps1
|
function New-PSUApiKey { <# .SYNOPSIS Generates a secure 24-hour API key for PSU AI Proxy. .DESCRIPTION Creates a Base64-encoded API key containing: The key is cached in $script:PSU_API_KEY for reuse in the current session. .PARAMETER ExpireTimeHours How many hours until the key expires. Default is 24 hours. .PARAMETER Force Force regeneration even if a valid cached key exists. .EXAMPLE $apiKey = New-PSUApiKey # Generates and caches API key for 24 hours .EXAMPLE $apiKey = New-PSUApiKey -ExpireTimeHours 48 # Generates key valid for 48 hours .EXAMPLE $apiKey = New-PSUApiKey -Force # Force regenerate even if cached key exists .OUTPUTS [String] .NOTES Author: Lakshmanachari Panuganti Cross-platform: Windows, Linux, macOS #> [CmdletBinding()] [OutputType([string])] param( [Parameter()] [ValidateRange(1, 8760)] [Alias('ExpireTime')] [int]$ExpireTimeHours = 24, [Parameter()] [switch]$Force ) begin { Write-Verbose "=== Starting PSU API Key Generation ===" } process { try { # Check for cached key first (unless Force is specified) if (-not $Force -and $script:PSU_API_KEY -and $script:PSU_API_KEY_EXPIRY) { $now = [DateTime]::UtcNow if ($now -lt $script:PSU_API_KEY_EXPIRY) { $timeLeft = $script:PSU_API_KEY_EXPIRY - $now Write-Verbose "Using cached API key (expires in $($timeLeft.TotalHours.ToString('F1')) hours)" Write-Host "✓ Using cached API key (expires in $($timeLeft.TotalHours.ToString('F1')) hours)" -ForegroundColor Green return $script:PSU_API_KEY } else { Write-Verbose "Cached API key has expired, generating new one" } } # ============================================ # 1. Get Username (REQUIRED - NO FALLBACK) # ============================================ Write-Verbose "Retrieving username..." $username = $null # Try environment variables first (cross-platform) $username = $env:USERNAME # Windows if (-not $username) { $username = $env:USER # Linux/macOS } # Try .NET method (Windows) if (-not $username) { try { $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() if ($identity -and $identity.Name) { $username = $identity.Name.Split('\')[-1] # Get username part only } } catch { Write-Verbose "WindowsIdentity method failed: $($_.Exception.Message)" } } # Try whoami command (Linux/macOS fallback) if (-not $username) { try { $username = (whoami 2>$null) if ($username) { $username = $username.Split('\')[-1].Trim() } } catch { Write-Verbose "whoami command failed: $($_.Exception.Message)" } } # Try id command (Linux/macOS alternative) if (-not $username) { try { $idOutput = (id -un 2>$null) if ($idOutput) { $username = $idOutput.Trim() } } catch { Write-Verbose "id command failed: $($_.Exception.Message)" } } # Validate username if ([string]::IsNullOrWhiteSpace($username)) { throw "CRITICAL: Unable to determine username. This is required for API key generation." } Write-Verbose "Username: $username" # ============================================ # 2. Get Computer Name (REQUIRED - NO FALLBACK) # ============================================ Write-Verbose "Retrieving computer name..." $computer = $null # Try environment variables $computer = $env:COMPUTERNAME # Windows if (-not $computer) { $computer = $env:HOSTNAME # Linux/macOS } # Try .NET DNS method if (-not $computer) { try { $computer = [System.Net.Dns]::GetHostName() } catch { Write-Verbose "DNS GetHostName failed: $($_.Exception.Message)" } } # Try hostname command (cross-platform) if (-not $computer) { try { $computer = (hostname 2>$null) if ($computer) { $computer = $computer.Trim() } } catch { Write-Verbose "hostname command failed: $($_.Exception.Message)" } } # Try uname command (Linux/macOS) if (-not $computer) { try { $computer = (uname -n 2>$null) if ($computer) { $computer = $computer.Trim() } } catch { Write-Verbose "uname command failed: $($_.Exception.Message)" } } # Validate computer name if ([string]::IsNullOrWhiteSpace($computer)) { throw "CRITICAL: Unable to determine computer name. This is required for API key generation." } Write-Verbose "Computer name: $computer" # ============================================ # 3. Get Public IP (REQUIRED - NO FALLBACK) # ============================================ Write-Verbose "Retrieving public IP address..." # Check if Get-PublicIP function exists if (-not (Get-Command -Name Get-PublicIP -ErrorAction SilentlyContinue)) { throw "CRITICAL: Get-PublicIP function not found. Please ensure it is loaded in the current session." } try { $publicIP = Get-PublicIP -TimeoutSec 5 -ErrorAction Stop # Validate IP format if ([string]::IsNullOrWhiteSpace($publicIP) -or $publicIP -eq "0.0.0.0") { throw "Invalid public IP address returned: $publicIP" } # Validate IP format with regex if ($publicIP -notmatch '^\d{1,3}(\.\d{1,3}){3}$') { throw "Invalid IP address format: $publicIP" } # Validate octets are in valid range (0-255) $octets = $publicIP -split '\.' foreach ($octet in $octets) { if ([int]$octet -gt 255) { throw "Invalid IP address (octet > 255): $publicIP" } } Write-Verbose "Public IP: $publicIP" } catch { $errorMsg = "CRITICAL: Failed to retrieve public IP address. This is required for API key generation.`n" $errorMsg += "Error: $($_.Exception.Message)`n" $errorMsg += "Ensure you have internet connectivity and the Get-PublicIP function is working correctly." throw $errorMsg } # ============================================ # 4. Get Timestamps # ============================================ $utcNow = [DateTime]::UtcNow $createdAt = $utcNow.ToString("o") $expiresAt = $utcNow.AddHours($ExpireTimeHours).ToString("o") Write-Verbose "Created: $createdAt" Write-Verbose "Expires: $expiresAt" # ============================================ # 5. Get Hardware Serial Number (OPTIONAL - CAN FALLBACK) # ============================================ Write-Verbose "Retrieving hardware serial number..." $serialNumber = "Unknown" # Windows: Try CIM/WMI if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) { try { # Try baseboard serial first $baseboard = Get-CimInstance -ClassName Win32_BaseBoard -ErrorAction Stop $serialNumber = $baseboard.SerialNumber # If empty, try system UUID if ([string]::IsNullOrWhiteSpace($serialNumber) -or $serialNumber -match '^(None|To be filled)') { $cs = Get-CimInstance -ClassName Win32_ComputerSystemProduct -ErrorAction Stop $serialNumber = $cs.UUID } # If still empty, try BIOS serial if ([string]::IsNullOrWhiteSpace($serialNumber) -or $serialNumber -match '^(None|To be filled)') { $bios = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop $serialNumber = $bios.SerialNumber } Write-Verbose "Windows serial number: $serialNumber" } catch { Write-Verbose "Windows CIM query failed: $($_.Exception.Message)" } } # Linux: Try DMI if (($IsLinux -or $serialNumber -eq "Unknown") -and (Test-Path "/sys/class/dmi/id/product_uuid" -ErrorAction SilentlyContinue)) { try { $uuid = Get-Content "/sys/class/dmi/id/product_uuid" -ErrorAction Stop if ($uuid -and $uuid -ne "Unknown") { $serialNumber = $uuid.Trim() Write-Verbose "Linux DMI UUID: $serialNumber" } } catch { Write-Verbose "Linux DMI read failed: $($_.Exception.Message)" } } # macOS: Try system_profiler if (($IsMacOS -or $serialNumber -eq "Unknown") -and (Get-Command "system_profiler" -ErrorAction SilentlyContinue)) { try { $hwInfo = system_profiler SPHardwareDataType 2>$null | Select-String "Serial Number" if ($hwInfo) { $serial = ($hwInfo.ToString() -replace '.*:\s*', '').Trim() if ($serial) { $serialNumber = $serial Write-Verbose "macOS serial number: $serialNumber" } } } catch { Write-Verbose "macOS system_profiler failed: $($_.Exception.Message)" } } # Final fallback is acceptable for serial number if ([string]::IsNullOrWhiteSpace($serialNumber)) { $serialNumber = "Unknown" } Write-Verbose "Hardware serial: $serialNumber" # ============================================ # 6. Build API Key String # ============================================ # Format: username|computer|publicIP|createdAt|expiresAt|serialNumber $parts = @( $username, $computer, $publicIP, $createdAt, $expiresAt, $serialNumber, (New-Guid).Guid ) $combined = $parts -join '|' Write-Verbose "Key components: $combined" # ============================================ # 7. Encode to Base64 # ============================================ $bytes = [System.Text.Encoding]::UTF8.GetBytes($combined) $encoded = [Convert]::ToBase64String($bytes) # ============================================ # 8. Cache the key for session reuse # ============================================ $script:PSU_API_KEY = $encoded $script:PSU_API_KEY_EXPIRY = $utcNow.AddHours($ExpireTimeHours) $script:PSU_API_KEY_USERNAME = $username $script:PSU_API_KEY_COMPUTER = $computer $script:PSU_API_KEY_IP = $publicIP # ============================================ # 9. Display success message # ============================================ Write-Host "" Write-Host "╔════════════════════════════════════════════════════════╗" -ForegroundColor Cyan Write-Host "║ ✓ API Key Generated Successfully ║" -ForegroundColor Cyan Write-Host "╠════════════════════════════════════════════════════════╣" -ForegroundColor Cyan Write-Host "║ User : $($username.PadRight(40)) ║" -ForegroundColor White Write-Host "║ Computer : $($computer.PadRight(40)) ║" -ForegroundColor White Write-Host "║ Public IP : $($publicIP.PadRight(40)) ║" -ForegroundColor White Write-Host "║ Hardware : $($serialNumber.Substring(0, [Math]::Min(40, $serialNumber.Length)).PadRight(40)) ║" -ForegroundColor Gray Write-Host "║ Expires : $($expiresAt.PadRight(40)) ║" -ForegroundColor Yellow Write-Host "║ Cached : Yes (session-wide reuse enabled) ║" -ForegroundColor Green Write-Host "╚════════════════════════════════════════════════════════╝" -ForegroundColor Cyan Write-Host "" Write-Verbose "API key length: $($encoded.Length) characters" return $encoded } catch { # Clear any partial cache on error $script:PSU_API_KEY = $null $script:PSU_API_KEY_EXPIRY = $null Write-Host "" Write-Host "╔════════════════════════════════════════════════════════╗" -ForegroundColor Red Write-Host "║ ✗ API Key Generation Failed ║" -ForegroundColor Red Write-Host "╚════════════════════════════════════════════════════════╝" -ForegroundColor Red Write-Host "" $errorMsg = "Failed to generate PSU API key: $($_.Exception.Message)" Write-Error $errorMsg throw } } end { Write-Verbose "=== API Key Generation Complete ===" } } |