You can now generate keys from the advanced settings interface

This commit is contained in:
Vertmo 2018-10-16 00:22:56 +02:00 committed by Cassie Tarakajian
parent db71a2b7c0
commit 4d4f636623
5 changed files with 64 additions and 60 deletions

View file

@ -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';

View file

@ -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)));
}

View file

@ -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;

View file

@ -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);

View file

@ -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;
} }