npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@nhz.io/slush-jwt-auth-proxy-conf

v1.0.1

Published

CouchDB JWT Auth Proxy nginx.conf generator

Downloads

7

Readme

CouchDB JWT Auth Proxy nginx.conf generator

Travis Build NPM Version

Nginx (openresty) configuration generator to act as JWT Proxy Authentication Gate for CouchDB.

Install

npm i -g slush @nhz.io/slush-jwt-auth-proxy-conf

Usage

mkdir jwt-auth-proxy && cd jwt-auth-proxy

slush @nhz.io/slush-jwt-auth-proxy-conf

Secrets

  • JWT_SECRET - key used to sign and verify JWT with
  • COUCH_PROXY_SECRET - key used to generate x_auth_token

Effect

  • JWT Auth Proxy will process requests and proxy them to CouchDB
  • Requests are authorized by verifying signature and expiration of JWT
  • JWT comes either from headers, url
  • JWT will always be cached in the cookie
  • JWT carries payload which contains username and roles which will be proxied in headers to CouchDB
  • Invalid JWT results in HTTP 403

JWT

JWT Payload example:

{
  "exp": 1510356100,
  "iat": 1510356220,
  "data": {
    "user": "boss",
    "roles": ["_admin"]
  }
}

JWT with such payload will grant admin access to boss user for 120 seconds

JWT From headers (Low priority)

  • JWT be extracted from X-JWT-Auth header
  • Request will be proxied to CouchDB as is
  • JWT will be cached in the cookie

JWT From URL (High priority)

Process URLs of form: http:// HOST : PORT / ${TOKEN_PREFIX} JWT PATH

  • Extract JWT from URL
  • PATH will be proxied to CouchDB
  • JWT will be cached in the cookie

CouchDB configuration

Make sure local.ini contains:

[chttpd]
authentication_handlers = {couch_httpd_auth, proxy_authentication_handler}

[couch_httpd_auth]
proxy_use_secret = true

Generated files

  • nginx.conf - openresty configuration
  • package.json - configuration settings are stored here for later reconfiguration

Notes:

  • Intended to run in Docker
  • JWT by URL is preferred method (rather than headers)
  • You can use JWT by URL as a key to open session, (JWT in cookie) and rest of requests with basename /
  • You can revisit the configuration later by running slush @nhz.io/slush-jwt-auth-proxy-conf again
  • You can distribute the package.json and regenerate nginx.conf anywhere by running npm i
  • Use jwt-hs256-proxy-auth-token to generate tokens

Imports

Builtins

path      = require 'path'

General

gulp      = require 'gulp'
pump      = require 'pump'
inquirer  = require 'inquirer'
transform = require 'vinyl-transform'
map       = require 'map-stream'

Gulp plugins

rename    = require 'gulp-rename'
template  = require 'gulp-template'
sequence  = (require 'run-sequence').use gulp

String utils imports

slugify   = require 'slugify'

Global package.json variable (Corresponds to current directory)

pkg       = try require './package.json'

Global flag which marks regeneration run (will be cleared if no package.json found)

regen     = true

Defaults

def = {
  pkgName: 'nginx.conf'
  pkgVersion: '1.0.0'

  HOST: ''
  PORT: 80

  COUCH_HOST: 'couch'
  COUCH_PORT: 5984
  COUCH_SCHEMA: 'http'

This is the only secret here, base JWT token to use when there is none. Could be used to setup default unpriviledged access. MUST HAVE VERY LONG expiration

  DEFAULT_JWT_TOKEN: ''

Those are not secrets, those are names of ENV variables

  JWT_SECRET: 'JWT_SECRET'
  COUCH_PROXY_SECRET: 'COUCH_PROXY_SECRET'

  JWT_COOKIE_NAME: 'JWT'
  JWT_HEADER_NAME: 'X-JWT-Auth'
  JWT_TOKEN_PREFIX: '!'

  ERROR_LOG: '/var/log/nginx/error.log warn'
  PID_PATH: '/var/run/nginx.pid'

  REWRITE: '^(/.*) $1'
  ROLES: []

  defaultRoutes: {
    '/': []
    '^/_': ['_admin']
    '^/_session': []
    '^/_users': []
  }
}

Prompts

New configuration prompt

newConfigurationPrompt = {
  name: 'task'
  type: 'list'
  message: 'JWT Auth Proxy configuration'
  choices: [
    'Create nginx.conf'
    'Done'
  ]
}

Reconfiguration prompt

reconfigurationPrompt = {
  name: 'task'
  type: 'list'
  message: 'JWT Auth Proxy configuration'
  choices: [
    'Configure Server'
    'Configure Proxy'

Route editing is unfinished, so disabled for now

    # 'Configure Routes'
    'Regenerate'
    'Done'
  ]
}

Route configuration prompts

routesPrompt = {
  name: 'task'
  type: 'list'
  message: 'JWT Auth Proxy Route configuration'
  newChoices: [
    'Add Route'
    'Done'
  ],
  reconfigureChoices: [
    'Add Route'
    'Remove Routes'
    'Edit Routes'
    'View Routes'
    'Done'
  ]
}

removeRoutesPrompt = {
  name: 'routes'
  type: 'checkbox'
  message: 'Select routes to remove'
  choices: ['Done']
}

editRoutesPrompt = {
  name: 'routes'
  type: 'list'
  message: 'Select route to edit'
  choices: ['Done']
}

viewRoutesPrompt = {
  name: 'routes'
  type: 'list'
  message: 'Select route to edit'
  choices: ['Done']
}

addRoutePropmts = [
  {
    name: 'MATCH'
    message: 'Match regexp'
    validate: true
  }
  {
    name: 'ROLES'
    message: 'Required roles (comma separated)'
    default: []
  }
  {
    name: 'REWRITE'
    message: 'Rewrite rule'
    default: '^(/.*) $1'
  }
]

Server configuration prompts

serverPrompts = [
  {
    name: 'HOST'
    message: 'JWT Proxy Auth server host'
    default: (pkg.server or def).HOST
  }
  {
    name: 'PORT'
    message: 'JWT Proxy Auth server port'
    default: (pkg.server or def).PORT
  }
  {
    name: 'COUCH_HOST'
    message: 'CouchDB host to proxy'
    default: (pkg.server or def).COUCH_HOST
  }
  {
    name: 'COUCH_PORT'
    message: 'CouchDB port to proxy'
    default: (pkg.server or def).COUCH_PORT
  }
  {
    name: 'COUCH_SCHEMA'
    message: 'CouchDB protocol schema'
    default: (pkg.server or def).COUCH_SCHEMA
  }
  {
    name: 'ERROR_LOG'
    message: 'Error log path and level'
    default: (pkg.server or def).ERROR_LOG
  }
  {
    name: 'PID_PATH'
    message: 'Nginx pid file path'
    default: (pkg.server or def).PID_PATH
  }
]

Proxy Auth configuration prompts

proxyPrompts = [
  {
    name: 'JWT_SECRET'
    message: 'JWT Secret ENV Variable name'
    default: (pkg.proxy or def).JWT_SECRET
  }
  {
    name: 'COUCH_PROXY_SECRET'
    message: 'CouchDB Proxy Auth secret ENV Variable name'
    default: (pkg.proxy or def).COUCH_PROXY_SECRET
  }
  {
    name: 'JWT_COOKIE_NAME'
    message: 'JWT token cookie name'
    default: (pkg.proxy or def).JWT_COOKIE_NAME
  }
  {
    name: 'JWT_HEADER_NAME'
    message: 'JWT header name'
    default: (pkg.proxy or def).JWT_HEADER_NAME
  }
  {
    name: 'JWT_TOKEN_PREFIX'
    message: 'JWT token prefix'
    default: (pkg.proxy or def).JWT_TOKEN_PREFIX
  }
]

Tasks

Package preloader

gulp.task 'load-pkg', ->
  pkg = try require (path.resolve process.cwd(), 'package.json') catch then regen = false
  pkg = Object.assign {}, def, pkg

  return

Server configuration

gulp.task '_server', -> try answers = await inquirer.prompt serverPrompts

gulp.task 'server', (cb) -> sequence 'load-pkg', '_server', '_regenerate',  cb

Proxy configuration

gulp.task '_proxy', -> try answers = await inquirer.prompt proxyPrompts

gulp.task 'proxy', (cb) -> sequence 'load-pkg', '_proxy', '_regenerate', cb

Routes configuration menu

gulp.task '_view-routes', ->
  anwsers = await inquirer.prompt [viewRoutesPrompt]

gulp.task 'view-routes', -> sequence 'load-pkg', '_view-routes'

gulp.task '_edit-routes', ->
  answers = await inquirer.prompt [editRoutesPrompt]

gulp.task 'edit-routes', (cb) -> sequence 'load-pkg', '_edit-routes'

gulp.task '_delete-routes', ->
  answers = await inqurer.prompt [deleteRoutesPrompt]

gulp.task 'delete-routes', -> sequence 'load-pkg', '_delete-routes'

gulp.task '_routes', ->
  prompt = Object.assign {}, routesPrompt

  prompt.choices = if regen then prompt.reconfigureChoices else prompt.newChoices

  answers = await inquirer.prompt [prompt]

  console.log JSON.stringify answers, null, 2

  new Promise (res) ->

    switch answers.task

      when 'Add Route' then sequence '_server', res

      when 'Remove Routes' then sequence '_remove_routes', '_regenerate', '_routes', res

      when 'Edit Routes' then sequence '_edit_routes', '_regenerate', '_routes', res

      when 'View Routes' then sequence '_view_routes', '_regenerate', '_routes', res

      else res()

gulp.task 'routes', (cb) -> sequence 'load-pkg', '_routes', cb

Main menu

gulp.task '_default', ->

  prompt = if regen then reconfigurationPrompt else newConfigurationPrompt

  loop
    answers = await inquirer.prompt [prompt]

    res = await new Promise (res) -> switch answers.task
      when 'Create nginx.conf' then sequence '_server', '_proxy', '_regenerate', res

      when 'Configure Server' then sequence '_server', '_regenerate', res

      when 'Configure Proxy' then sequence '_proxy', '_regenerate', res

      when 'Configure Routes' then sequence '_routes', '_regenerate', res

      when 'Regenerate' then sequence '_regenerate', res

      else res 'exit'

    if res is 'exit' then return

    prompt = reconfigurationPrompt

gulp.task 'default', (cb) -> sequence 'load-pkg', '_default', cb

Regenerate nginx.conf and package.json

gulp.task '_regenerate', ->

  server = pkg.server or {}

  proxy = pkg.proxy or {}

  defaultRoutes = pkg.defaultRoutes

  routes = Object.assign {}, defaultRoutes, (pkg.routes or {})

Remap routes into consumable form

  routes = (Object.keys routes).map (MATCH) ->

    value = routes[MATCH]

    if typeof value is 'string' then return { MATCH, REWRITE: value }

    if Array.isArray value then return { MATCH, ROLES: value }

    { MATCH, REWRITE: value?.rewrite or '', ROLES: value?.roles or [] }

Generate locations from routes fixing rewrite and roles

  locations = routes.map ({MATCH, REWRITE, ROLES}) ->

Transform rewrite rule or use default

    REWRITE =
      if REWRITE then REWRITE.replace /^(.+) +(.+)/, '$1 /rewrite$2'

      else '^(/.*) /rewrite$1'

Transform roles

    ROLES = ROLES.join ', '

    { MATCH, REWRITE, ROLES }

Create template context

  context = Object.assign {}, pkg, server, proxy, { locations, routes }

JWT_COOKIE_NAME and JWT_HEADER_NAME need snake_case version

  context.JWT_COOKIE_NAME_snake_case = (slugify context.JWT_COOKIE_NAME, '_').toLowerCase()
  context.JWT_HEADER_NAME_snake_case = (slugify context.JWT_HEADER_NAME, '_').toLowerCase()

  console.log JSON.stringify context, null, 2

  await pump [
    gulp.src __dirname + '/templates/**'

    (template context).on 'error', (err) -> console.log 'OMG', err

    rename (f) -> if f.basename[0] is '_' then f.basename = ".#{ f.basename.slice 1 }"

Prettify package.json

    transform (filename) -> map (chunk, next) ->
      if filename.match 'package.json'
        next null, JSON.stringify (JSON.parse chunk), null, 2
      else
        next null, chunk

    gulp.dest './'
  ]

  return

gulp.task 'regenerate', (cb) -> sequence 'load-pkg', '_regenerate', cb

Version 1.0.1

License MIT