diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2018-09-16 22:40:05 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2018-09-16 22:40:05 +0200 |
| commit | d3e4bb3ed2585859a3adeb7eeff35b7c75ebd840 (patch) | |
| tree | e88e9edae5a63328fb1acc625e5624990717d20f /app/client | |
| parent | 189be96150fbd49766228cf50c6a89279542565c (diff) | |
auth gate on main app. pull in auth routes from bucky.
Diffstat (limited to 'app/client')
| -rw-r--r-- | app/client/auth/auth.actions.js | 82 | ||||
| -rw-r--r-- | app/client/auth/auth.gate.js | 41 | ||||
| -rw-r--r-- | app/client/auth/auth.reducer.js | 82 | ||||
| -rw-r--r-- | app/client/auth/index.js | 11 | ||||
| -rw-r--r-- | app/client/auth/login.component.js | 86 | ||||
| -rw-r--r-- | app/client/auth/logout.component.js | 24 | ||||
| -rw-r--r-- | app/client/auth/signup.component.js | 101 | ||||
| -rw-r--r-- | app/client/common/index.js | 3 | ||||
| -rw-r--r-- | app/client/common/textInput.component.js | 3 | ||||
| -rw-r--r-- | app/client/index.jsx | 27 | ||||
| -rw-r--r-- | app/client/store.js | 2 | ||||
| -rw-r--r-- | app/client/types.js | 7 |
12 files changed, 456 insertions, 13 deletions
diff --git a/app/client/auth/auth.actions.js b/app/client/auth/auth.actions.js new file mode 100644 index 0000000..5968f87 --- /dev/null +++ b/app/client/auth/auth.actions.js @@ -0,0 +1,82 @@ +import * as types from '../types'; + +export const setToken = (data) => { + return { type: types.auth.set_token, data } +} +export const setError = (data) => { + return { type: types.auth.set_error, data } +} +export const setCurrentUser = (data) => { + return { type: types.auth.set_current_user, data } +} +export function logout() { + return { type: types.auth.logout_user }; +} +export function authLoading() { + return { type: types.auth.loading }; +} + +export function InvalidCredentialsException(message) { + this.message = message; + this.name = 'InvalidCredentialsException'; +} + +export function login(username, password) { + return (dispatch) => { + dispatch(authLoading()); + apiClient() + .post(api.GET_TOKEN, { + username, + password + }) + .then(function (response) { + dispatch(setToken(response.data.token)); + dispatch(getCurrentUser()); + }) + .catch(function (error) { + dispatch(setError(true)); + if (error.response.status === 400) { + throw new InvalidCredentialsException(error); + } + throw error; + }); + }; +} + +export function signup(data) { + return (dispatch) => { + dispatch(authLoading()); + apiClient() + .post(api.SIGNUP, data) + .then(function (response) { + console.log(response.data); + dispatch(login(data.username, data.password)); + }) + .catch(function (error) { + console.log(error) + if (error.response.status === 400) { + // dispatch(accountError("There was an error creating your account.")) + throw new InvalidCredentialsException(error); + } + throw error; + }); + }; +} + +export function getCurrentUser() { + return (dispatch) => { + dispatch(authLoading()); + apiClient() + .get(api.CURRENT_USER) + .then(function (response) { + dispatch(setCurrentUser(response.data)); + console.log('set current user') + }) + .catch(function (error) { + if (error.response.status === 400) { + throw new InvalidCredentialsException(error); + } + throw error; + }); + }; +} diff --git a/app/client/auth/auth.gate.js b/app/client/auth/auth.gate.js new file mode 100644 index 0000000..e7a9940 --- /dev/null +++ b/app/client/auth/auth.gate.js @@ -0,0 +1,41 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { BrowserRouter, Route } from 'react-router-dom' +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; + +import Login from './login.component'; +import Logout from './logout.component'; +import Signup from './signup.component'; + +import { randint } from '../util/math' + +class AuthGate extends Component { + render(){ + if (this.props.auth.isAuthenticated) return children + return ( + <BrowserRouter> + <div> + <div className="spinfx"></div> + <Route exact path='/' component={Login} /> + <Route exact path='/login' component={Login} /> + <Route exact path='/logout' component={Logout} /> + <Route exact path='/signup' component={Signup} /> + </div> + </BrowserRouter> + ) + } + componentDidMount(){ + document.querySelector('.spinfx').style.backgroundImage = 'linear-gradient(' + (randint(40)-5) + 'deg, #fde, #ffe)' + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth +}); + +const mapDispatchToProps = (dispatch) => ({ +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AuthGate); diff --git a/app/client/auth/auth.reducer.js b/app/client/auth/auth.reducer.js new file mode 100644 index 0000000..cacb0d5 --- /dev/null +++ b/app/client/auth/auth.reducer.js @@ -0,0 +1,82 @@ +import types from '../types'; + +const authInitialState = { + token: null, + user: {}, + groups: {}, + loading: false, + isAuthenticated: false, +}; + +const auth = (state = authInitialState, action) => { + switch(action.type) { + case types.auth.set_token: + return { + ...state, + token: action.data, + isAuthenticated: !!action.data, + loading: false, + error: null, + }; + + case types.auth.loading: + return { + ...state, + loading: true, + error: null, + }; + + case types.auth.set_current_user: + const groups = {} + action.data.groups.forEach(g => groups[g.name.toLowerCase()] = true) + if (action.data.is_staff) { + groups['staff'] = true + } + if (action.data.is_superuser) { + groups['superuser'] = true + } + return { + ...state, + user: action.data, + groups, + error: null, + }; + + case types.auth.logout_user: + return { + ...authInitialState + }; + + case types.auth.set_error: + return { + ...state, + loading: false, + error: action.data, + } + + case types.auth.loading: + // const initial_state_el = document.querySelector('#initial_state') + // if (initial_state_el) { + // try { + // const initial_state = JSON.parse(initial_state_el.innerHTML) + // if (initial_state && initial_state.auth && initial_state.auth.user) { + // console.log(initial_state.auth.user) + // return { + // ...state, + // user: { + // ...initial_state.auth.user, + // } + // } + // } + // } catch (e) { + // console.error("error loading initial state") + // } + // } + return state; + + default: + return state; + } +} + +export default auth; diff --git a/app/client/auth/index.js b/app/client/auth/index.js new file mode 100644 index 0000000..5e6b2b0 --- /dev/null +++ b/app/client/auth/index.js @@ -0,0 +1,11 @@ +import Gate from './auth.gate'; +import Login from './login.component'; +import Logout from './logout.component'; +import Signup from './signup.component'; + +export default { + Gate, + Login, + Logout, + Signup, +}
\ No newline at end of file diff --git a/app/client/auth/login.component.js b/app/client/auth/login.component.js new file mode 100644 index 0000000..4ffab34 --- /dev/null +++ b/app/client/auth/login.component.js @@ -0,0 +1,86 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +// import { Link } from 'react-router-dom'; +import * as authActions from './auth.actions'; + +import { Group, Param, TextInput, Button } from '../common'; + +class Login extends Component { + state = { + username: '', + password: '', + } + constructor() { + super() + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + handleChange(e) { + const name = e.target.name + const value = e.target.value + this.setState({ + [name]: value, + error: null, + }) + } + handleSubmit(e) { + e.preventDefault() + this.props.actions.login(this.state.username, this.state.password) + } + render(){ + if (this.props.auth.isAuthenticated) { + return <Redirect to="/" /> + } + return ( + <form onSubmit={this.handleSubmit}> + <h1>Log in</h1><br /> + <Group> + <TextInput + autofocus + autocapitalize="off" + autocomplete="off" + title="Username" + name="username" + type="text" + value={this.state.username} + onChange={this.handleChange} + /> + <TextInput + title="Password" + name="password" + type="password" + value={this.state.password} + onChange={this.handleChange} + /> + <Button + loading={this.props.auth.loading} + > + Login + </Button> + {this.renderAuthError()} + </Group> + </form> + ) + } + renderAuthError(){ + if (this.props.auth.error) { + return ( + <div className='form-input-hint'>{"There was an error logging you in (bad password?)"}</div> + ) + } + return null + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/app/client/auth/logout.component.js b/app/client/auth/logout.component.js new file mode 100644 index 0000000..bcc3bce --- /dev/null +++ b/app/client/auth/logout.component.js @@ -0,0 +1,24 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import * as authActions from './auth.actions'; + +class Logout extends Component { + componentWillMount(props){ + this.props.actions.logout() + } + render(){ + return <Redirect to="/" /> + } +} + +const mapStateToProps = (state) => ({ +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Logout); diff --git a/app/client/auth/signup.component.js b/app/client/auth/signup.component.js new file mode 100644 index 0000000..c86d31b --- /dev/null +++ b/app/client/auth/signup.component.js @@ -0,0 +1,101 @@ +import { h, Component } from 'preact'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import actions from './auth.actions'; + +import { Group, Param, TextInput, Button } from '../common'; + +class Signup extends Component { + state = { + username: '', + password: '', + password2: '', + } + constructor() { + super() + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + handleChange(e) { + const name = e.target.name + const value = e.target.value + this.setState({ + [name]: value, + error: null, + }) + } + validate(){ + if (!this.state.password || this.state.password !== this.state.password2) { + return false + } + return true + } + handleSubmit(e) { + e.preventDefault() + if (!this.validate) { + return this.props.actions.setError('bad password') + } + this.props.actions.signup(this.state) + } + render(){ + if (this.props.auth.isAuthenticated) { + return <Redirect to="/" /> + } + return ( + <form onSubmit={this.handleSubmit}> + <h1>New account</h1><br /> + <Group> + <TextInput + autofocus + autocapitalize="off" + autocomplete="off" + title="Username" + name="username" + type="text" + value={this.state.username} + onChange={this.handleChange} + /> + <TextInput + title="Password" + name="password" + type="password" + value={this.state.password} + onChange={this.handleChange} + /> + <TextInput + title="Password again :)" + name="password2" + type="password" + value={this.state.password2} + onChange={this.handleChange} + /> + <Button + loading={this.props.auth.loading} + > + Login + </Button> + {this.renderAuthError()} + </Group> + </form> + ) + } + renderAuthError(){ + if (this.props.auth.error) { + return ( + <div className='form-input-hint'>{"Please doublecheck the form"}</div> + ) + } + return null + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ ...actions }, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Signup); diff --git a/app/client/common/index.js b/app/client/common/index.js index 7448104..e6baafc 100644 --- a/app/client/common/index.js +++ b/app/client/common/index.js @@ -1,3 +1,4 @@ +import AudioPlayer from './audioPlayer/audioPlayer.component' import AugmentationGrid from './augmentationGrid.component' import Button from './button.component' import ButtonGrid from './buttonGrid.component' @@ -24,7 +25,7 @@ import * as Views from './views' export { Views, - Loading, Progress, Header, + Loading, Progress, Header, AudioPlayer, FolderList, FileList, FileRow, FileUpload, Gallery, Player, Group, ParamGroup, Param, diff --git a/app/client/common/textInput.component.js b/app/client/common/textInput.component.js index d429944..d3b16ad 100644 --- a/app/client/common/textInput.component.js +++ b/app/client/common/textInput.component.js @@ -34,6 +34,9 @@ class TextInput extends Component { value={this.state.changed ? this.state.value : this.props.value} onInput={this.handleInput} onKeydown={this.handleKeydown} + autofocus={this.props.autofocus} + autoComplete={this.props.autocomplete} + autoCapitalize={this.props.autocapitalize || 'off'} placeholder={this.props.placeholder} autofocus={this.props.autofocus} className={this.props.className || ''} diff --git a/app/client/index.jsx b/app/client/index.jsx index fd4679c..614bb35 100644 --- a/app/client/index.jsx +++ b/app/client/index.jsx @@ -7,8 +7,8 @@ import { store, history } from './store' import * as socket from './socket' import util from './util' -import Header from './common/header.component' -import AudioPlayer from './common/audioPlayer/audioPlayer.component' +import Auth from './auth' +import { Header, AudioPlayer } from './common' import System from './system/system.component' import Dashboard from './dashboard/dashboard.component' import modules from './modules' @@ -22,16 +22,19 @@ const module_list = Object.keys(modules).map(name => { const app = ( <Provider store={store}> - <BrowserRouter> - <div> - <Route exact path='/' component={Dashboard} /> - <Route path='/system/' component={System} /> - <Route path='/dashboard/' component={Dashboard} /> - {module_list} - <Route path='/' component={Header} /> - <AudioPlayer /> - </div> - </BrowserRouter> + <Auth.Gate> + <BrowserRouter> + <div> + <Route exact path='/' component={Dashboard} /> + <Route path='/system/' component={System} /> + <Route path='/dashboard/' component={Dashboard} /> + <Route path='/logout/' component={Auth.Logout} /> + {module_list} + <Route path='/' component={Header} /> + <AudioPlayer /> + </div> + </BrowserRouter> + </Auth.Gate> </Provider> ) diff --git a/app/client/store.js b/app/client/store.js index 8ffab15..654b22d 100644 --- a/app/client/store.js +++ b/app/client/store.js @@ -6,6 +6,7 @@ import createHistory from 'history/createBrowserHistory' import { routerReducer } from 'react-router-redux' // import navReducer from './nav.reducer' +import authReducer from './auth/auth.reducer' import systemReducer from './system/system.reducer' import dashboardReducer from './dashboard/dashboard.reducer' import liveReducer from './live/live.reducer' @@ -15,6 +16,7 @@ import audioPlayerReducer from './common/audioPlayer/audioPlayer.reducer' import { moduleReducer } from './modules/module.reducer' const appReducer = combineReducers({ + auth: authReducer, system: systemReducer, dashboard: dashboardReducer, live: liveReducer, diff --git a/app/client/types.js b/app/client/types.js index 22211dd..44fe434 100644 --- a/app/client/types.js +++ b/app/client/types.js @@ -36,6 +36,13 @@ export default { 'progress', 'epoch', ]), + auth: crud_type('auth', [ + 'set_token', + 'set_error', + 'set_current_user', + 'logout_user', + 'loading', + ]), socket: { connect: 'SOCKET_CONNECT', connect_error: 'SOCKET_CONNECT_ERROR', |
