Private/Vault/Get-SafehouseCredentialView.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Get-SafehouseCredentialView {
    <#
    .SYNOPSIS
        Returns the reconciled credential view for the vault.
    .DESCRIPTION
        The vault metadata (GUERRILLA_VAULT_METADATA) is the source of friendly
        descriptions/dates/expiry, but some store paths historically wrote a secret
        without registering metadata (the interactive admin-email write, Pushover, a
        bare MAILGUN key from an older version). Those secrets are present and working
        but invisible to any metadata-only status surface — a real troubleshooting blind
        spot ("are my creds loaded?" answered "no" when the secret is actually there).

        This helper enumerates the ACTUAL secret store (Get-SecretInfo) and reconciles it
        with the metadata, so every present secret shows up. Metadata-backed entries keep
        their rich fields; present-but-unregistered keys get a best-effort synthetic entry
        flagged with .unregistered = $true (so callers can surface "stored, no metadata").

        Returns a hashtable keyed by vault key name -> credential entry (hashtable).
    #>

    [CmdletBinding()]
    param(
        [string]$VaultName = 'PSGuerrilla'
    )

    $metadata = Get-VaultMetadata -VaultName $VaultName
    $view = @{}
    if ($metadata.credentials) {
        foreach ($k in $metadata.credentials.Keys) { $view[$k] = $metadata.credentials[$k] }
    }

    # Reconcile with the real store so present-but-unregistered keys are not hidden.
    if (Get-Command Get-SecretInfo -ErrorAction SilentlyContinue) {
        $actual = @()
        try { $actual = @(Get-SecretInfo -Vault $VaultName -ErrorAction Stop) } catch {}
        foreach ($info in $actual) {
            $name = [string]$info.Name
            if (-not $name -or $name -eq 'GUERRILLA_VAULT_METADATA') { continue }
            if ($view.ContainsKey($name)) { continue }
            $view[$name] = @{
                description  = (Resolve-SafehouseKeyLabel -Key $name)
                environment  = (Resolve-SafehouseKeyEnvironment -Key $name)
                type         = 'unregistered'
                storedDate   = $null
                unregistered = $true
            }
        }
    }

    return $view
}

function Resolve-SafehouseKeyLabel {
    # Best-effort friendly label for a vault key that has no metadata entry.
    [CmdletBinding()]
    param([string]$Key)
    switch -Regex ($Key) {
        '_ADMIN_EMAIL$'          { return 'Google Workspace admin email' }
        '^GUERRILLA_GWS_SA$'     { return 'Google Workspace service account' }
        '^GUERRILLA_GRAPH_TENANT$'   { return 'Entra ID Tenant ID' }
        '^GUERRILLA_GRAPH_CLIENTID$' { return 'App Registration Client ID' }
        '^GUERRILLA_GRAPH_SECRET$'   { return 'Microsoft Graph Client Secret' }
        'TEAMS'                  { return 'Microsoft Teams webhook' }
        'SLACK'                  { return 'Slack webhook' }
        'SENDGRID'               { return 'SendGrid API key' }
        'MAILGUN'                { return 'Mailgun email configuration' }
        'PAGERDUTY'              { return 'PagerDuty routing key' }
        'PUSHOVER'               { return 'Pushover alert credential' }
        default                  { return $Key }
    }
}

function Resolve-SafehouseKeyEnvironment {
    # Best-effort environment bucket for an unregistered vault key.
    [CmdletBinding()]
    param([string]$Key)
    switch -Regex ($Key) {
        'GWS|GOOGLE'                                  { return 'googleWorkspace' }
        'GRAPH'                                       { return 'microsoftGraph' }
        'TEAMS|SLACK|SENDGRID|MAILGUN|PAGERDUTY|PUSHOVER' { return 'alerting' }
        default                                       { return 'other' }
    }
}