From 4d4f636623448338b79edf9549e582b0e9ecc352 Mon Sep 17 00:00:00 2001 From: Vertmo Date: Tue, 16 Oct 2018 00:22:56 +0200 Subject: [PATCH] You can now generate keys from the advanced settings interface --- client/constants.js | 3 ++ client/modules/User/actions.js | 31 +++++++++++++ client/modules/User/components/APIKeyForm.jsx | 40 ++++++++-------- .../User/pages/AdvancedSettingsView.jsx | 46 ++++--------------- client/modules/User/reducers.js | 4 ++ 5 files changed, 64 insertions(+), 60 deletions(-) diff --git a/client/constants.js b/client/constants.js index 9cb3daa4..ffe8ba61 100644 --- a/client/constants.js +++ b/client/constants.js @@ -19,6 +19,9 @@ export const AUTH_ERROR = 'AUTH_ERROR'; 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 PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS'; diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 91e5f72b..53fbb68e 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -1,5 +1,6 @@ import { browserHistory } from 'react-router'; import axios from 'axios'; +import crypto from 'crypto'; import * as ActionTypes from '../../constants'; import { showErrorModal, justOpenedProject } from '../IDE/actions/ide'; import { showToast, setToastText } from '../IDE/actions/toast'; @@ -218,3 +219,33 @@ export function updateSettings(formValues) { }) .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))); +} diff --git a/client/modules/User/components/APIKeyForm.jsx b/client/modules/User/components/APIKeyForm.jsx index feb81135..2e85dce1 100644 --- a/client/modules/User/components/APIKeyForm.jsx +++ b/client/modules/User/components/APIKeyForm.jsx @@ -10,26 +10,25 @@ class APIKeyForm extends React.Component { } addKey(event) { - // TODO - console.log('addKey'); - this.props.updateSettings(); event.preventDefault(); + document.getElementById('addKeyForm').reset(); + this.props.addApiKey(this.state.keyLabel); return false; } - removeKey(k) { - // TODO - console.log(k); + removeKey(keyId) { + this.props.removeApiKey(keyId); } render() { return (

Key label

-
+ { this.setState({ keyLabel: event.target.value }); }} />
@@ -41,21 +40,11 @@ class APIKeyForm extends React.Component {
- {[{ - id: 1, - label: 'MyFirstAPI', - createdAt: new Date(), - lastUsedAt: new Date() - }, { - id: 2, - label: 'MyOtherAPI', - createdAt: new Date(), - lastUsedAt: new Date() - }].map(v => ( + {this.props.apiKeys && this.props.apiKeys.map(v => ( - - - + + + ))}
{v.label}
Created on: {v.createdAt.toLocaleDateString()} {v.createdAt.toLocaleTimeString()}
Last used on:
{v.lastUsedAt.toLocaleDateString()} {v.lastUsedAt.toLocaleTimeString()}
{v.label}
Created on: {v.createdAt}
Last used on:
{v.lastUsedAt}
@@ -65,7 +54,14 @@ class APIKeyForm extends React.Component { } 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; diff --git a/client/modules/User/pages/AdvancedSettingsView.jsx b/client/modules/User/pages/AdvancedSettingsView.jsx index e963e5a4..a8866bc1 100644 --- a/client/modules/User/pages/AdvancedSettingsView.jsx +++ b/client/modules/User/pages/AdvancedSettingsView.jsx @@ -1,22 +1,17 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { reduxForm } from 'redux-form'; +import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { browserHistory } from 'react-router'; import InlineSVG from 'react-inlinesvg'; -import axios from 'axios'; import { Helmet } from 'react-helmet'; -import { updateSettings, initiateVerification } from '../actions'; -import { validateSettings } from '../../../utils/reduxFormUtils'; +import { addApiKey, removeApiKey } from '../actions'; import APIKeyForm from '../components/APIKeyForm'; const exitUrl = require('../../../images/exit.svg'); const logoUrl = require('../../../images/p5js-logo.svg'); -// TODO tmp -const ident = () => {}; - -class AccountView extends React.Component { +class AdvancedSettingsView extends React.Component { constructor(props) { super(props); this.closeAccountPage = this.closeAccountPage.bind(this); @@ -51,9 +46,7 @@ class AccountView extends React.Component {

Advanced Settings

- +
); @@ -64,41 +57,18 @@ function mapStateToProps(state) { return { initialValues: state.user, // <- initialValues for reduxForm user: state.user, + apiKeys: state.user.apiKeys, previousPath: state.ide.previousPath, theme: state.preferences.theme }; } function mapDispatchToProps(dispatch) { - return bindActionCreators({ updateSettings, initiateVerification }, dispatch); + return bindActionCreators({ addApiKey, removeApiKey }, dispatch); } -function asyncValidate(formProps, dispatch, props) { - 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 = { +AdvancedSettingsView.propTypes = { theme: PropTypes.string.isRequired }; -export default reduxForm({ - form: 'updateAllSettings', - fields: ['username', 'email', 'currentPassword', 'newPassword'], - validate: validateSettings, - asyncValidate, - asyncBlurFields: ['username', 'email', 'currentPassword'] -}, mapStateToProps, mapDispatchToProps)(AccountView); +export default connect(mapStateToProps, mapDispatchToProps)(AdvancedSettingsView); diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.js index 73636f2b..718e967f 100644 --- a/client/modules/User/reducers.js +++ b/client/modules/User/reducers.js @@ -31,6 +31,10 @@ const user = (state = { authenticated: false }, action) => { return Object.assign({}, state, { emailVerificationTokenState: 'invalid' }); case ActionTypes.SETTINGS_UPDATED: return { ...state, ...action.user }; + case ActionTypes.REMOVED_API_KEY: + return { ...state, ...action.user }; + case ActionTypes.ADDED_API_KEY: + return { ...state, ...action.user }; default: return state; }