From 90c5b29fe7cef80c1b86db67328cb299ceebbe71 Mon Sep 17 00:00:00 2001
From: Scott Bender
Date: Thu, 13 Sep 2018 20:31:46 -0400
Subject: [PATCH] feature: provide a way for devices to obtain a security token
and new user to sign up
---
lib/serverroutes.js | 78 ++++++
lib/tokensecurity.js | 223 +++++++++++++++++-
packages/server-admin-ui/src/actions.js | 4 +-
.../src/components/Sidebar/Sidebar.js | 37 ++-
.../src/containers/Full/Full.js | 12 +-
packages/server-admin-ui/src/index.js | 9 +-
.../src/views/security/AccessRequests.js | 189 +++++++++++++++
.../views/{ => security}/EnableSecurity.js | 4 +-
.../src/views/{ => security}/Login.js | 15 +-
.../src/views/security/Register.js | 114 +++++++++
.../src/views/security/Settings.js | 196 +++++++++++++++
.../views/{Security.js => security/Users.js} | 119 +---------
12 files changed, 868 insertions(+), 132 deletions(-)
create mode 100644 packages/server-admin-ui/src/views/security/AccessRequests.js
rename packages/server-admin-ui/src/views/{ => security}/EnableSecurity.js (97%)
rename packages/server-admin-ui/src/views/{ => security}/Login.js (89%)
create mode 100644 packages/server-admin-ui/src/views/security/Register.js
create mode 100644 packages/server-admin-ui/src/views/security/Settings.js
rename packages/server-admin-ui/src/views/{Security.js => security/Users.js} (71%)
diff --git a/lib/serverroutes.js b/lib/serverroutes.js
index 762e7d230..0f241387f 100644
--- a/lib/serverroutes.js
+++ b/lib/serverroutes.js
@@ -257,6 +257,84 @@ module.exports = function(app, saveSecurityConfig, getSecurityConfig) {
)
})
+ app.put('/security/access/requests/:identifier/:status', (req, res) => {
+ if (app.securityStrategy.allowConfigure(req)) {
+ var config = getSecurityConfig(app)
+ app.securityStrategy.setAccessRequestStatus(
+ config,
+ req.params.identifier,
+ req.params.status,
+ req.body,
+ (err, config) => {
+ if (err) {
+ console.log(err)
+ res.status(500)
+ res.send(`Unable update request: ${err.message}`)
+ return
+ }
+ if (config) {
+ saveSecurityConfig(app, config, err => {
+ if (err) {
+ console.log(err)
+ res.status(500)
+ res.send('Unable to save configuration change')
+ return
+ }
+ res.send('Request updated')
+ })
+ } else {
+ res.send('Request updated')
+ }
+ }
+ )
+ } else {
+ res.status(401).json('Security config not allowed')
+ }
+ })
+
+ app.get('/security/access/requests', (req, res) => {
+ if (app.securityStrategy.allowConfigure(req)) {
+ res.json(app.securityStrategy.getAccessRequestsResponse())
+ } else {
+ res.status(401).json('Security config not allowed')
+ }
+ })
+
+ app.post('/access/requests', (req, res) => {
+ var config = getSecurityConfig(app)
+ let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
+ app.securityStrategy.requestAccess(
+ config,
+ req.body,
+ ip,
+ (err, response, config) => {
+ if (err) {
+ console.log(err)
+ res.status(500)
+ res.send(`Unable to create request: ${err.message}`)
+ return
+ }
+ if (!response) {
+ res.send('Request created')
+ } else {
+ res.json(response)
+ }
+ }
+ )
+ })
+
+ app.get('/access/requests/:id', (req, res) => {
+ app.securityStrategy.checkRequest(req.params.id, (err, result) => {
+ if (err) {
+ console.log(err)
+ res.status(500)
+ res.send(`Unable to check request: ${err.message}`)
+ return
+ }
+ res.json(result)
+ })
+ })
+
app.get('/settings', (req, res, next) => {
var settings = {
interfaces: {},
diff --git a/lib/tokensecurity.js b/lib/tokensecurity.js
index 169e6556c..9b755891a 100644
--- a/lib/tokensecurity.js
+++ b/lib/tokensecurity.js
@@ -22,6 +22,7 @@ const fs = require('fs')
const path = require('path')
const bcrypt = require('bcryptjs')
const getSourceId = require('@signalk/signalk-schema').getSourceId
+const uuidv4 = require('uuid/v4')
const CONFIG_PLUGINID = 'sk-simple-token-security-config'
const passwordSaltRounds = 10
@@ -30,6 +31,7 @@ const permissionDeniedMessage =
module.exports = function (app, config) {
const strategy = {}
+ let accessRequests = []
let {
allow_readonly = true,
@@ -39,8 +41,11 @@ module.exports = function (app, config) {
.randomBytes(256)
.toString('hex'),
users = [],
+ devices = [],
immutableConfig = false,
- acls = []
+ acls = [],
+ allowDeviceAccessRequests = true,
+ allowNewUserRegistration = true
} = config
if (process.env.ADMINUSER) {
@@ -64,13 +69,25 @@ module.exports = function (app, config) {
immutableConfig = true
}
+ if (process.env.ALLOW_DEVICE_ACCESS_REQUESTS) {
+ allowDeviceAccessRequests =
+ process.env.ALLOW_DEVICE_ACCESS_REQUESTS === 'true'
+ }
+
+ if (process.env.ALLOW_NEW_USER_REGISTRATION) {
+ allowNewUserRegistration =
+ process.env.ALLOW_NEW_USER_REGISTRATION === 'true'
+ }
+
let options = {
allow_readonly,
expiration,
secretKey,
users,
immutableConfig,
- acls
+ acls,
+ allowDeviceAccessRequests,
+ allowNewUserRegistration
}
// so that enableSecurity gets the defaults to save
@@ -273,7 +290,9 @@ module.exports = function (app, config) {
var result = {
status: req.skIsAuthenticated ? 'loggedIn' : 'notLoggedIn',
readOnlyAccess: configuration.allow_readonly,
- authenticationRequired: true
+ authenticationRequired: true,
+ allowNewUserRegistration: configuration.allowNewUserRegistration,
+ allowDeviceAccessRequests: configuration.allowDeviceAccessRequests
}
if (req.skIsAuthenticated) {
result.userLevel = req.skUser.type
@@ -294,6 +313,7 @@ module.exports = function (app, config) {
strategy.setConfig = (config, newConfig) => {
assertConfigImmutability()
newConfig.users = config.users
+ newConfig.devices = config.devices
newConfig.secretKey = config.secretKey
options = newConfig
return newConfig
@@ -701,6 +721,203 @@ module.exports = function (app, config) {
}
}
+ strategy.getAccessRequestsResponse = () => {
+ return accessRequests.filter(r => r.active).map(r => ({
+ identifier: r.identifier,
+ description: r.description,
+ ip: r.ip,
+ expiration: r.expiration,
+ config: r.config,
+ active: r.active
+ }))
+ }
+
+ function sendAccessRequestsUpdate () {
+ app.emit('serverevent', {
+ type: 'ACCESS_REQUEST',
+ from: CONFIG_PLUGINID,
+ data: strategy.getAccessRequestsResponse()
+ })
+ }
+
+ strategy.checkRequest = (requestId, cb) => {
+ var idx = accessRequests.findIndex(r => r.requestId == requestId)
+ if (idx == -1) {
+ cb(new Error('not found'))
+ return
+ }
+
+ var request = accessRequests[idx]
+ var result = {
+ status: request.active
+ ? 'PENDING'
+ : request.approved ? 'APPROVED' : 'DENIED'
+ }
+ if (request.approved) {
+ result.token = request.token
+ delete request.token
+ }
+ cb(null, result)
+ }
+
+ strategy.setAccessRequestStatus = (config, identifier, status, body, cb) => {
+ var idx = accessRequests.findIndex(r => r.identifier == identifier)
+ if (idx == -1) {
+ cb(new Error('not found'))
+ return
+ }
+
+ var request = accessRequests[idx]
+
+ if (status === 'approved') {
+ if (request.uuid) {
+ var payload = { device: identifier }
+ var jwtOptions = {}
+
+ expiration = body.expiration
+ if (expiration !== 'NEVER') {
+ jwtOptions.expiresIn = expiration
+ }
+ debug(`expiration ${expiration} ${body.expiration}`)
+ var token = jwt.sign(payload, config.secretKey, jwtOptions)
+
+ if (!config.devices) {
+ config.devices = []
+ }
+
+ config.devices.push({
+ uuid: request.uuid,
+ permissions: request.permissions,
+ config: body.config
+ })
+ request.token = token
+ } else {
+ config.users.push({
+ username: identifier,
+ password: request.password,
+ type: 'readonly'
+ })
+ }
+ request.approved = true
+ } else if (status === 'denied') {
+ request.approved = false
+ } else {
+ cb(new Error('Unkown status value'), config)
+ return
+ }
+ // accessRequests = accessRequests.filter(r => r.identifier != identifier)
+ request.active = false
+ cb(null, config)
+ options = config
+ sendAccessRequestsUpdate()
+ }
+
+ function validateAccessRequest (request) {
+ if (request.userId) {
+ return !_.isUndefined(request.password)
+ } else if (request.uuid) {
+ return !_.isUndefined(request.description)
+ } else {
+ return false
+ }
+ }
+
+ strategy.requestAccess = (config, request, sourceIp, cb) => {
+ if (!validateAccessRequest(request)) {
+ cb(new Error('Invalid request'))
+ return
+ }
+
+ request.ip = sourceIp
+ request.active = true
+ request.date = new Date()
+
+ var alertMessage
+ var response
+ if (request.uuid) {
+ if (!options.allowDeviceAccessRequests) {
+ cb(new Error('Device access not allowed'))
+ return
+ }
+
+ if (
+ config.devices &&
+ config.devices.find(device => device.uuid == request.uuid)
+ ) {
+ cb(new Error(`A device with uuid '${request.uuid}' already has access`))
+ return
+ }
+
+ if (accessRequests.find(r => r.uuid == request.uuid)) {
+ cb(
+ new Error(
+ `A device with uuid '${request.uuid}' has already requested access`
+ )
+ )
+ return
+ }
+
+ request.identifier = request.uuid
+ request.requestId = uuidv4()
+
+ /*
+ req.on('close', (err) => {
+ accessRequests = accessRequests.filter(r => r.uuid != request.uuid)
+ sendAccessRequestsUpdate()
+ })
+ */
+ response = { requestId: request.requestId }
+
+ alertMessage = `A device with IP ${request.ip} and UUID ${
+ request.uuid
+ } has requested access to the server`
+ debug(alertMessage)
+ } else {
+ if (!options.allowNewUserRegistration) {
+ cb(new Error('new user registration not allowed'))
+ return
+ }
+
+ var existing = options.users.find(user => user.username == request.userId)
+ if (existing) {
+ cb(new Error('User already exists'))
+ return
+ }
+ request.description = 'New User Request'
+ request.identifier = request.userId
+ request.password = bcrypt.hashSync(
+ request.password,
+ bcrypt.genSaltSync(passwordSaltRounds)
+ )
+ alertMessage = `${request.userid} has requested server access`
+ debug(alertMessage)
+ }
+
+ request.permissions = 'readonly'
+ request.expiration = config.expiration
+ accessRequests.push(request)
+ sendAccessRequestsUpdate()
+ app.handleMessage(CONFIG_PLUGINID, {
+ context: 'vessels.' + app.selfId,
+ updates: [
+ {
+ values: [
+ {
+ path: 'notifications.security.accessRequest',
+ value: {
+ state: 'alert',
+ method: ['visual', 'sound'],
+ message: alertMessage,
+ timestamp: new Date().toISOString()
+ }
+ }
+ ]
+ }
+ ]
+ })
+ cb(null, response, config)
+ }
+
setupApp()
return strategy
diff --git a/packages/server-admin-ui/src/actions.js b/packages/server-admin-ui/src/actions.js
index d7fdf571d..52e8c00cd 100644
--- a/packages/server-admin-ui/src/actions.js
+++ b/packages/server-admin-ui/src/actions.js
@@ -126,6 +126,7 @@ export const fetchLoginStatus = buildFetchAction('/loginStatus', 'RECEIVE_LOGIN_
export const fetchPlugins = buildFetchAction('/plugins', 'RECEIVE_PLUGIN_LIST')
export const fetchWebapps = buildFetchAction('/webapps', 'RECEIVE_WEBAPPS_LIST')
export const fetchApps = buildFetchAction('/appstore/available', 'RECEIVE_APPSTORE_LIST')
+export const fetchAccessRequests = buildFetchAction('/security/access/requests', 'ACCESS_REQUEST')
export const fetchServerSpecification = buildFetchAction('/signalk', 'RECEIVE_SERVER_SPEC')
export function fetchAllData (dispatch) {
@@ -133,7 +134,8 @@ export function fetchAllData (dispatch) {
fetchWebapps(dispatch)
fetchApps(dispatch)
fetchLoginStatus(dispatch)
- fetchServerSpecification(dispatch)
+ fetchServerSpecification(dispatch),
+ fetchAccessRequests(dispatch)
}
export function openServerEventsConnection (dispatch) {
diff --git a/packages/server-admin-ui/src/components/Sidebar/Sidebar.js b/packages/server-admin-ui/src/components/Sidebar/Sidebar.js
index b3d13c75b..b9de87501 100644
--- a/packages/server-admin-ui/src/components/Sidebar/Sidebar.js
+++ b/packages/server-admin-ui/src/components/Sidebar/Sidebar.js
@@ -154,6 +154,7 @@ const mapStateToProps = state => {
var appUpdates = state.appStore.updates.length
var updatesBadge = null
var availableBadge = null
+ var accessRequestsBadge = null
if (appUpdates > 0) {
updatesBadge = {
variant: 'danger',
@@ -162,6 +163,14 @@ const mapStateToProps = state => {
}
}
+ if ( state.accessRequests.length > 0 ) {
+ accessRequestsBadge = {
+ variant: 'danger',
+ text: `${state.accessRequests.length}`,
+ color: 'danger'
+ }
+ }
+
if (!state.appStore.storeAvailable) {
updatesBadge = availableBadge = {
variant: 'danger',
@@ -240,11 +249,33 @@ const mapStateToProps = state => {
state.loginStatus.authenticationRequired === false ||
state.loginStatus.userLevel == 'admin'
) {
- result.items.push({
+ var security = {
name: 'Security',
url: '/security',
- icon: 'icon-settings'
- })
+ icon: 'icon-settings',
+ badge: accessRequestsBadge,
+ children: [
+ {
+ name: 'Settings',
+ url: '/security/settings'
+ },
+ {
+ name: 'Users',
+ url: '/security/users'
+ }
+ ]
+ }
+ if (
+ state.loginStatus.allowNewUserRegistration ||
+ state.allowDeviceAccessRequests ) {
+ security.children.push({
+ name: 'Access Requests',
+ url: '/security/access/requests',
+ badge: accessRequestsBadge,
+ })
+
+ }
+ result.items.push(security)
}
return result
diff --git a/packages/server-admin-ui/src/containers/Full/Full.js b/packages/server-admin-ui/src/containers/Full/Full.js
index 2725b8ec2..f976340ae 100644
--- a/packages/server-admin-ui/src/containers/Full/Full.js
+++ b/packages/server-admin-ui/src/containers/Full/Full.js
@@ -13,8 +13,11 @@ import Dashboard from '../../views/Dashboard/'
import Webapps from '../../views/Webapps/'
import Apps from '../../views/appstore/Apps/'
import Configuration from '../../views/Configuration'
-import Login from '../../views/Login'
-import Security from '../../views/Security'
+import Login from '../../views/security/Login'
+import SecuritySettings from '../../views/security/Settings'
+import Users from '../../views/security/Users'
+import Register from '../../views/security/Register'
+import AccessRequests from '../../views/security/AccessRequests'
import VesselConfiguration from '../../views/ServerConfig/VesselConfiguration'
import ProvidersConfiguration from '../../views/ServerConfig/ProvidersConfiguration'
import Settings from '../../views/ServerConfig/Settings'
@@ -72,8 +75,11 @@ class Full extends Component {
path='/serverConfiguration/providers'
component={loginOrOriginal(ProvidersConfiguration)}
/>
-
+
+
+
+
diff --git a/packages/server-admin-ui/src/index.js b/packages/server-admin-ui/src/index.js
index a893477f0..0d750fca8 100644
--- a/packages/server-admin-ui/src/index.js
+++ b/packages/server-admin-ui/src/index.js
@@ -33,7 +33,8 @@ const state = {
serverSpecification: {},
websocketStatus: 'initial',
webSocket: null,
- restarting: false
+ restarting: false,
+ accessRequests: []
}
let store = createStore(
@@ -159,6 +160,12 @@ let store = createStore(
state.webSocket.close()
}
}
+ if ( action.type === 'ACCESS_REQUEST' ) {
+ return {
+ ...state,
+ accessRequests: action.data
+ }
+ }
return state
},
state,
diff --git a/packages/server-admin-ui/src/views/security/AccessRequests.js b/packages/server-admin-ui/src/views/security/AccessRequests.js
new file mode 100644
index 000000000..1317420a1
--- /dev/null
+++ b/packages/server-admin-ui/src/views/security/AccessRequests.js
@@ -0,0 +1,189 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import {
+ Button,
+ Card,
+ CardHeader,
+ CardBody,
+ CardFooter,
+ InputGroup,
+ InputGroupAddon,
+ Input,
+ Form,
+ Col,
+ Label,
+ FormGroup,
+ FormText,
+ Table,
+ Row
+} from 'reactstrap';
+import EnableSecurity from './EnableSecurity'
+
+class AccessRequests extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ selectedRequest: null,
+ accessRequestsApproving: [],
+ accessRequestsDenying: []
+ }
+ this.handleRequestChange = this.handleRequestChange.bind(this)
+ }
+
+ handleAccessRequest(identifier, approved) {
+ var stateKey = approved ? 'accessRequestsApproving' : 'accessRequestsDenying'
+ this.state[stateKey].push(identifier)
+ this.setState({stateKey: this.state})
+
+ var payload = {
+ permissions: this.state.selectedRequest.permissions || 'readonly',
+ config: this.state.selectedRequest.config,
+ expiration: this.state.selectedRequest.expiration || '1y'
+ }
+
+ fetch(`/security/access/requests/${identifier}/${approved ? 'approved' : 'denied'}`, {
+ method: 'PUT',
+ credentials: "include",
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload)
+ }).then(response => response.text())
+ .then(response => {
+ this.state[stateKey] = this.state[stateKey].filter(id => id != identifier)
+ this.setState({
+ stateKey: this.state[stateKey],
+ selectedRequest: null
+ })
+ });
+ }
+
+ requestClicked(event, request, index) {
+ this.setState({
+ selectedRequest: JSON.parse(JSON.stringify(request)),
+ selectedIndex: index
+ }, () => {
+ this.refs['selectedRequest'].scrollIntoView();
+ });
+ }
+
+ handleRequestChange(event) {
+ const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
+ this.state.selectedRequest[event.target.name] = value;
+ this.setState({
+ selectedRequest: this.state.selectedRequest
+ });
+ }
+ handleCancel(event) {
+ this.setState({ selectedRequest: null })
+ }
+
+
+ render () {
+ return (
+
+ {this.props.loginStatus.authenticationRequired === false &&
+
+ }
+ {this.props.loginStatus.authenticationRequired &&
+
+ }
+
+ )
+ }
+}
+
+const mapStateToProps = ({ accessRequests, loginStatus }) => ({ accessRequests, loginStatus })
+
+export default connect(mapStateToProps)(AccessRequests)
+
+
diff --git a/packages/server-admin-ui/src/views/EnableSecurity.js b/packages/server-admin-ui/src/views/security/EnableSecurity.js
similarity index 97%
rename from packages/server-admin-ui/src/views/EnableSecurity.js
rename to packages/server-admin-ui/src/views/security/EnableSecurity.js
index 96d206fab..8cc5940f0 100644
--- a/packages/server-admin-ui/src/views/EnableSecurity.js
+++ b/packages/server-admin-ui/src/views/security/EnableSecurity.js
@@ -14,8 +14,8 @@ import {
HelpBlock
} from 'reactstrap'
import { connect } from 'react-redux'
-import { login, enableSecurity, fetchLoginStatus } from '../actions'
-import Dashboard from './Dashboard/'
+import { login, enableSecurity, fetchLoginStatus } from '../../actions'
+import Dashboard from '../Dashboard/'
import Login from './Login'
class EnableSecurity extends Component {
diff --git a/packages/server-admin-ui/src/views/Login.js b/packages/server-admin-ui/src/views/security/Login.js
similarity index 89%
rename from packages/server-admin-ui/src/views/Login.js
rename to packages/server-admin-ui/src/views/security/Login.js
index 24419c017..2118d9c53 100644
--- a/packages/server-admin-ui/src/views/Login.js
+++ b/packages/server-admin-ui/src/views/security/Login.js
@@ -13,10 +13,12 @@ import {
InputGroupAddon,
HelpBlock
} from 'reactstrap'
+import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
-import { login, fetchAllData } from '../actions'
-import Dashboard from './Dashboard/'
+import { login, fetchAllData } from '../../actions'
+import Dashboard from '../Dashboard/'
import EnableSecurity from './EnableSecurity'
+import Register from './Register'
class Login extends Component {
constructor (props) {
@@ -104,7 +106,14 @@ class Login extends Component {
{this.state.loginErrorMessage}
-
+
+ {!this.state.loginErrorMessage && this.props.loginStatus.allowNewUserRegistration &&
+
+
+
+
+
+ }
diff --git a/packages/server-admin-ui/src/views/security/Register.js b/packages/server-admin-ui/src/views/security/Register.js
new file mode 100644
index 000000000..2573a3e67
--- /dev/null
+++ b/packages/server-admin-ui/src/views/security/Register.js
@@ -0,0 +1,114 @@
+import React, {Component} from 'react';
+import {Container, Row, Col, Card, CardBody, CardFooter, Button, Input, InputGroup, InputGroupAddon, FormText} from 'reactstrap';
+
+class Register extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ errorMessage: null,
+ email: '',
+ password: '',
+ confirmPassword: '',
+ registrationSent: false
+ }
+ this.handleInputChange = this.handleInputChange.bind(this);
+ this.handleCreate = this.handleCreate.bind(this);
+ }
+
+ handleInputChange(event) {
+ var targetName = event.target.name
+ this.setState({[event.target.name]: event.target.value}, () => {
+ if ( targetName === 'password' ||
+ targetName === 'confirmPassword' &&
+ this.state.password != this.state.confirmPassword ) {
+ this.setState({errorMessage: "Passwords do not match"})
+ } else {
+ this.setState({errorMessage: null})
+ }
+ })
+ }
+
+ handleCreate(event) {
+ if ( this.state.email.length == 0 ) {
+ this.setState({errorMessage: 'Please enter an email address'})
+ } else if ( this.state.password.length == 0 &&
+ this.state.confirmPassword.length == 0 ) {
+ this.setState({errorMessage: 'Please enter and conform your password'})
+ } else if ( this.state.password != this.state.confirmPassword ) {
+ //error message is already thwre
+ return
+ } else {
+ var payload = {
+ userId: this.state.email,
+ password: this.state.password
+ }
+ fetch(`/access/requests`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ credentials: "include"
+ })
+ .then(response => {
+ if ( response.status != 200 ) {
+ response.text().then(text => {
+ this.setState({errorMessage: text})
+ })
+ } else {
+ this.setState({registrationSent: true})
+ }
+ });
+ }
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+ Register
+ {this.state.registrationSent &&
+ Your registration has been sent
+ }
+ {!this.state.registrationSent &&
+
+ }
+
+ {!this.state.registrationSent &&
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+ );
+ }
+}
+
+export default Register;
diff --git a/packages/server-admin-ui/src/views/security/Settings.js b/packages/server-admin-ui/src/views/security/Settings.js
new file mode 100644
index 000000000..5c674b75b
--- /dev/null
+++ b/packages/server-admin-ui/src/views/security/Settings.js
@@ -0,0 +1,196 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import {
+ Button,
+ Card,
+ CardHeader,
+ CardBody,
+ CardFooter,
+ InputGroup,
+ InputGroupAddon,
+ Input,
+ Form,
+ Col,
+ Label,
+ FormGroup,
+ FormText,
+ Table,
+ Row
+} from 'reactstrap'
+import EnableSecurity from './EnableSecurity'
+
+export function fetchSecurityConfig () {
+ fetch(`/security/config`, {
+ credentials: 'include'
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log(JSON.stringify(data))
+ this.setState(data)
+ })
+}
+
+class Settings extends Component {
+ constructor (props) {
+ super(props)
+ this.state = {
+ allow_readonly: false,
+ expiration: '',
+ allowNewUserRegistration: true,
+ allowDeviceAccessRequests: true
+ }
+
+ this.handleChange = this.handleChange.bind(this)
+ this.handleSaveConfig = this.handleSaveConfig.bind(this)
+ this.fetchSecurityConfig = fetchSecurityConfig.bind(this)
+ }
+
+ componentDidMount () {
+ if (this.props.loginStatus.authenticationRequired) {
+ this.fetchSecurityConfig()
+ }
+ }
+
+ handleChange (event) {
+ const value =
+ event.target.type === 'checkbox'
+ ? event.target.checked
+ : event.target.value
+ this.setState({ [event.target.name]: value })
+ }
+
+ handleSaveConfig () {
+ var payload = {
+ allow_readonly: this.state.allow_readonly,
+ expiration: this.state.expiration,
+ allowNewUserRegistration: this.state.allowNewUserRegistration,
+ allowDeviceAccessRequests: this.state.allowDeviceAccessRequests
+ }
+ fetch('/security/config', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(payload),
+ credentials: 'include'
+ })
+ .then(response => response.text())
+ .then(response => {
+ this.fetchSecurityConfig()
+ alert(response)
+ })
+ }
+
+ render () {
+ return (
+
+ {this.props.loginStatus.authenticationRequired === false && (
+
+ )}
+ {this.props.loginStatus.authenticationRequired && (
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ )
+ }
+}
+
+const mapStateToProps = ({ securityConfig }) => ({ securityConfig })
+
+export default connect(mapStateToProps)(Settings)
+
diff --git a/packages/server-admin-ui/src/views/Security.js b/packages/server-admin-ui/src/views/security/Users.js
similarity index 71%
rename from packages/server-admin-ui/src/views/Security.js
rename to packages/server-admin-ui/src/views/security/Users.js
index b655a15db..57fb8d36d 100644
--- a/packages/server-admin-ui/src/views/Security.js
+++ b/packages/server-admin-ui/src/views/security/Users.js
@@ -19,16 +19,6 @@ import {
} from 'reactstrap'
import EnableSecurity from './EnableSecurity'
-export function fetchSecurityConfig () {
- fetch(`/security/config`, {
- credentials: 'include'
- })
- .then(response => response.json())
- .then(data => {
- this.setState(data)
- })
-}
-
export function fetchSecurityUsers () {
fetch(`/security/users`, {
credentials: 'include'
@@ -39,20 +29,15 @@ export function fetchSecurityUsers () {
})
}
-class Security extends Component {
+class Users extends Component {
constructor (props) {
super(props)
this.state = {
users: [],
- allow_readonly: false,
- expiration: ''
}
- this.handleChange = this.handleChange.bind(this)
this.handleAddUser = this.handleAddUser.bind(this)
- this.handleSaveConfig = this.handleSaveConfig.bind(this)
this.fetchSecurityUsers = fetchSecurityUsers.bind(this)
- this.fetchSecurityConfig = fetchSecurityConfig.bind(this)
this.handleCancel = this.handleCancel.bind(this)
this.handleApply = this.handleApply.bind(this)
this.handleUserChange = this.handleUserChange.bind(this)
@@ -61,19 +46,10 @@ class Security extends Component {
componentDidMount () {
if (this.props.loginStatus.authenticationRequired) {
- this.fetchSecurityConfig()
this.fetchSecurityUsers()
}
}
- handleChange (event) {
- const value =
- event.target.type === 'checkbox'
- ? event.target.checked
- : event.target.value
- this.setState({ [event.target.name]: value })
- }
-
handleUserChange (event) {
const value =
event.target.type === 'checkbox'
@@ -167,26 +143,6 @@ class Security extends Component {
})
}
- handleSaveConfig () {
- var payload = {
- allow_readonly: this.state.allow_readonly,
- expiration: this.state.expiration
- }
- fetch('/security/config', {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(payload),
- credentials: 'include'
- })
- .then(response => response.text())
- .then(response => {
- this.fetchSecurityConfig()
- alert(response)
- })
- }
-
userClicked (user, index) {
console.log(JSON.stringify(user))
this.setState(
@@ -211,72 +167,6 @@ class Security extends Component {
)}
{this.props.loginStatus.authenticationRequired && (
-
-
- Settings
-
-
-
-
-
-
-
-
-
Users
@@ -425,12 +315,9 @@ class Security extends Component {
}
}
-const mapStateToProps = ({ securityConfig, securityUsers }) => ({
- securityConfig,
- securityUsers
-})
+const mapStateToProps = ({ securityUsers }) => ({ securityUsers })
-export default connect(mapStateToProps)(Security)
+export default connect(mapStateToProps)(Users)
function convertType (type) {
if (type == 'readonly') {