Fix authentication

This commit is contained in:
Thomas Nordquist
2019-01-15 22:00:35 +01:00
parent f905ba8e89
commit 61b64a5ac2
4 changed files with 210 additions and 105 deletions

View File

@@ -3,8 +3,13 @@ import * as React from 'react'
import { import {
Button, Button,
CircularProgress, CircularProgress,
FormControl,
FormControlLabel, FormControlLabel,
Grid, Grid,
IconButton,
Input,
InputAdornment,
InputLabel,
MenuItem, MenuItem,
Modal, Modal,
Paper, Paper,
@@ -17,6 +22,10 @@ import { DataSourceState, MqttOptions } from '../../../../backend/src/DataSource
import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles' import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles'
import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../../events' import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../../events'
import Notification from './Notification'
import Visibility from '@material-ui/icons/Visibility'
import VisibilityOff from '@material-ui/icons/VisibilityOff'
import sha1 = require('sha1') import sha1 = require('sha1')
interface Props { interface Props {
@@ -34,7 +43,7 @@ const protocols = [
interface State { interface State {
connecting: boolean connecting: boolean
connectionId?: string connectionId?: string
error?: Error error?: string
visible: boolean visible: boolean
host: string host: string
protocol: string protocol: string
@@ -44,6 +53,7 @@ interface State {
clientId: string clientId: string
username: string username: string
password: string password: string
showPassword: boolean
} }
declare var window: any declare var window: any
@@ -71,6 +81,7 @@ class Connection extends React.Component<Props, State> {
password: '', password: '',
connecting: false, connecting: false,
connectionId: undefined, connectionId: undefined,
showPassword: false,
} }
this.state = Object.assign({}, defaultState, storedSettings) this.state = Object.assign({}, defaultState, storedSettings)
@@ -80,6 +91,10 @@ class Connection extends React.Component<Props, State> {
window.localStorage.setItem('connectionSettings', JSON.stringify(this.state)) window.localStorage.setItem('connectionSettings', JSON.stringify(this.state))
} }
private handleClickShowPassword = () => {
this.setState({ showPassword: !this.state.showPassword })
}
private optionsFromState(): MqttOptions { private optionsFromState(): MqttOptions {
const protocol = this.state.protocol === 'tcp://' ? 'mqtt://' : this.state.protocol const protocol = this.state.protocol === 'tcp://' ? 'mqtt://' : this.state.protocol
const url = `${protocol}${this.state.host}:${this.state.port}` const url = `${protocol}${this.state.host}:${this.state.port}`
@@ -87,7 +102,7 @@ class Connection extends React.Component<Props, State> {
return { return {
url, url,
username: this.state.username || undefined, username: this.state.username || undefined,
password: this.state.username || undefined, password: this.state.password || undefined,
tls: this.state.tls, tls: this.state.tls,
certValidation: this.state.certValidation, certValidation: this.state.certValidation,
} }
@@ -110,8 +125,8 @@ class Connection extends React.Component<Props, State> {
this.props.onConnection(connectionId) this.props.onConnection(connectionId)
this.setState({ visible: false }) this.setState({ visible: false })
} else if (state.error) { } else if (state.error) {
console.log('error', state.error)
this.setState({ error: state.error }) this.setState({ error: state.error })
this.disconnect()
} }
}) })
} }
@@ -150,6 +165,9 @@ class Connection extends React.Component<Props, State> {
button: { button: {
margin: theme.spacing.unit, margin: theme.spacing.unit,
}, },
passwordFormControl: {
marginTop: '16px',
},
} }
} }
@@ -163,108 +181,137 @@ class Connection extends React.Component<Props, State> {
public render() { public render() {
const { classes } = this.props const { classes } = this.props
return <Modal open={this.state.visible} disableAutoFocus={true} onClose={() => { console.log('close') }}> const passwordVisibilityButton = (
<Paper className={classes.root}> <InputAdornment position="end">
<Toolbar> <IconButton
<Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography> aria-label="Toggle password visibility"
</Toolbar> onClick={this.handleClickShowPassword}
<form className={classes.container} noValidate autoComplete="off"> >
<Grid container spacing={24}> {this.state.showPassword ? <Visibility /> : <VisibilityOff />}
<Grid item xs={2}> </IconButton>
<TextField </InputAdornment>
select )
label="Protocol"
className={classes.textField} let renderError = null
value={this.state.protocol} if (this.state.error) {
onChange={this.handleChange('protocol')} renderError = (
margin="normal" <Notification
> message={this.state.error}
{protocols.map((value: string) => ( type="error"
<MenuItem key={value} value={value}> onClose={() => { this.setState({ error: undefined }) }}
{value} />
</MenuItem> )
))} }
</TextField>
</Grid> return (
<Grid item xs={7}> <div>
<TextField {renderError}
label="Host" <Modal open={this.state.visible} disableAutoFocus={true}>
className={classes.textField} <Paper className={classes.root}>
value={this.state.host} <Toolbar>
onChange={this.handleChange('host')} <Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography>
margin="normal" </Toolbar>
/> <form className={classes.container} noValidate={true} autoComplete="off">
</Grid> <Grid container={true} spacing={24}>
<Grid item xs={3}> <Grid item={true} xs={2}>
<TextField <TextField
label="Port" select={true}
className={classes.textField} label="Protocol"
value={this.state.port} className={classes.textField}
onChange={this.handleChange('port')} value={this.state.protocol}
margin="normal" onChange={this.handleChange('protocol')}
/> margin="normal"
</Grid> >
<Grid item xs={5}> {protocols.map((value: string) => (
<TextField <MenuItem key={value} value={value}>
label="Username" {value}
className={classes.textField} </MenuItem>
value={this.state.username} ))}
onChange={this.handleChange('username')} </TextField>
margin="normal" </Grid>
/> <Grid item xs={7}>
</Grid> <TextField
<Grid item xs={5}> label="Host"
<TextField className={classes.textField}
label="Password" value={this.state.host}
type="type" onChange={this.handleChange('host')}
className={classes.textField} margin="normal"
value={this.state.password} />
onChange={this.handleChange('password')} </Grid>
margin="normal" <Grid item={true} xs={3}>
/> <TextField
</Grid> label="Port"
<Grid item xs={4}> className={classes.textField}
<div className={classes.switch}> value={this.state.port}
<FormControlLabel onChange={this.handleChange('port')}
control={( margin="normal"
<Switch />
checked={this.state.certValidation} </Grid>
onChange={() => this.setState({ certValidation: !this.state.certValidation })} <Grid item={true} xs={5}>
color="primary" <TextField
label="Username"
className={classes.textField}
value={this.state.username}
onChange={this.handleChange('username')}
margin="normal"
/>
</Grid>
<Grid item={true} xs={5}>
<FormControl className={`${classes.textField} ${classes.passwordFormControl}`}>
<InputLabel htmlFor="adornment-password">Password</InputLabel>
<Input
id="adornment-password"
type={this.state.showPassword ? 'text' : 'password'}
value={this.state.password}
onChange={this.handleChange('password')}
endAdornment={passwordVisibilityButton}
/> />
)} </FormControl>
label="Validate certificate" </Grid>
labelPlacement="bottom" <Grid item={true} xs={4}>
/> <div className={classes.switch}>
</div> <FormControlLabel
</Grid> control={(
<Grid item xs={4}> <Switch
<div className={classes.switch}> checked={this.state.certValidation}
<FormControlLabel onChange={() => this.setState({ certValidation: !this.state.certValidation })}
control={( color="primary"
<Switch />
checked={this.state.tls} )}
onChange={() => this.setState({ tls: !this.state.tls })} label="Validate certificate"
color="primary" labelPlacement="bottom"
/> />
)} </div>
label="Encryption (tls)" </Grid>
labelPlacement="bottom" <Grid item={true} xs={4}>
/> <div className={classes.switch}>
<FormControlLabel
control={(
<Switch
checked={this.state.tls}
onChange={() => this.setState({ tls: !this.state.tls })}
color="primary"
/>
)}
label="Encryption (tls)"
labelPlacement="bottom"
/>
</div>
</Grid>
<Grid item={true} xs={4} />
</Grid>
<br />
<div style={{ textAlign: 'right' }}>
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
Save
</Button>
{this.renderConnectButton()}
</div> </div>
</Grid> </form>
<Grid item xs={4}></Grid> </Paper>
</Grid> </Modal>
<br /> </div>
<div style={{ textAlign: 'right' }}> )
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
Save
</Button>
{ this.renderConnectButton() }
</div>
</form>
</Paper>
</Modal>
} }
private renderConnectButton() { private renderConnectButton() {

View File

@@ -0,0 +1,55 @@
import * as React from 'react'
import { Snackbar, SnackbarContent } from '@material-ui/core'
import { Theme, withStyles } from '@material-ui/core/styles'
import { green, red } from '@material-ui/core/colors'
enum MessageType {
success = 'success',
error = 'error',
}
interface Props {
message?: string
type: MessageType
onClose: () => void
classes: any
}
class Notification extends React.Component<Props, {}> {
constructor(props: Props) {
super(props)
}
public static styles = (theme: Theme) => ({
success: {
backgroundColor: green[600],
color: theme.typography.button.color,
},
error: {
backgroundColor: red[600],
color: theme.typography.button.color,
},
})
public render() {
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={Boolean(this.props.message)}
autoHideDuration={10000}
onClose={this.props.onClose}
>
<SnackbarContent
className={this.props.classes[this.props.type]}
message={this.props.message}
/>
</Snackbar>
)
}
}
export default withStyles(Notification.styles)(Notification)

View File

@@ -3,7 +3,7 @@ import { EventDispatcher } from '../../../events'
export interface DataSourceState { export interface DataSourceState {
connecting: boolean connecting: boolean
connected: boolean connected: boolean
error?: Error error?: string
} }
export class DataSourceStateMachine { export class DataSourceStateMachine {
@@ -25,7 +25,7 @@ export class DataSourceStateMachine {
public setError(error: Error) { public setError(error: Error) {
this.state = { this.state = {
error, error: error.message,
connected: false, connected: false,
connecting: false, connecting: false,
} }

View File

@@ -1,6 +1,7 @@
import * as Url from 'url'
import { Client, connect as mqttConnect } from 'mqtt' import { Client, connect as mqttConnect } from 'mqtt'
import { DataSource, DataSourceStateMachine } from './' import { DataSource, DataSourceStateMachine } from './'
import * as Url from 'url'
export interface MqttOptions { export interface MqttOptions {
url: string url: string
@@ -36,6 +37,8 @@ export class MqttSource implements DataSource<MqttOptions> {
const client = mqttConnect(url, { const client = mqttConnect(url, {
resubscribe: false, resubscribe: false,
rejectUnauthorized: !options.certValidation, rejectUnauthorized: !options.certValidation,
username: options.username,
password: options.password,
}) })
this.client = client this.client = client