diff --git a/Dockerfile b/Dockerfile index 50fbcdc..dca26ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,8 @@ ENV REDIS_USE_TLS false ENV REDIS_PASSWORD '' ENV BULL_PREFIX bull ENV BULL_VERSION BULLMQ +ENV USER_LOGIN '' +ENV USER_PASSWORD '' RUN yarn install @@ -21,4 +23,4 @@ ARG PORT=3000 ENV PORT $PORT EXPOSE $PORT -CMD ["node", "index.js"] +CMD ["node", "src/index.js"] diff --git a/README.md b/README.md index 18d1300..e1ca45e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,15 @@ see "Example with docker-compose" section for example with env parameters * `BULL_PREFIX` - prefix to your bull queue name (bull by default) * `BULL_VERSION` - version of bull lib to use 'BULLMQ' or 'BULL' ('BULLMQ' by default) * `BASE_PATH` - basePath for bull board, e.g. '/bull-board' ('/' by default) +* `USER_LOGIN` - login to restrict access to bull-board interface (disabled by default) +* `USER_PASSWORD` - password to restrict access to bull-board interface (disabled by default) + + +### Restrict access with login and password + +To restrict access to bull-board use `USER_LOGIN` and `USER_PASSWORD` env vars. +Only when both `USER_LOGIN` and `USER_PASSWORD` specified, access will be restricted with login/password + ### Example with docker-compose ``` diff --git a/index.js b/index.js deleted file mode 100644 index 861b898..0000000 --- a/index.js +++ /dev/null @@ -1,52 +0,0 @@ -const {router, setQueues, BullMQAdapter, BullAdapter} = require('bull-board') -const Queue = require('bull'); -const bullmq = require('bullmq'); -const express = require('express'); -const redis = require('redis'); - -const { - REDIS_PORT = 6379, - REDIS_HOST = 'localhost', - REDIS_PASSWORD, - REDIS_USE_TLS, - BULL_PREFIX = 'bull', - BULL_VERSION = 'BULLMQ', - PORT = 3000, - BASE_PATH = '/' -} = process.env; - -const redisConfig = { - redis: { - port: REDIS_PORT, - host: REDIS_HOST, - ...(REDIS_PASSWORD && {password: REDIS_PASSWORD}), - tls: REDIS_USE_TLS === 'true', - }, -}; - -const client = redis.createClient(redisConfig.redis); -const prefix = BULL_PREFIX; -const port = PORT; -const basePath = BASE_PATH; - -client.KEYS(`${prefix}:*`, (err, keys) => { - const uniqKeys = new Set(keys.map(key => key.replace(/^.+?:(.+?):.+?$/, '$1'))); - const queueList = Array.from(uniqKeys).sort().map( - (item) => { - if (BULL_VERSION === 'BULLMQ') { - return new BullMQAdapter(new bullmq.Queue(item, {connection: redisConfig.redis})); - } - - return new BullAdapter(new Queue(item, redisConfig)); - } - ); - - setQueues(queueList); -}); - -const app = express(); - -app.use(basePath, router); -app.listen(port, () => { - console.log(`bull-board listening on port ${port} on basePath ${basePath}!`); -}); diff --git a/package.json b/package.json index a61c294..817c32a 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,18 @@ { "name": "bullboard", "version": "1.0.0-alpha.7", - "main": "index.js", + "main": "src/index.js", "license": "MIT", "dependencies": { + "body-parser": "^1.19.0", "bull": "^3.13.0", - "bull-board": "^1.0.0-alpha.8", + "bull-board": "^1.2.0", "bullmq": "^1.8.4", + "connect-ensure-login": "^0.1.1", "express": "^4.17.1", + "express-session": "^1.17.1", + "passport": "^0.4.1", + "passport-local": "^1.0.0", "redis": "^3.0.2" } } diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..e47b915 --- /dev/null +++ b/src/config.js @@ -0,0 +1,24 @@ +let BASE_PATH = process.env.BASE_PATH || '/'; + +if (BASE_PATH.endsWith('/')) { + BASE_PATH = BASE_PATH.substr(0, BASE_PATH.length - 1); +} + +const config = { + REDIS_PORT: process.env.REDIS_PORT || 6379, + REDIS_HOST: process.env.REDIS_HOST || 'localhost', + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + REDIS_USE_TLS: process.env.REDIS_USE_TLS, + BULL_PREFIX: process.env.BULL_PREFIX || 'bull', + BULL_VERSION: process.env.BULL_VERSION || 'BULLMQ', + PORT: process.env.PORT || 3000, + BASE_PATH: BASE_PATH, + USER_LOGIN: process.env.USER_LOGIN, + USER_PASSWORD: process.env.USER_PASSWORD, + + AUTH_ENABLED: Boolean(process.env.USER_LOGIN && process.env.USER_PASSWORD), + HOME_PAGE: BASE_PATH || '/', + LOGIN_PAGE: `${BASE_PATH}/login`, +}; + +module.exports = config; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..99ada25 --- /dev/null +++ b/src/index.js @@ -0,0 +1,61 @@ +const {router, setQueues, BullMQAdapter, BullAdapter} = require('bull-board'); +const Queue = require('bull'); +const bullmq = require('bullmq'); +const express = require('express'); +const redis = require('redis'); +const session = require('express-session'); +const passport = require('passport'); +const {ensureLoggedIn} = require('connect-ensure-login'); +const bodyParser = require('body-parser'); + +const {authRouter} = require('./login'); +const config = require('./config'); + +const redisConfig = { + redis: { + port: config.REDIS_PORT, + host: config.REDIS_HOST, + ...(config.REDIS_PASSWORD && {password: config.REDIS_PASSWORD}), + tls: config.REDIS_USE_TLS === 'true', + }, +}; + +const client = redis.createClient(redisConfig.redis); + +client.KEYS(`${config.BULL_PREFIX}:*`, (err, keys) => { + const uniqKeys = new Set(keys.map(key => key.replace(/^.+?:(.+?):.+?$/, '$1'))); + const queueList = Array.from(uniqKeys).sort().map( + (item) => { + if (config.BULL_VERSION === 'BULLMQ') { + return new BullMQAdapter(new bullmq.Queue(item, {connection: redisConfig.redis})); + } + + return new BullAdapter(new Queue(item, redisConfig)); + } + ); + + setQueues(queueList); +}); + +const app = express(); + +app.set('views', __dirname + '/views'); +app.set('view engine', 'ejs'); + +app.use(session({secret: Math.random().toString(), resave: false, saveUninitialized: false})); +app.use(passport.initialize({})); +app.use(passport.session({})); + +app.use(bodyParser.urlencoded({extended: false})); + +if (config.AUTH_ENABLED) { + app.use(config.LOGIN_PAGE, authRouter); + app.use(config.HOME_PAGE, ensureLoggedIn(config.LOGIN_PAGE), router); +} +else { + app.use(config.HOME_PAGE, router); +} + +app.listen(config.PORT, () => { + console.log(`bull-board is started http://localhost:${config.PORT}${config.HOME_PAGE}`); +}); diff --git a/src/login.js b/src/login.js new file mode 100644 index 0000000..f151a47 --- /dev/null +++ b/src/login.js @@ -0,0 +1,36 @@ +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; +const express = require('express'); + +const config = require('./config'); + +const authRouter = express.Router(); + +passport.use(new LocalStrategy( + function (username, password, cb) { + if (username === config.USER_LOGIN && password === config.USER_PASSWORD) { + return cb(null, {user: 'bull-board'}); + } + + return cb(null, false); + }) +); + +passport.serializeUser((user, cb) => { + cb(null, user); +}); + +passport.deserializeUser((user, cb) => { + cb(null, user); +}); + +authRouter.route('/') + .get((req, res) => { + res.render('login'); + }) + .post(passport.authenticate('local', { + successRedirect: config.HOME_PAGE, + failureRedirect: config.LOGIN_PAGE, + })); + +exports.authRouter = authRouter; diff --git a/src/views/login.ejs b/src/views/login.ejs new file mode 100644 index 0000000..e998f6c --- /dev/null +++ b/src/views/login.ejs @@ -0,0 +1,119 @@ + + +
+
+ +
+
diff --git a/yarn.lock b/yarn.lock index 7b02bae..58781e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -135,7 +135,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -body-parser@1.19.0: +body-parser@1.19.0, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== @@ -159,7 +159,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -bull-board@^1.0.0-alpha.8: +bull-board@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/bull-board/-/bull-board-1.2.0.tgz#daf69472c306bebff5918da4715c34ff5cac3063" integrity sha512-ma0bEdxpfwpTVYiOVAxFmAZgZHwKNYkLLkDqJGuM8WrP+n6UHnqS7rWxajxBZurQSKmwEyQH8gtemX8UFZnOIg== @@ -259,6 +259,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +connect-ensure-login@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz#174dcc51243b9eac23f8d98215aeb6694e2e8a12" + integrity sha1-F03MUSQ7nqwj+NmCFa62aU4uihI= + content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -340,6 +345,11 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -421,6 +431,20 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +express-session@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" + integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.0" + uid-safe "~2.1.5" + express@4.17.1, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -835,6 +859,11 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -857,6 +886,26 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" + integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4= + dependencies: + passport-strategy "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -869,6 +918,11 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + pretty-bytes@5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" @@ -914,6 +968,11 @@ query-string@^6.13.7: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -1029,6 +1088,11 @@ safe-buffer@5.1.2: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -1159,6 +1223,13 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"