Skip to content

Commit a7c794d

Browse files
authored
Refactor createTier, createSponsor, createCompany + add logging (VandyHacks#526)
* Small refactor * Update logging, some logic * Add unique name checks to tier/company * Add httpOnly cookies setting * Set more cookie options
1 parent db61d45 commit a7c794d

File tree

7 files changed

+109
-84
lines changed

7 files changed

+109
-84
lines changed

src/client/routes/dashboard/OrganizerDash.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Bar, Pie, ChartData } from 'react-chartjs-2';
33
import { Link } from 'react-router-dom';
44
import gql from 'graphql-tag';
55
import styled from 'styled-components';
6-
import chartjs, { ChartOptions } from 'chart.js';
6+
import { ChartData as OldChartData, ChartOptions } from 'chart.js';
77

88
import Spinner from '../../components/Loading/Spinner';
99
import { GraphQLErrorMessage } from '../../components/Text/ErrorMessage';
@@ -111,7 +111,7 @@ const colorPalette = STRINGS.COLOR_PALETTE.slice(1);
111111
const generateColor = (n: number): string[] =>
112112
[...Array(n).keys()].map((i: number) => colorPalette[i % colorPalette.length]);
113113

114-
const barStatusData = (data: { [key: string]: number }): ChartData<chartjs.ChartData> => {
114+
const barStatusData = (data: { [key: string]: number }): ChartData<OldChartData> => {
115115
const statusData = Object.values(data).slice(0, -1);
116116
const statusLabels = Object.keys(data).slice(0, -1);
117117
return {
@@ -164,7 +164,7 @@ const barStatusOptions: ChartOptions = {
164164
},
165165
};
166166

167-
const pieShirtData = (data: { [key: string]: number }): ChartData<chartjs.ChartData> => {
167+
const pieShirtData = (data: { [key: string]: number }): ChartData<OldChartData> => {
168168
const shirtLabels = Object.keys(data).slice(0, -1);
169169
const shirtData = Object.values(data).slice(0, -1);
170170

@@ -193,7 +193,7 @@ const pieShirtOptions = {
193193
},
194194
};
195195

196-
const pieGenderData = (data: { [key: string]: number }): ChartData<chartjs.ChartData> => {
196+
const pieGenderData = (data: { [key: string]: number }): ChartData<OldChartData> => {
197197
const genderLabels = Object.keys(data).slice(0, -1);
198198
const genderData = Object.values(data).slice(0, -1);
199199

src/client/routes/manage/SponsorHackerView.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const SponsorHackerView: FunctionComponent = (): JSX.Element => {
4848
}
4949

5050
if (false && !viewHackerTable) {
51-
// TODO: FIX
51+
// TODO: handle sponsor permissions properly
5252
return <p>You dont have permissions to view hacker information</p>;
5353
}
5454

src/server/auth/helpers.ts

+24-24
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,30 @@ import DB, { Models } from '../models';
88
import { fetchUser } from '../resolvers/helpers';
99
import logger from '../logger';
1010

11-
export async function getUserFromDb(email: string, userType?: string): Promise<UserDbInterface> {
12-
const { Hackers, Organizers, Sponsors } = await new DB().collections;
13-
14-
let user: UserDbInterface | null = null;
15-
switch (userType) {
16-
case UserType.Hacker:
17-
user = await Hackers.findOne({ email });
18-
break;
19-
case UserType.Organizer:
20-
user = await Organizers.findOne({ email });
21-
break;
22-
case UserType.Sponsor:
23-
user = await Sponsors.findOne({ email });
24-
break;
25-
default:
26-
throw new Error(`invalid userType '${userType}'`);
27-
}
28-
29-
if (!user) {
30-
throw new Error(`couldn't find user (${user}) with email ${email}`);
31-
}
32-
33-
return user;
34-
}
11+
// export async function getUserFromDb(email: string, userType?: string): Promise<UserDbInterface> {
12+
// const { Hackers, Organizers, Sponsors } = await new DB().collections;
13+
14+
// let user: UserDbInterface | null = null;
15+
// switch (userType) {
16+
// case UserType.Hacker:
17+
// user = await Hackers.findOne({ email });
18+
// break;
19+
// case UserType.Organizer:
20+
// user = await Organizers.findOne({ email });
21+
// break;
22+
// case UserType.Sponsor:
23+
// user = await Sponsors.findOne({ email });
24+
// break;
25+
// default:
26+
// throw new Error(`invalid userType '${userType}'`);
27+
// }
28+
29+
// if (!user) {
30+
// throw new Error(`couldn't find user (${user}) with email ${email}`);
31+
// }
32+
33+
// return user;
34+
// }
3535

3636
export const verifyCallback = async (
3737
models: Models,

src/server/events/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ical, { CalendarResponse, CalendarComponent, VEvent } from 'node-ical';
1+
import { fromURL, CalendarResponse, CalendarComponent, VEvent } from 'node-ical';
22
import { AuthenticationError, UserInputError } from 'apollo-server-express';
33
import { ObjectID } from 'mongodb';
44
import { EventUpdateInput, EventDbObject, CompanyDbObject } from '../generated/graphql';
@@ -54,7 +54,7 @@ export const transformCalEventToDBUpdate = (event: Record<string, string>): Even
5454
export async function pullCalendar(calendarID: string | undefined): Promise<EventUpdate[] | null> {
5555
if (calendarID) {
5656
const url = `https://www.google.com/calendar/ical/${calendarID}/public/basic.ics`;
57-
const cal = await ical.fromURL(url).catch(err => {
57+
const cal = await fromURL(url).catch(err => {
5858
throw new Error(`Could not fetch calendar at URL: ${url}; Error: ${err.message}`);
5959
});
6060
const calKeys = Object.keys(cal);

src/server/index.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,16 @@ export const schema = makeExecutableSchema({
4646
store: new (MongoStore(session))(({
4747
clientPromise: dbClient.client,
4848
} as unknown) as MongoUrlOptions),
49-
/*
50-
can't use secure cookies b/c only HTTP connection between dyno and Heroku servers,
51-
but don't need it as long as connection as Heroku servers and client are HTTPS
52-
*/
53-
// cookie: { secure: IS_PROD },
49+
cookie: {
50+
/*
51+
can't use secure cookies b/c only HTTP connection between dyno and Heroku servers,
52+
but don't need it as long as connection as Heroku servers and client are HTTPS
53+
*/
54+
// secure: IS_PROD,
55+
httpOnly: true, // protects against XSS attacks
56+
signed: true,
57+
sameSite: true,
58+
},
5459
})
5560
);
5661
app.use(passport.initialize());

src/server/nfc/index.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ObjectID } from 'mongodb';
22
import { AuthenticationError, UserInputError } from 'apollo-server-express';
33
import { HackerDbObject, UserDbInterface } from '../generated/graphql';
44
import { Models } from '../models';
5+
import logger from '../logger';
56

67
// TODO: (kenli/timliang) Expand functions for Organizers, Mentors collections
78

@@ -68,6 +69,8 @@ export async function registerNFCUIDWithUser(
6869
{ returnOriginal: false }
6970
);
7071

72+
logger.info(`registered user ${userID} with NFC id ${nfcID}`);
73+
7174
if (ret.value) {
7275
if (ret.value.secondaryIds.length > 1) {
7376
throw new Error(`Associated new NFC ID with user overriding the old NFC ID`);
@@ -106,8 +109,10 @@ export async function removeUserFromEvent(
106109
}
107110
),
108111
]);
109-
110112
if (ret[0].result.ok && ret[1].result.ok) {
113+
logger.info(
114+
`removed user ${userID} (${user.firstName} ${user.lastName}) from event ${eventID}`
115+
);
111116
return user;
112117
}
113118

@@ -156,7 +161,10 @@ export async function checkInUserToEvent(
156161
},
157162
{ returnOriginal: false }
158163
);
159-
if (retEvent.ok && retUsr.ok && retUsr.value) return retUsr.value;
164+
if (retEvent.ok && retUsr.ok && retUsr.value) {
165+
logger.info(`checked in user ${userID} to event ${eventID}`);
166+
return retUsr.value;
167+
}
160168

161169
throw new Error(`failed checking user <${userID}> into event <${eventID}>`);
162170
}

src/server/resolvers/index.ts

+58-46
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
HackerDbObject,
1212
SponsorStatus,
1313
SponsorDbObject,
14+
TierDbObject,
15+
CompanyDbObject,
1416
} from '../generated/graphql';
1517
import Context from '../context';
1618
import {
@@ -27,6 +29,7 @@ import { checkInUserToEvent, removeUserFromEvent, registerNFCUIDWithUser, getUse
2729
import { addOrUpdateEvent, assignEventToCompany, removeAbsentEvents } from '../events';
2830
import { getSignedUploadUrl, getSignedReadUrl } from '../storage/gcp';
2931
import { sendStatusEmail } from '../mail/aws';
32+
import logger from '../logger';
3033

3134
// added here b/c webpack JSON compilation with 'use-strict' is broken (10/31/19)
3235
const DEADLINE_TS = 1572497940000;
@@ -191,36 +194,33 @@ export const resolvers: CustomResolvers<Context> = {
191194
root,
192195
{ input: { email, name, companyId } },
193196
{ models, user }: Context
194-
) => {
195-
if (!user || user.userType !== UserType.Organizer)
196-
throw new AuthenticationError(`user '${JSON.stringify(user)}' must be organizer`);
197-
const sponsor = await models.Sponsors.findOne({ email });
197+
): Promise<SponsorDbObject> => {
198+
checkIsAuthorized(UserType.Organizer, user);
198199
const company = await models.Companies.findOne({ _id: new ObjectID(companyId) });
199200
if (!company) throw new UserInputError(`Company with '${companyId}' doesn't exist.`);
200-
if (!sponsor) {
201-
await models.Sponsors.insertOne({
202-
_id: new ObjectID(),
203-
company,
204-
createdAt: new Date(),
205-
email,
206-
firstName: name,
207-
lastName: '',
208-
logins: [],
209-
phoneNumber: '',
210-
dietaryRestrictions: '',
211-
emailUnsubscribed: false,
212-
eventsAttended: [],
213-
preferredName: '',
214-
secondaryIds: [],
215-
status: SponsorStatus.Added,
216-
userType: UserType.Sponsor,
217-
});
218-
} else {
219-
throw new UserInputError(`sponsor with '${email}' is already added.`);
220-
}
221-
const sponsorCreated = await models.Sponsors.findOne({ email });
222-
if (!sponsorCreated) throw new AuthenticationError(`sponsor not found: ${email}`);
223-
return sponsorCreated;
201+
const sponsor = await models.Sponsors.findOne({ email });
202+
if (sponsor) throw new UserInputError(`sponsor with '${email}' is already added.`);
203+
204+
const newSponsor: SponsorDbObject = {
205+
_id: new ObjectID(),
206+
company,
207+
createdAt: new Date(),
208+
email,
209+
firstName: name,
210+
lastName: '',
211+
logins: [],
212+
phoneNumber: '',
213+
dietaryRestrictions: '',
214+
emailUnsubscribed: false,
215+
eventsAttended: [],
216+
preferredName: '',
217+
secondaryIds: [],
218+
status: SponsorStatus.Added,
219+
userType: UserType.Sponsor,
220+
};
221+
logger.info(`creating new sponsor ${JSON.stringify(newSponsor)}`);
222+
await models.Sponsors.insertOne(newSponsor);
223+
return newSponsor;
224224
},
225225
confirmMySpot: async (root, _, { models, user }) => {
226226
const { _id, status } = checkIsAuthorized(UserType.Hacker, user) as HackerDbObject;
@@ -256,33 +256,46 @@ export const resolvers: CustomResolvers<Context> = {
256256
// no email sent if declined
257257
return value;
258258
},
259-
createTier: async (root, { input: { name, permissions } }, { models, user }: Context) => {
260-
if (!user || user.userType !== UserType.Organizer)
261-
throw new AuthenticationError(`user '${JSON.stringify(user)}' must be organizer`);
262-
await models.Tiers.insertOne({
259+
createTier: async (
260+
root,
261+
{ input: { name, permissions } },
262+
{ models, user }: Context
263+
): Promise<TierDbObject> => {
264+
checkIsAuthorized(UserType.Organizer, user);
265+
const tier = await models.Tiers.findOne({ name });
266+
// currently does not support updating
267+
if (tier) throw new UserInputError(`Tier ${name} already exists, no action performed.`);
268+
269+
const newTier: TierDbObject = {
263270
_id: new ObjectID(),
264271
name,
265272
permissions: permissions || [],
266-
});
267-
const tierCreated = await models.Tiers.findOne({ name });
268-
if (!tierCreated) throw new AuthenticationError(`tier not found: ${name}`);
269-
return tierCreated;
273+
};
274+
logger.info(`creating tier ${JSON.stringify(newTier)}`);
275+
await models.Tiers.insertOne(newTier);
276+
return newTier;
270277
},
271-
createCompany: async (root, { input: { name, tierId } }, { models, user }: Context) => {
272-
if (!user || user.userType !== UserType.Organizer)
273-
throw new AuthenticationError(`user '${JSON.stringify(user)}' must be organizer`);
274-
278+
createCompany: async (
279+
root,
280+
{ input: { name, tierId } },
281+
{ models, user }: Context
282+
): Promise<CompanyDbObject> => {
283+
checkIsAuthorized(UserType.Organizer, user);
275284
const tier = await models.Tiers.findOne({ _id: new ObjectID(tierId) });
276285
if (!tier) throw new UserInputError(`Tier with id ${tierId}' doesn't exist.`);
277-
await models.Companies.insertOne({
286+
// currently does not support updating
287+
const comp = await models.Companies.findOne({ name });
288+
if (comp) throw new UserInputError(`Company ${name} already exists, no action performed.`);
289+
290+
const newCompany: CompanyDbObject = {
278291
_id: new ObjectID(),
279292
name,
280293
tier,
281294
eventsOwned: [],
282-
});
283-
const companyCreated = await models.Companies.findOne({ name });
284-
if (!companyCreated) throw new AuthenticationError(`company not found: ${name}`);
285-
return companyCreated;
295+
};
296+
logger.info(`creating company ${JSON.stringify(newCompany)}`);
297+
await models.Companies.insertOne(newCompany);
298+
return newCompany;
286299
},
287300
checkInUserToEventByNfc: async (root, { input }, { models, user }) => {
288301
checkIsAuthorizedArray([UserType.Organizer, UserType.Volunteer, UserType.Sponsor], user);
@@ -419,7 +432,6 @@ export const resolvers: CustomResolvers<Context> = {
419432
}))
420433
);
421434

422-
// TODO: Update this to set the hacker's profile fields (name, school, gender, etc.) with application data.
423435
if (!result.ok) {
424436
throw new UserInputError(
425437
`error inputting user application input for user "${id}" ${JSON.stringify(result)}`

0 commit comments

Comments
 (0)