diff --git a/app/src/actions/ConnectionManager.ts b/app/src/actions/ConnectionManager.ts index b1b7951..27c9a74 100644 --- a/app/src/actions/ConnectionManager.ts +++ b/app/src/actions/ConnectionManager.ts @@ -42,7 +42,8 @@ export const loadConnectionSettings = () => async (dispatch: Dispatch, getS } } -export const selectCertificate = (connectionId: string) => async ( +export type CertificateTypes = 'selfSignedCertificate' | 'clientCertificate' | 'clientKey' +export const selectCertificate = (type: CertificateTypes, connectionId: string) => async ( dispatch: Dispatch, getState: () => AppState ) => { @@ -50,7 +51,7 @@ export const selectCertificate = (connectionId: string) => async ( const certificate = await openCertificate() dispatch( updateConnection(connectionId, { - selfSignedCertificate: certificate, + [type]: certificate, }) ) } catch (error) { @@ -147,6 +148,10 @@ export const toggleAdvancedSettings = (): Action => ({ type: ActionTypes.CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS, }) +export const toggleCertificateSettings = (): Action => ({ + type: ActionTypes.CONNECTION_MANAGER_TOGGLE_CERTIFICATE_SETTINGS, +}) + export const deleteConnection = (connectionId: string) => (dispatch: Dispatch, getState: () => AppState) => { const connectionIds = Object.keys(getState().connectionManager.connections) const connectionIdLocation = connectionIds.indexOf(connectionId) diff --git a/app/src/components/ConnectionSetup/AdvancedConnectionSettings.tsx b/app/src/components/ConnectionSetup/AdvancedConnectionSettings.tsx index cd451a1..550f6bb 100644 --- a/app/src/components/ConnectionSetup/AdvancedConnectionSettings.tsx +++ b/app/src/components/ConnectionSetup/AdvancedConnectionSettings.tsx @@ -44,29 +44,6 @@ class ConnectionSettings extends React.Component { }) } - private renderCertificateInfo() { - if (!this.props.connection.selfSignedCertificate) { - return null - } - - return ( - - - - - {this.props.connection.selfSignedCertificate.name} - - - - ) - } - - private clearCertificate = () => { - this.props.managerActions.updateConnection(this.props.connection.id, { - selfSignedCertificate: undefined, - }) - } - private renderSubscriptions() { const connection = this.props.connection return connection.subscriptions.map(subscription => ( @@ -122,16 +99,15 @@ class ConnectionSettings extends React.Component {
- + - {this.renderCertificateInfo()}
diff --git a/app/src/components/ConnectionSetup/CertificateFileSelection.tsx b/app/src/components/ConnectionSetup/CertificateFileSelection.tsx new file mode 100644 index 0000000..9addfa6 --- /dev/null +++ b/app/src/components/ConnectionSetup/CertificateFileSelection.tsx @@ -0,0 +1,87 @@ +import * as React from 'react' +import Add from '@material-ui/icons/Add' +import ClearAdornment from '../helper/ClearAdornment' +import Delete from '@material-ui/icons/Delete' +import Lock from '@material-ui/icons/Lock' +import { bindActionCreators } from 'redux' +import { Button, Theme, Tooltip, Typography } from '@material-ui/core' +import { CertificateParameters, ConnectionOptions } from '../../model/ConnectionOptions' +import { CertificateTypes } from '../../actions/ConnectionManager' +import { connect } from 'react-redux' +import { connectionManagerActions } from '../../actions' +import { withStyles } from '@material-ui/styles' + +function CertificateFileSelection(props: { + certificateType: CertificateTypes + title: string + certificate?: CertificateParameters + classes: any + actions: { + connectionManager: typeof connectionManagerActions + } + connection: ConnectionOptions +}) { + const clearCertificate = React.useCallback(() => { + props.actions.connectionManager.updateConnection(props.connection.id, { + [props.certificateType]: undefined, + }) + }, [props.connection, props.certificateType]) + + return ( + + + + + + + ) +} + +function ClearCertificate(props: { classes: any; certificate?: CertificateParameters; action: () => void }) { + if (!props.certificate) { + return null + } + + return ( + + + + {props.certificate.name} + + + ) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: { + connectionManager: bindActionCreators(connectionManagerActions, dispatch), + }, + } +} + +const styles = (theme: Theme) => ({ + certificateName: { + width: '100%', + height: 'calc(1em + 4px)', + overflow: 'hidden' as 'hidden', + whiteSpace: 'nowrap' as 'nowrap', + textOverflow: 'ellipsis' as 'ellipsis', + color: theme.palette.text.hint, + }, + button: { + marginTop: theme.spacing(3), + marginRight: theme.spacing(2), + }, +}) + +export default connect( + undefined, + mapDispatchToProps +)(withStyles(styles)(CertificateFileSelection)) diff --git a/app/src/components/ConnectionSetup/Certificates.tsx b/app/src/components/ConnectionSetup/Certificates.tsx new file mode 100644 index 0000000..149e207 --- /dev/null +++ b/app/src/components/ConnectionSetup/Certificates.tsx @@ -0,0 +1,107 @@ +import * as React from 'react' +import CertificateFileSelection from './CertificateFileSelection' +import Undo from '@material-ui/icons/Undo' +import { bindActionCreators } from 'redux' +import { Button, Grid } from '@material-ui/core' +import { connect } from 'react-redux' +import { connectionManagerActions } from '../../actions' +import { ConnectionOptions } from '../../model/ConnectionOptions' +import { Theme, withStyles } from '@material-ui/core/styles' + +interface Props { + connection: ConnectionOptions + classes: any + managerActions: typeof connectionManagerActions +} + +interface State { + subscription: string +} + +class Certificates extends React.Component { + constructor(props: any) { + super(props) + this.state = { subscription: '' } + } + + private handleChange = (name: string) => (event: any) => { + this.props.managerActions.updateConnection(this.props.connection.id, { + [name]: event.target.value, + }) + } + + private renderCertificateInfo() { + if (!this.props.connection.selfSignedCertificate) { + return null + } + + return + } + + public render() { + const { classes } = this.props + return ( +
+
+ + + + + + + + + + + + +
+
+ ) + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + managerActions: bindActionCreators(connectionManagerActions, dispatch), + } +} + +const styles = (theme: Theme) => ({ + fullWidth: { + width: '100%', + }, + gridPadding: { + padding: '0 12px !important', + }, + button: { + marginTop: theme.spacing(3), + marginRight: theme.spacing(2), + }, +}) + +export default connect( + undefined, + mapDispatchToProps +)(withStyles(styles)(Certificates)) diff --git a/app/src/components/ConnectionSetup/ConnectionSetup.tsx b/app/src/components/ConnectionSetup/ConnectionSetup.tsx index 77b88d1..cd1090d 100644 --- a/app/src/components/ConnectionSetup/ConnectionSetup.tsx +++ b/app/src/components/ConnectionSetup/ConnectionSetup.tsx @@ -9,6 +9,7 @@ import { ConnectionOptions, toMqttConnection } from '../../model/ConnectionOptio import { Theme, withStyles } from '@material-ui/core/styles' import { Modal, Paper, Toolbar, Typography, Collapse } from '@material-ui/core' import AdvancedConnectionSettings from './AdvancedConnectionSettings' +import Certificates from './Certificates' interface Props { actions: any @@ -16,6 +17,7 @@ interface Props { connection?: ConnectionOptions visible: boolean showAdvancedSettings: boolean + showCertificateSettings: boolean } class ConnectionSetup extends React.Component { @@ -24,19 +26,22 @@ class ConnectionSetup extends React.Component { } private renderSettings() { - const { connection, showAdvancedSettings } = this.props + const { connection, showAdvancedSettings, showCertificateSettings } = this.props if (!connection) { return null } return (
- + - + + + +
) } @@ -115,6 +120,7 @@ const mapStateToProps = (state: AppState) => { return { visible: !state.connection.connected, showAdvancedSettings: state.connectionManager.showAdvancedSettings, + showCertificateSettings: state.connectionManager.showCertificateSettings, connection: state.connectionManager.selected ? state.connectionManager.connections[state.connectionManager.selected] : undefined, diff --git a/app/src/model/ConnectionOptions.ts b/app/src/model/ConnectionOptions.ts index fe98ffe..46beba7 100644 --- a/app/src/model/ConnectionOptions.ts +++ b/app/src/model/ConnectionOptions.ts @@ -20,6 +20,8 @@ export interface ConnectionOptions { encryption: boolean certValidation: boolean selfSignedCertificate?: CertificateParameters + clientCertificate?: CertificateParameters + clientKey?: CertificateParameters clientId?: string subscriptions: Array } @@ -38,6 +40,8 @@ export function toMqttConnection(options: ConnectionOptions): MqttOptions | unde certValidation: options.certValidation, subscriptions: options.subscriptions, certificateAuthority: options.selfSignedCertificate ? options.selfSignedCertificate.data : undefined, + clientCertificate: options.clientCertificate ? options.clientCertificate.data : undefined, + clientKey: options.clientKey ? options.clientKey.data : undefined, } } diff --git a/app/src/reducers/ConnectionManager.ts b/app/src/reducers/ConnectionManager.ts index 10c93af..56de274 100644 --- a/app/src/reducers/ConnectionManager.ts +++ b/app/src/reducers/ConnectionManager.ts @@ -6,12 +6,14 @@ export interface ConnectionManagerState { connections: { [s: string]: ConnectionOptions } selected?: string showAdvancedSettings: boolean + showCertificateSettings: boolean } const initialState: ConnectionManagerState = { connections: {}, selected: undefined, showAdvancedSettings: false, + showCertificateSettings: false, } export type Action = @@ -21,6 +23,7 @@ export type Action = | AddConnection | DeleteConnection | ToggleAdvancedSettings + | ToggleCertificateSettings | DeleteSubscription | AddSubscription @@ -31,6 +34,7 @@ export enum ActionTypes { CONNECTION_MANAGER_ADD_CONNECTION = 'CONNECTION_MANAGER_ADD_CONNECTION', CONNECTION_MANAGER_DELETE_CONNECTION = 'CONNECTION_MANAGER_DELETE_CONNECTION', CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS = 'CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS', + CONNECTION_MANAGER_TOGGLE_CERTIFICATE_SETTINGS = 'CONNECTION_MANAGER_TOGGLE_CERTIFICATE_SETTINGS', CONNECTION_MANAGER_ADD_SUBSCRIPTION = 'CONNECTION_MANAGER_ADD_SUBSCRIPTION', CONNECTION_MANAGER_DELETE_SUBSCRIPTION = 'CONNECTION_MANAGER_DELETE_SUBSCRIPTION', } @@ -77,6 +81,10 @@ export interface ToggleAdvancedSettings { type: ActionTypes.CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS } +export interface ToggleCertificateSettings { + type: ActionTypes.CONNECTION_MANAGER_TOGGLE_CERTIFICATE_SETTINGS +} + export const connectionManagerReducer = createReducer(initialState, { CONNECTION_MANAGER_SET_CONNECTIONS: setConnections, CONNECTION_MANAGER_SELECT_CONNECTION: selectConnection, @@ -84,6 +92,7 @@ export const connectionManagerReducer = createReducer(initialState, { CONNECTION_MANAGER_ADD_CONNECTION: addConnection, CONNECTION_MANAGER_DELETE_CONNECTION: deleteConnection, CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS: toggleAdvancedSettings, + CONNECTION_MANAGER_TOGGLE_CERTIFICATE_SETTINGS: toggleCertificateSettings, CONNECTION_MANAGER_DELETE_SUBSCRIPTION: deleteSubscription, CONNECTION_MANAGER_ADD_SUBSCRIPTION: addSubscription, }) @@ -109,6 +118,16 @@ function toggleAdvancedSettings(state: ConnectionManagerState, action: ToggleAdv } } +function toggleCertificateSettings( + state: ConnectionManagerState, + action: ToggleCertificateSettings +): ConnectionManagerState { + return { + ...state, + showCertificateSettings: !state.showCertificateSettings, + } +} + function addConnection(state: ConnectionManagerState, action: AddConnection): ConnectionManagerState { return { ...state, diff --git a/backend/src/DataSource/MqttSource.ts b/backend/src/DataSource/MqttSource.ts index 91c77ca..a7a5ea7 100644 --- a/backend/src/DataSource/MqttSource.ts +++ b/backend/src/DataSource/MqttSource.ts @@ -14,6 +14,8 @@ export interface MqttOptions { clientId?: string subscriptions: Array certificateAuthority?: string + clientCertificate?: string + clientKey?: string } export class MqttSource implements DataSource { @@ -44,8 +46,10 @@ export class MqttSource implements DataSource { username: options.username, password: options.password, clientId: options.clientId, - servername: options.tls ? url.host : undefined, + servername: options.tls ? url.hostname : undefined, ca: options.certificateAuthority ? Buffer.from(options.certificateAuthority, 'base64') : undefined, + cert: options.clientCertificate ? Buffer.from(options.clientCertificate, 'base64') : undefined, + key: options.clientKey ? Buffer.from(options.clientKey, 'base64') : undefined, } as any) this.client = client diff --git a/backend/src/Model/spec/Edge.spec.ts b/backend/src/Model/spec/Edge.spec.ts index e94c049..dbb971c 100644 --- a/backend/src/Model/spec/Edge.spec.ts +++ b/backend/src/Model/spec/Edge.spec.ts @@ -1,8 +1,6 @@ -import 'mocha' - -import { Edge, TreeNode, TreeNodeFactory } from '../' - +import { Edge, TreeNodeFactory } from '../' import { expect } from 'chai' +import 'mocha' describe('Edge', () => { it('should contain a name', () => {