You can now generate keys from the advanced settings interface
This commit is contained in:
parent
db71a2b7c0
commit
4d4f636623
5 changed files with 64 additions and 60 deletions
|
@ -19,6 +19,9 @@ export const AUTH_ERROR = 'AUTH_ERROR';
|
||||||
|
|
||||||
export const SETTINGS_UPDATED = 'SETTINGS_UPDATED';
|
export const SETTINGS_UPDATED = 'SETTINGS_UPDATED';
|
||||||
|
|
||||||
|
export const ADDED_API_KEY = 'ADDED_API_KEY';
|
||||||
|
export const REMOVED_API_KEY = 'REMOVED_API_KEY';
|
||||||
|
|
||||||
export const SET_PROJECT_NAME = 'SET_PROJECT_NAME';
|
export const SET_PROJECT_NAME = 'SET_PROJECT_NAME';
|
||||||
|
|
||||||
export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
|
export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import crypto from 'crypto';
|
||||||
import * as ActionTypes from '../../constants';
|
import * as ActionTypes from '../../constants';
|
||||||
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
|
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
|
||||||
import { showToast, setToastText } from '../IDE/actions/toast';
|
import { showToast, setToastText } from '../IDE/actions/toast';
|
||||||
|
@ -218,3 +219,33 @@ export function updateSettings(formValues) {
|
||||||
})
|
})
|
||||||
.catch(response => Promise.reject(new Error(response.data.error)));
|
.catch(response => Promise.reject(new Error(response.data.error)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addApiKey(label) {
|
||||||
|
return ((dispatch) => {
|
||||||
|
crypto.randomBytes(20, (err, buf) => {
|
||||||
|
const key = buf.toString('hex');
|
||||||
|
const hashedKey = Buffer.from(key).toString('base64');
|
||||||
|
axios.put(`${ROOT_URL}/account/api-keys`, { label, hashedKey }, { withCredentials: true })
|
||||||
|
.then((response) => {
|
||||||
|
window.alert(`Here is your key :\n${key}\nNote it somewhere, you won't be able to see it later !`);
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.ADDED_API_KEY,
|
||||||
|
user: response.data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(response => Promise.reject(new Error(response.data.error)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeApiKey(keyId) {
|
||||||
|
return dispatch =>
|
||||||
|
axios.delete(`${ROOT_URL}/account/api-keys/${keyId}`, { withCredentials: true })
|
||||||
|
.then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.REMOVED_API_KEY,
|
||||||
|
user: response.data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(response => Promise.reject(new Error(response.data.error)));
|
||||||
|
}
|
||||||
|
|
|
@ -10,26 +10,25 @@ class APIKeyForm extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
addKey(event) {
|
addKey(event) {
|
||||||
// TODO
|
|
||||||
console.log('addKey');
|
|
||||||
this.props.updateSettings();
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
document.getElementById('addKeyForm').reset();
|
||||||
|
this.props.addApiKey(this.state.keyLabel);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeKey(k) {
|
removeKey(keyId) {
|
||||||
// TODO
|
this.props.removeApiKey(keyId);
|
||||||
console.log(k);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="form__label">Key label</h2>
|
<h2 className="form__label">Key label</h2>
|
||||||
<form className="form" onSubmit={this.addKey}>
|
<form id="addKeyForm" className="form" onSubmit={this.addKey}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form__input"
|
className="form__input"
|
||||||
|
placeholder="A name you will be able to recognize"
|
||||||
id="keyLabel"
|
id="keyLabel"
|
||||||
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
|
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
|
||||||
/><br />
|
/><br />
|
||||||
|
@ -41,21 +40,11 @@ class APIKeyForm extends React.Component {
|
||||||
</form>
|
</form>
|
||||||
<table className="form__table">
|
<table className="form__table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{[{
|
{this.props.apiKeys && this.props.apiKeys.map(v => (
|
||||||
id: 1,
|
|
||||||
label: 'MyFirstAPI',
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUsedAt: new Date()
|
|
||||||
}, {
|
|
||||||
id: 2,
|
|
||||||
label: 'MyOtherAPI',
|
|
||||||
createdAt: new Date(),
|
|
||||||
lastUsedAt: new Date()
|
|
||||||
}].map(v => (
|
|
||||||
<tr key={v.id}>
|
<tr key={v.id}>
|
||||||
<td><b>{v.label}</b><br />Created on: {v.createdAt.toLocaleDateString()} {v.createdAt.toLocaleTimeString()}</td>
|
<td><b>{v.label}</b><br />Created on: {v.createdAt}</td>
|
||||||
<td>Last used on:<br /> {v.lastUsedAt.toLocaleDateString()} {v.lastUsedAt.toLocaleTimeString()}</td>
|
<td>Last used on:<br /> {v.lastUsedAt}</td>
|
||||||
<td><button className="form__button-remove" onClick={() => this.removeKey(v)}>Delete</button></td>
|
<td><button className="form__button-remove" onClick={() => this.removeKey(v.id)}>Delete</button></td>
|
||||||
</tr>))}
|
</tr>))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -65,7 +54,14 @@ class APIKeyForm extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
APIKeyForm.propTypes = {
|
APIKeyForm.propTypes = {
|
||||||
updateSettings: PropTypes.func.isRequired,
|
addApiKey: PropTypes.func.isRequired,
|
||||||
|
removeApiKey: PropTypes.func.isRequired,
|
||||||
|
apiKeys: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
id: PropTypes.object.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
createdAt: PropTypes.object.isRequired,
|
||||||
|
lastUsedAt: PropTypes.object.isRequired,
|
||||||
|
})).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default APIKeyForm;
|
export default APIKeyForm;
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { reduxForm } from 'redux-form';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
import InlineSVG from 'react-inlinesvg';
|
||||||
import axios from 'axios';
|
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { updateSettings, initiateVerification } from '../actions';
|
import { addApiKey, removeApiKey } from '../actions';
|
||||||
import { validateSettings } from '../../../utils/reduxFormUtils';
|
|
||||||
import APIKeyForm from '../components/APIKeyForm';
|
import APIKeyForm from '../components/APIKeyForm';
|
||||||
|
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
const exitUrl = require('../../../images/exit.svg');
|
||||||
const logoUrl = require('../../../images/p5js-logo.svg');
|
const logoUrl = require('../../../images/p5js-logo.svg');
|
||||||
|
|
||||||
// TODO tmp
|
class AdvancedSettingsView extends React.Component {
|
||||||
const ident = () => {};
|
|
||||||
|
|
||||||
class AccountView extends React.Component {
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.closeAccountPage = this.closeAccountPage.bind(this);
|
this.closeAccountPage = this.closeAccountPage.bind(this);
|
||||||
|
@ -51,9 +46,7 @@ class AccountView extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className="form-container__content">
|
<div className="form-container__content">
|
||||||
<h2 className="form-container__title">Advanced Settings</h2>
|
<h2 className="form-container__title">Advanced Settings</h2>
|
||||||
<APIKeyForm
|
<APIKeyForm {...this.props} />
|
||||||
updateSettings={ident}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -64,41 +57,18 @@ function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
initialValues: state.user, // <- initialValues for reduxForm
|
initialValues: state.user, // <- initialValues for reduxForm
|
||||||
user: state.user,
|
user: state.user,
|
||||||
|
apiKeys: state.user.apiKeys,
|
||||||
previousPath: state.ide.previousPath,
|
previousPath: state.ide.previousPath,
|
||||||
theme: state.preferences.theme
|
theme: state.preferences.theme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators({ updateSettings, initiateVerification }, dispatch);
|
return bindActionCreators({ addApiKey, removeApiKey }, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
function asyncValidate(formProps, dispatch, props) {
|
AdvancedSettingsView.propTypes = {
|
||||||
const fieldToValidate = props.form._active;
|
|
||||||
if (fieldToValidate) {
|
|
||||||
const queryParams = {};
|
|
||||||
queryParams[fieldToValidate] = formProps[fieldToValidate];
|
|
||||||
queryParams.check_type = fieldToValidate;
|
|
||||||
return axios.get('/api/signup/duplicate_check', { params: queryParams })
|
|
||||||
.then((response) => {
|
|
||||||
if (response.data.exists) {
|
|
||||||
const error = {};
|
|
||||||
error[fieldToValidate] = response.data.message;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.resolve(true).then(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountView.propTypes = {
|
|
||||||
theme: PropTypes.string.isRequired
|
theme: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reduxForm({
|
export default connect(mapStateToProps, mapDispatchToProps)(AdvancedSettingsView);
|
||||||
form: 'updateAllSettings',
|
|
||||||
fields: ['username', 'email', 'currentPassword', 'newPassword'],
|
|
||||||
validate: validateSettings,
|
|
||||||
asyncValidate,
|
|
||||||
asyncBlurFields: ['username', 'email', 'currentPassword']
|
|
||||||
}, mapStateToProps, mapDispatchToProps)(AccountView);
|
|
||||||
|
|
|
@ -31,6 +31,10 @@ const user = (state = { authenticated: false }, action) => {
|
||||||
return Object.assign({}, state, { emailVerificationTokenState: 'invalid' });
|
return Object.assign({}, state, { emailVerificationTokenState: 'invalid' });
|
||||||
case ActionTypes.SETTINGS_UPDATED:
|
case ActionTypes.SETTINGS_UPDATED:
|
||||||
return { ...state, ...action.user };
|
return { ...state, ...action.user };
|
||||||
|
case ActionTypes.REMOVED_API_KEY:
|
||||||
|
return { ...state, ...action.user };
|
||||||
|
case ActionTypes.ADDED_API_KEY:
|
||||||
|
return { ...state, ...action.user };
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue