diff --git a/keyserver/src/fetchers/link-fetchers.js b/keyserver/src/fetchers/link-fetchers.js --- a/keyserver/src/fetchers/link-fetchers.js +++ b/keyserver/src/fetchers/link-fetchers.js @@ -39,4 +39,20 @@ }; } -export { verifyInviteLink }; +async function checkIfInviteLinkIsValid( + secret: string, + communityID: string, +): Promise { + const query = SQL` + SELECT i.id + FROM invite_links i + INNER JOIN threads c ON c.id = i.community + WHERE i.name = ${secret} + AND i.community = ${communityID} + AND c.community IS NULL + `; + const [result] = await dbQuery(query); + return result.length === 1; +} + +export { verifyInviteLink, checkIfInviteLinkIsValid }; diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js --- a/keyserver/src/responders/thread-responders.js +++ b/keyserver/src/responders/thread-responders.js @@ -169,6 +169,7 @@ const joinThreadRequestInputValidator = tShape({ threadID: t.String, calendarQuery: t.maybe(entryQueryInputValidator), + inviteLinkSecret: t.maybe(t.String), }); async function threadJoinResponder( viewer: Viewer, diff --git a/keyserver/src/updaters/link-updaters.js b/keyserver/src/updaters/link-updaters.js new file mode 100644 --- /dev/null +++ b/keyserver/src/updaters/link-updaters.js @@ -0,0 +1,14 @@ +// @flow + +import { dbQuery, SQL } from '../database/database.js'; + +async function reportLinkUsage(secret: string): Promise { + const query = SQL` + UPDATE invite_links + SET number_of_uses = number_of_uses + 1 + WHERE name = ${secret} + `; + await dbQuery(query); +} + +export { reportLinkUsage }; diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js --- a/keyserver/src/updaters/thread-updaters.js +++ b/keyserver/src/updaters/thread-updaters.js @@ -35,6 +35,7 @@ import { promiseAll } from 'lib/utils/promises.js'; import { firstLine } from 'lib/utils/string-utils.js'; +import { reportLinkUsage } from './link-updaters.js'; import { updateRoles } from './role-updaters.js'; import { changeRole, @@ -45,6 +46,7 @@ import { createUpdates } from '../creators/update-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import { fetchEntryInfos } from '../fetchers/entry-fetchers.js'; +import { checkIfInviteLinkIsValid } from '../fetchers/link-fetchers.js'; import { fetchMessageInfos, fetchMessageInfoByID, @@ -64,6 +66,7 @@ verifyUserIDs, verifyUserOrCookieIDs, } from '../fetchers/user-fetchers.js'; +import { handleAsyncPromise } from '../responders/handlers.js'; import type { Viewer } from '../session/viewer.js'; import RelationshipChangeset from '../utils/relationship-changeset.js'; @@ -809,13 +812,16 @@ throw new ServerError('not_logged_in'); } + const permissionCheck = request.inviteLinkSecret + ? checkIfInviteLinkIsValid(request.inviteLinkSecret, request.threadID) + : checkThreadPermission( + viewer, + request.threadID, + threadPermissions.JOIN_THREAD, + ); const [isMember, hasPermission] = await Promise.all([ fetchViewerIsMember(viewer, request.threadID), - checkThreadPermission( - viewer, - request.threadID, - threadPermissions.JOIN_THREAD, - ), + permissionCheck, ]); if (!hasPermission) { throw new ServerError('invalid_parameters'); @@ -861,6 +867,10 @@ calendarQuery, }); + if (request.inviteLinkSecret) { + handleAsyncPromise(reportLinkUsage(request.inviteLinkSecret)); + } + const messageData = { type: messageTypes.JOIN_THREAD, threadID: request.threadID, diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -454,10 +454,12 @@ export type ServerThreadJoinRequest = { +threadID: string, +calendarQuery?: ?CalendarQuery, + +inviteLinkSecret?: string, }; export type ClientThreadJoinRequest = { +threadID: string, +calendarQuery: CalendarQuery, + +inviteLinkSecret?: string, }; export type ThreadJoinResult = { threadInfos?: { +[id: string]: RawThreadInfo },