src/public/Security/Set-AitherCredential.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Securely store credentials for later use

.DESCRIPTION
    Stores credentials (username/password or API keys) in an encrypted format
    for use in automation scripts. Credentials are stored per-user and encrypted
    using Windows Data Protection API (DPAPI) on Windows or similar mechanisms on Linux/macOS.

    This cmdlet is essential for:
    - Storing credentials for remote connections (SSH, WinRM, etc.)
    - Storing API keys and tokens securely
    - Avoiding hardcoded credentials in scripts
    - Enabling credential reuse across automation scripts

.PARAMETER Name
    Unique name for the credential. This name will be used to retrieve the credential later.
    Use descriptive names like "Production-SSH", "GitHub-Token", "AWS-AccessKey".

.PARAMETER Credential
    PSCredential object containing username and password. Use Get-Credential to create one,
    or pass a pre-existing PSCredential object.

.PARAMETER ApiKey
    SecureString containing an API key or token. Use Read-Host -AsSecureString to create one.

.PARAMETER Force
    Overwrite existing credential if it already exists. Without this parameter, the cmdlet
    will throw an error if a credential with the same name already exists.

.EXAMPLE
    Set-AitherCredential -Name "Production-SSH" -Credential (Get-Credential)

    Stores a username/password credential for production SSH access. Prompts for username and password.

.EXAMPLE
    $token = Read-Host -AsSecureString -Prompt "Enter GitHub token"
    Set-AitherCredential -Name "GitHub-Token" -ApiKey $token

    Stores a GitHub API token securely.

.EXAMPLE
    Set-AitherCredential -Name "AWS-Prod" -Credential $awsCred -Force

    Overwrites an existing AWS credential.

.INPUTS
    PSCredential
    SecureString
    You can pipe PSCredential objects to Set-AitherCredential.

.OUTPUTS
    None
    This cmdlet does not produce output.

.NOTES
    Security:
    - Credentials are encrypted using Windows DPAPI (Windows) or similar mechanisms (Linux/macOS)
    - Credentials are stored per-user and cannot be accessed by other users
    - Credential files are stored in ~/.aitherzero/credentials/ (Linux/macOS) or %USERPROFILE%\.aitherzero\credentials\ (Windows)
    - File permissions are automatically set to restrict access (600 on Linux/macOS)

    Best Practices:
    - Use descriptive names that indicate the purpose and environment
    - Store API keys separately from username/password credentials
    - Regularly rotate stored credentials
    - Use -Force only when intentionally updating credentials

.LINK
    Get-AitherCredential
    Remove-AitherCredential
    Get-AitherCredentialList
#>

function Set-AitherCredential {
[OutputType()]
[CmdletBinding(DefaultParameterSetName = 'Credential', SupportsShouldProcess)]
param(
    [Parameter(Mandatory=$false, Position = 0, ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [string]$Name,

    [Parameter(Mandatory=$false, ParameterSetName = 'Credential', ValueFromPipeline)]
    [ValidateNotNull()]
    [PSCredential]$Credential,

    [Parameter(Mandatory=$false, ParameterSetName = 'ApiKey')]
    [ValidateNotNull()]
    [SecureString]$ApiKey,

    [Parameter()]
    [switch]$Force,

    [switch]$ShowOutput
)

begin {
    # Save original log targets
    $originalLogTargets = $script:AitherLogTargets

    # Set log targets based on ShowOutput parameter
    if ($ShowOutput) {
        # Ensure Console is in the log targets
        if ($script:AitherLogTargets -notcontains 'Console') {
            $script:AitherLogTargets += 'Console'
        }
    }
    else {
        # Remove Console from log targets if present (default behavior)
        if ($script:AitherLogTargets -contains 'Console') {
            $script:AitherLogTargets = $script:AitherLogTargets | Where-Object { $_ -ne 'Console' }
        }
    }

    $moduleRoot = Get-AitherModuleRoot

    # Determine credential storage path
    $credentialPath = if ($IsWindows) {
        Join-Path $env:USERPROFILE ".aitherzero" "credentials"
    }
    else {
        Join-Path $env:HOME ".aitherzero" "credentials"
    }
}

process {
    try {
        try {
        # During module validation, skip execution
        if ($PSCmdlet.MyInvocation.InvocationName -eq '.' -and -not $Name) {
            return
        }

        $hasWriteAitherLog = Get-Command Write-AitherLog -ErrorAction SilentlyContinue

        # Create directory if it doesn't exist
        if (-not (Test-Path $credentialPath)) {
            New-Item -ItemType Directory -Path $credentialPath -Force | Out-Null

            # Set restrictive permissions on Linux/macOS
            if (-not $IsWindows) {
                try {
                    chmod 700 $credentialPath 2>$null
                }
                catch {
                    if ($hasWriteAitherLog) {
                        Write-AitherLog -Level Warning -Message "Could not set permissions on credential directory: $_" -Source 'Set-AitherCredential'
                    }
                }
            }
        }

        $credFile = Join-Path $credentialPath "$Name.cred"

        # Check if credential already exists
        if ((Test-Path $credFile) -and -not $Force) {
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                [System.Exception]::new("Credential '$Name' already exists. Use -Force to overwrite."),
                "CredentialExists",
                [System.Management.Automation.ErrorCategory]::InvalidOperation,
                $Name
            )
            Invoke-AitherErrorHandler -ErrorRecord $errorRecord -Operation "Storing credential: $Name" -Parameters $PSBoundParameters -ThrowOnError
            return
        }

        if ($PSCmdlet.ShouldProcess($Name, "Store credential")) {
            try {
                if ($PSCmdlet.ParameterSetName -eq 'Credential') {
                    # Store PSCredential
                    $credData = @{
                        Type = 'Credential'
                        Username = $Credential.UserName
                        Password = $Credential.Password | ConvertFrom-SecureString
                        Created = (Get-Date).ToString('o')
                    }
                }
                else {
                    # Store API Key
                    $credData = @{
                        Type = 'ApiKey'
                        Key = $ApiKey | ConvertFrom-SecureString
                        Created = (Get-Date).ToString('o')
                    }
                }

                # Export to encrypted file
                $credData | Export-Clixml -Path $credFile -Force

                # Set restrictive permissions on Linux/macOS
                if (-not $IsWindows) {
                    try {
                        chmod 600 $credFile 2>$null
                    }
                    catch {
                        if ($hasWriteAitherLog) {
                            Write-AitherLog -Level Warning -Message "Could not set permissions on credential file: $_" -Source 'Set-AitherCredential'
                        }
                    }
                }

                if ($hasWriteAitherLog) {
                    Write-AitherLog -Level Information -Message "Credential stored successfully" -Source 'Set-AitherCredential' -Data @{
                        Name = $Name
                        Type = $credData.Type
                        Path = $credFile
                    }
                }
            }
            catch {
                Invoke-AitherErrorHandler -ErrorRecord $_ -Operation "Storing credential: $Name" -Parameters $PSBoundParameters -ThrowOnError
            }
        }
    }
    catch {
        Invoke-AitherErrorHandler -ErrorRecord $_ -Operation "Setting up credential storage" -Parameters $PSBoundParameters -ThrowOnError
    }
    }
    finally {
        # Restore original log targets
        $script:AitherLogTargets = $originalLogTargets
    }
}

}