Skip to content

mmarchini-oss/npm-otp-publish

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

NPM OTP Publisher

Action to publish npm pacakges that require 2FA by providing a way for users to provide One-Time Passwords.

This action is intended for users who require 2FA to publish their projects. If your project doesn't require 2FA, take a look at other npm publishing Actions in the marketplace.

Usage

on:
  release:
    types: [published]
name: releaser
jobs:
  releaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: mmarchini-oss/npm-otp-publish@v0
        with:
          npm_token: ${{ secrets.NPM_TOKEN }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          github_actor: ${{ github.actor }}

To create a npm token, follow this guide: how to generate npm token. For extra security, it's highly recommended that you create a bot account on NPM instead of adding a token from your own account (if you do, remember to enable 2FA in the bot account). You can then add it to repository Secrets.

Entering the OTP

Every time the Action runs, it will notify the user using the selected notifier and will wait for user input before trying to publish. Here's an example GitHub notification:

GitHub issue with link

Once the user clicks the link provided in the issue, they'll be redirected to the page below to enter the One-Time Password.

page with One Time Password field

Usage with release-please

If you're using release-please or another Acton to auto-generate releases and changelogs, running npm-otp-publish Action on release event won't work because GitHub won't trigger events generated by another Action. To workaround this issue, use npm-otp-publish on the same job as release-please:

on:
  push:
    branches:
      - main

name: releaser
jobs:
  releaser:
    runs-on: ubuntu-latest
    steps:
      - uses: GoogleCloudPlatform/release-please-action@v1.6.3
        id: release
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          release-type: node
          package-name: '<your package name>'

      - uses: actions/checkout@v2
        if: ${{ steps.release.outputs.release_created }}
      - uses: mmarchini-oss/npm-otp-publish@v0
        if: ${{ steps.release.outputs.release_created }}
        with:
          npm_token: ${{ secrets.NPM_TOKEN }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          version_url: https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag_name }}
          github_actor: ${{ github.actor }}

Input

Input Required Default Description
npm_token yes npm token used to publish
npm_user no npm user name (used on messages)
version_url no URL to the release on GitHub
notifier no github-issue notifier to be used. Options are console, github-issue
timeout no 15 timeout (in minutes) before the Action forcefully stops
github_token no github token (required if notifier is github-issue)
github_actor no user who triggered the action
github_release_team no release team to assign the issue

How it works

npm 2FA requires users to provide a One-Time Password while publishing. Since these passwords are not known ahead of time, users need to look at an authenticator app to get the password every time they publish a package.

When publishing with Actions that's not normally possible though, since the Action will run entirely without user interaction. So for npm 2FA to work on Actions, we need to provide a way for the user to interact with a running Action and provide the One-Time Password.

To acomplish that, we start a web server in the Action and use ngrok to create a public-facing URL that users can access. There the user will find a form to input the One-Time Password found in the authenticator app. Once the user enters the OTP, it will be sent to the Action, which will try to publish the package using that OTP. If it succeeds, the Action will stop, otherwise it will keep running until either the user provides a valid OTP, or the timeout expires.