mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
fix(public-api): a key narrowed away from days stops getting days back through places
Places, notes and reservations hang off days, so asking for one of them implies the day container. That implication ran ahead of the grant: a key minted with scopes ['trips','places'] and deliberately no 'days' still received every day's title and the day's own free-text notes, either by naming places or by naming nothing at all, since an absent include narrows to the granted sections and lands back in the same branch. Asking for days by name was a 403 the whole time, so the two routes disagreed about the same fields. The grant now reaches the service alongside the narrowed include. The container is still implied, because the places inside it are useless without their date, but the day's title and notes only go out to a key that was granted days. A key with full access is unaffected: it is granted days whether or not it asks.
This commit is contained in:
@@ -12,6 +12,7 @@ import { ApiTokenGuard } from './api-token.guard';
|
||||
import { PublicApiService } from './public-api.service';
|
||||
import {
|
||||
enforcePublicApiRateLimit,
|
||||
grantedScopes,
|
||||
narrowToGrant,
|
||||
requireScope,
|
||||
requireUserId,
|
||||
@@ -98,7 +99,10 @@ export class PublicApiController {
|
||||
}
|
||||
const allowed = narrowToGrant(asked, req);
|
||||
|
||||
const trip = this.api.getTrip(tripId, requireUserId(req), allowed);
|
||||
// The grant goes in as well as the narrowed include: days are implied by any
|
||||
// section that hangs off them, and the implied block must still be measured
|
||||
// against what the key may read.
|
||||
const trip = this.api.getTrip(tripId, requireUserId(req), allowed, grantedScopes(req));
|
||||
if (!trip) {
|
||||
throw new HttpException({ error: 'Trip not found' }, 404);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export class PublicApiService {
|
||||
* turns both into the same 404, so the endpoint cannot be used to probe which
|
||||
* trip ids exist.
|
||||
*/
|
||||
getTrip(tripId: number, userId: number, include: PublicApiInclude[]): PublicApiTrip | null {
|
||||
getTrip(tripId: number, userId: number, include: PublicApiInclude[], granted: readonly string[] = include): PublicApiTrip | null {
|
||||
if (!this.db.canAccessTrip(tripId, userId)) return null;
|
||||
const row = this.db.get<TripRow>(
|
||||
`SELECT id, title, description, start_date, end_date, currency, is_archived, updated_at
|
||||
@@ -74,8 +74,13 @@ export class PublicApiService {
|
||||
// Places, notes and reservations hang off days, so asking for one of them
|
||||
// and not for `days` used to return the trip and nothing else — silently,
|
||||
// which is the worst way to answer. Days are implied instead.
|
||||
//
|
||||
// Implied, not granted: `granted` is what the key may read at all, and a key
|
||||
// narrowed to places must not get the day's title and free-text notes back
|
||||
// through the container those places arrive in. The shell it does get is the
|
||||
// join key the children are useless without.
|
||||
if (DAY_SCOPED.some((section) => include.includes(section))) {
|
||||
trip.days = this.buildDays(tripId, include);
|
||||
trip.days = this.buildDays(tripId, include, granted.includes('days'));
|
||||
}
|
||||
if (include.includes('places')) {
|
||||
trip.unplanned_places = this.buildUnplannedPlaces(tripId);
|
||||
@@ -100,7 +105,7 @@ export class PublicApiService {
|
||||
* rather than queried per day — a two-week trip would otherwise cost 42 round
|
||||
* trips for the same rows.
|
||||
*/
|
||||
private buildDays(tripId: number, include: PublicApiInclude[]): PublicApiDay[] {
|
||||
private buildDays(tripId: number, include: PublicApiInclude[], dayFields: boolean): PublicApiDay[] {
|
||||
const days = this.db.all<DayRow>(
|
||||
`SELECT id, day_number, date, title, notes
|
||||
FROM days WHERE trip_id = ? ORDER BY day_number ASC`,
|
||||
@@ -117,8 +122,8 @@ export class PublicApiService {
|
||||
return days.map((day) => ({
|
||||
date: day.date,
|
||||
day_number: day.day_number,
|
||||
title: day.title ?? null,
|
||||
notes: day.notes ?? null,
|
||||
title: dayFields ? day.title ?? null : null,
|
||||
notes: dayFields ? day.notes ?? null : null,
|
||||
places: placesByDay.get(day.id) ?? [],
|
||||
day_notes: notesByDay.get(day.id) ?? [],
|
||||
reservations: reservationsByDay.get(day.id) ?? [],
|
||||
|
||||
Reference in New Issue
Block a user