From 499a7fe686bb75f5a388c1d29e44ac15c77e85da Mon Sep 17 00:00:00 2001 From: Kit Westneat Date: Fri, 16 Feb 2018 14:40:28 -0500 Subject: [PATCH] add Google auth plugin --- .gitignore | 1 + docs/_docs/02-02-advanced-configuration.md | 26 +++++++++++- .../talk-plugin-google-auth/.eslintrc.json | 3 ++ .../client/.eslintrc.json | 3 ++ .../talk-plugin-google-auth/client/actions.js | 7 ++++ .../client/components/GoogleButton.css | 13 ++++++ .../client/components/GoogleButton.js | 11 +++++ .../client/components/SignIn.js | 9 ++++ .../client/components/SignUp.js | 9 ++++ .../client/containers/GoogleButton.js | 9 ++++ .../talk-plugin-google-auth/client/index.js | 11 +++++ .../client/translations.yml | 20 +++++++++ plugins/talk-plugin-google-auth/index.js | 7 ++++ plugins/talk-plugin-google-auth/package.json | 9 ++++ .../server/passport.js | 41 +++++++++++++++++++ .../talk-plugin-google-auth/server/router.js | 29 +++++++++++++ plugins/talk-plugin-google-auth/yarn.lock | 34 +++++++++++++++ 17 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-google-auth/.eslintrc.json create mode 100644 plugins/talk-plugin-google-auth/client/.eslintrc.json create mode 100644 plugins/talk-plugin-google-auth/client/actions.js create mode 100644 plugins/talk-plugin-google-auth/client/components/GoogleButton.css create mode 100644 plugins/talk-plugin-google-auth/client/components/GoogleButton.js create mode 100644 plugins/talk-plugin-google-auth/client/components/SignIn.js create mode 100644 plugins/talk-plugin-google-auth/client/components/SignUp.js create mode 100644 plugins/talk-plugin-google-auth/client/containers/GoogleButton.js create mode 100644 plugins/talk-plugin-google-auth/client/index.js create mode 100644 plugins/talk-plugin-google-auth/client/translations.yml create mode 100644 plugins/talk-plugin-google-auth/index.js create mode 100644 plugins/talk-plugin-google-auth/package.json create mode 100644 plugins/talk-plugin-google-auth/server/passport.js create mode 100644 plugins/talk-plugin-google-auth/server/router.js create mode 100644 plugins/talk-plugin-google-auth/yarn.lock diff --git a/.gitignore b/.gitignore index a27a08f15..182f854dc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ plugins.json plugins/* !plugins/talk-plugin-akismet !plugins/talk-plugin-facebook-auth +!plugins/talk-plugin-google-auth !plugins/talk-plugin-auth !plugins/talk-plugin-respect !plugins/talk-plugin-offtopic diff --git a/docs/_docs/02-02-advanced-configuration.md b/docs/_docs/02-02-advanced-configuration.md index 50c430252..4fc60a2cc 100644 --- a/docs/_docs/02-02-advanced-configuration.md +++ b/docs/_docs/02-02-advanced-configuration.md @@ -77,6 +77,30 @@ or by visiting the guide. This is only required while the `talk-plugin-facebook-auth` plugin is enabled. +## TALK_GOOGLE_CLIENT_ID + +The Google OAuth2 client ID for your Google login web app. You can learn more +about getting a Google Client ID at the +[Google API Console](https://console.developers.google.com/apis/){:target="_blank"}. + +You will need to enable the Google+ API in the dashboard and create credentials +for a new OAuth client ID web application. The authorized JavaScript origin +should be set to the Talk domain, and the authorized redirect URI should be set +to http:///api/v1/auth/google/callback. This is only required while +the `talk-plugin-google-auth` plugin is enabled. + +## TALK_GOOGLE_CLIENT_SECRET + +The Google OAuth2 client ID for your Google login web app. You can learn more +about getting a Google Client ID at the +[Google API Console](https://console.developers.google.com/apis/){:target="_blank"}. + +You will need to enable the Google+ API in the dashboard and create credentials +for a new OAuth client ID web application. The authorized JavaScript origin +should be set to the Talk domain, and the authorized redirect URI should be set +to http:///api/v1/auth/google/callback. This is only required while +the `talk-plugin-google-auth` plugin is enabled. + ## TALK_HELMET_CONFIGURATION A JSON string representing the configuration passed to the @@ -538,4 +562,4 @@ This is a **Build Variable** and must be consumed during build. If using the [Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) image you can specify it with `--build-arg TALK_REPLY_COMMENTS_LOAD_DEPTH=3`. -Specifies the initial replies to load for a comment. (Default `3`) \ No newline at end of file +Specifies the initial replies to load for a comment. (Default `3`) diff --git a/plugins/talk-plugin-google-auth/.eslintrc.json b/plugins/talk-plugin-google-auth/.eslintrc.json new file mode 100644 index 000000000..78f7c2397 --- /dev/null +++ b/plugins/talk-plugin-google-auth/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk" +} diff --git a/plugins/talk-plugin-google-auth/client/.eslintrc.json b/plugins/talk-plugin-google-auth/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-google-auth/client/actions.js b/plugins/talk-plugin-google-auth/client/actions.js new file mode 100644 index 000000000..6519d9f2b --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/actions.js @@ -0,0 +1,7 @@ +export const loginWithGoogle = () => (dispatch, _, { rest }) => { + window.open( + `${rest.uri}/auth/google`, + 'Continue with Google', + 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/components/GoogleButton.css b/plugins/talk-plugin-google-auth/client/components/GoogleButton.css new file mode 100644 index 000000000..fb5e8abf3 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/GoogleButton.css @@ -0,0 +1,13 @@ +.button { + background-color: #db3236; + border-color: #db3236; + color: rgb(255, 255, 255); + width: 100%; + box-sizing: border-box; + padding: 10px 20px; +} + +.button:hover { + background-color: #c71e22; + border-color: #c71e22; +} diff --git a/plugins/talk-plugin-google-auth/client/components/GoogleButton.js b/plugins/talk-plugin-google-auth/client/components/GoogleButton.js new file mode 100644 index 000000000..8451595f7 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/GoogleButton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { BareButton } from 'plugin-api/beta/client/components/ui'; +import styles from './GoogleButton.css'; + +export default ({ onClick, children }) => { + return ( + + {children} + + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/components/SignIn.js b/plugins/talk-plugin-google-auth/client/components/SignIn.js new file mode 100644 index 000000000..527d41238 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/SignIn.js @@ -0,0 +1,9 @@ +import React from 'react'; +import GoogleButton from '../containers/GoogleButton'; +import { t } from 'plugin-api/beta/client/services'; + +export default () => { + return ( + {t('talk-plugin-google-auth.sign_in')} + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/components/SignUp.js b/plugins/talk-plugin-google-auth/client/components/SignUp.js new file mode 100644 index 000000000..704cc3963 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/SignUp.js @@ -0,0 +1,9 @@ +import React from 'react'; +import GoogleButton from '../containers/GoogleButton'; +import { t } from 'plugin-api/beta/client/services'; + +export default () => { + return ( + {t('talk-plugin-google-auth.sign_up')} + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/containers/GoogleButton.js b/plugins/talk-plugin-google-auth/client/containers/GoogleButton.js new file mode 100644 index 000000000..d1982e351 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/containers/GoogleButton.js @@ -0,0 +1,9 @@ +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import { loginWithGoogle } from '../actions'; +import GoogleButton from '../components/GoogleButton'; + +const mapDispatchToProps = dispatch => + bindActionCreators({ onClick: loginWithGoogle }, dispatch); + +export default connect(null, mapDispatchToProps)(GoogleButton); diff --git a/plugins/talk-plugin-google-auth/client/index.js b/plugins/talk-plugin-google-auth/client/index.js new file mode 100644 index 000000000..cb8a8f059 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/index.js @@ -0,0 +1,11 @@ +import SignIn from './components/SignIn'; +import SignUp from './components/SignUp'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + authExternalSignIn: [SignIn], + authExternalSignUp: [SignUp], + }, +}; diff --git a/plugins/talk-plugin-google-auth/client/translations.yml b/plugins/talk-plugin-google-auth/client/translations.yml new file mode 100644 index 000000000..fd926f3ee --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/translations.yml @@ -0,0 +1,20 @@ +en: + talk-plugin-google-auth: + sign_in: "Sign in with Google" + sign_up: "Sign up with Google" +es: + talk-plugin-google-auth: + google_sign_in: "Entrar con Google" + google_sign_up: "Registrarse con Google" +fr: + talk-plugin-google-auth: + google_sign_in: "Connectez-vous avec Google" + google_sign_up: "Inscrivez-vous avec Google" +zh_CN: + talk-plugin-google-auth: + google_sign_in: "使用 Google 帐号" + google_sign_up: "使用 Google 帐号" +zh_TW: + talk-plugin-google-auth: + google_sign_in: "使用 Google 帳號" + google_sign_up: "使用 Google 帳號" diff --git a/plugins/talk-plugin-google-auth/index.js b/plugins/talk-plugin-google-auth/index.js new file mode 100644 index 000000000..b9694613e --- /dev/null +++ b/plugins/talk-plugin-google-auth/index.js @@ -0,0 +1,7 @@ +const passport = require('./server/passport'); +const router = require('./server/router'); + +module.exports = { + passport, + router, +}; diff --git a/plugins/talk-plugin-google-auth/package.json b/plugins/talk-plugin-google-auth/package.json new file mode 100644 index 000000000..385556fce --- /dev/null +++ b/plugins/talk-plugin-google-auth/package.json @@ -0,0 +1,9 @@ +{ + "name": "talk-plugin-google-auth", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "passport-google-oauth2": "^0.1.6" + } +} diff --git a/plugins/talk-plugin-google-auth/server/passport.js b/plugins/talk-plugin-google-auth/server/passport.js new file mode 100644 index 000000000..c3463557b --- /dev/null +++ b/plugins/talk-plugin-google-auth/server/passport.js @@ -0,0 +1,41 @@ +const GoogleStrategy = require('passport-google-oauth2').Strategy; +const UsersService = require('services/users'); +const { ValidateUserLogin } = require('services/passport'); +let { ROOT_URL } = require('config'); + +if (ROOT_URL[ROOT_URL.length - 1] !== '/') { + ROOT_URL += '/'; +} + +module.exports = passport => { + if ( + process.env.TALK_GOOGLE_CLIENT_ID && + process.env.TALK_GOOGLE_CLIENT_SECRET && + process.env.TALK_ROOT_URL + ) { + passport.use( + new GoogleStrategy( + { + clientID: process.env.TALK_GOOGLE_CLIENT_ID, + clientSecret: process.env.TALK_GOOGLE_CLIENT_SECRET, + callbackURL: `${ROOT_URL}api/v1/auth/google/callback`, + passReqToCallback: true, + }, + async (req, accessToken, refreshToken, profile, done) => { + let user; + try { + user = await UsersService.findOrCreateExternalUser(profile); + } catch (err) { + return done(err.toString()); + } + + return ValidateUserLogin(profile, user, done); + } + ) + ); + } else if (process.env.NODE_ENV !== 'test') { + throw new Error( + 'Google cannot be enabled, missing one of TALK_GOOGLE_CLIENT_ID, TALK_GOOGLE_CLIENT_SECRET, TALK_ROOT_URL' + ); + } +}; diff --git a/plugins/talk-plugin-google-auth/server/router.js b/plugins/talk-plugin-google-auth/server/router.js new file mode 100644 index 000000000..89977f35b --- /dev/null +++ b/plugins/talk-plugin-google-auth/server/router.js @@ -0,0 +1,29 @@ +module.exports = router => { + const { passport, HandleAuthPopupCallback } = require('services/passport'); + + /** + * Google auth endpoint, this will redirect the user immediatly to google + * for authorization. + */ + router.get( + '/api/v1/auth/google', + passport.authenticate('google', { + display: 'popup', + authType: 'rerequest', + scope: ['profile'], + }) + ); + + /** + * Google callback endpoint, this will send the user a html page designed to + * send back the user credentials upon sucesfull login. + */ + router.get('/api/v1/auth/google/callback', (req, res, next) => { + // Perform the google login flow and pass the data back through the opener. + passport.authenticate( + 'google', + { session: false }, + HandleAuthPopupCallback(req, res, next) + )(req, res, next); + }); +}; diff --git a/plugins/talk-plugin-google-auth/yarn.lock b/plugins/talk-plugin-google-auth/yarn.lock new file mode 100644 index 000000000..e1f5c4202 --- /dev/null +++ b/plugins/talk-plugin-google-auth/yarn.lock @@ -0,0 +1,34 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + +passport-google-oauth2@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz#dfd7016ac7449fe27cfeb252ae974afc23257a0d" + dependencies: + passport-oauth2 "^1.1.2" + +passport-oauth2@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + +utils-merge@1.x.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"