101

I'm trying to automate npm publish inside a Docker container, but I receive an error when the npm login command tries to read the username and email:

npm login << EOF
username
password
email
EOF

It works in a Bash terminal, but in a container (without stdin) it shows error:

Username: Password: npm ERR! cb() never called!
npm ERR! not ok code 0

According to npm-adduser:

The username, password, and email are read in from prompts.

How can I run npm login without using stdin?

Christopher Peisert
  • 21,862
  • 3
  • 86
  • 117
shawnzhu
  • 7,233
  • 4
  • 35
  • 51
  • Your command actually worked for me, thanks! :) The only thing missing was to add the `--repository` parameter... – dokaspar Jan 27 '17 at 15:29

16 Answers16

58
npm set //<registry>/:_authToken $TOKEN

Example for Github Package Registry:

npm set //npm.pkg.github.com/:_authToken $GITHUB_TOKEN

This is the simplest solution that I have found.

Željko Šević
  • 3,743
  • 2
  • 26
  • 23
54

TL;DR: Make an HTTP request directly to the registry:

TOKEN=$(curl -s \
  -H "Accept: application/json" \
  -H "Content-Type:application/json" \
  -X PUT --data '{"name": "username_here", "password": "password_here"}' \
  http://your_registry/-/user/org.couchdb.user:username_here 2>&1 | grep -Po \
  '(?<="token": ")[^"]*')

npm set registry "http://your_registry"
npm set //your_registry/:_authToken $TOKEN

Rationale

Behind the scenes npm adduser makes an HTTP request to the registry. Instead of forcing adduser to behave the way you want, you could make the request directly to the registry without going through the cli and then set the auth token with npm set.

The source code suggests that you could make a PUT request to http://your_registry/-/user/org.couchdb.user:your-username with the following payload

{
  name: username,
  password: password
}

and that would create a new user in the registry.

Many thanks to @shawnzhu for having found a more cleaner approach to solve the problem.

9000
  • 39,899
  • 9
  • 66
  • 104
danielepolencic
  • 4,905
  • 1
  • 26
  • 25
  • This is clever but doesn't seems to work in my case. We're using ProGet and the output of that command see to only return and object like { "ok":"true" } and no tokens whatsoever. Seems like proget change the response from that command... I'll kept you posted if I found anything. – Marc-Andre R. Sep 30 '16 at 14:41
  • 4
    What if we just want to login, and not add a new user? I get bcak "user exists" with the above PUT request. – AntouanK Jan 08 '17 at 13:05
  • 3
    got the solution : https://github.com/rlidwka/sinopia/issues/329#issuecomment-217406747 – AntouanK Jan 08 '17 at 13:17
  • 7
    If you have already logged in with `npm login`, the information of the token can be found at file `~/.npmrc` in your home folder. Then, with only `npm set //your-registry.com/:_authToken="wKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa="` – King Midas May 31 '17 at 07:11
  • 2
    Has anyone else the problem that this solution is not working? I always get "You don't have permission to publish .." – Highriser Aug 24 '17 at 07:01
  • For some reason the regex did not work for me on the new Github registry. This one fixed it: `grep -oP "token\":\"\K\w+")` – vedo27 Sep 11 '19 at 20:19
  • This gives me an error of `user registration disabled` when using verdaccio... – Chris Stryczynski Feb 17 '20 at 15:44
16

npm-cli-login allows you to log in to NPM without STDIN.

In order to install run:

npm install -g npm-cli-login

Example usage:

npm-cli-login -u Username -p Password -e test@example.com -r https://your-private-registry-link
oxfist
  • 749
  • 6
  • 22
Arun Dhiman
  • 169
  • 1
  • 4
14

An expect script worked for me. You need to make sure expect is installed, this command should do it for ubuntu:

apt-get install expect-dev

Your script could look something like this (npm_login_expect):

#!/usr/bin/expect -f

# set our args into variables
set i 0; foreach n $argv {set "p[incr i]" $n}

set timeout 60
#npm login command, add whatever command-line args are necessary
spawn npm login
match_max 100000

expect "Username"    
send "$p1\r"

expect "Password"
send "$p2\r" 

expect "Email"
send "$p3\r"

expect {
   timeout      exit 1
   eof
}

And then call it like this:

expect -f npm_login_expect myuser mypassword "myuser@domain.com"
Ted Elliott
  • 3,415
  • 1
  • 27
  • 30
  • 1
    Yes if there is tty that `expect` can read. my problem is, there's no STDIN nor sudo tty at all. – shawnzhu Jan 23 '15 at 15:37
  • Worked a treat in a chef recipe as well. – NikG Feb 13 '15 at 15:41
  • 1
    I am getting error : `spawn npm login npm ERR! wrong # args: should be "eof channelId" while executing "eof" invoked from within "expect { timeout exit 1 eof }" (file "npm_login.sh" line 20)` – was_777 Mar 17 '20 at 06:51
13

I took a slightly different approach that seems to work great still. To begin with, you will need an auth token. This is easily obtainable by locally running npm adduser and then grabbing the generated token from your ~/.npmrc located in your user folder. In order to be authenticated on your ci server this auth token needs to be appended to the registry URL in the user's .npmrc (similar to how it was locally), not the .npmrc located in the repo, so these worked great as script steps in my CI configuration

- echo "//<npm-registry>:8080/:_authToken=$AUTH_TOKEN" > ~/.npmrc
- npm publish

where AUTH_TOKEN is stored as a secret variable in your settings. A good way to test this is to replace npm publish with npm whoami to test and make sure it successfully logged you in.

Here is my entire publish configuration

publish:
  stage: deploy
  only:
    - tags
  script:
    - yarn run build
    - echo "//<npm-registry>:8080/:_authToken=$NPME_AUTH_TOKEN" > ~/.npmrc
    - npm publish
    - echo 'Congrats on your publication!'

I'm using gitlab-ci but I don't see why this wouldn't apply to any ci application.

Bryson Reynolds
  • 191
  • 1
  • 12
  • [`npm ping`](https://www.npmjs.com/package/ping) has been set up precisely to allow testing connectivity and credentials. Also note that sometimes it's useful to output to a local `.npmrc` file instead of always putting it in the user's HOME folder—especially to avoid blowing up the user's normal day-to-day configuration just because some automation command was run. – ErikE Mar 05 '21 at 16:12
13

This worked in one of my devops flows

steps

  1. Generate _auth from npm registry credentials with base 64 using shell for security:
    echo -n 'myuser:mypassword' | openssl base64
    Result will be something like : eWFob29vb2E=
  1. Set npm registry url and _auth before npm install ...
    npm config set registry https://nexus-acme.com/repository/npm-group/
    npm config set _auth eWFob29vb2E=

That's all. You could run npm install, and your private modules will be downloaded.

JRichardsz
  • 14,356
  • 6
  • 59
  • 94
7

One solution is to fetch the token and update the ~/.npmrc

export ARTIFACTORY_TOKEN=`curl --silent --show-error --fail -u $ARTIFACTORY_USERNAME:$ARTIFACTORY_API_KEY https://artifactory.my.io/artifactory/api/npm/auth | \
grep -oP '_auth[\s?]=[\s?]\K(.*)$'`

echo "@my:registry=https://artifactory.my.io/artifactory/api/npm/npm-release-local/" > ~/.npmrc
echo "//artifactory.my.io/artifactory/api/npm/npm-release-local/:_auth=${ARTIFACTORY_TOKEN}" >> ~/.npmrc
echo "//artifactory.my.io/artifactory/api/npm/npm-release-local/:email=${ARTIFACTORY_USERNAME}" >> ~/.npmrc
echo "//artifactory.my.io/artifactory/api/npm/npm-release-local/:always-auth=true" >> ~/.npmrc

This prevents issues with @scope package retrieval from npmjs

dres
  • 1,172
  • 11
  • 15
6

npm login command stores all the credentials in the global .npmrc file. The pattern is not similar and it changes. Explained below:

There are TWO patterns, either one should work. NOTE: There may be other patterns in which npm stores the auth data, hence it's always better to cross-check the content of the .npmrc file in a global context.

Every time we do npm login an entry is made in .npmrc if it does not exist.

So the two pattern(I have copied the below lines from .npmrc)

//your_registry/:_authToken=amvasdfjkhjkjsdhflakjsdfhalskjh== //your_registry/:_authToken=NpmToken.6ca7867aba-d66a-32c0-9srr-fba908987d7987f

So the only thing to do is to copy the line from global .npmrc and put it in local or project .npmrc and run the npm publish command from CI. Explicit npm login is not required.

Abhishek
  • 763
  • 7
  • 18
3

This builds on top of Alexander F's answer. This is just a simplified version of the code he provided, mashed up with the example code provided by npm-registry-client.

"use strict";

var RegClient = require('npm-registry-client')
var client = new RegClient()
var uri = "https://registry.npmjs.org/npm"
var params = {timeout: 1000}

var username = 'my.npm.username'
var password = 'myPassword'
var email = 'my.email@gmail.com'

var params = {
  auth: {
    username,
    password,
    email
  }
};

client.adduser(uri, params, function (error, data, raw, res) {
  if(error) {
    console.error(error);
    return;
  }
  console.log(`Login succeeded`);
  console.log(`data: ${JSON.stringify(data,null,2)}`);
  console.log(`NPM access token: ${data.token}`);
});
Chris Troutner
  • 477
  • 6
  • 7
3

npm login is an interactive method. So prefer using npm-cli-login login.

Follow the following steps:

               1. npm install -g npm-cli-login
               2. npm-cli-login login -u username -p password -e abc@gmail.com -r http://registry.npmjs.org
               3. npm publish src --registry=http://registry.npmjs.org

Here I want to publish the module to http://registry.npmjs.org and I have registered in it with my emailid(abc@gmail.com), username and password

2

Hard to believe that after all this time there is still no solution for npm login. Sure you can grab a token once and use it for all your CI needs, but what about the security implications of a never expiring token? And what if one day admins decide that tokens should expire?

Below is my hacky javascript solution using npm-registry-client package. Just pass a json string argument and it will login and write an .npmrc file into your current dir. To log out use npm logout as usual.

var client = new (require('npm-registry-client'))({});
var std_in = JSON.parse(process.argv[2]);

if (std_in.uri === undefined) {
    console.error('Must input registry uri!');
    return;
}

// fix annoying trailing '/' thing in registry uri
if (std_in.uri[std_in.uri.length - 1] !== '/') {
    std_in.uri = std_in.uri + '/';
}

if (std_in.scope === undefined) {
    console.error('Must input scope!');
    return;
    //std_in.scope = '@my-scope'; // or add default scope of your own
}

if (std_in.scope[0] !== '@') {
    std_in.scope = '@' + std_in.scope;
}

client.adduser(std_in.uri, std_in.params, function(err, data, raw, res) {
    if (err) {
        console.error(err);
        return;
    } 
    require('fs').writeFileSync('.npmrc', `${std_in.scope}:registry=${std_in.uri}\n//${(std_in.uri.split('//'))[1]}:_authToken=${data.token}`);
});

Example input:

{ 
    "uri": "https://my-nmp.reg",
    "scope": "@my-scope",
    "params": {
        "auth": {
            "username": "secret-agent",
            "password": "12345",
            "email": "secret-agent@007.com"
        }
    }
}
AlexanderF
  • 919
  • 1
  • 13
  • 28
2

Depends on jq and three ENV vars set:

export NPM_REGISTRY_DOMAIN=
export NPM_REGISTRY_USER=
export NPM_REGISTRY_PASSWORD=

json="{\"name\": \""$NPM_REGISTRY_USER"\", \"password\": \""$NPM_REGISTRY_PASSWORD"\"}"

TOKEN=$(curl -s \
  -H "Accept: application/json" \
  -H "Content-Type:application/json" \
  -X PUT --data "$json" \
  --user "$NPM_REGISTRY_USER":"$NPM_REGISTRY_PASSWORD" \
  https://"$NPM_REGISTRY_DOMAIN"/-/user/org.couchdb.user:"$NPM_REGISTERY_USER" | \
            jq -r '.token'
    )

npm config set registry https://"$NPM_REGISTRY_DOMAIN"
npm set //"$NPM_REGISTRY_DOMAIN"/:_authToken "$TOKEN"

Based on the answer from https://stackoverflow.com/a/35831310/1663462

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
1

For the solution 2 exposed by ke_wa in this duplicated post has worked.

Mashup:

export NPM_USERNAME=mUs34
export NPM_PASSWORD=mypassW0rD
export NPM_EMAIL=user@domain.com
npm adduser<<!
$NPM_USERNAME
$NPM_PASSWORD
$NPM_EMAIL
!
Community
  • 1
  • 1
l.cotonea
  • 2,037
  • 1
  • 15
  • 17
  • 13
    Not working for me. I keep getting ```Username: Password: npm ERR! cb() never called!``` – Lovey Oct 24 '17 at 23:11
1

i'm using gitlab-ci for this.

but my private npm response without authtoken, so that the npm-cli-login can't run correctly.

i do a trick for my question, like this:

 // If no entry for the auth token is found, add one
        if (authWrite === -1) {
            lines.push(args.registry.slice(args.registry.search(/\:\/\//, '') +
            1) + '/:_authToken=' + (args.quotes ? '"' : '') + response.token + (args.quotes ? '"' : ''));
        }
        // DIY
        var loginInfo = [
            {pass: '_password'},
            {user: 'username'},
            {email: 'email'}
        ]
        loginInfo.forEach(function(ele) {
            var key = Object.keys(ele);
            lines.push(args.registry.slice(args.registry.search(/\:\/\//, '') +
            1) + '/:' + ele[key] + '=' + (args.quotes ? '"' : '') + args[key] + (args.quotes ? '"' : ''));
        })
        // DIYend
        var toWrite = lines.filter(function (element) {
            if (element === '') {
                return false;
            }
            return true;
        });

though it is foolish,but works

Orange
  • 11
  • 1
0

You could use an expect script instead or write a node script that uses pty.js.

mscdex
  • 104,356
  • 15
  • 192
  • 153
  • Thanks. the root cause is `npm adduser` always ask for input from stdin which I can't make. Then I found another way which is kind of trick to generate a working `~/.npmrc`. see https://github.com/npm/npm-registry-client/blob/master/lib/adduser.js#L71-L72 – shawnzhu May 05 '14 at 02:17
  • 7
    @shawnzu it'd be nice if you'd post the solution for the rest of the internet, as the link you gave uses master instead of a sha, so it's out of date now :( – Adam K Dean Sep 24 '15 at 11:49
0

Generally speaking, the use of _auth to pass in the base64 processing of username and password is good, I wrote a publishing tool to solve this problem, I hope to help you; https://www.npmjs.com/package/ausiliario

Dinn
  • 1
  • 1