From b223a4dba1c744186fbadce349490760d35357c2 Mon Sep 17 00:00:00 2001 From: Darin Alleman Date: Sun, 17 Jan 2021 22:05:39 -0500 Subject: [PATCH 1/5] Modify controller to check publicity status before returning --- api/controllers/pack.js | 16 +++++++++++++--- api/models/pack.js | 3 +++ api/utils/jwt.js | 24 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/api/controllers/pack.js b/api/controllers/pack.js index 9ba1c10..d3ee97e 100644 --- a/api/controllers/pack.js +++ b/api/controllers/pack.js @@ -4,11 +4,11 @@ import Sequelize from 'sequelize'; let router = express.Router(); import models from '../models'; -import { authenticate } from "../utils/jwt"; +import { authenticate, authenicatePublicRequest } from "../utils/jwt"; import { csvItems, packItemPayload, packPayload } from "../utils/build-payload"; // Get -router.get('/:id', async (req, res) => { +router.get('/:id', authenicatePublicRequest, async (req, res) => { let { id } = req.params; models.Pack.findOne({ where: { id }, @@ -21,7 +21,17 @@ router.get('/:id', async (req, res) => { { model: models.User, attributes: ['id', 'username'] }, ] }) - .then(pack => res.json(pack)) + .then(pack => { + if (pack.public) { + res.json(pack); + } else { //when the pack is private, only allow the owner of the pack to view + if (req.user && pack.userId == req.user.id) { + res.json(pack); + } else { + res.sendStatus(401); + } + } + }) .catch(err => res.json(err)); }); diff --git a/api/models/pack.js b/api/models/pack.js index db93b56..6b41dce 100644 --- a/api/models/pack.js +++ b/api/models/pack.js @@ -43,6 +43,9 @@ const pack = (sequelize, DataTypes) => { }, gender: { type: DataTypes.ENUM(Object.values(gender)) + }, + userId: { + type: DataTypes.INTEGER } }); diff --git a/api/utils/jwt.js b/api/utils/jwt.js index e7b9c47..50406ef 100644 --- a/api/utils/jwt.js +++ b/api/utils/jwt.js @@ -36,4 +36,26 @@ export const authenticate = (req, res, next) => { next(); }) .catch(() => res.sendStatus(400)); -}; \ No newline at end of file +}; + +/* +This method will authenticate the user if one is logged in, but does not return a 400/401 +if there the user is not logged in to allow for public viewing of packs. +This could also be used for public/private profiles. +*/ +export const authenicatePublicRequest = (req, res, next) => { + const authHeader = req.get('authorization'); + if (!authHeader) { + req.user = undefined; + next(); + } else { + const token = authHeader.split(' ')[1]; + const decoded = jwt.verify(token, process.env.JWT_SECRET); + models.User.findOne({where: {id: decoded.id}}) + .then(user => { + req.user = user; + next(); + }) + .catch(() => res.sendStatus(400)); + } +} \ No newline at end of file From 0e660ea71eb837eb3b04ecfcb58a1289bddfef4b Mon Sep 17 00:00:00 2001 From: Darin Alleman Date: Fri, 22 Jan 2021 20:13:53 -0500 Subject: [PATCH 2/5] Fully functional, but still needs to be styled --- frontend/src/app/PackForm/PackForm.tsx | 8 ++++++- .../components/FormFields/CheckboxInput.tsx | 23 +++++++++++++++++++ .../src/app/components/FormFields/styles.ts | 6 ++++- frontend/src/types/pack.ts | 1 + 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/components/FormFields/CheckboxInput.tsx diff --git a/frontend/src/app/PackForm/PackForm.tsx b/frontend/src/app/PackForm/PackForm.tsx index ddd5b44..2724cf5 100644 --- a/frontend/src/app/PackForm/PackForm.tsx +++ b/frontend/src/app/PackForm/PackForm.tsx @@ -22,6 +22,7 @@ import Loading from "app/components/Loading"; import { useSidebar } from "app/components/Sidebar/Context"; import { PageTitle, Controls, Box, Grid } from "styles/common"; +import CheckboxInput from "app/components/FormFields/CheckboxInput"; const PackForm: React.FC = ({ history, packId, getPack, exportItems, getItems, createPack, updatePack, user }) => { const [loading, setLoading] = React.useState(true); @@ -115,7 +116,8 @@ const PackForm: React.FC = ({ history, packId, getPack, exp duration_unit: packData ? packData.duration_unit : undefined, temp_range: packData ? packData.temp_range : '', season: packData ? packData.season : '', - gender: packData ? packData.gender : undefined + gender: packData ? packData.gender : undefined, + public: packData ? packData.public : false }} validationSchema={Yup.object().shape({ title: Yup.string().required("Trail name or location is required.") @@ -232,6 +234,10 @@ const PackForm: React.FC = ({ history, packId, getPack, exp /> + setFieldValue('public', v)} + > diff --git a/frontend/src/app/components/FormFields/CheckboxInput.tsx b/frontend/src/app/components/FormFields/CheckboxInput.tsx new file mode 100644 index 0000000..9aaf59b --- /dev/null +++ b/frontend/src/app/components/FormFields/CheckboxInput.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { Checkbox } from 'antd'; +import { SharedInputProps } from "./types"; +import { InputContainer } from "./utils"; + +interface CheckboxProps extends SharedInputProps { + checked: boolean; + onChange: (checked: boolean) => void; +} + +const CheckboxInput: React.FC = ({ checked, onChange, label, tip, style }) => { + const handleChange = (value: any) => onChange(value.target.checked); + return ( + + + + ); +}; + +export default CheckboxInput; \ No newline at end of file diff --git a/frontend/src/app/components/FormFields/styles.ts b/frontend/src/app/components/FormFields/styles.ts index 27576f1..40d1b00 100644 --- a/frontend/src/app/components/FormFields/styles.ts +++ b/frontend/src/app/components/FormFields/styles.ts @@ -108,4 +108,8 @@ export const selectStyles: StylesConfig = { ...p, color: '#ccc' }) -}; \ No newline at end of file +}; + +export const checkboxStyles: StylesConfig = { + +} diff --git a/frontend/src/types/pack.ts b/frontend/src/types/pack.ts index 7d2b55f..e48aff2 100644 --- a/frontend/src/types/pack.ts +++ b/frontend/src/types/pack.ts @@ -10,6 +10,7 @@ export interface BasePack { temp_range?: string; season?: string; gender?: Gender; + public: boolean; } export interface Pack extends BasePack { From 285d2545806d55c95597024cf1365b176f4f949a Mon Sep 17 00:00:00 2001 From: Darin Alleman Date: Sat, 23 Jan 2021 11:47:20 -0500 Subject: [PATCH 3/5] Add styling --- frontend/src/app/PackForm/PackForm.tsx | 8 ++++---- frontend/src/app/components/FormFields/CheckboxInput.tsx | 2 +- frontend/src/app/components/FormFields/types.ts | 1 + frontend/src/app/components/FormFields/utils.tsx | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/PackForm/PackForm.tsx b/frontend/src/app/PackForm/PackForm.tsx index 2724cf5..f0b86f5 100644 --- a/frontend/src/app/PackForm/PackForm.tsx +++ b/frontend/src/app/PackForm/PackForm.tsx @@ -230,13 +230,13 @@ const PackForm: React.FC = ({ history, packId, getPack, exp label: values.gender }} onChange={(option: Option) => setFieldValue('gender', option.value)} - last={true} /> - setFieldValue('public', v)} + setFieldValue('public', !v)} + tip="When unchecked, the pack will be viewable by anyone with a link" > diff --git a/frontend/src/app/components/FormFields/CheckboxInput.tsx b/frontend/src/app/components/FormFields/CheckboxInput.tsx index 9aaf59b..8bce6dd 100644 --- a/frontend/src/app/components/FormFields/CheckboxInput.tsx +++ b/frontend/src/app/components/FormFields/CheckboxInput.tsx @@ -11,7 +11,7 @@ interface CheckboxProps extends SharedInputProps { const CheckboxInput: React.FC = ({ checked, onChange, label, tip, style }) => { const handleChange = (value: any) => onChange(value.target.checked); return ( - + void; } \ No newline at end of file diff --git a/frontend/src/app/components/FormFields/utils.tsx b/frontend/src/app/components/FormFields/utils.tsx index 7fe465b..28d0d83 100644 --- a/frontend/src/app/components/FormFields/utils.tsx +++ b/frontend/src/app/components/FormFields/utils.tsx @@ -18,10 +18,10 @@ const tooltip = (tip: string) => ( ); -export const InputContainer: React.FC = ({ label, errorMsg, error, tip, last, style, children }) => ( +export const InputContainer: React.FC = ({ label, errorMsg, error, tip, last, style, children, labelStyle }) => ( {label && ( - - setFieldValue('public', !v)} - tip="When unchecked, the pack will be viewable by anyone with a link" - > + setFieldValue('public', v)} + tip="When public, the pack will be viewable by anyone with a link" + > diff --git a/frontend/src/app/components/FormFields/CheckboxInput.tsx b/frontend/src/app/components/FormFields/CheckboxInput.tsx deleted file mode 100644 index 8bce6dd..0000000 --- a/frontend/src/app/components/FormFields/CheckboxInput.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import { Checkbox } from 'antd'; -import { SharedInputProps } from "./types"; -import { InputContainer } from "./utils"; - -interface CheckboxProps extends SharedInputProps { - checked: boolean; - onChange: (checked: boolean) => void; -} - -const CheckboxInput: React.FC = ({ checked, onChange, label, tip, style }) => { - const handleChange = (value: any) => onChange(value.target.checked); - return ( - - - - ); -}; - -export default CheckboxInput; \ No newline at end of file diff --git a/frontend/src/app/components/FormFields/SwitchInput.tsx b/frontend/src/app/components/FormFields/SwitchInput.tsx new file mode 100644 index 0000000..bb04b1a --- /dev/null +++ b/frontend/src/app/components/FormFields/SwitchInput.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { Switch } from 'antd'; +import { SharedInputProps } from "./types"; +import { InputContainer } from "./utils"; + +interface SwitchProps extends SharedInputProps { + checked: boolean; + checkedText?: string | undefined; + uncheckedText?: string | undefined; + onChange: (checked: boolean) => void; +} + +const SwitchInput: React.FC = ({ checked, checkedText, uncheckedText, onChange, label, tip, style }) => { + const handleChange = (value: any) => onChange(value); + return ( + + + + ); +}; + +export default SwitchInput; \ No newline at end of file From 352a59ff3d04199eae4e000b7189e5c728f6dce9 Mon Sep 17 00:00:00 2001 From: Darin Alleman Date: Tue, 26 Jan 2021 21:27:36 -0500 Subject: [PATCH 5/5] Add full page error view Add indicator to owner's pack view of private pack --- api/controllers/pack.js | 2 +- frontend/src/app/Pack/Pack.tsx | 17 ++++++++++++++-- .../app/components/FullPageError/index.tsx | 20 +++++++++++++++++++ .../src/app/components/Statistics/index.tsx | 3 ++- 4 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 frontend/src/app/components/FullPageError/index.tsx diff --git a/api/controllers/pack.js b/api/controllers/pack.js index d3ee97e..0be6d83 100644 --- a/api/controllers/pack.js +++ b/api/controllers/pack.js @@ -28,7 +28,7 @@ router.get('/:id', authenicatePublicRequest, async (req, res) => { if (req.user && pack.userId == req.user.id) { res.json(pack); } else { - res.sendStatus(401); + res.sendStatus(403); //return 'forbidden' } } }) diff --git a/frontend/src/app/Pack/Pack.tsx b/frontend/src/app/Pack/Pack.tsx index f8d2825..6960080 100644 --- a/frontend/src/app/Pack/Pack.tsx +++ b/frontend/src/app/Pack/Pack.tsx @@ -21,9 +21,11 @@ import Items from './Items'; import { getWeightByCategory } from 'lib/utils/weight'; import { Credit, PackWrapper, SectionHeader, SectionTitle, TripDescription } from "./styles"; +import FullPageError from 'app/components/FullPageError'; const Pack: React.FC = ({ getPack, weightUnit, packId }) => { const [loading, setLoading] = React.useState(true); + const [packForbidden, setPackForbidden] = React.useState(true); const [pack, setPack] = React.useState(null); const [unit, setUnit] = React.useState(weightUnit); const { dispatch } = useSidebar(); @@ -34,9 +36,16 @@ const Pack: React.FC = ({ getPack, weightUnit, packId }) => { .then(pack => { setPack(pack); setLoading(false); + setPackForbidden(false); }) - .catch(() => { - alertError({ message: 'Unable to retrieve pack.' }); + .catch(e => { + setLoading(false); + if (e.message.includes("failed with status code 403")){//when forbidden to view + setPackForbidden(true); + } + else { + alertError({ message: 'Unable to retrieve pack.' }); + } }); return function cleanup() { @@ -68,6 +77,10 @@ const Pack: React.FC = ({ getPack, weightUnit, packId }) => { if (loading) { return } + + if (packForbidden) { + return + } if (!pack) { return

Pack not found!

diff --git a/frontend/src/app/components/FullPageError/index.tsx b/frontend/src/app/components/FullPageError/index.tsx new file mode 100644 index 0000000..0b543e3 --- /dev/null +++ b/frontend/src/app/components/FullPageError/index.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { PageDescription, PageTitle } from 'styles/common'; + +interface FullPageErrorProps { + text?: string; + styles? : React.CSSProperties; +} + +const FullPageError: React.FC = ({ text, styles }) => { + const defaultStyles: React.CSSProperties = {}; + + return ( +
+

Oops!

+

{text}

+
+ ) +}; + +export default FullPageError; \ No newline at end of file diff --git a/frontend/src/app/components/Statistics/index.tsx b/frontend/src/app/components/Statistics/index.tsx index c15ea2d..4bc7ae8 100644 --- a/frontend/src/app/components/Statistics/index.tsx +++ b/frontend/src/app/components/Statistics/index.tsx @@ -24,6 +24,7 @@ const Statistics: React.FC = ({ pack }) => { return `${duration} ${duration_unit}`; }; + let packUrlLabel = "Pack URL " + (pack.public ? "" : "(private)"); const packUrl = `https://packstack.io${getPackPath(id, title)}`; return ( @@ -32,7 +33,7 @@ const Statistics: React.FC = ({ pack }) => { }/> }/> - }/> + }> )