From b47c2453bd98d8306d90adec21e001c18aeeeb85 Mon Sep 17 00:00:00 2001 From: Dwayne Harris Date: Wed, 11 Sep 2019 01:30:31 -0400 Subject: [PATCH] WIP --- src/lib/util.ts | 4 +++ src/plugins/api/authentication.ts | 59 ++++++++++++++++++++++++++----- src/plugins/api/groups.ts | 1 - src/types/collections.ts | 16 ++++++++- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/lib/util.ts b/src/lib/util.ts index 51c5f39..202c372 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -11,6 +11,10 @@ export function createPostId(): string { return 'p' + v1().replace(/-/g, '') } +export function createInvitationCode(): string { + return 'i' + v1().replace(/-/g, '') +} + export function wait(ms: number = 5000): Promise { return new Promise(resolve => { setTimeout(() => { diff --git a/src/plugins/api/authentication.ts b/src/plugins/api/authentication.ts index 107bcb5..63b2e6d 100644 --- a/src/plugins/api/authentication.ts +++ b/src/plugins/api/authentication.ts @@ -14,11 +14,11 @@ import { MIN_ID_LENGTH, MAX_ID_LENGTH, MAX_NAME_LENGTH, MIN_PASSWORD_LENGTH } fr import { tokenResponseSchema, userSchema, errorSchema } from '../../schemas' import { createAccessToken, createRefreshToken } from '../../lib/authentication' import { hashPassword, verifyPassword, JWT } from '../../lib/crypto' -import { containerFor, getItem, normalize } from '../../lib/database' +import { containerFor, getItem, queryItems, normalize, createQuerySpec } from '../../lib/database' import { badRequestError, badRequestFormError, unauthorizedError, serverError } from '../../lib/errors' import { tokenFromHeader } from '../../lib/http' -import { User, UserToken, Group, GroupPartial } from '../../types/collections' +import { User, UserToken, Group, GroupInvitation, GroupPartial, GroupMembership } from '../../types/collections' interface PluginOptions {} @@ -28,7 +28,8 @@ function registerRoute(server: FastifyInstance('/api/register', options, async (request, reply) => { if (!server.database) return serverError(reply) - const { name, email, password } = request.body + const { name, email, password, invitation } = request.body const id = normalize(request.body.id) const userContainer = containerFor(server.database.client, 'Users') + const groupContainer = containerFor(server.database.client, 'Groups') + const existingUser = await getItem({ container: userContainer, id, @@ -80,17 +84,42 @@ function registerRoute(server: FastifyInstance({ - container: containerFor(server.database.client, 'Groups'), + group = await getItem({ + container: groupContainer, id: request.body.group, logger: request.log }) if (!group) return badRequestFormError(reply, 'group', 'Group not found') - if (!group.open) return badRequestFormError(reply, 'group', 'Group registration closed') + + if (invitation) { + const invitationQuery = createQuerySpec(` + SELECT g.id FROM Groups g WHERE + g.id = @invitation + g.pk = @group AND + g.type = 'invitation' AND + g.active = true AND + g.expiration < GETCURRENTTIMESTAMP() AND + g.uses < g.limit + `, { + invitation, + group: group.id, + }) + + const invitations = await queryItems({ + container: groupContainer, + query: invitationQuery, + logger: request.log, + }) + + if (invitations.length === 0) return badRequestFormError(reply, 'invitation', 'Invalid invitation code') + } + + if (!group.open && !invitation) return badRequestFormError(reply, 'group', 'Group registration closed') if (group.requiresApproval) userPending = true @@ -128,8 +157,20 @@ function registerRoute(server: FastifyInstance(user) + await userContainer.items.create(refreshToken) + + if (group) { + await groupContainer.items.create({ + pk: group.id, + t: 'membership', + userId: user.id, + pending: userPending, + membership: 'member', + invitation, + created: Date.now(), + }) + } return { id, diff --git a/src/plugins/api/groups.ts b/src/plugins/api/groups.ts index dfe2536..be913c5 100644 --- a/src/plugins/api/groups.ts +++ b/src/plugins/api/groups.ts @@ -14,7 +14,6 @@ import { MIN_ID_LENGTH, MAX_NAME_LENGTH } from '../../constants' import { errorSchema, groupListingSchema } from '../../schemas' import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors' import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' -import { wait } from '../../lib/util' import { User, Group, GroupListing, GroupMembership, UserBlock, GroupBlock } from '../../types/collections' interface PluginOptions {} diff --git a/src/types/collections.ts b/src/types/collections.ts index 9b04cad..414b117 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -43,6 +43,7 @@ export interface Group { userId: string name: string // Prenormalized ID about?: string + codeOfConduct?: string imageUrl?: string coverImageUrl?: string open: boolean @@ -60,12 +61,25 @@ export interface GroupPartial { } export interface GroupMembership { - id: string + id?: string pk: string // Group ID t: 'membership' userId: string pending: boolean membership: GroupMembershipType + invitation?: string + created: number +} + +export interface GroupInvitation { + id: string + pk: string // Group ID + t: 'invitation' + userId: string + limit: number + expiration: number + uses: number + active: boolean created: number }