Skip to content

Commit af4f0ef

Browse files
committed
Ensure scanners can only see active items
1 parent b34c760 commit af4f0ef

File tree

5 files changed

+70
-15
lines changed

5 files changed

+70
-15
lines changed

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const environmentConfig: EnvironmentConfigType = {
5656
"f8dfc4cf-456b-4da3-9053-f7fdeda5d5d6": allAppRoles, // Infra Leads
5757
"0": allAppRoles, // Dummy Group for development only
5858
"1": [], // Dummy Group for development only
59+
"scanner-only": [AppRoles.TICKETS_SCANNER],
5960
},
6061
UserRoleMapping: {
6162
"[email protected]": [AppRoles.TICKETS_SCANNER],

src/routes/tickets.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,32 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
126126
},
127127
},
128128
async (request, reply) => {
129+
let isTicketingManager = true;
130+
try {
131+
await fastify.authorize(request, reply, [AppRoles.TICKETS_MANAGER]);
132+
} catch {
133+
isTicketingManager = false;
134+
}
135+
129136
const merchCommand = new ScanCommand({
130137
TableName: genericConfig.MerchStoreMetadataTableName,
131138
ProjectionExpression:
132139
"item_id, item_name, item_sales_active_utc, item_price",
133140
});
134-
const isTicketingManager = await fastify.authorize(request, reply, [
135-
AppRoles.TICKETS_MANAGER,
136-
]);
141+
137142
const merchItems: ItemMetadata[] = [];
138143
const response = await dynamoClient.send(merchCommand);
144+
const now = new Date();
145+
139146
if (response.Items) {
140147
for (const item of response.Items.map((x) => unmarshall(x))) {
141-
if (!isTicketingManager && item.item_sales_active_utc === -1) {
142-
continue;
148+
// Skip inactive items for non-managers
149+
if (!isTicketingManager) {
150+
if (item.item_sales_active_utc === -1) continue;
151+
const itemDate = new Date(parseInt(item.item_sales_active_utc, 10));
152+
if (itemDate > now) continue;
143153
}
154+
144155
const memberPrice = parseInt(item.item_price?.paid, 10) || 0;
145156
const nonMemberPrice = parseInt(item.item_price?.others, 10) || 0;
146157
merchItems.push({
@@ -157,18 +168,27 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
157168
});
158169
}
159170
}
171+
160172
const ticketCommand = new ScanCommand({
161173
TableName: genericConfig.TicketMetadataTableName,
162174
ProjectionExpression:
163175
"event_id, event_name, event_sales_active_utc, event_capacity, tickets_sold, eventCost",
164176
});
177+
165178
const ticketItems: TicketItemMetadata[] = [];
166179
const ticketResponse = await dynamoClient.send(ticketCommand);
180+
167181
if (ticketResponse.Items) {
168182
for (const item of ticketResponse.Items.map((x) => unmarshall(x))) {
169-
if (!isTicketingManager && item.event_sales_active_utc === -1) {
170-
continue;
183+
// Skip inactive items for non-managers
184+
if (!isTicketingManager) {
185+
if (item.event_sales_active_utc === -1) continue;
186+
const itemDate = new Date(
187+
parseInt(item.event_sales_active_utc, 10),
188+
);
189+
if (itemDate > now) continue;
171190
}
191+
172192
const memberPrice = parseInt(item.eventCost?.paid, 10) || 0;
173193
const nonMemberPrice = parseInt(item.eventCost?.others, 10) || 0;
174194
ticketItems.push({
@@ -187,6 +207,7 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
187207
});
188208
}
189209
}
210+
190211
reply.send({ merch: merchItems, tickets: ticketItems });
191212
},
192213
);

tests/unit/auth.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@ const ddbMock = mockClient(SecretsManagerClient);
1919
const app = await init();
2020
const jwt_secret = secretObject["jwt_key"];
2121
export function createJwt(date?: Date, group?: string) {
22-
let modifiedPayload = jwtPayload;
22+
let modifiedPayload = {
23+
...jwtPayload,
24+
groups: [...jwtPayload.groups],
25+
};
2326
if (date) {
2427
const nowMs = Math.floor(date.valueOf() / 1000);
2528
const laterMs = nowMs + 3600 * 24;
2629
modifiedPayload = {
27-
...jwtPayload,
30+
...modifiedPayload,
2831
iat: nowMs,
2932
nbf: nowMs,
3033
exp: laterMs,
3134
};
3235
}
36+
3337
if (group) {
34-
modifiedPayload["groups"][0] = group;
38+
modifiedPayload.groups[0] = group;
3539
}
3640
return jwt.sign(modifiedPayload, jwt_secret, { algorithm: "HS256" });
3741
}

tests/unit/data/mockTIcketsMerchMetadata.testdata.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const merchMetadata = [
6666
},
6767
{
6868
item_id: {
69-
S: "2024_fa_barcrawl",
69+
S: "2024_fa_barcrawl_2",
7070
},
7171
item_email_desc: {
7272
S: "Shirts will be availiable for pickup one week before the event. Check your email near then for more details. Make sure to join the ACM Discord for updates!",
@@ -88,7 +88,7 @@ const merchMetadata = [
8888
},
8989
},
9090
item_sales_active_utc: {
91-
N: "0",
91+
N: "-1",
9292
},
9393
member_price: {
9494
S: "price_1QFSCiDiGOXU9RuSNJ90SblG",

tests/unit/tickets.test.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ describe("Test getting ticketing + merch metadata", async () => {
5959
priceDollars: { member: 22, nonMember: 26 },
6060
},
6161
{
62-
itemId: "2024_fa_barcrawl",
62+
itemId: "2024_fa_barcrawl_2",
6363
itemName: "ACM Bar Crawl: Fall 2024 (Nov 14)",
64-
itemSalesActive: "1970-01-01T00:00:00.000Z",
64+
itemSalesActive: false,
6565
priceDollars: { member: 15, nonMember: 18 },
6666
},
6767
],
@@ -84,7 +84,36 @@ describe("Test getting ticketing + merch metadata", async () => {
8484
},
8585
],
8686
});
87-
expect(responseDataJson.tickets).toHaveLength(2);
87+
});
88+
test("Ensure scanners can only see active items", async () => {
89+
ddbMock
90+
.on(ScanCommand)
91+
.resolvesOnce({
92+
Items: merchMetadata as Record<string, AttributeValue>[],
93+
})
94+
.resolvesOnce({
95+
Items: ticketsMetadata as Record<string, AttributeValue>[],
96+
})
97+
.rejects();
98+
const testJwt = createJwt(undefined, "scanner-only");
99+
await app.ready();
100+
const response = await supertest(app.server)
101+
.get("/api/v1/tickets")
102+
.set("authorization", `Bearer ${testJwt}`);
103+
const responseDataJson = response.body;
104+
console.log(responseDataJson);
105+
expect(response.statusCode).toEqual(200);
106+
expect(responseDataJson).toEqual({
107+
merch: [
108+
{
109+
itemId: "2024_spr_tshirt",
110+
itemName: "ACM T-Shirt: Spring 2024 Series",
111+
itemSalesActive: "1970-01-01T00:00:00.000Z",
112+
priceDollars: { member: 22, nonMember: 26 },
113+
},
114+
],
115+
tickets: [],
116+
});
88117
});
89118
});
90119

0 commit comments

Comments
 (0)