From f6490b7e7664c2e00f281fe267064451d3de58c3 Mon Sep 17 00:00:00 2001 From: zawapete <33979808+zawapete@users.noreply.github.com> Date: Sat, 29 Sep 2018 10:34:40 +0200 Subject: [PATCH] User management: disable Authentication tab content for non admin users (#718) * User management: first commit * User management: remove wrong setState * User management: remove debug log * lint * format * cr fixes * refactor from services to middleware * remove userService.js --- .../modals/settings-modal/AuthTab.js | 31 +- client/src/javascript/i18n/en.js | 2 + client/src/javascript/i18n/es.js | 2 + client/src/javascript/i18n/fr.js | 2 + client/src/javascript/i18n/nl.js | 2 + client/src/javascript/stores/AuthStore.js | 8 + package-lock.json | 282 +++++++++++++++++- package.json | 1 + server/middleware/requireAdmin.js | 9 + server/models/Users.js | 4 +- server/routes/auth.js | 25 +- 11 files changed, 352 insertions(+), 16 deletions(-) create mode 100644 server/middleware/requireAdmin.js diff --git a/client/src/javascript/components/modals/settings-modal/AuthTab.js b/client/src/javascript/components/modals/settings-modal/AuthTab.js index 91aae9e4..3b272869 100644 --- a/client/src/javascript/components/modals/settings-modal/AuthTab.js +++ b/client/src/javascript/components/modals/settings-modal/AuthTab.js @@ -1,4 +1,4 @@ -import {Button, Form, FormError, FormRowItem, FormRow, LoadingRing, Textbox} from 'flood-ui-kit'; +import {Button, Checkbox, Form, FormError, FormRowItem, FormRow, LoadingRing, Textbox} from 'flood-ui-kit'; import classnames from 'classnames'; import CSSTransitionGroup from 'react-addons-css-transition-group'; import {FormattedMessage, injectIntl} from 'react-intl'; @@ -16,6 +16,7 @@ class AuthTab extends SettingsTab { addUserError: null, hasFetchedUserList: false, isAddingUser: false, + isAdmin: false, users: [], }; @@ -24,9 +25,14 @@ class AuthTab extends SettingsTab { componentWillMount() { this.setState({users: AuthStore.getUsers()}); + this.setState({isAdmin: AuthStore.isAdmin()}); } componentDidMount() { + if (!this.state.isAdmin) { + return; + } + AuthStore.listen(EventTypes.AUTH_LIST_USERS_SUCCESS, this.handleUserListChange); AuthStore.listen(EventTypes.AUTH_CREATE_USER_ERROR, this.handleUserAddError); AuthStore.listen(EventTypes.AUTH_CREATE_USER_SUCCESS, this.handleUserAddSuccess); @@ -36,6 +42,9 @@ class AuthTab extends SettingsTab { } componentWillUnmount() { + if (!this.state.isAdmin) { + return; + } AuthStore.unlisten(EventTypes.AUTH_LIST_USERS_SUCCESS, this.handleUserListChange); AuthStore.unlisten(EventTypes.AUTH_CREATE_USER_ERROR, this.handleUserAddError); AuthStore.unlisten(EventTypes.AUTH_CREATE_USER_SUCCESS, this.handleUserAddSuccess); @@ -104,12 +113,14 @@ class AuthTab extends SettingsTab { }); } else { this.setState({isAddingUser: true}); + AuthStore.createUser({ username: this.formData.username, password: this.formData.password, host: this.formData.rtorrentHost, port: this.formData.rtorrentPort, socketPath: this.formData.rtorrentSocketPath, + isAdmin: this.formData.isAdmin === '1', }); } }; @@ -135,6 +146,21 @@ class AuthTab extends SettingsTab { } render() { + if (!this.state.isAdmin) { + return ( +
+ + + + + + + + +
+ ); + } + const isLoading = !this.state.hasFetchedUserList && this.state.users.length === 0; const interactiveListClasses = classnames('interactive-list', { 'interactive-list--loading': isLoading, @@ -197,6 +223,9 @@ class AuthTab extends SettingsTab { defaultMessage: 'Password', })} /> + + + diff --git a/client/src/javascript/i18n/en.js b/client/src/javascript/i18n/en.js index 5b78851f..03f0e5ac 100644 --- a/client/src/javascript/i18n/en.js +++ b/client/src/javascript/i18n/en.js @@ -36,6 +36,8 @@ export default { 'auth.password': 'Password', 'auth.user.accounts': 'User Accounts', 'auth.username': 'Username', + 'auth.admin': 'Admin', + 'auth.message.not.admin': 'User is not Admin', 'button.add': 'Add', 'button.cancel': 'Cancel', diff --git a/client/src/javascript/i18n/es.js b/client/src/javascript/i18n/es.js index 3b584f9c..3723ff6f 100644 --- a/client/src/javascript/i18n/es.js +++ b/client/src/javascript/i18n/es.js @@ -34,6 +34,8 @@ export default { 'auth.password': 'ContraseƱa', 'auth.user.accounts': 'Cuentas de Usuario', 'auth.username': 'Nombre de Usuario', + 'auth.admin': 'Admin', + 'auth.message.not.admin': 'El Usuario no es Administrador', 'button.add': 'Agregar', 'button.cancel': 'Cancelar', diff --git a/client/src/javascript/i18n/fr.js b/client/src/javascript/i18n/fr.js index 4ab4a02e..50952172 100644 --- a/client/src/javascript/i18n/fr.js +++ b/client/src/javascript/i18n/fr.js @@ -8,6 +8,8 @@ export default { 'auth.password': 'Mot de Passe', 'auth.user.accounts': 'Comptes', 'auth.username': "Nom d'Utilisateur", + 'auth.admin': 'Admin', + 'auth.message.not.admin': "L'Utilisateur n'est pas Administrateur", 'button.add': 'Ajouter', 'button.cancel': 'Annuler', diff --git a/client/src/javascript/i18n/nl.js b/client/src/javascript/i18n/nl.js index c9c18817..864d249b 100644 --- a/client/src/javascript/i18n/nl.js +++ b/client/src/javascript/i18n/nl.js @@ -8,6 +8,8 @@ export default { 'auth.password': 'Wachtwoord', 'auth.user.accounts': 'Accounts', 'auth.username': 'Gebruikersnaam', + 'auth.admin': 'Beheerder', + 'auth.message.not.admin': 'Gebruiker is geen Beheerder', 'button.add': 'Toevoegen', 'button.cancel': 'Annuleren', diff --git a/client/src/javascript/stores/AuthStore.js b/client/src/javascript/stores/AuthStore.js index 030edc3d..d10b2302 100644 --- a/client/src/javascript/stores/AuthStore.js +++ b/client/src/javascript/stores/AuthStore.js @@ -38,6 +38,10 @@ class AuthStoreClass extends BaseStore { return this.currentUser.username; } + isAdmin() { + return this.currentUser.isAdmin; + } + getToken() { return this.token; } @@ -80,6 +84,7 @@ class AuthStoreClass extends BaseStore { handleLoginSuccess(data) { this.emit(EventTypes.AUTH_LOGIN_SUCCESS); this.currentUser.username = data.username; + this.currentUser.isAdmin = data.isAdmin; this.token = data.token; } @@ -90,6 +95,7 @@ class AuthStoreClass extends BaseStore { handleRegisterSuccess(data) { this.currentUser.username = data.username; + this.currentUser.isAdmin = data.isAdmin; this.emit(EventTypes.AUTH_REGISTER_SUCCESS, data); } @@ -104,6 +110,7 @@ class AuthStoreClass extends BaseStore { host: credentials.host, port: credentials.port, socketPath: credentials.socketPath, + isAdmin: true, }); } @@ -113,6 +120,7 @@ class AuthStoreClass extends BaseStore { handleAuthVerificationSuccess(data) { this.currentUser.username = data.username; + this.currentUser.isAdmin = data.isAdmin; AuthStore.emit(EventTypes.AUTH_VERIFY_SUCCESS, data); } diff --git a/package-lock.json b/package-lock.json index 827b7222..cf1fceb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,19 @@ "safe-buffer": "^5.1.1" } }, + "@types/babel-types": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", + "integrity": "sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw==" + }, + "@types/babylon": { + "version": "6.16.3", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.3.tgz", + "integrity": "sha512-lyJ8sW1PbY3uwuvpOBZ9zMYKshMnQpXmeDHh8dj9j2nJm/xrW0FgB5gLSYOArj5X0IfaXnmhFoJnhS4KbqIMug==", + "requires": { + "@types/babel-types": "*" + } + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -72,6 +85,21 @@ } } }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "^4.0.4" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", @@ -2175,6 +2203,14 @@ "supports-color": "^5.3.0" } }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, "chardet": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", @@ -2572,6 +2608,17 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "requires": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + } + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3351,6 +3398,11 @@ "esutils": "^2.0.2" } }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, "dom-converter": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", @@ -6466,6 +6518,22 @@ "is-primitive": "^2.0.0" } }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "requires": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -7592,6 +7660,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==" }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -7950,6 +8023,25 @@ } } }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + }, + "dependencies": { + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + } + } + }, "jsx-ast-utils": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", @@ -9534,8 +9626,7 @@ "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" }, "path-to-regexp": { "version": "0.1.7", @@ -11564,6 +11655,163 @@ "randombytes": "^2.0.1" } }, + "pug": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.3.tgz", + "integrity": "sha1-ccuoJTfJWl6rftBGluQiH1Oqh44=", + "requires": { + "pug-code-gen": "^2.0.1", + "pug-filters": "^3.1.0", + "pug-lexer": "^4.0.0", + "pug-linker": "^3.0.5", + "pug-load": "^2.0.11", + "pug-parser": "^5.0.0", + "pug-runtime": "^2.0.4", + "pug-strip-comments": "^1.0.3" + } + }, + "pug-attrs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.3.tgz", + "integrity": "sha1-owlflw5kFR972tlX7vVftdeQXRU=", + "requires": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.4" + } + }, + "pug-code-gen": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.1.tgz", + "integrity": "sha1-CVHsgyJddNjPxHan+Zolm199BQw=", + "requires": { + "constantinople": "^3.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.3", + "pug-error": "^1.3.2", + "pug-runtime": "^2.0.4", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "pug-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", + "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" + }, + "pug-filters": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.0.tgz", + "integrity": "sha1-JxZVVbwEwjbkqisDZiRt+gIbYm4=", + "requires": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.2", + "pug-walk": "^1.1.7", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "yargs": { + "version": "3.10.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "pug-lexer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.0.0.tgz", + "integrity": "sha1-IQwYRX7y4XYCQnQMXmR715TOwng=", + "requires": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.2" + } + }, + "pug-linker": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.5.tgz", + "integrity": "sha1-npp65ABWgtAn3uuWsAD4juuDoC8=", + "requires": { + "pug-error": "^1.3.2", + "pug-walk": "^1.1.7" + } + }, + "pug-load": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.11.tgz", + "integrity": "sha1-5kjlftET/iwfRdV4WOorrWvAFSc=", + "requires": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.7" + } + }, + "pug-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.0.tgz", + "integrity": "sha1-45Stmz/KkxI5QK/4hcBuRKt+aOQ=", + "requires": { + "pug-error": "^1.3.2", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.4.tgz", + "integrity": "sha1-4XjhvaaKsujArPybztLFT9iM61g=" + }, + "pug-strip-comments": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.3.tgz", + "integrity": "sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8=", + "requires": { + "pug-error": "^1.3.2" + } + }, + "pug-walk": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.7.tgz", + "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" + }, "pullstream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz", @@ -12376,7 +12624,6 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "dev": true, "requires": { "path-parse": "^1.0.5" } @@ -13394,7 +13641,8 @@ }, "stringstream": { "version": "0.0.5", - "resolved": "", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", "dev": true }, "strip-ansi": { @@ -13778,6 +14026,11 @@ "to-no-case": "^1.0.0" } }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + }, "topo": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz", @@ -14390,6 +14643,11 @@ "indexof": "0.0.1" } }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -14951,6 +15209,22 @@ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "requires": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", diff --git a/package.json b/package.json index ed9a3697..8d4ede2e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "postcss-flexbugs-fixes": "^3.3.0", "postcss-loader": "2.1.3", "promise": "^8.0.1", + "pug": "^2.0.3", "react": "^15.6.2", "react-addons-css-transition-group": "^15.6.2", "react-custom-scrollbars": "^4.2.1", diff --git a/server/middleware/requireAdmin.js b/server/middleware/requireAdmin.js new file mode 100644 index 00000000..e5a2b2c7 --- /dev/null +++ b/server/middleware/requireAdmin.js @@ -0,0 +1,9 @@ +module.exports = (req, res, next) => { + if (req.user == null || !req.user.isAdmin) { + return res + .status(403) + .json({message: 'User is not admin.'}) + .send(); + } + next(); +}; diff --git a/server/models/Users.js b/server/models/Users.js index 1b78aad5..2ceeff5c 100644 --- a/server/models/Users.js +++ b/server/models/Users.js @@ -36,10 +36,10 @@ class Users { .verify(user.password, credentials.password) .then(argon2Match => { if (argon2Match) { - return callback(argon2Match); + return callback(argon2Match, user.isAdmin); } - callback(null, argon2Match); + callback(null, argon2Match, false); }) .catch(error => callback(null, error)); }); diff --git a/server/routes/auth.js b/server/routes/auth.js index 65386c41..45ddff7b 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -4,14 +4,14 @@ const joi = require('joi'); const jwt = require('jsonwebtoken'); const passport = require('passport'); +const requireAdmin = require('../middleware/requireAdmin'); const config = require('../../config'); const router = express.Router(); const services = require('../services'); const Users = require('../models/Users'); - const failedLoginResponse = 'Failed login.'; -const setAuthToken = (res, username) => { +const setAuthToken = (res, username, isAdmin) => { let expirationSeconds = 60 * 60 * 24 * 7; // one week let cookieExpiration = Date.now() + expirationSeconds * 1000; @@ -22,7 +22,7 @@ const setAuthToken = (res, username) => { res.cookie('jwt', token, {expires: new Date(cookieExpiration), httpOnly: true}); - return res.json({success: true, token: `JWT ${token}`, username}); + return res.json({success: true, token: `JWT ${token}`, username, isAdmin}); }; const authValidation = joi.object().keys({ @@ -31,6 +31,7 @@ const authValidation = joi.object().keys({ host: joi.string(), port: joi.string(), socketPath: joi.string(), + isAdmin: joi.bool(), }); router.use('/', (req, res, next) => { @@ -46,20 +47,22 @@ router.use('/', (req, res, next) => { } }); +router.use('/users', passport.authenticate('jwt', {session: false}), requireAdmin); + router.post('/authenticate', (req, res) => { const credentials = { password: req.body.password, username: req.body.username, }; - Users.comparePassword(credentials, (isMatch, err) => { + Users.comparePassword(credentials, (isMatch, isAdmin, err) => { if (isMatch == null) { // Incorrect username. return res.status(401).json({message: failedLoginResponse}); } if (isMatch && !err) { - return setAuthToken(res, credentials.username); + return setAuthToken(res, credentials.username, isAdmin); } else { // Incorrect password. return res.status(401).json({message: failedLoginResponse}); @@ -98,7 +101,7 @@ router.post('/register', (req, res) => { return; } - setAuthToken(res, req.body.username); + setAuthToken(res, req.body.username, true); } ); }); @@ -118,7 +121,11 @@ router.use('/verify', (req, res, next) => { }); router.get('/verify', (req, res, next) => { - res.json({initialUser: req.initialUser, username: req.user && req.user.username}); + res.json({ + initialUser: req.initialUser, + username: req.user && req.user.username, + isAdmin: req.user && req.user.isAdmin, + }); }); // All subsequent routes are protected. @@ -148,7 +155,7 @@ router.patch('/users/:username', (req, res, next) => { userPatch.port = null; } - Users.updateUser(username, userPatch, user => { + Users.patchUser(username, userPatch, user => { Users.lookupUser({username}, (err, user) => { if (err) return req.status(500).json({error: err}); services.updateUserServices(user); @@ -165,7 +172,7 @@ router.put('/users', (req, res, next) => { host: req.body.host, port: req.body.port, socketPath: req.body.socketPath, - isAdmin: false, + isAdmin: req.body.isAdmin, }, ajaxUtil.getResponseFn(res) );