mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
refactor(server): trip-access primitives async at every caller
This commit is contained in:
@@ -91,7 +91,7 @@ export class DayScheduleController {
|
||||
if (!pluginsEnabled()) return { items: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { items: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { items: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('dayScheduleProvider');
|
||||
if (ids.length === 0) return { items: [] };
|
||||
|
||||
@@ -168,7 +168,7 @@ export class DayTintsController {
|
||||
if (!pluginsEnabled()) return { tints: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { tints: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { tints: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('dayTintProvider');
|
||||
if (ids.length === 0) return { tints: [] };
|
||||
|
||||
@@ -158,7 +158,7 @@ export class MapLayersController {
|
||||
if (!pluginsEnabled()) return { layers: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { layers: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { layers: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('mapLayerProvider');
|
||||
const perProvider = await Promise.all(
|
||||
|
||||
@@ -91,7 +91,7 @@ export class MapMarkersController {
|
||||
if (!pluginsEnabled()) return { markers: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { markers: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { markers: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('mapMarkerProvider');
|
||||
const perProvider = await Promise.all(
|
||||
|
||||
@@ -87,7 +87,7 @@ export class PdfSectionsController {
|
||||
if (!pluginsEnabled()) return { sections: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { sections: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { sections: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('pdfSectionProvider');
|
||||
const perProvider = await Promise.all(
|
||||
|
||||
@@ -78,7 +78,7 @@ export class PlaceDetailsController {
|
||||
|
||||
// The place must belong to a trip the caller can access — same gate as a read.
|
||||
const row = this.dbs.connection.prepare('SELECT trip_id FROM places WHERE id = ?').get(placeId) as { trip_id: number } | undefined;
|
||||
if (!row || !this.dbs.canAccessTrip(row.trip_id, userId)) return { providers: [] };
|
||||
if (!row || !(await this.dbs.canAccessTrip(row.trip_id, userId))) return { providers: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('placeDetailProvider');
|
||||
const results = await Promise.all(
|
||||
|
||||
@@ -45,7 +45,7 @@ export class PluginRoutesController {
|
||||
if (!pluginsEnabled()) return { route: null };
|
||||
const userId = req.user?.id;
|
||||
const tripId = Number(body?.tripId);
|
||||
if (userId == null || !Number.isFinite(tripId) || !this.dbs.canAccessTrip(tripId, userId)) return { route: null };
|
||||
if (userId == null || !Number.isFinite(tripId) || !(await this.dbs.canAccessTrip(tripId, userId))) return { route: null };
|
||||
if (!PROFILE_RE.test(profileId)) return { route: null };
|
||||
const waypoints = readWaypoints(body?.waypoints);
|
||||
if (!waypoints) return { route: null };
|
||||
|
||||
@@ -103,7 +103,9 @@ export class TripCardContributionsController {
|
||||
.map((s) => Number(s.trim()))
|
||||
.filter((n) => Number.isInteger(n) && n > 0)
|
||||
.slice(0, MAX_TRIP_IDS);
|
||||
const accessible = [...new Set(requested)].filter((id) => this.dbs.canAccessTrip(id, userId));
|
||||
const uniqueRequested = [...new Set(requested)];
|
||||
const accessFlags = await Promise.all(uniqueRequested.map((id) => this.dbs.canAccessTrip(id, userId)));
|
||||
const accessible = uniqueRequested.filter((_, i) => accessFlags[i]);
|
||||
if (accessible.length === 0) return { contributions: [] };
|
||||
const allowed = new Set(accessible);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export class TripWarningsController {
|
||||
if (!pluginsEnabled()) return { warnings: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { warnings: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { warnings: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('warningProvider');
|
||||
const perProvider = await Promise.all(
|
||||
|
||||
@@ -55,7 +55,7 @@ export class TripWarningsMcp {
|
||||
async getTripWarnings({ tripId }: { tripId: number }, ctx: McpContext): Promise<McpTextResult> {
|
||||
// Access first, so the answer to "may I look at this trip" does not depend on
|
||||
// whether an admin has the plugin system switched on.
|
||||
if (!this.dbs.canAccessTrip(tripId, ctx.userId)) return noAccess();
|
||||
if (!(await this.dbs.canAccessTrip(tripId, ctx.userId))) return noAccess();
|
||||
if (!pluginsEnabled()) return ok({ warnings: [] });
|
||||
|
||||
const ids = this.hooks.providersOf('warningProvider');
|
||||
|
||||
@@ -116,7 +116,7 @@ export class ViewContributionsController {
|
||||
if (!pluginsEnabled() || !VIEWS.has(view)) return { contributions: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { contributions: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !(await this.dbs.canAccessTrip(tripId, userId))) return { contributions: [] };
|
||||
|
||||
const ids = this.hooks.providersOf('tableContributor');
|
||||
const perProvider = await Promise.all(
|
||||
|
||||
@@ -36,7 +36,7 @@ export class PluginGuards {
|
||||
if (ctx.actingUserId === undefined) {
|
||||
throw new ForbiddenResource('trip reads require an authenticated user context');
|
||||
}
|
||||
if (!this.db.canAccessTrip(tripId, ctx.actingUserId)) {
|
||||
if (!(await this.db.canAccessTrip(tripId, ctx.actingUserId))) {
|
||||
throw new ForbiddenResource(`no access to trip ${tripId}`);
|
||||
}
|
||||
// The read runs only for a bound, membership-checked user, so hand the id through
|
||||
@@ -57,7 +57,7 @@ export class PluginGuards {
|
||||
|
||||
/** A write is allowed only if the acting user can access AND edit the trip. */
|
||||
async requireTripEdit(tripId: number, userId: number, action: string): Promise<void> {
|
||||
if (!this.db.canAccessTrip(tripId, userId)) throw new ForbiddenResource(`no access to trip ${tripId}`);
|
||||
if (!(await this.db.canAccessTrip(tripId, userId))) throw new ForbiddenResource(`no access to trip ${tripId}`);
|
||||
if (!(await this.canEditAs(action, tripId, userId))) throw new ForbiddenResource(`no permission to edit trip ${tripId}`);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class PluginGuards {
|
||||
* message the refusal carries.
|
||||
*/
|
||||
async canEditAs(action: string, tripId: number, userId: number): Promise<boolean> {
|
||||
const trip = this.db.canAccessTrip(tripId, userId);
|
||||
const trip = await this.db.canAccessTrip(tripId, userId);
|
||||
if (!trip) return false;
|
||||
const user = this.db.prepare('SELECT role FROM users WHERE id = ?').get(userId) as { role?: string } | undefined;
|
||||
if (!user) return false;
|
||||
|
||||
@@ -62,7 +62,7 @@ export class HostSurfaceRpc {
|
||||
// Namespacing the event type alone does not cross the membership boundary.
|
||||
const tripId = num(params.tripId, 'tripId');
|
||||
if (ctx.actingUserId === undefined) throw new ForbiddenResource('broadcasts require an authenticated user context');
|
||||
if (!this.db.canAccessTrip(tripId, ctx.actingUserId)) throw new ForbiddenResource(`no access to trip ${tripId}`);
|
||||
if (!(await this.db.canAccessTrip(tripId, ctx.actingUserId))) throw new ForbiddenResource(`no access to trip ${tripId}`);
|
||||
// The host forces the plugin:{id}:{event} namespace, so a plugin cannot forge a
|
||||
// core event.
|
||||
this.realtime.broadcast(tripId, `plugin:${ctx.pluginId}:${str(params.event, 'event')}`, asPayload(params.data));
|
||||
@@ -101,7 +101,7 @@ export class HostSurfaceRpc {
|
||||
if (scope !== 'user' && scope !== 'trip') throw new BadParams("scope must be 'user' or 'trip'");
|
||||
const targetId = num(input.targetId, 'targetId');
|
||||
if (scope === 'user' && targetId !== actor) throw new ForbiddenResource('a plugin may only notify the acting user');
|
||||
if (scope === 'trip' && !this.db.canAccessTrip(targetId, actor)) {
|
||||
if (scope === 'trip' && !(await this.db.canAccessTrip(targetId, actor))) {
|
||||
throw new ForbiddenResource('the acting user is not a member of that trip');
|
||||
}
|
||||
const link = this.safeLink(input.link);
|
||||
|
||||
@@ -130,7 +130,7 @@ export class MetaRpc {
|
||||
const entityId = num(params.entityId, 'entityId');
|
||||
if (ctx.actingUserId === undefined) throw new ForbiddenResource('metadata requires an authenticated user context');
|
||||
const tripId = await this.entityTrip(entityType, entityId);
|
||||
if (tripId === undefined || !this.db.canAccessTrip(tripId, ctx.actingUserId)) {
|
||||
if (tripId === undefined || !(await this.db.canAccessTrip(tripId, ctx.actingUserId))) {
|
||||
throw new ForbiddenResource(`no access to ${entityType} ${entityId}`);
|
||||
}
|
||||
if (write && !(await this.guards.canEditAs(EDIT_ACTION[entityType], tripId, ctx.actingUserId))) {
|
||||
|
||||
Reference in New Issue
Block a user