Plugins/NameCom.ps1

function Get-CurrentPluginType { 'dns-01' }

function Add-DnsTxt {
    [CmdletBinding(DefaultParameterSetName='Secure')]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$TxtValue,
        [Parameter(Mandatory,Position=2)]
        [string]$NameComUsername,
        [Parameter(ParameterSetName='Secure',Mandatory,Position=3)]
        [securestring]$NameComTokenSecure,
        [Parameter(ParameterSetName='DeprecatedInsecure',Mandatory,Position=3)]
        [string]$NameComToken,
        [switch]$NameComUseTestEnv,
        [Parameter(ValueFromRemainingArguments)]
        $ExtraParams
    )

    $apiRoot = 'https://api.name.com/v4'
    if ($NameComUseTestEnv) { $apiRoot = 'https://api.dev.name.com/v4' }

    if ('Secure' -eq $PSCmdlet.ParameterSetName) {
        $NameComToken = [pscredential]::new('a',$NameComTokenSecure).GetNetworkCredential().Password
    }

    $restParams = Get-RestHeaders $NameComUsername $NameComToken

    # check for an existing record
    $domainName,$rec = Get-NameComTxtRecord $RecordName $TxtValue $restParams $apiRoot

    if ($rec) {
        Write-Debug "Record $RecordName already contains $TxtValue. Nothing to do."
        return
    } else {
        # build the body
        $hostShort = ($RecordName -ireplace [regex]::Escape($domainName), [string]::Empty).TrimEnd('.')
        $bodyJson = @{host=$hostShort; type='TXT'; answer=$TxtValue; ttl=300} | ConvertTo-Json -Compress

        # add the new record
        try {
            Write-Verbose "Adding a TXT record for $RecordName with value $TxtValue"
            $url = "$apiRoot/domains/$($domainName)/records"
            Write-Debug "POST $url`n$bodyJson"
            Invoke-RestMethod $url -Method Post -Body $bodyJson @restParams @script:UseBasic | Out-Null
        } catch { throw }
    }




    <#
    .SYNOPSIS
        Add a DNS TXT record to Name.com DNS.
 
    .DESCRIPTION
        Add a DNS TXT record to Name.com DNS.
 
    .PARAMETER RecordName
        The fully qualified name of the TXT record.
 
    .PARAMETER TxtValue
        The value of the TXT record.
 
    .PARAMETER NameComUsername
        The account API username.
 
    .PARAMETER NameComTokenSecure
        The account API token.
 
    .PARAMETER NameComToken
        (DEPRECATED) The account API token.
 
    .PARAMETER NameComUseTestEnv
        If specified, use the name.com testing environment.
 
    .PARAMETER ExtraParams
        This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports.
 
    .EXAMPLE
        $token = Read-Host 'Token' -AsSecureString
        Add-DnsTxt '_acme-challenge.example.com' 'txt-value' 'username' $token
 
        Adds a TXT record for the specified site with the specified value.
    #>

}

function Remove-DnsTxt {
    [CmdletBinding(DefaultParameterSetName='Secure')]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$TxtValue,
        [Parameter(Mandatory,Position=2)]
        [string]$NameComUsername,
        [Parameter(ParameterSetName='Secure',Mandatory,Position=3)]
        [securestring]$NameComTokenSecure,
        [Parameter(ParameterSetName='DeprecatedInsecure',Mandatory,Position=3)]
        [string]$NameComToken,
        [switch]$NameComUseTestEnv,
        [Parameter(ValueFromRemainingArguments)]
        $ExtraParams
    )

    $apiRoot = 'https://api.name.com/v4'
    if ($NameComUseTestEnv) { $apiRoot = 'https://api.dev.name.com/v4' }

    if ('Secure' -eq $PSCmdlet.ParameterSetName) {
        $NameComToken = [pscredential]::new('a',$NameComTokenSecure).GetNetworkCredential().Password
    }

    $restParams = Get-RestHeaders $NameComUsername $NameComToken

    # check for an existing record
    $domainName,$rec = Get-NameComTxtRecord $RecordName $TxtValue $restParams $apiRoot

    if ($rec) {
        # remove the record
        try {
            Write-Verbose "Removing TXT record for $RecordName with value $TxtValue"
            $url = "$apiRoot/domains/$($domainName)/records/$($rec.id)"
            Write-Debug "DELETE $url"
            Invoke-RestMethod $url -Method Delete @restParams @script:UseBasic | Out-Null
        } catch { throw }
    } else {
        Write-Debug "Record $RecordName with value $TxtValue doesn't exist. Nothing to do."
        return
    }




    <#
    .SYNOPSIS
        Remove a DNS TXT record from Name.com DNS.
 
    .DESCRIPTION
        Remove a DNS TXT record from Name.com DNS.
 
    .PARAMETER RecordName
        The fully qualified name of the TXT record.
 
    .PARAMETER TxtValue
        The value of the TXT record.
 
    .PARAMETER NameComUsername
        The account API username.
 
    .PARAMETER NameComTokenSecure
        The account API token.
 
    .PARAMETER NameComToken
        (DEPRECATED) The account API token.
 
    .PARAMETER NameComUseTestEnv
        If specified, use the name.com testing environment.
 
    .PARAMETER ExtraParams
        This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports.
 
    .EXAMPLE
        $token = Read-Host 'Token' -AsSecureString
        Remove-DnsTxt '_acme-challenge.example.com' 'txt-value' 'username' $token
 
        Remove a TXT record for the specified site with the specified value.
    #>

}

function Save-DnsTxt {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromRemainingArguments)]
        $ExtraParams
    )
    <#
    .SYNOPSIS
        Not required.
 
    .DESCRIPTION
        This provider does not require calling this function to commit changes to DNS records.
 
    .PARAMETER ExtraParams
        This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports.
    #>

}

############################
# Helper Functions
############################

# API Docs
# https://www.name.com/api-docs/DNS

function Find-NameComZone {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [hashtable]$RestParams,
        [Parameter(Mandatory,Position=2)]
        [string]$ApiRoot
    )

    # This provider doesn't appear to host sub-zones. But their API is nice enough to return the apex
    # domain automatically when using their GetDomain method even if you pass it a record within that
    # domain.
    # https://www.name.com/api-docs/Domains#GetDomain

    # So we just have to call it once and assuming they have a domain for that record, it'll return
    # the apex that we care about for later calls.
    try {
        $url = "$ApiRoot/domains/$RecordName"
        Write-Debug "GET $url"
        $domain = Invoke-RestMethod $url @RestParams @script:UseBasic
        Write-Debug "Response:`n$($domain|ConvertTo-Json)"

        if ($domain -and $domain.domainName) {
            return $domain.domainName
        }
    } catch {
        # re-throw everything but a 404 which we can just ignore
        if (404 -ne $_.Exception.Response.StatusCode) {
            Get-ErrorBody $_
        }
    }

    return $null
}

function Get-NameComTxtRecord {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$TxtValue,
        [Parameter(Mandatory,Position=2)]
        [hashtable]$RestParams,
        [Parameter(Mandatory,Position=3)]
        [string]$ApiRoot
    )

    # Find the zone apex based on the record name
    $zoneName = Find-NameComZone $RecordName $RestParams $ApiRoot
    if (-not $zoneName) {
        throw "Domain not found for $RecordName"
    }

    # Unfortunately, there's no way to get a specific record without knowing it's ID. So we have to list (and
    # potentially page through) all of them and filter the results on our side.
    # https://www.name.com/api-docs/DNS#ListRecords

    $nextPage = ''
    $recs = do {
        $url = "$ApiRoot/domains/$zoneName/records$nextPage"
        Write-Debug "GET $url"
        $response = Invoke-RestMethod $url @RestParams @script:UseBasic

        # send results to the pipeline
        $response.records

        # check for paging
        if ([String]::IsNullOrWhiteSpace($response.nextPage)) { break }
        $nextPage = "?page=$($response.nextPage)"
    } while ($true)

    # Return the zone in case the record doesn't exist and the record that matches the specified $RecordName
    $rec = ($recs | Where-Object { $_.fqdn -eq "$RecordName." -and $_.answer -eq $TxtValue -and $_.type -eq 'TXT' })
    return $zoneName,$rec
}

function Get-RestHeaders {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$NameComUsername,
        [Parameter(Mandatory,Position=1)]
        [string]$NameComUserToken
    )

    $restParams = @{
        Headers = @{
            Accept='application/json'
            Authorization = "Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $NameComUsername,$NameComToken)))
        }
        ContentType = 'application/json'
        Verbose = $false
        ErrorAction = 'Stop'
    }

    return $restParams
}

function Get-ErrorBody {
    [CmdletBinding()]
    param(
        [object]$ex
    )

    $exType = $ex.Exception.GetType().FullName

    if ('System.Net.WebException' -eq $exType) {

        $response = $ex.Exception.Response
        $sr = New-Object IO.StreamReader($response.GetResponseStream())
        $sr.BaseStream.Position = 0
        $sr.DiscardBufferedData()
        $body = $sr.ReadToEnd()
        throw $body

    } elseif ('Microsoft.PowerShell.Commands.HttpResponseException' -eq $exType) {

        $response = $ex.Exception.Response
        $body = $ex.ErrorDetails.Message
        throw $body

    } else { throw }

}