Add support for client certificates
This commit is contained in:
parent
d9b63dd0ef
commit
937560e859
File diff suppressed because one or more lines are too long
@ -96,7 +96,7 @@ gulp.task('dev', ['html', 'css', 'fonts', 'config', 'gzip:dev', 'bindata:dev'],
|
|||||||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(3000, 'localhost', function (err) {
|
app.listen(3000, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return;
|
return;
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autolinker": "^0.22.0",
|
"autolinker": "^0.22.0",
|
||||||
"backo": "^1.1.0",
|
"backo": "^1.1.0",
|
||||||
|
"base64-arraybuffer": "^0.1.5",
|
||||||
"eventemitter2": "^0.4.14",
|
"eventemitter2": "^0.4.14",
|
||||||
"history": "^1.17.0",
|
"history": "^1.17.0",
|
||||||
"immutable": "^3.7.6",
|
"immutable": "^3.7.6",
|
||||||
|
@ -9,6 +9,15 @@ body {
|
|||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
font: 16px Droid Sans Mono, monospace;
|
font: 16px Droid Sans Mono, monospace;
|
||||||
border: none;
|
border: none;
|
||||||
@ -134,14 +143,13 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connect .navicon {
|
.connect .navicon, .settings .navicon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connect-form h1 {
|
.connect-form h1 {
|
||||||
font: 32px Montserrat, sans-serif;
|
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -406,6 +414,58 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||||||
background: #DDD;
|
background: #DDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings p {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings h1 {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings h2 {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings button {
|
||||||
|
margin: 5px;
|
||||||
|
color: #FFF;
|
||||||
|
background: #6BB758;
|
||||||
|
padding: 10px 20px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings button:hover {
|
||||||
|
background: #7BBF6A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings button:active {
|
||||||
|
background: #6BB758;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings .error {
|
||||||
|
margin: 10px;
|
||||||
|
color: #F6546A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-file {
|
||||||
|
color: #FFF;
|
||||||
|
background: #222 !important;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px;
|
||||||
|
width: 200px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.tablist {
|
.tablist {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<title>Dispatch</title>
|
<title>Dispatch</title>
|
||||||
|
|
||||||
<link href="//fonts.googleapis.com/css?family=Montserrat|Droid+Sans+Mono" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono|Montserrat:400,700" rel="stylesheet">
|
||||||
<link href="/bundle.css" rel="stylesheet">
|
<link href="/bundle.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -18,8 +18,13 @@ export const PART = 'PART';
|
|||||||
export const SEARCH_MESSAGES = 'SEARCH_MESSAGES';
|
export const SEARCH_MESSAGES = 'SEARCH_MESSAGES';
|
||||||
export const SELECT_TAB = 'SELECT_TAB';
|
export const SELECT_TAB = 'SELECT_TAB';
|
||||||
export const SEND_MESSAGE = 'SEND_MESSAGE';
|
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_ENVIRONMENT = 'SET_ENVIRONMENT';
|
||||||
|
export const SET_KEY = 'SET_KEY';
|
||||||
export const SET_NICK = 'SET_NICK';
|
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_CHANNELS = 'SOCKET_CHANNELS';
|
||||||
export const SOCKET_JOIN = 'SOCKET_JOIN';
|
export const SOCKET_JOIN = 'SOCKET_JOIN';
|
||||||
export const SOCKET_MESSAGE = 'SOCKET_MESSAGE';
|
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 TAB_HISTORY_POP = 'TAB_HISTORY_POP';
|
||||||
export const TOGGLE_MENU = 'TOGGLE_MENU';
|
export const TOGGLE_MENU = 'TOGGLE_MENU';
|
||||||
export const TOGGLE_SEARCH = 'TOGGLE_SEARCH';
|
export const TOGGLE_SEARCH = 'TOGGLE_SEARCH';
|
||||||
|
export const UPLOAD_CERT = 'UPLOAD_CERT';
|
||||||
export const WHOIS = 'WHOIS';
|
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 privateChats from './privateChats';
|
||||||
import search from './search';
|
import search from './search';
|
||||||
import servers from './servers';
|
import servers from './servers';
|
||||||
|
import settings from './settings';
|
||||||
import showMenu from './showMenu';
|
import showMenu from './showMenu';
|
||||||
import tab from './tab';
|
import tab from './tab';
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ export default combineReducers({
|
|||||||
privateChats,
|
privateChats,
|
||||||
search,
|
search,
|
||||||
servers,
|
servers,
|
||||||
|
settings,
|
||||||
showMenu,
|
showMenu,
|
||||||
tab
|
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 App from './containers/App';
|
||||||
import Connect from './containers/Connect';
|
import Connect from './containers/Connect';
|
||||||
import Chat from './containers/Chat';
|
import Chat from './containers/Chat';
|
||||||
import Settings from './components/Settings';
|
import Settings from './containers/Settings';
|
||||||
|
|
||||||
export default function createRoutes() {
|
export default function createRoutes() {
|
||||||
return (
|
return (
|
||||||
@ -13,7 +13,7 @@ export default function createRoutes() {
|
|||||||
<Route path="/:server" component={Chat} />
|
<Route path="/:server" component={Chat} />
|
||||||
<Route path="/:server/:channel" component={Chat} />
|
<Route path="/:server/:channel" component={Chat} />
|
||||||
<Route path="/:server/pm/:user" component={Chat} />
|
<Route path="/:server/pm/:user" component={Chat} />
|
||||||
<IndexRoute component={Settings} />
|
<IndexRoute component={null} />
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
18
irc/conn.go
18
irc/conn.go
@ -49,20 +49,24 @@ func (c *Client) connect() error {
|
|||||||
|
|
||||||
if c.TLS {
|
if c.TLS {
|
||||||
if c.TLSConfig == nil {
|
if c.TLSConfig == nil {
|
||||||
c.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
c.TLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn, err := tls.DialWithDialer(c.dialer, "tcp", c.Server, c.TLSConfig); err != nil {
|
conn, err := tls.DialWithDialer(c.dialer, "tcp", c.Server, c.TLSConfig)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
c.conn = conn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.conn = conn
|
||||||
} else {
|
} else {
|
||||||
if conn, err := c.dialer.Dial("tcp", c.Server); err != nil {
|
conn, err := c.dialer.Dial("tcp", c.Server)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
c.conn = conn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.conn = conn
|
||||||
}
|
}
|
||||||
|
|
||||||
c.connected = true
|
c.connected = true
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/irc"
|
"github.com/khlieng/dispatch/irc"
|
||||||
"github.com/khlieng/dispatch/storage"
|
"github.com/khlieng/dispatch/storage"
|
||||||
)
|
)
|
||||||
@ -20,6 +22,12 @@ func reconnectIRC() {
|
|||||||
i.Password = server.Password
|
i.Password = server.Password
|
||||||
i.Realname = server.Realname
|
i.Realname = server.Realname
|
||||||
|
|
||||||
|
if user.Certificate != nil {
|
||||||
|
i.TLSConfig = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{*user.Certificate},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
i.Connect(server.Address)
|
i.Connect(server.Address)
|
||||||
session.setIRC(i.Host, i)
|
session.setIRC(i.Host, i)
|
||||||
go newIRCHandler(i, session).run()
|
go newIRCHandler(i, session).run()
|
||||||
|
@ -127,6 +127,11 @@ type SearchResult struct {
|
|||||||
Results []storage.Message `json:"results"`
|
Results []storage.Message `json:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientCert struct {
|
||||||
|
Cert []byte `json:"cert"`
|
||||||
|
Key []byte `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
@ -104,6 +105,12 @@ func (h *wsHandler) connect(b []byte) {
|
|||||||
i.Password = data.Password
|
i.Password = data.Password
|
||||||
i.Realname = data.Realname
|
i.Realname = data.Realname
|
||||||
|
|
||||||
|
if h.session.user.Certificate != nil {
|
||||||
|
i.TLSConfig = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{*h.session.user.Certificate},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if idx := strings.Index(data.Server, ":"); idx < 0 {
|
if idx := strings.Index(data.Server, ":"); idx < 0 {
|
||||||
h.session.setIRC(data.Server, i)
|
h.session.setIRC(data.Server, i)
|
||||||
} else {
|
} else {
|
||||||
@ -231,6 +238,19 @@ func (h *wsHandler) search(b []byte) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *wsHandler) cert(b []byte) {
|
||||||
|
var data ClientCert
|
||||||
|
json.Unmarshal(b, &data)
|
||||||
|
|
||||||
|
err := h.session.user.SetCertificate(data.Cert, data.Key)
|
||||||
|
if err != nil {
|
||||||
|
h.session.sendJSON("cert_fail", Error{Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.session.sendJSON("cert_success", nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *wsHandler) initHandlers() {
|
func (h *wsHandler) initHandlers() {
|
||||||
h.handlers = map[string]func([]byte){
|
h.handlers = map[string]func([]byte){
|
||||||
"connect": h.connect,
|
"connect": h.connect,
|
||||||
@ -244,5 +264,6 @@ func (h *wsHandler) initHandlers() {
|
|||||||
"whois": h.whois,
|
"whois": h.whois,
|
||||||
"away": h.away,
|
"away": h.away,
|
||||||
"search": h.search,
|
"search": h.search,
|
||||||
|
"cert": h.cert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,22 @@ func (d directory) Index(userID string) string {
|
|||||||
return filepath.Join(d.Logs(), userID+".idx")
|
return filepath.Join(d.Logs(), userID+".idx")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d directory) Users() string {
|
||||||
|
return filepath.Join(d.Root(), "users")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d directory) User(userID string) string {
|
||||||
|
return filepath.Join(d.Users(), userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d directory) Certificate(userID string) string {
|
||||||
|
return filepath.Join(d.User(userID), "cert.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d directory) Key(userID string) string {
|
||||||
|
return filepath.Join(d.User(userID), "key.pem")
|
||||||
|
}
|
||||||
|
|
||||||
func (d directory) Config() string {
|
func (d directory) Config() string {
|
||||||
return filepath.Join(d.Root(), "config.toml")
|
return filepath.Join(d.Root(), "config.toml")
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/blevesearch/bleve"
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/blevesearch/bleve"
|
||||||
@ -39,10 +41,12 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
UUID string
|
UUID string
|
||||||
|
Certificate *tls.Certificate `json:"-"`
|
||||||
|
|
||||||
messageLog *bolt.DB
|
messageLog *bolt.DB
|
||||||
messageIndex bleve.Index
|
messageIndex bleve.Index
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(uuid string) *User {
|
func NewUser(uuid string) *User {
|
||||||
@ -73,6 +77,7 @@ func LoadUsers() []*User {
|
|||||||
b.ForEach(func(k, v []byte) error {
|
b.ForEach(func(k, v []byte) error {
|
||||||
user := User{UUID: string(k)}
|
user := User{UUID: string(k)}
|
||||||
user.openMessageLog()
|
user.openMessageLog()
|
||||||
|
user.loadCertificate()
|
||||||
|
|
||||||
users = append(users, &user)
|
users = append(users, &user)
|
||||||
|
|
||||||
|
60
storage/user_cert.go
Normal file
60
storage/user_cert.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidCert = errors.New("Invalid certificate")
|
||||||
|
ErrCouldNotSaveCert = errors.New("Could not save certificate")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *User) SetCertificate(certPEM, keyPEM []byte) error {
|
||||||
|
cert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidCert
|
||||||
|
}
|
||||||
|
u.lock.Lock()
|
||||||
|
u.Certificate = &cert
|
||||||
|
u.lock.Unlock()
|
||||||
|
|
||||||
|
err = os.MkdirAll(Path.User(u.UUID), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return ErrCouldNotSaveCert
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(Path.Certificate(u.UUID), certPEM, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return ErrCouldNotSaveCert
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(Path.Key(u.UUID), keyPEM, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return ErrCouldNotSaveCert
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) loadCertificate() error {
|
||||||
|
certPEM, err := ioutil.ReadFile(Path.Certificate(u.UUID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPEM, err := ioutil.ReadFile(Path.Key(u.UUID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Certificate = &cert
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user