From 69ea48774da20bcbb66983b6d5da6b82095aad46 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Mon, 20 Jul 2020 17:06:59 -0400 Subject: [PATCH 01/10] Handle email duplicates for OAuth signup --- server/config/passport.js | 32 ++++++++++++++++++--------- server/controllers/user.controller.js | 7 +----- server/models/user.js | 23 ++++++++++++++----- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/server/config/passport.js b/server/config/passport.js index 163db374e4..21b686c10e 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -10,6 +10,11 @@ import { BasicStrategy } from 'passport-http'; import User from '../models/user'; +function generateUniqueUsername(username) { + const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)]; + return slugify(`${username} ${adj}`); +} + passport.serializeUser((user, done) => { done(null, user.id); }); @@ -108,14 +113,20 @@ passport.use(new GitHubStrategy({ existingEmailUser.verified = User.EmailConfirmation.Verified; existingEmailUser.save(saveErr => done(null, existingEmailUser)); } else { - const user = new User(); - user.email = primaryEmail; - user.github = profile.id; - user.username = profile.username; - user.tokens.push({ kind: 'github', accessToken }); - user.name = profile.displayName; - user.verified = User.EmailConfirmation.Verified; - user.save(saveErr => done(null, user)); + let { username } = profile; + User.findByUsername(username, true, (findByUsernameErr, existingUsernameUser) => { + if (existingUsernameUser) { + username = generateUniqueUsername(username); + } + const user = new User(); + user.email = primaryEmail; + user.github = profile.id; + user.username = profile.username; + user.tokens.push({ kind: 'github', accessToken }); + user.name = profile.displayName; + user.verified = User.EmailConfirmation.Verified; + user.save(saveErr => done(null, user)); + }); } }); }); @@ -141,10 +152,9 @@ passport.use(new GoogleStrategy({ User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => { let username = profile._json.emails[0].value.split('@')[0]; - User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => { + User.findByUsername(username, true, (findByUsernameErr, existingUsernameUser) => { if (existingUsernameUser) { - const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)]; - username = slugify(`${username} ${adj}`); + username = generateUniqueUsername(username); } // what if a username is already taken from the display name too? // then, append a random friendly word? diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index 0ff791c6d8..fcb5833bc6 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -93,12 +93,7 @@ export function createUser(req, res, next) { export function duplicateUserCheck(req, res) { const checkType = req.query.check_type; const value = req.query[checkType]; - const query = {}; - query[checkType] = value; - // Don't want to use findByEmailOrUsername here, because in this case we do - // want to use case-insensitive search for usernames to prevent username - // duplicates, which overrides the default behavior. - User.findOne(query).collation({ locale: 'en', strength: 2 }).exec((err, user) => { + User.findByEmailOrUsername(value, true, (err, user) => { if (user) { return res.json({ exists: true, diff --git a/server/models/user.js b/server/models/user.js index c4097e8793..37f9286948 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -170,14 +170,19 @@ userSchema.statics.findByEmail = function findByEmail(email, cb) { * Queries User collection by username and returns one User document. * * @param {string} username - Username string + * @param {boolean} [caseInsensitive] - Does a caseInsensitive query, defaults to false * @callback [cb] - Optional error-first callback that passes User document * @return {Promise} - Returns Promise fulfilled by User document */ -userSchema.statics.findByUsername = function findByUsername(username, cb) { +userSchema.statics.findByUsername = function findByUsername(username, caseInsensitive, cb) { const query = { username }; - return this.findOne(query, cb); + if (arguments.length === 3 + || (arguments.length === 2 && typeof caseInsensitive === 'boolean' && caseInsensitive)) { + return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb); + } + return this.findOne(query, cb || caseInsensitive); }; /** @@ -187,15 +192,23 @@ userSchema.statics.findByUsername = function findByUsername(username, cb) { * a username or email. * * @param {string} value - Email or username + * @param {boolean} caseInsensitive - Does a caseInsensitive query rather than + * default query for username or email, defaults + * to false * @callback [cb] - Optional error-first callback that passes User document * @return {Promise} - Returns Promise fulfilled by User document */ -userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, cb) { +userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, caseInsensitive, cb) { const isEmail = value.indexOf('@') > -1; + if (arguments.length === 3 + || (arguments.length === 2 && typeof caseInsensitive === 'boolean' && caseInsensitive)) { + const query = isEmail ? { email: value } : { username: value }; + return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb); + } if (isEmail) { - return this.findByEmail(value, cb); + return this.findByEmail(value, cb || caseInsensitive); } - return this.findByUsername(value, cb); + return this.findByUsername(value, cb || caseInsensitive); }; /** From c12f0cd85617d5c9a221a6cbb602115736b0b824 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Wed, 22 Jul 2020 14:07:45 -0400 Subject: [PATCH 02/10] [#915] Update copy on Settings page --- .../User/components/SocialAuthButton.jsx | 30 +++++++++++++++++-- client/modules/User/pages/AccountView.jsx | 22 ++++++++++++-- server/controllers/user.controller.js | 4 ++- server/models/user.js | 1 + 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/client/modules/User/components/SocialAuthButton.jsx b/client/modules/User/components/SocialAuthButton.jsx index afcb605dd6..57789ad2e5 100644 --- a/client/modules/User/components/SocialAuthButton.jsx +++ b/client/modules/User/components/SocialAuthButton.jsx @@ -17,6 +17,17 @@ const labels = { google: 'Login with Google' }; +const linkLabels = { + github: { + connect: 'Connect GitHub Account', + connected: 'Re-link GitHub Account' + }, + google: { + connect: 'Connect Google Account', + connected: 'Re-link Google Account' + } +}; + const icons = { github: GithubIcon, google: GoogleIcon @@ -31,14 +42,20 @@ const StyledButton = styled(Button)` width: ${remSize(300)}; `; -function SocialAuthButton({ service }) { +function SocialAuthButton({ service, link, connected }) { const ServiceIcon = icons[service]; + let label; + if (link) { + label = connected ? linkLabels[service].connected : linkLabels[service].connect; + } else { + label = labels[service]; + } return ( } href={authUrls[service]} > - {labels[service]} + {label} ); } @@ -46,7 +63,14 @@ function SocialAuthButton({ service }) { SocialAuthButton.services = services; SocialAuthButton.propTypes = { - service: PropTypes.oneOf(['github', 'google']).isRequired + service: PropTypes.oneOf(['github', 'google']).isRequired, + link: PropTypes.bool, + connected: PropTypes.bool +}; + +SocialAuthButton.defaultProps = { + link: false, + connected: false }; export default SocialAuthButton; diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index fff8d3318a..134a164026 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -13,21 +13,37 @@ import APIKeyForm from '../components/APIKeyForm'; import Nav from '../../../components/Nav'; function SocialLoginPanel(props) { + const { user } = props; return (

Social Login

- Use your GitHub or Google account to log into the p5.js Web Editor. + Use your GitHub or Google account to login to the p5.js Web Editor.

- - + +
); } +SocialLoginPanel.propTypes = { + user: PropTypes.shape({ + github: PropTypes.string, + google: PropTypes.string + }).isRequired +}; + class AccountView extends React.Component { componentDidMount() { document.body.className = this.props.theme; diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index fcb5833bc6..8f43ff9eed 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -18,7 +18,9 @@ export function userResponse(user) { apiKeys: user.apiKeys, verified: user.verified, id: user._id, - totalSize: user.totalSize + totalSize: user.totalSize, + github: user.github, + google: user.google }; } diff --git a/server/models/user.js b/server/models/user.js index 37f9286948..00cde0610c 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -50,6 +50,7 @@ const userSchema = new Schema({ verifiedToken: String, verifiedTokenExpires: Date, github: { type: String }, + google: { type: String }, email: { type: String, unique: true }, tokens: Array, apiKeys: { type: [apiKeySchema] }, From ac6a5a8f256047dc1a7fc71a13a4a3d3e5a4dfd5 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 23 Jul 2020 14:49:56 -0400 Subject: [PATCH 03/10] [#1317] Add options param to User model funcs --- server/config/passport.js | 4 ++-- server/controllers/user.controller.js | 2 +- server/models/user.js | 31 ++++++++++++++++----------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/server/config/passport.js b/server/config/passport.js index 21b686c10e..ae302084db 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -114,7 +114,7 @@ passport.use(new GitHubStrategy({ existingEmailUser.save(saveErr => done(null, existingEmailUser)); } else { let { username } = profile; - User.findByUsername(username, true, (findByUsernameErr, existingUsernameUser) => { + User.findByUsername(username, { caseInsensitive: true }, (findByUsernameErr, existingUsernameUser) => { if (existingUsernameUser) { username = generateUniqueUsername(username); } @@ -152,7 +152,7 @@ passport.use(new GoogleStrategy({ User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => { let username = profile._json.emails[0].value.split('@')[0]; - User.findByUsername(username, true, (findByUsernameErr, existingUsernameUser) => { + User.findByUsername(username, { caseInsensitive: true }, (findByUsernameErr, existingUsernameUser) => { if (existingUsernameUser) { username = generateUniqueUsername(username); } diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index 8f43ff9eed..ee24e0f469 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -95,7 +95,7 @@ export function createUser(req, res, next) { export function duplicateUserCheck(req, res) { const checkType = req.query.check_type; const value = req.query[checkType]; - User.findByEmailOrUsername(value, true, (err, user) => { + User.findByEmailOrUsername(value, { caseInsensitive: true }, (err, user) => { if (user) { return res.json({ exists: true, diff --git a/server/models/user.js b/server/models/user.js index 00cde0610c..1e5bc32dd4 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -171,19 +171,21 @@ userSchema.statics.findByEmail = function findByEmail(email, cb) { * Queries User collection by username and returns one User document. * * @param {string} username - Username string - * @param {boolean} [caseInsensitive] - Does a caseInsensitive query, defaults to false + * @param {Object} [options] - Optional options + * @param {boolean} options.caseInsensitive - Does a caseInsensitive query, defaults to false * @callback [cb] - Optional error-first callback that passes User document * @return {Promise} - Returns Promise fulfilled by User document */ -userSchema.statics.findByUsername = function findByUsername(username, caseInsensitive, cb) { +userSchema.statics.findByUsername = function findByUsername(username, options, cb) { const query = { username }; - if (arguments.length === 3 - || (arguments.length === 2 && typeof caseInsensitive === 'boolean' && caseInsensitive)) { + if ((arguments.length === 3 && options.caseInsensitive) + || (arguments.length === 2 && typeof options === 'object' && options.caseInsensitive)) { return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb); } - return this.findOne(query, cb || caseInsensitive); + const callback = typeof options === 'function' ? options : cb; + return this.findOne(query, callback); }; /** @@ -193,23 +195,26 @@ userSchema.statics.findByUsername = function findByUsername(username, caseInsens * a username or email. * * @param {string} value - Email or username - * @param {boolean} caseInsensitive - Does a caseInsensitive query rather than - * default query for username or email, defaults - * to false + * @param {Object} [options] - Optional options + * @param {boolean} options.caseInsensitive - Does a caseInsensitive query rather than + * default query for username or email, defaults + * to false * @callback [cb] - Optional error-first callback that passes User document * @return {Promise} - Returns Promise fulfilled by User document */ -userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, caseInsensitive, cb) { +userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, options, cb) { + // do the case insensitive stuff const isEmail = value.indexOf('@') > -1; - if (arguments.length === 3 - || (arguments.length === 2 && typeof caseInsensitive === 'boolean' && caseInsensitive)) { + if ((arguments.length === 3 && options.caseInsensitive) + || (arguments.length === 2 && typeof options === 'object' && options.caseInsensitive)) { const query = isEmail ? { email: value } : { username: value }; return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb); } + const callback = typeof options === 'function' ? options : cb; if (isEmail) { - return this.findByEmail(value, cb || caseInsensitive); + return this.findByEmail(value, callback); } - return this.findByUsername(value, cb || caseInsensitive); + return this.findByUsername(value, callback); }; /** From e5f5a2e3c8a731594e0941aa8684fd21c96e9454 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 6 Aug 2020 12:45:04 -0400 Subject: [PATCH 04/10] [#915] Change name of SocialAuthButton params --- .../modules/User/components/SocialAuthButton.jsx | 14 +++++++------- client/modules/User/pages/AccountView.jsx | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/modules/User/components/SocialAuthButton.jsx b/client/modules/User/components/SocialAuthButton.jsx index 57789ad2e5..0e236b0bae 100644 --- a/client/modules/User/components/SocialAuthButton.jsx +++ b/client/modules/User/components/SocialAuthButton.jsx @@ -42,11 +42,11 @@ const StyledButton = styled(Button)` width: ${remSize(300)}; `; -function SocialAuthButton({ service, link, connected }) { +function SocialAuthButton({ service, linkStyle, isConnected }) { const ServiceIcon = icons[service]; let label; - if (link) { - label = connected ? linkLabels[service].connected : linkLabels[service].connect; + if (linkStyle) { + label = isConnected ? linkLabels[service].connected : linkLabels[service].connect; } else { label = labels[service]; } @@ -64,13 +64,13 @@ SocialAuthButton.services = services; SocialAuthButton.propTypes = { service: PropTypes.oneOf(['github', 'google']).isRequired, - link: PropTypes.bool, - connected: PropTypes.bool + linkStyle: PropTypes.bool, + isConnected: PropTypes.bool }; SocialAuthButton.defaultProps = { - link: false, - connected: false + linkStyle: false, + isConnected: false }; export default SocialAuthButton; diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 134a164026..73f4e2f99f 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -24,13 +24,13 @@ function SocialLoginPanel(props) {
From bb8c4a238356b266b31065f3568d312812783fad Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Tue, 15 Sep 2020 16:06:02 -0400 Subject: [PATCH 05/10] Link OAuth accounts with different email from account email --- server/config/passport.js | 150 ++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 62 deletions(-) diff --git a/server/config/passport.js b/server/config/passport.js index ae302084db..b9b3c946ef 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -96,6 +96,10 @@ passport.use(new GitHubStrategy({ }, (req, accessToken, refreshToken, profile, done) => { User.findOne({ github: profile.id }, (findByGithubErr, existingUser) => { if (existingUser) { + if (req.user && req.user.email !== existingUser.email) { + done(new Error('GitHub account is already linked to another account.')); + return; + } done(null, existingUser); return; } @@ -103,32 +107,41 @@ passport.use(new GitHubStrategy({ const emails = getVerifiedEmails(profile.emails); const primaryEmail = getPrimaryEmail(profile.emails); - User.findByEmail(emails, (findByEmailErr, existingEmailUser) => { - if (existingEmailUser) { - existingEmailUser.email = existingEmailUser.email || primaryEmail; - existingEmailUser.github = profile.id; - existingEmailUser.username = existingEmailUser.username || profile.username; - existingEmailUser.tokens.push({ kind: 'github', accessToken }); - existingEmailUser.name = existingEmailUser.name || profile.displayName; - existingEmailUser.verified = User.EmailConfirmation.Verified; - existingEmailUser.save(saveErr => done(null, existingEmailUser)); - } else { - let { username } = profile; - User.findByUsername(username, { caseInsensitive: true }, (findByUsernameErr, existingUsernameUser) => { - if (existingUsernameUser) { - username = generateUniqueUsername(username); - } - const user = new User(); - user.email = primaryEmail; - user.github = profile.id; - user.username = profile.username; - user.tokens.push({ kind: 'github', accessToken }); - user.name = profile.displayName; - user.verified = User.EmailConfirmation.Verified; - user.save(saveErr => done(null, user)); - }); + if (req.user) { + if (!req.user.github) { + req.user.github = profile.id; + req.user.tokens.push({ kind: 'github', accessToken }); + req.user.verified = User.EmailConfirmation.Verified; } - }); + req.user.save(saveErr => done(null, req.user)); + } else { + User.findByEmail(emails, (findByEmailErr, existingEmailUser) => { + if (existingEmailUser) { + existingEmailUser.email = existingEmailUser.email || primaryEmail; + existingEmailUser.github = profile.id; + existingEmailUser.username = existingEmailUser.username || profile.username; + existingEmailUser.tokens.push({ kind: 'github', accessToken }); + existingEmailUser.name = existingEmailUser.name || profile.displayName; + existingEmailUser.verified = User.EmailConfirmation.Verified; + existingEmailUser.save(saveErr => done(null, existingEmailUser)); + } else { + let { username } = profile; + User.findByUsername(username, { caseInsensitive: true }, (findByUsernameErr, existingUsernameUser) => { + if (existingUsernameUser) { + username = generateUniqueUsername(username); + } + const user = new User(); + user.email = primaryEmail; + user.github = profile.id; + user.username = profile.username; + user.tokens.push({ kind: 'github', accessToken }); + user.name = profile.displayName; + user.verified = User.EmailConfirmation.Verified; + user.save(saveErr => done(null, user)); + }); + } + }); + } }); })); @@ -144,49 +157,62 @@ passport.use(new GoogleStrategy({ }, (req, accessToken, refreshToken, profile, done) => { User.findOne({ google: profile._json.emails[0].value }, (findByGoogleErr, existingUser) => { if (existingUser) { + if (req.user && req.user.email !== existingUser.email) { + done(new Error('Google account is already linked to another account.')); + return; + } done(null, existingUser); return; } const primaryEmail = profile._json.emails[0].value; - User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => { - let username = profile._json.emails[0].value.split('@')[0]; - User.findByUsername(username, { caseInsensitive: true }, (findByUsernameErr, existingUsernameUser) => { - if (existingUsernameUser) { - username = generateUniqueUsername(username); - } - // what if a username is already taken from the display name too? - // then, append a random friendly word? - if (existingEmailUser) { - existingEmailUser.email = existingEmailUser.email || primaryEmail; - existingEmailUser.google = profile._json.emails[0].value; - existingEmailUser.username = existingEmailUser.username || username; - existingEmailUser.tokens.push({ kind: 'google', accessToken }); - existingEmailUser.name = existingEmailUser.name || profile._json.displayName; - existingEmailUser.verified = User.EmailConfirmation.Verified; - existingEmailUser.save((saveErr) => { - if (saveErr) { - console.log(saveErr); - } - done(null, existingEmailUser); - }); - } else { - const user = new User(); - user.email = primaryEmail; - user.google = profile._json.emails[0].value; - user.username = username; - user.tokens.push({ kind: 'google', accessToken }); - user.name = profile._json.displayName; - user.verified = User.EmailConfirmation.Verified; - user.save((saveErr) => { - if (saveErr) { - console.log(saveErr); - } - done(null, user); - }); - } + if (req.user) { + if (!req.user.google) { + req.user.google = profile._json.emails[0].value; + req.user.tokens.push({ kind: 'google', accessToken }); + req.user.verified = User.EmailConfirmation.Verified; + } + req.user.save(saveErr => done(null, req.user)); + } else { + User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => { + let username = profile._json.emails[0].value.split('@')[0]; + User.findByUsername(username, { caseInsensitive: true }, (findByUsernameErr, existingUsernameUser) => { + if (existingUsernameUser) { + username = generateUniqueUsername(username); + } + // what if a username is already taken from the display name too? + // then, append a random friendly word? + if (existingEmailUser) { + existingEmailUser.email = existingEmailUser.email || primaryEmail; + existingEmailUser.google = profile._json.emails[0].value; + existingEmailUser.username = existingEmailUser.username || username; + existingEmailUser.tokens.push({ kind: 'google', accessToken }); + existingEmailUser.name = existingEmailUser.name || profile._json.displayName; + existingEmailUser.verified = User.EmailConfirmation.Verified; + existingEmailUser.save((saveErr) => { + if (saveErr) { + console.log(saveErr); + } + done(null, existingEmailUser); + }); + } else { + const user = new User(); + user.email = primaryEmail; + user.google = profile._json.emails[0].value; + user.username = username; + user.tokens.push({ kind: 'google', accessToken }); + user.name = profile._json.displayName; + user.verified = User.EmailConfirmation.Verified; + user.save((saveErr) => { + if (saveErr) { + console.log(saveErr); + } + done(null, user); + }); + } + }); }); - }); + } }); })); From 170e20125bfe350d17d1a2156648045e57bf9d81 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Tue, 15 Sep 2020 17:07:41 -0400 Subject: [PATCH 06/10] Redirect to /account when linking fails --- server/server.js | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/server/server.js b/server/server.js index d82aa6d8e3..b25948eaa6 100644 --- a/server/server.js +++ b/server/server.js @@ -136,18 +136,45 @@ app.use(assetRoutes); app.use('/', embedRoutes); app.get('/auth/github', passport.authenticate('github')); -app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => { - res.redirect('/'); +app.get('/auth/github/callback', (req, res, next) => { + passport.authenticate('github', { failureRedirect: '/login' }, (err, user) => { + if (err) { + // use query string param to show error; + res.redirect('/account'); + return; + } + + req.logIn(user, (loginErr) => { + if (loginErr) { + next(loginErr); + return; + } + res.redirect('/'); + }); + })(req, res, next); }); app.get('/auth/google', passport.authenticate('google')); -app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => { - res.redirect('/'); +app.get('/auth/google/callback', (req, res, next) => { + passport.authenticate('google', { failureRedirect: '/login' }, (err, user) => { + if (err) { + // use query string param to show error; + res.redirect('/account'); + return; + } + + req.logIn(user, (loginErr) => { + if (loginErr) { + next(loginErr); + return; + } + res.redirect('/'); + }); + })(req, res, next); }); // configure passport require('./config/passport'); -// const passportConfig = require('./config/passport'); // Connect to MongoDB mongoose.Promise = global.Promise; @@ -190,7 +217,7 @@ app.get('*', (req, res) => { // start app app.listen(process.env.PORT, (error) => { if (!error) { - console.log(`p5js web editor is running on port: ${process.env.PORT}!`); // eslint-disable-line + console.log(`p5.js Web Editor is running on port: ${process.env.PORT}!`); // eslint-disable-line } }); From bcf25c1f37b464a55b2d2d1d9a4e26f8aa8cb5e8 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Fri, 18 Sep 2020 14:51:11 -0400 Subject: [PATCH 07/10] Show error popup when linking OAuth accounts --- client/modules/IDE/components/ErrorModal.jsx | 11 +++++ .../modules/User/components/AccountForm.jsx | 2 +- client/modules/User/pages/AccountView.jsx | 31 +++++++++++-- client/styles/components/_error-modal.scss | 6 ++- package-lock.json | 46 +++++++++++++++++-- package.json | 1 + server/server.js | 4 +- 7 files changed, 89 insertions(+), 12 deletions(-) diff --git a/client/modules/IDE/components/ErrorModal.jsx b/client/modules/IDE/components/ErrorModal.jsx index 099065a549..4706130ed2 100644 --- a/client/modules/IDE/components/ErrorModal.jsx +++ b/client/modules/IDE/components/ErrorModal.jsx @@ -15,6 +15,15 @@ class ErrorModal extends React.Component { ); } + oauthError() { + return ( +

+ {'There was an error linking your GitHub account. This account has already been linked'} + {' to another account.'} +

+ ); + } + staleSession() { return (

@@ -42,6 +51,8 @@ class ErrorModal extends React.Component { return this.staleSession(); } else if (this.props.type === 'staleProject') { return this.staleProject(); + } else if (this.props.type === 'oauthError') { + return this.oauthError(); } })()} diff --git a/client/modules/User/components/AccountForm.jsx b/client/modules/User/components/AccountForm.jsx index c1bfe6dbfc..9682171b44 100644 --- a/client/modules/User/components/AccountForm.jsx +++ b/client/modules/User/components/AccountForm.jsx @@ -115,7 +115,7 @@ AccountForm.propTypes = { newPassword: PropTypes.object.isRequired, // eslint-disable-line }).isRequired, user: PropTypes.shape({ - verified: PropTypes.number.isRequired, + verified: PropTypes.string.isRequired, emailVerificationInitiate: PropTypes.bool.isRequired, }).isRequired, handleSubmit: PropTypes.func.isRequired, diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 126c6bc464..5f6da3f472 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -5,6 +5,8 @@ import { bindActionCreators } from 'redux'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Helmet } from 'react-helmet'; import { withTranslation } from 'react-i18next'; +import { withRouter, browserHistory } from 'react-router'; +import { parse } from 'query-string'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import AccountForm from '../components/AccountForm'; import apiClient from '../../../utils/apiClient'; @@ -12,6 +14,8 @@ import { validateSettings } from '../../../utils/reduxFormUtils'; import SocialAuthButton from '../components/SocialAuthButton'; import APIKeyForm from '../components/APIKeyForm'; import Nav from '../../../components/Nav'; +import ErrorModal from '../../IDE/components/ErrorModal'; +import Overlay from '../../App/components/Overlay'; function SocialLoginPanel(props) { const { user } = props; @@ -53,6 +57,9 @@ class AccountView extends React.Component { } render() { + const queryParams = parse(this.props.location.search); + const showError = !!queryParams.error; + const errorType = queryParams.error; const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED; return ( @@ -63,6 +70,20 @@ class AccountView extends React.Component {