From d7512ace72448242b392299cf459c9c72c8dbee5 Mon Sep 17 00:00:00 2001 From: Georgios Andreadis Date: Fri, 11 Aug 2017 14:48:42 +0300 Subject: Get Google authentication flow working --- package.json | 6 ++-- src/actions/auth.js | 15 ++++++++ src/api/index.js | 0 src/auth/index.js | 55 ++++++++++++++++++++++++++++ src/components/navigation/LogoutButton.js | 16 +++++++++ src/components/navigation/LogoutButton.sass | 17 +++++++++ src/components/navigation/Navbar.js | 6 ++-- src/components/navigation/Navbar.sass | 16 --------- src/containers/auth/Login.js | 56 +++++++++++++++++++++++++++++ src/containers/auth/Logout.js | 20 +++++++++++ src/pages/Home.js | 8 +++++ src/pages/Projects.js | 2 ++ src/reducers/auth.js | 12 +++++++ src/reducers/index.js | 2 ++ src/routes/index.js | 11 ++++-- src/store/configureStore.js | 14 +++++--- 16 files changed, 226 insertions(+), 30 deletions(-) create mode 100644 src/actions/auth.js create mode 100644 src/api/index.js create mode 100644 src/auth/index.js create mode 100644 src/components/navigation/LogoutButton.js create mode 100644 src/components/navigation/LogoutButton.sass create mode 100644 src/containers/auth/Login.js create mode 100644 src/containers/auth/Logout.js create mode 100644 src/pages/Home.js create mode 100644 src/reducers/auth.js diff --git a/package.json b/package.json index 03fbeb84..33138c33 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,14 @@ "react": "^15.6.1", "react-dom": "^15.6.1", "react-fontawesome": "^1.6.1", - "react-google-login": "^2.9.2", + "react-google-login": "^2.9.3", "react-mailto": "^0.4.0", "react-redux": "^5.0.5", "react-router-dom": "^4.1.1", "react-scripts": "1.0.10", "redux": "^3.7.2", - "redux-logger": "^3.0.6", - "redux-thunk": "^2.2.0" + "redux-localstorage": "^0.4.1", + "redux-logger": "^3.0.6" }, "scripts": { "build-css": "node-sass-chokidar src/ -o src/", diff --git a/src/actions/auth.js b/src/actions/auth.js new file mode 100644 index 00000000..f54563ae --- /dev/null +++ b/src/actions/auth.js @@ -0,0 +1,15 @@ +export const COMPLETE_LOGIN = "COMPLETE_LOGIN"; +export const LOG_OUT = "LOG_OUT"; + +export const completeLogin = (payload) => { + return { + type: COMPLETE_LOGIN, + payload + }; +}; + +export const logOut = () => { + return { + type: LOG_OUT + }; +}; diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 00000000..e69de29b diff --git a/src/auth/index.js b/src/auth/index.js new file mode 100644 index 00000000..5ea24ff9 --- /dev/null +++ b/src/auth/index.js @@ -0,0 +1,55 @@ +import {COMPLETE_LOGIN, LOG_OUT} from "../actions/auth"; + +const getAuthObject = () => { + const authItem = localStorage.getItem("auth"); + if (!authItem) { + return undefined; + } + return JSON.parse(authItem); +}; + +export const userIsLoggedIn = () => { + const authObj = getAuthObject(); + + if (!authObj || !authObj.googleId) { + return false; + } + + const currentTime = (new Date()).getTime(); + return parseInt(authObj.expiresAt, 10) - currentTime > 0; +}; + +export const getAuthToken = () => { + const authObj = getAuthObject(); + if (!authObj) { + return undefined; + } + + return authObj.authToken; +}; + +export const saveAuthLocalStorage = (payload) => { + localStorage.setItem("auth", JSON.stringify(payload)); +}; + +export const clearAuthLocalStorage = () => { + localStorage.setItem("auth", "{}"); +}; + +export const authRedirectMiddleware = store => next => action => { + switch (action.type) { + case COMPLETE_LOGIN: + saveAuthLocalStorage(action.payload); + window.location.href = "/projects"; + break; + case LOG_OUT: + clearAuthLocalStorage(); + window.location.href = "/"; + break; + default: + next(action); + return; + } + + next(action); +}; diff --git a/src/components/navigation/LogoutButton.js b/src/components/navigation/LogoutButton.js new file mode 100644 index 00000000..e2da7751 --- /dev/null +++ b/src/components/navigation/LogoutButton.js @@ -0,0 +1,16 @@ +import PropTypes from "prop-types"; +import React from "react"; +import {Link} from "react-router-dom"; +import "./LogoutButton.css"; + +const LogoutButton = ({onLogout}) => ( + + + +); + +LogoutButton.propTypes = { + onLogout: PropTypes.func.isRequired, +}; + +export default LogoutButton; diff --git a/src/components/navigation/LogoutButton.sass b/src/components/navigation/LogoutButton.sass new file mode 100644 index 00000000..b63494ab --- /dev/null +++ b/src/components/navigation/LogoutButton.sass @@ -0,0 +1,17 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.logout + float: right + margin-top: -1px + width: $navbar-height + + font-size: 14pt + + +clickable + + &:hover + background: #e3474d + + &:active + background: #a73438 diff --git a/src/components/navigation/Navbar.js b/src/components/navigation/Navbar.js index bd6fd750..bbd08591 100644 --- a/src/components/navigation/Navbar.js +++ b/src/components/navigation/Navbar.js @@ -2,6 +2,7 @@ import React, {Component} from 'react'; import FontAwesome from "react-fontawesome"; import Mailto from "react-mailto"; import {Link} from "react-router-dom"; +import Logout from "../../containers/auth/Logout"; import "./Navbar.css"; class Navbar extends Component { @@ -23,11 +24,8 @@ class Navbar extends Component { Profile - - - + -
); } diff --git a/src/components/navigation/Navbar.sass b/src/components/navigation/Navbar.sass index a592eab0..8be622db 100644 --- a/src/components/navigation/Navbar.sass +++ b/src/components/navigation/Navbar.sass @@ -114,19 +114,3 @@ .username:active background: #2d6527 - - .sign-out - float: right - margin-top: -1px - width: $navbar-height - - font-size: 14pt - - .sign-out:hover - background: #e3474d - - .sign-out:active - background: #a73438 - -#google-signin - display: none diff --git a/src/containers/auth/Login.js b/src/containers/auth/Login.js new file mode 100644 index 00000000..358ea7e9 --- /dev/null +++ b/src/containers/auth/Login.js @@ -0,0 +1,56 @@ +import PropTypes from "prop-types"; +import React from "react"; +import GoogleLogin from "react-google-login"; +import {connect} from "react-redux"; +import {completeLogin} from "../../actions/auth"; + +class LoginContainer extends React.Component { + static propTypes = { + visible: PropTypes.bool.isRequired, + onLogin: PropTypes.func.isRequired, + }; + + onAuthResponse(response) { + this.props.onLogin({ + googleId: response.googleId, + authToken: response.accessToken, + expiresAt: response.getAuthResponse().expires_at + }); + } + + render() { + if (!this.props.visible) { + return ; + } + + return ( + + + {' '} + Login with Google + + ); + } +} + +const mapStateToProps = (state, ownProps) => { + return { + visible: ownProps.visible, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onLogin: (payload) => dispatch(completeLogin(payload)), + }; +}; + +const Login = connect( + mapStateToProps, + mapDispatchToProps +)(LoginContainer); + +export default Login; diff --git a/src/containers/auth/Logout.js b/src/containers/auth/Logout.js new file mode 100644 index 00000000..8d329c1f --- /dev/null +++ b/src/containers/auth/Logout.js @@ -0,0 +1,20 @@ +import {connect} from "react-redux"; +import {logOut} from "../../actions/auth"; +import LogoutButton from "../../components/navigation/LogoutButton"; + +const mapStateToProps = state => { + return {}; +}; + +const mapDispatchToProps = dispatch => { + return { + onLogout: () => dispatch(logOut()), + }; +}; + +const Logout = connect( + mapStateToProps, + mapDispatchToProps +)(LogoutButton); + +export default Logout; diff --git a/src/pages/Home.js b/src/pages/Home.js new file mode 100644 index 00000000..bd32d0d9 --- /dev/null +++ b/src/pages/Home.js @@ -0,0 +1,8 @@ +import React from 'react'; +import Login from "../containers/auth/Login"; + +const Index = () => ( + +); + +export default Index; diff --git a/src/pages/Projects.js b/src/pages/Projects.js index 40902d97..69a28f9d 100644 --- a/src/pages/Projects.js +++ b/src/pages/Projects.js @@ -4,6 +4,7 @@ import {addProject, openNewProjectModal} from "../actions/projects"; import Navbar from "../components/navigation/Navbar"; import ProjectFilterPanel from "../components/projects/FilterPanel"; import NewProjectButton from "../components/projects/NewProjectButton"; +import Login from "../containers/auth/Login"; import NewProjectModal from "../containers/projects/NewProjectModal"; import VisibleProjectList from "../containers/projects/VisibleProjectAuthList"; import "./Projects.css"; @@ -27,6 +28,7 @@ class Projects extends React.Component { {this.props.dispatch(openNewProjectModal())}}/> + ); } diff --git a/src/reducers/auth.js b/src/reducers/auth.js new file mode 100644 index 00000000..0d01b300 --- /dev/null +++ b/src/reducers/auth.js @@ -0,0 +1,12 @@ +import {COMPLETE_LOGIN, LOG_OUT} from "../actions/auth"; + +export const auth = (state = {}, action) => { + switch (action.type) { + case COMPLETE_LOGIN: + return action.payload; + case LOG_OUT: + return {}; + default: + return state; + } +}; diff --git a/src/reducers/index.js b/src/reducers/index.js index 3974ce4a..4e35f6e8 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,7 +1,9 @@ import {combineReducers} from "redux"; +import {auth} from "./auth"; import {authorizations, authVisibilityFilter, newProjectModalVisible} from "./projects"; const rootReducer = combineReducers({ + auth, authorizations, newProjectModalVisible, authVisibilityFilter, diff --git a/src/routes/index.js b/src/routes/index.js index 54dc0703..6b3a454d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,5 +1,6 @@ import React from 'react'; -import {BrowserRouter, Route, Switch} from "react-router-dom"; +import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom"; +import {userIsLoggedIn} from "../auth/index"; import Home from "../pages/Home"; import NotFound from "../pages/NotFound"; import Projects from "../pages/Projects"; @@ -8,7 +9,13 @@ const Routes = () => ( - + ( + userIsLoggedIn() ? ( + + ) : ( + + ) + )}/> diff --git a/src/store/configureStore.js b/src/store/configureStore.js index 9584f591..ec932cf7 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -1,15 +1,19 @@ -import {applyMiddleware, createStore} from "redux"; +import {applyMiddleware, compose, createStore} from "redux"; +import persistState from "redux-localstorage"; import {createLogger} from "redux-logger"; -import thunkMiddleware from "redux-thunk"; +import {authRedirectMiddleware} from "../auth/index"; import rootReducer from "../reducers/index"; const logger = createLogger(); const configureStore = () => createStore( rootReducer, - applyMiddleware( - thunkMiddleware, - logger, + compose( + persistState("auth"), + applyMiddleware( + logger, + authRedirectMiddleware, + ) ) ); -- cgit v1.2.3