Dwayne Harris 5 years ago
parent
commit
1f70ba6f6e
  1. 2
      src/lib/collections.ts
  2. 4
      src/lib/crypto.ts
  3. 4
      src/plugins/api/authentication.ts
  4. 12
      src/plugins/api/posts.ts
  5. 83
      src/plugins/api/users.ts
  6. 15
      src/schemas.ts
  7. 15
      src/types/collections.ts

2
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`,

4
src/lib/crypto.ts

@ -6,8 +6,8 @@ export async function hashPassword(password: string): Promise<string> {
return await hash(password, 8)
}
export async function verifyPassword(hash: string, password: string): Promise<boolean> {
return await compare(hash, password)
export async function comparePassword(password: string, hash: string): Promise<boolean> {
return await compare(password, hash)
}
export const generateString = (length: number) => randomBytes(Math.max(Math.round(length / 2), 5)).toString('hex')

4
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<Server, IncomingMessage, Serv
const user = await getItem<User>({ 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)

12
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<PostRelationship>(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<UserSubscription>({
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<UserTimelinePost>({
id: postId,
pk: uid,

83
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<Server, IncomingMessage, ServerRespons
id: string
}
interface Subscription {
from: string
to: string
pending: boolean
}
const options: RouteShorthandOptions = {
schema: {
params: {
@ -190,10 +196,9 @@ function getRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespons
const user = await getUser(server.database.client, request.params.id)
if (!user) return notFoundError(reply)
let subscribed = false
let subscribedToYou = false
const subscriptions: Subscription[] = []
if (request.viewer) {
if (request.viewer && request.viewer.id !== user.id) {
const viewer = await getItem<User>({ 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<Server, IncomingMessage, ServerRespons
const blocks = await getUserBlocks(server.database.client, user.id, [viewer.id, viewer.groupId], request.log)
if (blocks.length > 0) return unauthorizedError(reply)
subscribed = !!(await getItem<UserSubscription>({
const subscription = (await queryItems<UserSubscription>({
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<UserSubscription>({
const inverseSubscription = (await queryItems<UserInverseSubscription>({
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<Server, IncomingMessage, ServerR
if (blocks.length > 0) return badRequestError(reply, 'Invalid operation')
await userContainer.items.create<UserSubscription>({
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<UserInverseSubscription>({
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<Server, IncomingMessage, Serve
if (!user) return notFoundError(reply)
if (!viewer) return serverError(reply)
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.id = @user AND u.pk = @viewer AND u.t = @type`, {
const subscriptionQuery = createQuerySpec(`SELECT u.id, u.pk FROM Users u WHERE u.userId = @user AND u.pk = @viewer AND u.t = @type`, {
user: user.id,
viewer: viewer.id,
type: UserItemType.Subscription,
@ -343,7 +379,18 @@ function unsubscribeRoute(server: FastifyInstance<Server, IncomingMessage, Serve
const subscriptions = await queryItems<UserSubscription>({ 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<UserInverseSubscription>({ container: userContainer, query: inverseSubscriptionQuery, logger: request.log })
for (const inverseSubscription of inverseSubscriptions) {
await userContainer.item(inverseSubscription.id!, inverseSubscription.pk).delete()
}
reply.code(204)

15
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' },

15
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

Loading…
Cancel
Save