11cb0ef41Sopenharmony_ciconst { fixer } = require('normalize-package-data')
21cb0ef41Sopenharmony_ciconst npmFetch = require('npm-registry-fetch')
31cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg')
41cb0ef41Sopenharmony_ciconst log = require('proc-log')
51cb0ef41Sopenharmony_ciconst semver = require('semver')
61cb0ef41Sopenharmony_ciconst { URL } = require('url')
71cb0ef41Sopenharmony_ciconst ssri = require('ssri')
81cb0ef41Sopenharmony_ciconst ciInfo = require('ci-info')
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_ciconst { generateProvenance, verifyProvenance } = require('./provenance')
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ciconst TLOG_BASE_URL = 'https://search.sigstore.dev/'
131cb0ef41Sopenharmony_ci
141cb0ef41Sopenharmony_ciconst publish = async (manifest, tarballData, opts) => {
151cb0ef41Sopenharmony_ci  if (manifest.private) {
161cb0ef41Sopenharmony_ci    throw Object.assign(
171cb0ef41Sopenharmony_ci      new Error(`This package has been marked as private
181cb0ef41Sopenharmony_ciRemove the 'private' field from the package.json to publish it.`),
191cb0ef41Sopenharmony_ci      { code: 'EPRIVATE' }
201cb0ef41Sopenharmony_ci    )
211cb0ef41Sopenharmony_ci  }
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ci  // spec is used to pick the appropriate registry/auth combo
241cb0ef41Sopenharmony_ci  const spec = npa.resolve(manifest.name, manifest.version)
251cb0ef41Sopenharmony_ci  opts = {
261cb0ef41Sopenharmony_ci    access: 'public',
271cb0ef41Sopenharmony_ci    algorithms: ['sha512'],
281cb0ef41Sopenharmony_ci    defaultTag: 'latest',
291cb0ef41Sopenharmony_ci    ...opts,
301cb0ef41Sopenharmony_ci    spec,
311cb0ef41Sopenharmony_ci  }
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ci  const reg = npmFetch.pickRegistry(spec, opts)
341cb0ef41Sopenharmony_ci  const pubManifest = patchManifest(manifest, opts)
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_ci  // registry-frontdoor cares about the access level,
371cb0ef41Sopenharmony_ci  // which is only configurable for scoped packages
381cb0ef41Sopenharmony_ci  if (!spec.scope && opts.access === 'restricted') {
391cb0ef41Sopenharmony_ci    throw Object.assign(
401cb0ef41Sopenharmony_ci      new Error("Can't restrict access to unscoped packages."),
411cb0ef41Sopenharmony_ci      { code: 'EUNSCOPED' }
421cb0ef41Sopenharmony_ci    )
431cb0ef41Sopenharmony_ci  }
441cb0ef41Sopenharmony_ci
451cb0ef41Sopenharmony_ci  const { metadata, transparencyLogUrl } = await buildMetadata(
461cb0ef41Sopenharmony_ci    reg,
471cb0ef41Sopenharmony_ci    pubManifest,
481cb0ef41Sopenharmony_ci    tarballData,
491cb0ef41Sopenharmony_ci    spec,
501cb0ef41Sopenharmony_ci    opts
511cb0ef41Sopenharmony_ci  )
521cb0ef41Sopenharmony_ci
531cb0ef41Sopenharmony_ci  const res = await npmFetch(spec.escapedName, {
541cb0ef41Sopenharmony_ci    ...opts,
551cb0ef41Sopenharmony_ci    method: 'PUT',
561cb0ef41Sopenharmony_ci    body: metadata,
571cb0ef41Sopenharmony_ci    ignoreBody: true,
581cb0ef41Sopenharmony_ci  })
591cb0ef41Sopenharmony_ci  if (transparencyLogUrl) {
601cb0ef41Sopenharmony_ci    res.transparencyLogUrl = transparencyLogUrl
611cb0ef41Sopenharmony_ci  }
621cb0ef41Sopenharmony_ci  return res
631cb0ef41Sopenharmony_ci}
641cb0ef41Sopenharmony_ci
651cb0ef41Sopenharmony_ciconst patchManifest = (_manifest, opts) => {
661cb0ef41Sopenharmony_ci  const { npmVersion } = opts
671cb0ef41Sopenharmony_ci  // we only update top-level fields, so a shallow clone is fine
681cb0ef41Sopenharmony_ci  const manifest = { ..._manifest }
691cb0ef41Sopenharmony_ci
701cb0ef41Sopenharmony_ci  manifest._nodeVersion = process.versions.node
711cb0ef41Sopenharmony_ci  if (npmVersion) {
721cb0ef41Sopenharmony_ci    manifest._npmVersion = npmVersion
731cb0ef41Sopenharmony_ci  }
741cb0ef41Sopenharmony_ci
751cb0ef41Sopenharmony_ci  fixer.fixNameField(manifest, { strict: true, allowLegacyCase: true })
761cb0ef41Sopenharmony_ci  const version = semver.clean(manifest.version)
771cb0ef41Sopenharmony_ci  if (!version) {
781cb0ef41Sopenharmony_ci    throw Object.assign(
791cb0ef41Sopenharmony_ci      new Error('invalid semver: ' + manifest.version),
801cb0ef41Sopenharmony_ci      { code: 'EBADSEMVER' }
811cb0ef41Sopenharmony_ci    )
821cb0ef41Sopenharmony_ci  }
831cb0ef41Sopenharmony_ci  manifest.version = version
841cb0ef41Sopenharmony_ci  return manifest
851cb0ef41Sopenharmony_ci}
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ciconst buildMetadata = async (registry, manifest, tarballData, spec, opts) => {
881cb0ef41Sopenharmony_ci  const { access, defaultTag, algorithms, provenance, provenanceFile } = opts
891cb0ef41Sopenharmony_ci  const root = {
901cb0ef41Sopenharmony_ci    _id: manifest.name,
911cb0ef41Sopenharmony_ci    name: manifest.name,
921cb0ef41Sopenharmony_ci    description: manifest.description,
931cb0ef41Sopenharmony_ci    'dist-tags': {},
941cb0ef41Sopenharmony_ci    versions: {},
951cb0ef41Sopenharmony_ci    access,
961cb0ef41Sopenharmony_ci  }
971cb0ef41Sopenharmony_ci
981cb0ef41Sopenharmony_ci  root.versions[manifest.version] = manifest
991cb0ef41Sopenharmony_ci  const tag = manifest.tag || defaultTag
1001cb0ef41Sopenharmony_ci  root['dist-tags'][tag] = manifest.version
1011cb0ef41Sopenharmony_ci
1021cb0ef41Sopenharmony_ci  const tarballName = `${manifest.name}-${manifest.version}.tgz`
1031cb0ef41Sopenharmony_ci  const provenanceBundleName = `${manifest.name}-${manifest.version}.sigstore`
1041cb0ef41Sopenharmony_ci  const tarballURI = `${manifest.name}/-/${tarballName}`
1051cb0ef41Sopenharmony_ci  const integrity = ssri.fromData(tarballData, {
1061cb0ef41Sopenharmony_ci    algorithms: [...new Set(['sha1'].concat(algorithms))],
1071cb0ef41Sopenharmony_ci  })
1081cb0ef41Sopenharmony_ci
1091cb0ef41Sopenharmony_ci  manifest._id = `${manifest.name}@${manifest.version}`
1101cb0ef41Sopenharmony_ci  manifest.dist = { ...manifest.dist }
1111cb0ef41Sopenharmony_ci  // Don't bother having sha1 in the actual integrity field
1121cb0ef41Sopenharmony_ci  manifest.dist.integrity = integrity.sha512[0].toString()
1131cb0ef41Sopenharmony_ci  // Legacy shasum support
1141cb0ef41Sopenharmony_ci  manifest.dist.shasum = integrity.sha1[0].hexDigest()
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_ci  // NB: the CLI always fetches via HTTPS if the registry is HTTPS,
1171cb0ef41Sopenharmony_ci  // regardless of what's here.  This makes it so that installing
1181cb0ef41Sopenharmony_ci  // from an HTTP-only mirror doesn't cause problems, though.
1191cb0ef41Sopenharmony_ci  manifest.dist.tarball = new URL(tarballURI, registry).href
1201cb0ef41Sopenharmony_ci    .replace(/^https:\/\//, 'http://')
1211cb0ef41Sopenharmony_ci
1221cb0ef41Sopenharmony_ci  root._attachments = {}
1231cb0ef41Sopenharmony_ci  root._attachments[tarballName] = {
1241cb0ef41Sopenharmony_ci    content_type: 'application/octet-stream',
1251cb0ef41Sopenharmony_ci    data: tarballData.toString('base64'),
1261cb0ef41Sopenharmony_ci    length: tarballData.length,
1271cb0ef41Sopenharmony_ci  }
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci  // Handle case where --provenance flag was set to true
1301cb0ef41Sopenharmony_ci  let transparencyLogUrl
1311cb0ef41Sopenharmony_ci  if (provenance === true || provenanceFile) {
1321cb0ef41Sopenharmony_ci    let provenanceBundle
1331cb0ef41Sopenharmony_ci    const subject = {
1341cb0ef41Sopenharmony_ci      name: npa.toPurl(spec),
1351cb0ef41Sopenharmony_ci      digest: { sha512: integrity.sha512[0].hexDigest() },
1361cb0ef41Sopenharmony_ci    }
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_ci    if (provenance === true) {
1391cb0ef41Sopenharmony_ci      await ensureProvenanceGeneration(registry, spec, opts)
1401cb0ef41Sopenharmony_ci      provenanceBundle = await generateProvenance([subject], opts)
1411cb0ef41Sopenharmony_ci
1421cb0ef41Sopenharmony_ci      /* eslint-disable-next-line max-len */
1431cb0ef41Sopenharmony_ci      log.notice('publish', `Signed provenance statement with source and build information from ${ciInfo.name}`)
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci      const tlogEntry = provenanceBundle?.verificationMaterial?.tlogEntries[0]
1461cb0ef41Sopenharmony_ci      /* istanbul ignore else */
1471cb0ef41Sopenharmony_ci      if (tlogEntry) {
1481cb0ef41Sopenharmony_ci        transparencyLogUrl = `${TLOG_BASE_URL}?logIndex=${tlogEntry.logIndex}`
1491cb0ef41Sopenharmony_ci        log.notice(
1501cb0ef41Sopenharmony_ci          'publish',
1511cb0ef41Sopenharmony_ci          `Provenance statement published to transparency log: ${transparencyLogUrl}`
1521cb0ef41Sopenharmony_ci        )
1531cb0ef41Sopenharmony_ci      }
1541cb0ef41Sopenharmony_ci    } else {
1551cb0ef41Sopenharmony_ci      provenanceBundle = await verifyProvenance(subject, provenanceFile)
1561cb0ef41Sopenharmony_ci    }
1571cb0ef41Sopenharmony_ci
1581cb0ef41Sopenharmony_ci    const serializedBundle = JSON.stringify(provenanceBundle)
1591cb0ef41Sopenharmony_ci    root._attachments[provenanceBundleName] = {
1601cb0ef41Sopenharmony_ci      content_type: provenanceBundle.mediaType,
1611cb0ef41Sopenharmony_ci      data: serializedBundle,
1621cb0ef41Sopenharmony_ci      length: serializedBundle.length,
1631cb0ef41Sopenharmony_ci    }
1641cb0ef41Sopenharmony_ci  }
1651cb0ef41Sopenharmony_ci
1661cb0ef41Sopenharmony_ci  return {
1671cb0ef41Sopenharmony_ci    metadata: root,
1681cb0ef41Sopenharmony_ci    transparencyLogUrl,
1691cb0ef41Sopenharmony_ci  }
1701cb0ef41Sopenharmony_ci}
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci// Check that all the prereqs are met for provenance generation
1731cb0ef41Sopenharmony_ciconst ensureProvenanceGeneration = async (registry, spec, opts) => {
1741cb0ef41Sopenharmony_ci  if (ciInfo.GITHUB_ACTIONS) {
1751cb0ef41Sopenharmony_ci    // Ensure that the GHA OIDC token is available
1761cb0ef41Sopenharmony_ci    if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
1771cb0ef41Sopenharmony_ci      throw Object.assign(
1781cb0ef41Sopenharmony_ci        /* eslint-disable-next-line max-len */
1791cb0ef41Sopenharmony_ci        new Error('Provenance generation in GitHub Actions requires "write" access to the "id-token" permission'),
1801cb0ef41Sopenharmony_ci        { code: 'EUSAGE' }
1811cb0ef41Sopenharmony_ci      )
1821cb0ef41Sopenharmony_ci    }
1831cb0ef41Sopenharmony_ci  } else if (ciInfo.GITLAB) {
1841cb0ef41Sopenharmony_ci    // Ensure that the Sigstore OIDC token is available
1851cb0ef41Sopenharmony_ci    if (!process.env.SIGSTORE_ID_TOKEN) {
1861cb0ef41Sopenharmony_ci      throw Object.assign(
1871cb0ef41Sopenharmony_ci        /* eslint-disable-next-line max-len */
1881cb0ef41Sopenharmony_ci        new Error('Provenance generation in GitLab CI requires "SIGSTORE_ID_TOKEN" with "sigstore" audience to be present in "id_tokens". For more info see:\nhttps://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html'),
1891cb0ef41Sopenharmony_ci        { code: 'EUSAGE' }
1901cb0ef41Sopenharmony_ci      )
1911cb0ef41Sopenharmony_ci    }
1921cb0ef41Sopenharmony_ci  } else {
1931cb0ef41Sopenharmony_ci    throw Object.assign(
1941cb0ef41Sopenharmony_ci      new Error('Automatic provenance generation not supported for provider: ' + ciInfo.name),
1951cb0ef41Sopenharmony_ci      { code: 'EUSAGE' }
1961cb0ef41Sopenharmony_ci    )
1971cb0ef41Sopenharmony_ci  }
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ci  // Some registries (e.g. GH packages) require auth to check visibility,
2001cb0ef41Sopenharmony_ci  // and always return 404 when no auth is supplied. In this case we assume
2011cb0ef41Sopenharmony_ci  // the package is always private and require `--access public` to publish
2021cb0ef41Sopenharmony_ci  // with provenance.
2031cb0ef41Sopenharmony_ci  let visibility = { public: false }
2041cb0ef41Sopenharmony_ci  if (opts.access !== 'public') {
2051cb0ef41Sopenharmony_ci    try {
2061cb0ef41Sopenharmony_ci      const res = await npmFetch
2071cb0ef41Sopenharmony_ci        .json(`${registry}/-/package/${spec.escapedName}/visibility`, opts)
2081cb0ef41Sopenharmony_ci      visibility = res
2091cb0ef41Sopenharmony_ci    } catch (err) {
2101cb0ef41Sopenharmony_ci      if (err.code !== 'E404') {
2111cb0ef41Sopenharmony_ci        throw err
2121cb0ef41Sopenharmony_ci      }
2131cb0ef41Sopenharmony_ci    }
2141cb0ef41Sopenharmony_ci  }
2151cb0ef41Sopenharmony_ci
2161cb0ef41Sopenharmony_ci  if (!visibility.public && opts.provenance === true && opts.access !== 'public') {
2171cb0ef41Sopenharmony_ci    throw Object.assign(
2181cb0ef41Sopenharmony_ci      /* eslint-disable-next-line max-len */
2191cb0ef41Sopenharmony_ci      new Error("Can't generate provenance for new or private package, you must set `access` to public."),
2201cb0ef41Sopenharmony_ci      { code: 'EUSAGE' }
2211cb0ef41Sopenharmony_ci    )
2221cb0ef41Sopenharmony_ci  }
2231cb0ef41Sopenharmony_ci}
2241cb0ef41Sopenharmony_ci
2251cb0ef41Sopenharmony_cimodule.exports = publish
226