Public/Update-AdoNpmToken.ps1
|
function Set-AdoNpmTokenOwnerOnlyAcl { param( [Parameter(Mandatory)] [string]$Path, [switch]$Directory ) if (-not $IsWindows) { return } $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().User $acl = Get-Acl -LiteralPath $Path $acl.SetOwner($identity) $acl.SetAccessRuleProtection($true, $false) foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) } if ($Directory) { $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( $identity, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit', [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow) } else { $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( $identity, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow) } $acl.SetAccessRule($rule) Set-Acl -LiteralPath $Path -AclObject $acl } function Clear-AdoNpmTokenFile { param([Parameter(Mandatory)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { return } try { $item = Get-Item -LiteralPath $Path -ErrorAction Stop if ($item.Length -gt 0) { [System.IO.File]::WriteAllBytes($item.FullName, [byte[]]::new([int]$item.Length)) } } catch { Write-Verbose "Failed to overwrite temporary npm token file before deletion: $_" } } function Update-AdoNpmToken { <# .SYNOPSIS Updates ADO npm feed tokens using artifacts-npm-credprovider. .DESCRIPTION Runs the @microsoft/artifacts-npm-credprovider against a temporary .npmrc to authenticate with Azure DevOps and obtain fresh access tokens. The tokens are set as environment variables referenced by the user-level .npmrc. Use -Feed to select the feed and -Name to control the environment variable name. .PARAMETER Feed The ADO npm feed registry URL to authenticate against. .PARAMETER Name The environment variable name to set. Defaults to ADO_NPM_TOKEN. .PARAMETER Force Force credential refresh even if current authentication is valid. .EXAMPLE Update-AdoNpmToken -Feed "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/npm/registry/" Refreshes a specific feed into ADO_NPM_TOKEN. .EXAMPLE Update-AdoNpmToken -Feed "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/npm/registry/" -Name MY_TOKEN Refreshes a specific feed into a custom environment variable. .EXAMPLE Update-AdoNpmToken -Feed "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/npm/registry/" -Force Forces a credential refresh for the selected feed. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Feed, [Parameter()] [ValidatePattern('^[A-Za-z_][A-Za-z0-9_]*$')] [string]$Name, [Parameter()] [switch]$Force ) $feedsToRefresh = @(@{ Feed = $Feed Name = if ($Name) { $Name } else { 'ADO_NPM_TOKEN' } }) $npmRoot = npm root -g 2>$null if (-not $npmRoot) { Write-Error "npm is not installed or not in PATH." return } $credProviderBin = Join-Path $npmRoot '@microsoft\artifacts-npm-credprovider\bin\index.js' if (-not (Test-Path $credProviderBin)) { Write-Error "artifacts-npm-credprovider is not installed globally. Install with: npm install -g @microsoft/artifacts-npm-credprovider" return } foreach ($entry in $feedsToRefresh) { if (-not $PSCmdlet.ShouldProcess($entry.Name, "Refresh token for feed $($entry.Feed)")) { continue } # Build a temporary .npmrc with the feed entry so the credprovider writes the token into it $tempDir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) "ado-npm-$([System.IO.Path]::GetRandomFileName())") Set-AdoNpmTokenOwnerOnlyAcl -Path $tempDir.FullName -Directory $tempNpmrc = Join-Path $tempDir.FullName '.npmrc' New-Item -ItemType File -Path $tempNpmrc -Force | Out-Null Set-AdoNpmTokenOwnerOnlyAcl -Path $tempNpmrc Set-Content -Path $tempNpmrc -Value "//$($entry.Feed -replace '^https?://', ''):_authToken=" try { Write-Verbose "Requesting token for feed: $($entry.Feed)" $arguments = @('-c', $tempNpmrc) if ($Force) { $arguments += '--force' } # Run from the temp directory so the credprovider doesn't walk up # to ~ and find the user-level .npmrc (which would conflict with -c) Push-Location $tempDir try { & node $credProviderBin @arguments 2>&1 | ForEach-Object { Write-Verbose $_ } } finally { Pop-Location } $npmrcContent = Get-Content $tempNpmrc -Raw if ($npmrcContent -match ':_authToken=(.+)') { [System.Environment]::SetEnvironmentVariable($entry.Name, $Matches[1].Trim(), 'Process') Write-Verbose "$($entry.Name) updated successfully." } else { Write-Error "Failed to obtain token for $($entry.Name) from artifacts-npm-credprovider." } } finally { Clear-AdoNpmTokenFile -Path $tempNpmrc Remove-Item $tempDir.FullName -Recurse -Force -ErrorAction SilentlyContinue } } } |