From 1f70ba6f6e86b1035737c954dc195b9c2b68006a Mon Sep 17 00:00:00 2001 From: Dwayne Harris Date: Thu, 31 Oct 2019 01:47:19 -0400 Subject: [PATCH] WIP --- src/lib/collections.ts | 2 +- src/lib/crypto.ts | 4 +- src/plugins/api/authentication.ts | 4 +- src/plugins/api/posts.ts | 12 +++-- src/plugins/api/users.ts | 83 ++++++++++++++++++++++++------- src/schemas.ts | 15 +++++- src/types/collections.ts | 15 +++++- 7 files changed, 104 insertions(+), 31 deletions(-) diff --git a/src/lib/collections.ts b/src/lib/collections.ts index d627a4b..43c4a3a 100644 --- a/src/lib/collections.ts +++ b/src/lib/collections.ts @@ -64,7 +64,7 @@ export async function getApprovedSubscriptions(client: CosmosClient, from: strin container: containerFor(client, 'Users'), query: createQuerySpec( `SELECT u.id FROM Users u WHERE - u.id = @to + u.userId = @to u.pk = @from AND u.t = @type AND u.pending = false`, diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index a2bf82d..3dc822b 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -6,8 +6,8 @@ export async function hashPassword(password: string): Promise { return await hash(password, 8) } -export async function verifyPassword(hash: string, password: string): Promise { - return await compare(hash, password) +export async function comparePassword(password: string, hash: string): Promise { + return await compare(password, hash) } export const generateString = (length: number) => randomBytes(Math.max(Math.round(length / 2), 5)).toString('hex') diff --git a/src/plugins/api/authentication.ts b/src/plugins/api/authentication.ts index db778c8..0b3d4b7 100644 --- a/src/plugins/api/authentication.ts +++ b/src/plugins/api/authentication.ts @@ -14,7 +14,7 @@ import { MIN_ID_LENGTH, MAX_ID_LENGTH, MAX_NAME_LENGTH, MIN_PASSWORD_LENGTH, INS import { tokenResponseSchema, selfSchema, errorSchema } from '../../schemas' import { createAccessToken, createRefreshToken } from '../../lib/authentication' import { getUser } from '../../lib/collections' -import { hashPassword, verifyPassword, JWT } from '../../lib/crypto' +import { hashPassword, comparePassword, JWT } from '../../lib/crypto' import { containerFor, getItem, queryItems, normalize, createQuerySpec } from '../../lib/database' import { badRequestError, badRequestFormError, unauthorizedError, serverError } from '../../lib/errors' import { tokenFromHeader } from '../../lib/http' @@ -266,7 +266,7 @@ function authenticateRoute(server: FastifyInstance({ container, id }) if (!user) return badRequestFormError(reply, 'id', 'User not found') - const result = await verifyPassword(user.passwordHash, request.body.password) + const result = await comparePassword(request.body.password, user.passwordHash) if (!result) return badRequestFormError(reply, 'password', 'Incorrect password') const refreshToken = createRefreshToken(user.id, request.headers['user-agent'], request.ip) diff --git a/src/plugins/api/posts.ts b/src/plugins/api/posts.ts index ec43091..7f2d3ed 100644 --- a/src/plugins/api/posts.ts +++ b/src/plugins/api/posts.ts @@ -10,7 +10,7 @@ import { Logger, } from 'fastify' -import { Server, IncomingMessage, ServerResponse } from 'http' +import { Server, IncomingMessage, ServerResponse, request } from 'http' import { createHmac } from 'crypto' import { CosmosClient } from '@azure/cosmos' @@ -160,14 +160,18 @@ async function createPost(client: CosmosClient, userId: string, body: PostBody, if (newPostRelationship) await ancestryContainer.items.create(newPostRelationship) - const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.t = @type`, { pk: userId, type: UserItemType.Subscription }) const subscribers = await queryItems({ container: userContainer, - query, + query: createQuerySpec(`SELECT * FROM Users u WHERE u.pk = @pk AND u.t = @type AND u.pending = false`, { + pk: userId, + type: UserItemType.Subscription, + }), logger, }) - for (const uid of [userId, ...subscribers.map(s => s.id)]) { + logger.debug('subscribers', subscribers) + + for (const uid of [userId, ...subscribers.map(s => s.userId)]) { await userContainer.items.create({ id: postId, pk: uid, diff --git a/src/plugins/api/users.ts b/src/plugins/api/users.ts index bbbc359..fe446e5 100644 --- a/src/plugins/api/users.ts +++ b/src/plugins/api/users.ts @@ -27,7 +27,7 @@ import { UserItemType, GroupItemType, BlockType, - Group, + UserInverseSubscription, } from '../../types/collections' import { PluginOptions } from '../../types' @@ -168,6 +168,12 @@ function getRoute(server: FastifyInstance({ container: userContainer, id: request.viewer.id }) if (!viewer) return serverError(reply) if (!viewer.groupId) return unauthorizedError(reply) @@ -201,23 +206,46 @@ function getRoute(server: FastifyInstance 0) return unauthorizedError(reply) - subscribed = !!(await getItem({ + const subscription = (await queryItems({ container: userContainer, - id: user.id, - partitionKey: viewer.id, - })) + query: createQuerySpec('SELECT * FROM Users u WHERE u.pk = @pk AND u.userId = @userId AND u.t = @type', { + userId: viewer.id, + pk: user.id, + type: UserItemType.Subscription, + }), + logger: request.log, + }))[0] + + if (subscription) { + subscriptions.push({ + from: subscription.userId, + to: subscription.pk, + pending: subscription.pending, + }) + } - subscribedToYou = !!(await getItem({ + const inverseSubscription = (await queryItems({ container: userContainer, - id: viewer.id, - partitionKey: user.id, - })) + query: createQuerySpec('SELECT * FROM Users u WHERE u.pk = @pk AND u.userId = @userId AND u.t = @type', { + userId: user.id, + pk: viewer.id, + type: UserItemType.InverseSubscription, + }), + logger: request.log, + }))[0] + + if (inverseSubscription) { + subscriptions.push({ + from: inverseSubscription.pk, + to: inverseSubscription.userId, + pending: inverseSubscription.pending, + }) + } } return { ...user, - subscribed, - subscribedToYou, + subscriptions, } }) } @@ -297,13 +325,21 @@ function subscribeRoute(server: FastifyInstance 0) return badRequestError(reply, 'Invalid operation') await userContainer.items.create({ - id: user.id, - pk: request.viewer.id, + userId: request.viewer.id, + pk: user.id, t: UserItemType.Subscription, pending, created: Date.now(), }) + await userContainer.items.create({ + userId: user.id, + pk: request.viewer.id, + t: UserItemType.InverseSubscription, + pending, + created: Date.now(), + }) + reply.code(204) }) } @@ -335,7 +371,7 @@ function unsubscribeRoute(server: FastifyInstance({ container: userContainer, query: subscriptionQuery, logger: request.log }) for (const subscription of subscriptions) { - await userContainer.item(subscription.id!, viewer.id).delete() + await userContainer.item(subscription.id!, subscription.pk).delete() + } + + const inverseSubscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.userId = @viewer AND u.pk = @user AND u.t = @type`, { + user: user.id, + viewer: viewer.id, + type: UserItemType.InverseSubscription, + }) + + const inverseSubscriptions = await queryItems({ container: userContainer, query: inverseSubscriptionQuery, logger: request.log }) + for (const inverseSubscription of inverseSubscriptions) { + await userContainer.item(inverseSubscription.id!, inverseSubscription.pk).delete() } reply.code(204) diff --git a/src/schemas.ts b/src/schemas.ts index c573cef..60bb01c 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -44,6 +44,15 @@ export const groupListingSchema: JSONSchema = { }, } +export const subscriptionSchema: JSONSchema = { + type: 'object', + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + pending: { type: 'boolean' }, + }, +} + export const userSchema: JSONSchema = { type: 'object', properties: { @@ -53,8 +62,10 @@ export const userSchema: JSONSchema = { about: { type: 'string' }, imageUrl: { type: 'string' }, coverImageUrl: { type: 'string' }, - subscribed: { type: 'boolean' }, - subscribedToYou: { type: 'boolean' }, + subscriptions: { + type: 'array', + items: subscriptionSchema, + }, membership: { type: 'string' }, posts: { type: 'number' }, awards: { type: 'number' }, diff --git a/src/types/collections.ts b/src/types/collections.ts index 6d55aaa..6e5f834 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -26,6 +26,7 @@ export enum UserItemType { Follow = 'follow', Timeline = 'timeline', Subscription = 'subscription', + InverseSubscription = 'isubscription', Block = 'block', Transaction = 'transation', } @@ -213,13 +214,23 @@ export interface UserPost { } export interface UserSubscription { - id: string - pk: string + id?: string + userId: string // from + pk: string // to t: UserItemType.Subscription pending: boolean created: number } +export interface UserInverseSubscription { + id?: string + userId: string // to + pk: string // from + t: UserItemType.InverseSubscription + pending: boolean + created: number +} + export interface UserBlock { id?: string blockedId: string