Dwayne Harris 5 years ago
parent
commit
b47c2453bd
  1. 4
      src/lib/util.ts
  2. 59
      src/plugins/api/authentication.ts
  3. 1
      src/plugins/api/groups.ts
  4. 16
      src/types/collections.ts

4
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<void> {
return new Promise(resolve => {
setTimeout(() => {

59
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<Server, IncomingMessage, ServerRe
name: string
email: string
password: string
group: string
group?: string
invitation?: string
}
const options: RouteShorthandOptions = {
@ -55,6 +56,7 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
minLength: MIN_PASSWORD_LENGTH,
},
group: { type: 'string' },
invitation: { type: 'string' },
},
},
response: {
@ -67,10 +69,12 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
server.post<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/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<User>({
container: userContainer,
id,
@ -80,17 +84,42 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
if (existingUser) return badRequestFormError(reply, 'id', 'User id already taken')
let userPending = false
let group: Group | undefined
let groupPartial: GroupPartial | undefined
if (request.body.group) {
const group = await getItem<Group>({
container: containerFor(server.database.client, 'Groups'),
group = await getItem<Group>({
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<GroupInvitation>({
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<Server, IncomingMessage, ServerRe
const refreshToken = createRefreshToken(id, request.headers['user-agent'], request.ip)
await userContainer.items.create(user)
await userContainer.items.create(refreshToken)
await userContainer.items.create<User>(user)
await userContainer.items.create<UserToken>(refreshToken)
if (group) {
await groupContainer.items.create<GroupMembership>({
pk: group.id,
t: 'membership',
userId: user.id,
pending: userPending,
membership: 'member',
invitation,
created: Date.now(),
})
}
return {
id,

1
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 {}

16
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
}

Loading…
Cancel
Save