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)
);