Public/User/New-VergeUser.ps1

function New-VergeUser {
    <#
    .SYNOPSIS
        Creates a new user in VergeOS.

    .DESCRIPTION
        New-VergeUser creates a new user account with the specified configuration.
        The user is created in an enabled state by default.

    .PARAMETER Name
        The username for the new user. Must be unique and 1-128 characters.
        Will be converted to lowercase automatically.

    .PARAMETER Password
        The password for the new user. Can be a SecureString or plain text string.

    .PARAMETER DisplayName
        The display name for the user (shown in the UI).

    .PARAMETER Email
        The email address for the user.

    .PARAMETER Type
        The user type. Valid values: Normal, API, VDI.
        Default is Normal.

    .PARAMETER Enabled
        Whether the user account is enabled. Default is $true.

    .PARAMETER RequirePasswordChange
        Require the user to change their password at next login.

    .PARAMETER PhysicalAccess
        Enable console/SSH access for this user. This grants administrator privileges.

    .PARAMETER TwoFactorEnabled
        Enable two-factor authentication for this user.

    .PARAMETER TwoFactorType
        The type of 2FA to use. Valid values: Email, Authenticator.
        Default is Email.

    .PARAMETER TwoFactorSetupRequired
        Require the user to set up 2FA at next login.

    .PARAMETER SSHKeys
        SSH public keys for the user (one per line or as an array).

    .PARAMETER PassThru
        Return the created user object.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        New-VergeUser -Name "jsmith" -Password (ConvertTo-SecureString "TempPass123!" -AsPlainText -Force) -DisplayName "John Smith" -Email "jsmith@company.com"

        Creates a new normal user account.

    .EXAMPLE
        New-VergeUser -Name "apiuser" -Password "ApiSecret123!" -Type API -PassThru

        Creates a new API user and returns the created user object.

    .EXAMPLE
        New-VergeUser -Name "vdiuser" -Password "VdiPass123!" -Type VDI -TwoFactorEnabled -TwoFactorType Authenticator

        Creates a new VDI user with authenticator-based 2FA enabled.

    .EXAMPLE
        $cred = Get-Credential -UserName "newuser"
        New-VergeUser -Name $cred.UserName -Password $cred.Password -RequirePasswordChange

        Creates a user from a credential object, requiring password change at first login.

    .OUTPUTS
        None by default. Verge.User when -PassThru is specified.

    .NOTES
        Use Get-VergeUser to retrieve users.
        Use Set-VergeUser to modify existing users.
        Use Add-VergeGroupMember to add users to groups.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 128)]
        [ValidatePattern('^[^/]+$', ErrorMessage = 'Username cannot contain forward slashes')]
        [string]$Name,

        [Parameter(Mandatory, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [object]$Password,

        [Parameter()]
        [string]$DisplayName,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@([a-zA-Z0-9][a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])+(\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])*$', ErrorMessage = 'Invalid email address format')]
        [string]$Email,

        [Parameter()]
        [ValidateSet('Normal', 'API', 'VDI')]
        [string]$Type = 'Normal',

        [Parameter()]
        [bool]$Enabled = $true,

        [Parameter()]
        [switch]$RequirePasswordChange,

        [Parameter()]
        [switch]$PhysicalAccess,

        [Parameter()]
        [switch]$TwoFactorEnabled,

        [Parameter()]
        [ValidateSet('Email', 'Authenticator')]
        [string]$TwoFactorType = 'Email',

        [Parameter()]
        [switch]$TwoFactorSetupRequired,

        [Parameter()]
        [string[]]$SSHKeys,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Map friendly names to API values
        $typeMap = @{
            'Normal' = 'normal'
            'API'    = 'api'
            'VDI'    = 'vdi'
        }

        $twoFactorTypeMap = @{
            'Email'         = 'email'
            'Authenticator' = 'authenticator'
        }
    }

    process {
        # Convert SecureString password if needed
        $plainPassword = if ($Password -is [System.Security.SecureString]) {
            [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
                [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
            )
        }
        else {
            $Password.ToString()
        }

        # Convert username to lowercase as required by API
        $userName = $Name.ToLower()

        # Validate 2FA requirements
        if ($TwoFactorEnabled -and -not $Email) {
            throw "Email address is required when enabling two-factor authentication."
        }

        # Build request body
        $body = @{
            name     = $userName
            password = $plainPassword
            type     = $typeMap[$Type]
            enabled  = $Enabled
        }

        # Add optional parameters
        if ($DisplayName) {
            $body['displayname'] = $DisplayName
        }

        if ($Email) {
            $body['email'] = $Email.ToLower()
        }

        if ($RequirePasswordChange) {
            $body['change_password'] = $true
        }

        if ($PhysicalAccess) {
            $body['physical_access'] = $true
        }

        if ($TwoFactorEnabled) {
            if ($TwoFactorType -eq 'Authenticator') {
                # Authenticator requires TOTP setup - user must set up at next login
                $body['two_factor_setup_next_login'] = $true
                $body['two_factor_type'] = $twoFactorTypeMap[$TwoFactorType]
            }
            else {
                # Email-based 2FA can be enabled immediately
                $body['two_factor_authentication'] = $true
                $body['two_factor_type'] = $twoFactorTypeMap[$TwoFactorType]
            }
        }

        if ($TwoFactorSetupRequired) {
            $body['two_factor_setup_next_login'] = $true
        }

        if ($SSHKeys -and $SSHKeys.Count -gt 0) {
            $body['ssh_keys'] = $SSHKeys -join "`n"
        }

        # Confirm action
        $actionDescription = "Create $Type user '$userName'"
        if ($Email) {
            $actionDescription += " ($Email)"
        }

        if ($PSCmdlet.ShouldProcess($userName, 'Create User')) {
            try {
                Write-Verbose "Creating user '$userName'"
                $response = Invoke-VergeAPI -Method POST -Endpoint 'users' -Body $body -Connection $Server

                # Get the created user key
                $userKey = $response.'$key'
                if (-not $userKey -and $response.key) {
                    $userKey = $response.key
                }

                Write-Verbose "User '$userName' created with Key: $userKey"

                if ($PassThru -and $userKey) {
                    # Return the created user
                    Start-Sleep -Milliseconds 500
                    Get-VergeUser -Key $userKey -Server $Server
                }
            }
            catch {
                $errorMessage = $_.Exception.Message
                if ($errorMessage -match 'already in use') {
                    throw "A user with the name '$userName' already exists."
                }
                throw "Failed to create user '$userName': $errorMessage"
            }
        }
    }
}