From 711428df5aa125998258f6e280a3b14f9745fcce Mon Sep 17 00:00:00 2001 From: Matthew Walowski Date: Mon, 18 Jan 2021 12:21:17 -0600 Subject: [PATCH] Attribute editing fix (#37) * Create basic endpoint skeleton * get endpoint 500 error * Fix routing * Get member endpoint to work * Return more fields from endpoint * Return object id from endpoint * Add filterSensitiveInfo util * Give all non-required Member fields a default of null * Move passport-setup to utils * Remove console.log * Add members endpoints * Fix filterSensitiveInfo util * Use passport-setup.js in utils * Use auth endpoint * Move /api/auth/user to /api/members/current * Add omit fields to GET /:memberId * Change current user endpoint in frontend * Add more auth utils * Use isDirector from module in members routes * Change level default in model back to TBD * Add level priority explanation * Add detailed permission checking in members endpoint * Fix difference function * Fix typos in user-utils * Add _id as neverEditable field * Fix allFields not being checked correctly * Filter viewable fields when returning from PUT * Pull enum options from backend * Add status options to endpoint * Disable dropdowns * Add labels to fields * Create wrapper for getting member by ID * Add boolean selector * Add number fields * Add fields for basic string attributes * Retrieve schema type from DB * Move preprocessing to backend * Fix enum type detection * Remove duplicated code * Fix attribute change error * Run formatter * Remove warnings * Format client * Populate edit fields from DB * Add types to props * Run formatter * Remove alert * Run formatter * Get permissions from backend * Disable input boxes if missing permissions * Add enum dropdowns * Format client * Remove user ID override * Change to ES6 defaults * Remove unused package * Change var to let * Concisen member options endpoint * Delete env file * Unexport schema * Use concise property iteration * Make /options endpoint concise * Remove unneeded exports * Capitalize constant * Shorten attribute change callback * Run formatter * Turn off default-props in linter * Fix var name typo * Change member page routing * Run formatter * Use useParams hook * Change object syntax * Run formatter * Start creating tool to transfer data * Consolidate envs and clean up app.js * Fix yarn test not using .env * Add lint-staged pre-commit hook (#27) * Add lint-staged pre-commit hook * Format "unstaged" files * Sync prettier versions * Format all files using new Prettier config * Finish writing tool * Format remaining fields * Check for member uniqueness * Fix enum formatting * Run formatter * Run formatter * Run format with new hook * Change new sign-in process to attempt to link user via email * Show login error on frontend * Remove unused Member page (renamed) * Fix navbar welcome text spacing * Fix lint * Switch to cookie-session * Change date attribute prop type to number * Allow numbers for string attribute value * Change enum attribute prop types * Add keys to fields * Only pass string to enum callback * Fix default enum attribute option * Rename StringAttribute to TextAttribute Co-authored-by: ishaansharma Co-authored-by: Jeffrey Tang Co-authored-by: Yousef Ahmed --- api/package.json | 2 +- api/src/api/auth.js | 1 - api/src/app.js | 18 ++--- api/src/utils/passport-setup.js | 34 +++++++--- api/src/utils/user-utils.js | 4 +- api/yarn.lock | 67 +++++++++---------- .../EditableAttribute/DateAttribute.js | 4 +- .../EditableAttribute/EnumAttribute.js | 25 +++++-- .../{StringAttribute.js => TextAttribute.js} | 8 +-- client/src/components/navbar/Navbar.js | 5 +- client/src/pages/Login.js | 51 ++++++++++---- client/src/pages/Profile.js | 13 ++-- 12 files changed, 136 insertions(+), 96 deletions(-) rename client/src/components/EditableAttribute/{StringAttribute.js => TextAttribute.js} (80%) diff --git a/api/package.json b/api/package.json index e96a6dc..956f2fd 100644 --- a/api/package.json +++ b/api/package.json @@ -12,12 +12,12 @@ "test": "jest --setupFiles dotenv/config" }, "dependencies": { + "cookie-session": "^1.4.0", "cors": "^2.8.5", "csvtojson": "^2.0.10", "debug": "~2.6.9", "dotenv": "^8.2.0", "express": "~4.16.1", - "express-session": "^1.17.1", "helmet": "^3.21.1", "http-errors": "~1.6.3", "if-env": "^1.0.4", diff --git a/api/src/api/auth.js b/api/src/api/auth.js index 2fe5149..2818728 100644 --- a/api/src/api/auth.js +++ b/api/src/api/auth.js @@ -61,7 +61,6 @@ router.get( const auth = passport.authenticate('google', { successRedirect, failureRedirect, - failureFlash: true, }); auth(req, res, next); }, diff --git a/api/src/app.js b/api/src/app.js index 5fe1231..354f215 100644 --- a/api/src/app.js +++ b/api/src/app.js @@ -3,7 +3,7 @@ const cors = require('cors'); const express = require('express'); const helmet = require('helmet'); const logger = require('morgan'); -const session = require('express-session'); +const cookieSession = require('cookie-session'); const bodyParser = require('body-parser'); const passport = require('passport'); const apiRoutes = require('./api'); @@ -24,14 +24,14 @@ app.use(bodyParser.json({ limit: '2.1mb' })); app.use(bodyParser.urlencoded({ limit: '2.1mb', extended: false })); // Session support, needed for authentication -app.use( - session({ - secret: process.env.SESSION_SECRET, - cookie: {}, - resave: false, - saveUninitialized: false, - }), -); +const sessionConfig = { + secret: process.env.SESSION_SECRET, +}; +if (environment == 'production') { + app.set('trust proxy', 1); + sessionConfig.secure = true; +} +app.use(cookieSession(sessionConfig)); // Mongo setup require('./utils/mongo-setup'); diff --git a/api/src/utils/passport-setup.js b/api/src/utils/passport-setup.js index 8de969d..d2c714e 100644 --- a/api/src/utils/passport-setup.js +++ b/api/src/utils/passport-setup.js @@ -2,9 +2,6 @@ const GoogleStrategy = require('passport-google-oauth20').Strategy; const passport = require('passport'); const Member = require('../models/member'); -// Defines the default level a user gets assigned with upon first sign-in -const DEFAULT_LEVEL = process.env.DEFAULT_LEVEL || Member.levelEnum.TBD; - passport.serializeUser((user, done) => { done(null, user._id); }); @@ -33,17 +30,32 @@ passport.use( if (user) { // user exists - return cb(null, user); + cb(null, user); } else { - const newUser = await new Member({ - firstName: profile.name.givenName, - lastName: profile.name.familyName, - oauthID: profile.id, + // try to find a user with the same email + const unlinkedUser = await Member.findOne({ email: profile.emails[0].value, - level: DEFAULT_LEVEL, - }).save(); + oauthID: null, + }); + + if (!unlinkedUser) { + // no unlinked user with matching email found + // don't link and reject authentication + cb(null, false); + } else { + // user with matching email found + // link user to oauthID and fill in name + unlinkedUser.oauthID = profile.id; + if (!unlinkedUser.firstName) { + unlinkedUser.firstName = profile.name.givenName; + } + if (!unlinkedUser.lastName) { + unlinkedUser.lastName = profile.name.familyName; + } + unlinkedUser.save(); - cb(null, newUser); + cb(null, unlinkedUser); + } } }, ), diff --git a/api/src/utils/user-utils.js b/api/src/utils/user-utils.js index 7148cc6..de5f87b 100644 --- a/api/src/utils/user-utils.js +++ b/api/src/utils/user-utils.js @@ -18,7 +18,7 @@ const getViewableFields = (currentUser, memberId) => { const viewableFields = difference(allFields, neverViewableFields); if (isDirector(currentUser)) { return viewableFields; - } else if (currentUser._id == memberId) { + } else if (currentUser._id.toString() === memberId) { return viewableFields; } else { return difference(viewableFields, nonViewableFields); @@ -29,7 +29,7 @@ const getEditableFields = (currentUser, memberId) => { const editableFields = difference(allFields, neverEditableFields); if (isDirector(currentUser)) { return editableFields; - } else if (currentUser._id == memberId) { + } else if (currentUser._id.toString() === memberId) { return difference(editableFields, nonEditableFields); } else { return []; // Non-directors can never edit other users' info diff --git a/api/yarn.lock b/api/yarn.lock index 70b3aac..fe191a1 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -1259,6 +1259,15 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +cookie-session@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cookie-session/-/cookie-session-1.4.0.tgz#c325aea685ceb9c8e4fd00b0313a46d547747380" + integrity sha512-0hhwD+BUIwMXQraiZP/J7VP2YFzqo6g4WqZlWHtEHQ22t0MeZZrNBSCxC1zcaLAs8ApT3BzAKizx9gW/AP9vNA== + dependencies: + cookies "0.8.0" + debug "2.6.9" + on-headers "~1.0.2" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1269,16 +1278,19 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - cookiejar@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== +cookies@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" + integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow== + dependencies: + depd "~2.0.0" + keygrip "~1.1.0" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -1827,20 +1839,6 @@ expect@^25.5.0: jest-message-util "^25.5.0" jest-regex-util "^25.2.6" -express-session@^1.17.1: - version "1.17.1" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" - integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== - dependencies: - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~2.0.0" - on-headers "~1.0.2" - parseurl "~1.3.3" - safe-buffer "5.2.0" - uid-safe "~2.1.5" - express@~4.16.1: version "4.16.4" resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" @@ -3231,6 +3229,13 @@ kareem@2.3.2: resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93" integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ== +keygrip@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" + integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== + dependencies: + tsscmp "1.0.6" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -3862,7 +3867,7 @@ parse5@5.1.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.2: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -4082,11 +4087,6 @@ qs@^6.5.1: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== -random-bytes@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" - integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= - range-parser@~1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -4379,11 +4379,6 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -5011,6 +5006,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tsscmp@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -5065,13 +5065,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -uid-safe@~2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" - integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== - dependencies: - random-bytes "~1.0.0" - uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" diff --git a/client/src/components/EditableAttribute/DateAttribute.js b/client/src/components/EditableAttribute/DateAttribute.js index b5e33ef..7908d9e 100644 --- a/client/src/components/EditableAttribute/DateAttribute.js +++ b/client/src/components/EditableAttribute/DateAttribute.js @@ -4,7 +4,7 @@ import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; const DateAttribute = ({ - value = '', + value = 0, attributeLabel = '', isDisabled = false, onChange, @@ -26,7 +26,7 @@ const DateAttribute = ({ }; DateAttribute.propTypes = { - value: PropTypes.string, + value: PropTypes.number, attributeLabel: PropTypes.string, isDisabled: PropTypes.bool, onChange: PropTypes.func.isRequired, diff --git a/client/src/components/EditableAttribute/EnumAttribute.js b/client/src/components/EditableAttribute/EnumAttribute.js index ddc2bcd..a574d85 100644 --- a/client/src/components/EditableAttribute/EnumAttribute.js +++ b/client/src/components/EditableAttribute/EnumAttribute.js @@ -2,24 +2,30 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; +const defaultDropdownOption = { label: '', value: '' }; + const EnumAttribute = ({ - value = '', + value = defaultDropdownOption.value, valueOptions = [], attributeLabel = '', isDisabled = false, onChange, }) => { - const onValueChange = (option) => { - onChange(option, attributeLabel); + const onValueChange = (selectedOption) => { + onChange(selectedOption.value, attributeLabel); + }; + + const getOptionFromValue = (val) => { + const dropdownOption = valueOptions.find((option) => option.value === val); + if (dropdownOption) return dropdownOption; + return defaultDropdownOption; }; return (

{attributeLabel}