From ebfa480064b402a6b57eaaad0c826b71b8cc1573 Mon Sep 17 00:00:00 2001 From: videah Date: Tue, 16 Jan 2024 14:57:51 -0700 Subject: [PATCH] potential DoS fix for notifications --- lib/util.dart | 4 +++- routes/api/v1/notifications/index.dart | 6 ++++++ routes/api/v1/timelines/home.dart | 9 --------- routes/api/v1/timelines/list/[id].dart | 18 +++++++++++++++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/util.dart b/lib/util.dart index ed43af8..f65f22f 100644 --- a/lib/util.dart +++ b/lib/util.dart @@ -262,6 +262,8 @@ String stringifyModifiedUri(Uri uri, String originalUri) { return host + uri.toString().substring(host.length); } +/// Generates pagination headers for a Mastodon feed like a timeline or +/// notifications. Map generatePaginationHeaders({ required List items, required Uri requestUri, @@ -272,5 +274,5 @@ Map generatePaginationHeaders({ final prevURI = requestUri.replace(queryParameters: {'min_id': highestID.toString()}); final nextURI = requestUri.replace(queryParameters: {'cursor': nextCursor}); - return {'Link': '<$prevURI>; rel="prev", <$nextURI>; rel="next"'}; + return {'Link': '<$nextURI>; rel="next", <$prevURI>; rel="prev"'}; } diff --git a/routes/api/v1/notifications/index.dart b/routes/api/v1/notifications/index.dart index 888ed76..3c22453 100644 --- a/routes/api/v1/notifications/index.dart +++ b/routes/api/v1/notifications/index.dart @@ -38,6 +38,12 @@ Future onRequest(RequestContext context) async { bluesky, ); + // If we have a min_id, filter out any notifications that are older than it. + if (encodedParams.minId != null) { + final minId = BigInt.parse(encodedParams.minId!); + notifs.removeWhere((notif) => BigInt.parse(notif.id) <= minId); + } + var headers = {}; if (notifs.isNotEmpty) { headers = generatePaginationHeaders( diff --git a/routes/api/v1/timelines/home.dart b/routes/api/v1/timelines/home.dart index 9fd1839..8dd7e75 100644 --- a/routes/api/v1/timelines/home.dart +++ b/routes/api/v1/timelines/home.dart @@ -98,15 +98,6 @@ Future onRequest(RequestContext context) async { nextCursor: nextCursor ?? '', getId: (post) => BigInt.parse(post.id), ); - - // final ids = processedPosts.map((post) => BigInt.parse(post.id)).toList(); - // final highestID = ids.reduce((a, b) => a > b ? a : b); - // - // final prevParams = {'min_id': highestID.toString()}; - // final nextParams = {'cursor': nextCursor}; - // final prevURI = uri.replace(queryParameters: prevParams); - // final nextURI = uri.replace(queryParameters: nextParams); - // headers['Link'] = '<$prevURI>; rel="prev", <$nextURI>; rel="next"'; } // If the user prefers not to see replies, we need to filter them out. diff --git a/routes/api/v1/timelines/list/[id].dart b/routes/api/v1/timelines/list/[id].dart index f8e9917..64f63c9 100644 --- a/routes/api/v1/timelines/list/[id].dart +++ b/routes/api/v1/timelines/list/[id].dart @@ -5,6 +5,7 @@ import 'package:dart_frog/dart_frog.dart'; import 'package:sky_bridge/auth.dart'; import 'package:sky_bridge/database.dart'; import 'package:sky_bridge/models/mastodon/mastodon_post.dart'; +import 'package:sky_bridge/models/params/timeline_params.dart'; import 'package:sky_bridge/src/generated/prisma/prisma_client.dart'; import 'package:sky_bridge/util.dart'; @@ -22,6 +23,11 @@ Future onRequest(RequestContext context, String id) async { return Response(statusCode: HttpStatus.notFound); } + // Get the next cursor from the request parameters. + final params = context.request.uri.queryParameters; + final encodedParams = TimelineParams.fromJson(params); + final nextCursor = encodedParams.cursor; + // Construct bluesky connection. // Get a bluesky connection/session from the a provided bearer token. // If the token is invalid, bail out and return an error. @@ -49,8 +55,18 @@ Future onRequest(RequestContext context, String id) async { // Get the parent posts for each post. final processedPosts = await processParentPosts(bluesky, posts); - // Return the post that we just liked. + var headers = {}; + if (processedPosts.isNotEmpty) { + headers = generatePaginationHeaders( + items: processedPosts, + requestUri: context.request.uri, + nextCursor: nextCursor ?? '', + getId: (post) => BigInt.parse(post.id), + ); + } + return threadedJsonResponse( body: processedPosts, + headers: headers, ); }