auth.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. 'use strict'
  2. const fs = require('fs')
  3. const npa = require('npm-package-arg')
  4. const { URL } = require('url')
  5. // Find the longest registry key that is used for some kind of auth
  6. // in the options.
  7. const regKeyFromURI = (uri, opts) => {
  8. const parsed = new URL(uri)
  9. // try to find a config key indicating we have auth for this registry
  10. // can be one of :_authToken, :_auth, :_password and :username, or
  11. // :certfile and :keyfile
  12. // We walk up the "path" until we're left with just //<host>[:<port>],
  13. // stopping when we reach '//'.
  14. let regKey = `//${parsed.host}${parsed.pathname}`
  15. while (regKey.length > '//'.length) {
  16. // got some auth for this URI
  17. if (hasAuth(regKey, opts)) {
  18. return regKey
  19. }
  20. // can be either //host/some/path/:_auth or //host/some/path:_auth
  21. // walk up by removing EITHER what's after the slash OR the slash itself
  22. regKey = regKey.replace(/([^/]+|\/)$/, '')
  23. }
  24. }
  25. const hasAuth = (regKey, opts) => (
  26. opts[`${regKey}:_authToken`] ||
  27. opts[`${regKey}:_auth`] ||
  28. opts[`${regKey}:username`] && opts[`${regKey}:_password`] ||
  29. opts[`${regKey}:certfile`] && opts[`${regKey}:keyfile`]
  30. )
  31. const sameHost = (a, b) => {
  32. const parsedA = new URL(a)
  33. const parsedB = new URL(b)
  34. return parsedA.host === parsedB.host
  35. }
  36. const getRegistry = opts => {
  37. const { spec } = opts
  38. const { scope: specScope, subSpec } = spec ? npa(spec) : {}
  39. const subSpecScope = subSpec && subSpec.scope
  40. const scope = subSpec ? subSpecScope : specScope
  41. const scopeReg = scope && opts[`${scope}:registry`]
  42. return scopeReg || opts.registry
  43. }
  44. const maybeReadFile = file => {
  45. try {
  46. return fs.readFileSync(file, 'utf8')
  47. } catch (er) {
  48. if (er.code !== 'ENOENT') {
  49. throw er
  50. }
  51. return null
  52. }
  53. }
  54. const getAuth = (uri, opts = {}) => {
  55. const { forceAuth } = opts
  56. if (!uri) {
  57. throw new Error('URI is required')
  58. }
  59. const regKey = regKeyFromURI(uri, forceAuth || opts)
  60. // we are only allowed to use what's in forceAuth if specified
  61. if (forceAuth && !regKey) {
  62. return new Auth({
  63. scopeAuthKey: null,
  64. token: forceAuth._authToken || forceAuth.token,
  65. username: forceAuth.username,
  66. password: forceAuth._password || forceAuth.password,
  67. auth: forceAuth._auth || forceAuth.auth,
  68. certfile: forceAuth.certfile,
  69. keyfile: forceAuth.keyfile,
  70. })
  71. }
  72. // no auth for this URI, but might have it for the registry
  73. if (!regKey) {
  74. const registry = getRegistry(opts)
  75. if (registry && uri !== registry && sameHost(uri, registry)) {
  76. return getAuth(registry, opts)
  77. } else if (registry !== opts.registry) {
  78. // If making a tarball request to a different base URI than the
  79. // registry where we logged in, but the same auth SHOULD be sent
  80. // to that artifact host, then we track where it was coming in from,
  81. // and warn the user if we get a 4xx error on it.
  82. const scopeAuthKey = regKeyFromURI(registry, opts)
  83. return new Auth({ scopeAuthKey })
  84. }
  85. }
  86. const {
  87. [`${regKey}:_authToken`]: token,
  88. [`${regKey}:username`]: username,
  89. [`${regKey}:_password`]: password,
  90. [`${regKey}:_auth`]: auth,
  91. [`${regKey}:certfile`]: certfile,
  92. [`${regKey}:keyfile`]: keyfile,
  93. } = opts
  94. return new Auth({
  95. scopeAuthKey: null,
  96. token,
  97. auth,
  98. username,
  99. password,
  100. certfile,
  101. keyfile,
  102. })
  103. }
  104. class Auth {
  105. constructor ({ token, auth, username, password, scopeAuthKey, certfile, keyfile }) {
  106. this.scopeAuthKey = scopeAuthKey
  107. this.token = null
  108. this.auth = null
  109. this.isBasicAuth = false
  110. this.cert = null
  111. this.key = null
  112. if (token) {
  113. this.token = token
  114. } else if (auth) {
  115. this.auth = auth
  116. } else if (username && password) {
  117. const p = Buffer.from(password, 'base64').toString('utf8')
  118. this.auth = Buffer.from(`${username}:${p}`, 'utf8').toString('base64')
  119. this.isBasicAuth = true
  120. }
  121. // mTLS may be used in conjunction with another auth method above
  122. if (certfile && keyfile) {
  123. const cert = maybeReadFile(certfile, 'utf-8')
  124. const key = maybeReadFile(keyfile, 'utf-8')
  125. if (cert && key) {
  126. this.cert = cert
  127. this.key = key
  128. }
  129. }
  130. }
  131. }
  132. module.exports = getAuth