Add support for client certificates
This commit is contained in:
parent
d9b63dd0ef
commit
937560e859
20 changed files with 376 additions and 39 deletions
|
@ -18,8 +18,13 @@ export const PART = 'PART';
|
|||
export const SEARCH_MESSAGES = 'SEARCH_MESSAGES';
|
||||
export const SELECT_TAB = 'SELECT_TAB';
|
||||
export const SEND_MESSAGE = 'SEND_MESSAGE';
|
||||
export const SET_CERT = 'SET_CERT';
|
||||
export const SET_CERT_ERROR = 'SET_CERT_ERROR';
|
||||
export const SET_ENVIRONMENT = 'SET_ENVIRONMENT';
|
||||
export const SET_KEY = 'SET_KEY';
|
||||
export const SET_NICK = 'SET_NICK';
|
||||
export const SOCKET_CERT_FAIL = 'SOCKET_CERT_FAIL';
|
||||
export const SOCKET_CERT_SUCCESS = 'SOCKET_CERT_SUCCESS';
|
||||
export const SOCKET_CHANNELS = 'SOCKET_CHANNELS';
|
||||
export const SOCKET_JOIN = 'SOCKET_JOIN';
|
||||
export const SOCKET_MESSAGE = 'SOCKET_MESSAGE';
|
||||
|
@ -34,4 +39,5 @@ export const SOCKET_USERS = 'SOCKET_USERS';
|
|||
export const TAB_HISTORY_POP = 'TAB_HISTORY_POP';
|
||||
export const TOGGLE_MENU = 'TOGGLE_MENU';
|
||||
export const TOGGLE_SEARCH = 'TOGGLE_SEARCH';
|
||||
export const UPLOAD_CERT = 'UPLOAD_CERT';
|
||||
export const WHOIS = 'WHOIS';
|
||||
|
|
45
client/src/js/actions/settings.js
Normal file
45
client/src/js/actions/settings.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import base64 from 'base64-arraybuffer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
export function setCertError(message) {
|
||||
return {
|
||||
type: actions.SET_CERT_ERROR,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadCert() {
|
||||
return (dispatch, getState) => {
|
||||
const { settings } = getState();
|
||||
if (settings.has('cert') && settings.has('key')) {
|
||||
dispatch({
|
||||
type: actions.UPLOAD_CERT,
|
||||
socket: {
|
||||
type: 'cert',
|
||||
data: {
|
||||
cert: settings.get('cert'),
|
||||
key: settings.get('key')
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatch(setCertError('Missing certificate or key'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setCert(fileName, cert) {
|
||||
return {
|
||||
type: actions.SET_CERT,
|
||||
fileName,
|
||||
cert: base64.encode(cert)
|
||||
};
|
||||
}
|
||||
|
||||
export function setKey(fileName, key) {
|
||||
return {
|
||||
type: actions.SET_KEY,
|
||||
fileName,
|
||||
key: base64.encode(key)
|
||||
};
|
||||
}
|
30
client/src/js/components/FileInput.js
Normal file
30
client/src/js/components/FileInput.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import React, { Component } from 'react';
|
||||
import pure from 'pure-render-decorator';
|
||||
|
||||
@pure
|
||||
export default class FileInput extends Component {
|
||||
componentWillMount() {
|
||||
this.input = window.document.createElement('input');
|
||||
this.input.setAttribute('type', 'file');
|
||||
|
||||
this.input.addEventListener('change', e => {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
console.log(reader.result.byteLength);
|
||||
this.props.onChange(file.name, reader.result);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
handleClick = () => this.input.click();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<button className="input-file" onClick={this.handleClick}>{this.props.name}</button>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import pure from 'pure-render-decorator';
|
||||
import Navicon from './Navicon';
|
||||
|
||||
@pure
|
||||
export default class Settings extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Navicon />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
47
client/src/js/containers/Settings.js
Normal file
47
client/src/js/containers/Settings.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import pure from 'pure-render-decorator';
|
||||
import Navicon from '../components/Navicon';
|
||||
import FileInput from '../components/FileInput';
|
||||
import { setCert, setKey, uploadCert } from '../actions/settings';
|
||||
|
||||
@pure
|
||||
class Settings extends Component {
|
||||
handleCertChange = (name, data) => this.props.dispatch(setCert(name, data));
|
||||
handleKeyChange = (name, data) => this.props.dispatch(setKey(name, data));
|
||||
handleCertUpload = () => this.props.dispatch(uploadCert());
|
||||
|
||||
render() {
|
||||
const { settings } = this.props;
|
||||
const status = settings.get('uploadingCert') ? 'Uploading...' : 'Upload';
|
||||
const error = settings.get('certError');
|
||||
|
||||
return (
|
||||
<div className="settings">
|
||||
<Navicon />
|
||||
<h1>Settings</h1>
|
||||
<h2>Client Certificate</h2>
|
||||
<div>
|
||||
<p>Certificate</p>
|
||||
<FileInput
|
||||
name={settings.get('certFile') || 'Select Certificate'}
|
||||
onChange={this.handleCertChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p>Private Key</p>
|
||||
<FileInput
|
||||
name={settings.get('keyFile') || 'Select Key'}
|
||||
onChange={this.handleKeyChange}
|
||||
/>
|
||||
</div>
|
||||
<button onClick={this.handleCertUpload}>{status}</button>
|
||||
{ error ? <p className="error">{error}</p> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
settings: state.settings
|
||||
}))(Settings);
|
|
@ -7,6 +7,7 @@ import messages from './messages';
|
|||
import privateChats from './privateChats';
|
||||
import search from './search';
|
||||
import servers from './servers';
|
||||
import settings from './settings';
|
||||
import showMenu from './showMenu';
|
||||
import tab from './tab';
|
||||
|
||||
|
@ -19,6 +20,7 @@ export default combineReducers({
|
|||
privateChats,
|
||||
search,
|
||||
servers,
|
||||
settings,
|
||||
showMenu,
|
||||
tab
|
||||
});
|
||||
|
|
41
client/src/js/reducers/settings.js
Normal file
41
client/src/js/reducers/settings.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Map } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
export default createReducer(Map(), {
|
||||
[actions.UPLOAD_CERT](state) {
|
||||
return state.set('uploadingCert', true);
|
||||
},
|
||||
|
||||
[actions.SOCKET_CERT_SUCCESS]() {
|
||||
return Map({ uploadingCert: false });
|
||||
},
|
||||
|
||||
[actions.SOCKET_CERT_FAIL](state, action) {
|
||||
return state.merge({
|
||||
uploadingCert: false,
|
||||
certError: action.message
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SET_CERT_ERROR](state, action) {
|
||||
return state.merge({
|
||||
uploadingCert: false,
|
||||
certError: action.message
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SET_CERT](state, action) {
|
||||
return state.merge({
|
||||
certFile: action.fileName,
|
||||
cert: action.cert
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SET_KEY](state, action) {
|
||||
return state.merge({
|
||||
keyFile: action.fileName,
|
||||
key: action.key
|
||||
});
|
||||
}
|
||||
});
|
|
@ -3,7 +3,7 @@ import { Route, IndexRoute } from 'react-router';
|
|||
import App from './containers/App';
|
||||
import Connect from './containers/Connect';
|
||||
import Chat from './containers/Chat';
|
||||
import Settings from './components/Settings';
|
||||
import Settings from './containers/Settings';
|
||||
|
||||
export default function createRoutes() {
|
||||
return (
|
||||
|
@ -13,7 +13,7 @@ export default function createRoutes() {
|
|||
<Route path="/:server" component={Chat} />
|
||||
<Route path="/:server/:channel" component={Chat} />
|
||||
<Route path="/:server/pm/:user" component={Chat} />
|
||||
<IndexRoute component={Settings} />
|
||||
<IndexRoute component={null} />
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue