|
|
@ -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, |
|
|
|