From b72acf8e6f51a25e79e154513c60648458dab4d6 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 5 Sep 2023 13:51:11 +0800 Subject: [PATCH] fix permission issue --- packages/api/src/resolvers/article/index.ts | 9 +-- packages/api/src/resolvers/labels/index.ts | 12 ++- .../api/src/resolvers/newsletters/index.ts | 27 +++---- .../api/src/resolvers/recent_emails/index.ts | 79 ++++++------------- .../api/src/resolvers/subscriptions/index.ts | 12 +-- packages/api/src/resolvers/webhooks/index.ts | 24 +++--- packages/api/src/services/groups.ts | 7 +- packages/api/src/services/labels.ts | 2 +- packages/api/src/services/library_item.ts | 2 +- packages/api/src/services/newsletters.ts | 2 +- packages/api/src/services/save_email.ts | 34 ++++---- packages/api/src/services/save_file.ts | 4 +- packages/api/src/services/save_page.ts | 6 +- packages/api/src/services/subscriptions.ts | 6 +- packages/db/migrations/0123.do.add_rls.sql | 9 +++ 15 files changed, 103 insertions(+), 132 deletions(-) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 41bc2c62b..83ab3dfff 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -63,7 +63,7 @@ import { createPageSaveRequest } from '../../services/create_page_save_request' import { addLabelsToLibraryItem, findLabelsByIds, - getLabelsAndCreateIfNotExist, + findOrCreateLabels, } from '../../services/labels' import { createLibraryItem, @@ -340,10 +340,7 @@ export const createArticleResolver = authorized< libraryItemToSave.archivedAt = state === ArticleSavingRequestStatus.Archived ? new Date() : null if (inputLabels) { - libraryItemToSave.labels = await getLabelsAndCreateIfNotExist( - inputLabels, - uid - ) + libraryItemToSave.labels = await findOrCreateLabels(inputLabels, uid) } let libraryItemToReturn: LibraryItem @@ -862,7 +859,7 @@ export const setFavoriteArticleResolver = authorized< return { errorCodes: [SetFavoriteArticleErrorCode.BadRequest] } } - const labels = await getLabelsAndCreateIfNotExist([label], uid) + const labels = await findOrCreateLabels([label], uid) // adds Favorites label to page await addLabelsToLibraryItem(labels, id, uid) diff --git a/packages/api/src/resolvers/labels/index.ts b/packages/api/src/resolvers/labels/index.ts index b1b046e90..ca825631c 100644 --- a/packages/api/src/resolvers/labels/index.ts +++ b/packages/api/src/resolvers/labels/index.ts @@ -29,7 +29,7 @@ import { } from '../../generated/graphql' import { labelRepository } from '../../repository/label' import { - getLabelsAndCreateIfNotExist, + findOrCreateLabels, saveLabelsInHighlight, saveLabelsInLibraryItem, } from '../../services/labels' @@ -127,7 +127,11 @@ export const deleteLabelResolver = authorized< }) return { - label: deleteResult.raw as Label, + label: { + id: labelId, + name: '', + color: '', + }, } } catch (error) { log.error('error deleting label', error) @@ -160,7 +164,7 @@ export const setLabelsResolver = authorized< if (labels && labels.length > 0) { // for new clients that send label names // create labels if they don't exist - labelsSet = await getLabelsAndCreateIfNotExist(labels, uid) + labelsSet = await findOrCreateLabels(labels, uid) } else if (labelIds && labelIds.length > 0) { // for old clients that send labelIds labelsSet = await authTrx(async (tx) => { @@ -260,7 +264,7 @@ export const setLabelsForHighlightResolver = authorized< if (labels && labels.length > 0) { // for new clients that send label names // create labels if they don't exist - labelsSet = await getLabelsAndCreateIfNotExist(labels, uid) + labelsSet = await findOrCreateLabels(labels, uid) } else if (labelIds && labelIds.length > 0) { // for old clients that send labelIds labelsSet = await authTrx(async (tx) => { diff --git a/packages/api/src/resolvers/newsletters/index.ts b/packages/api/src/resolvers/newsletters/index.ts index 243d1c3a9..eea6bb4f1 100644 --- a/packages/api/src/resolvers/newsletters/index.ts +++ b/packages/api/src/resolvers/newsletters/index.ts @@ -1,5 +1,4 @@ import { NewsletterEmail } from '../../entity/newsletter_email' -import { User } from '../../entity/user' import { env } from '../../env' import { CreateNewsletterEmailError, @@ -13,7 +12,6 @@ import { NewsletterEmailsErrorCode, NewsletterEmailsSuccess, } from '../../generated/graphql' -import { getRepository } from '../../repository' import { createNewsletterEmail, deleteNewsletterEmail, @@ -80,10 +78,9 @@ export const deleteNewsletterEmailResolver = authorized< DeleteNewsletterEmailSuccess, DeleteNewsletterEmailError, MutationDeleteNewsletterEmailArgs ->(async (_parent, args, { claims, log }) => { - log.info('deleteNewsletterEmailResolver') +>(async (_parent, args, { authTrx, uid, log }) => { analytics.track({ - userId: claims.uid, + userId: uid, event: 'newsletter_email_address_deleted', properties: { env: env.server.apiEnv, @@ -91,12 +88,14 @@ export const deleteNewsletterEmailResolver = authorized< }) try { - const newsletterEmail = await getRepository(NewsletterEmail).findOne({ - where: { - id: args.newsletterEmailId, - }, - relations: ['user', 'subscriptions'], - }) + const newsletterEmail = await authTrx((t) => + t.getRepository(NewsletterEmail).findOne({ + where: { + id: args.newsletterEmailId, + }, + relations: ['user', 'subscriptions'], + }) + ) if (!newsletterEmail) { return { @@ -104,12 +103,6 @@ export const deleteNewsletterEmailResolver = authorized< } } - if (newsletterEmail.user.id !== claims.uid) { - return { - errorCodes: [DeleteNewsletterEmailErrorCode.Unauthorized], - } - } - // unsubscribe all before deleting await unsubscribeAll(newsletterEmail) diff --git a/packages/api/src/resolvers/recent_emails/index.ts b/packages/api/src/resolvers/recent_emails/index.ts index 203eebdbf..7833533e6 100644 --- a/packages/api/src/resolvers/recent_emails/index.ts +++ b/packages/api/src/resolvers/recent_emails/index.ts @@ -11,7 +11,6 @@ import { RecentEmailsErrorCode, RecentEmailsSuccess, } from '../../generated/graphql' -import { getRepository } from '../../repository' import { updateReceivedEmail } from '../../services/received_emails' import { saveNewsletter } from '../../services/save_newsletter_email' import { authorized } from '../../utils/helpers' @@ -21,34 +20,20 @@ import { sendEmail } from '../../utils/sendEmail' export const recentEmailsResolver = authorized< RecentEmailsSuccess, RecentEmailsError ->(async (_, __, { claims, log }) => { - log.info('Getting recent emails', { - labels: { - source: 'resolver', - resolver: 'recentEmailsResolver', - uid: claims.uid, - }, - }) - +>(async (_, __, { authTrx, log }) => { try { - const recentEmails = await getRepository(ReceivedEmail).find({ - where: { user: { id: claims.uid } }, - order: { createdAt: 'DESC' }, - take: 20, - }) + const recentEmails = await authTrx((t) => + t.getRepository(ReceivedEmail).find({ + order: { createdAt: 'DESC' }, + take: 20, + }) + ) return { recentEmails, } } catch (error) { - log.error('Error getting recent emails', { - error, - labels: { - source: 'resolver', - resolver: 'recentEmailsResolver', - uid: claims.uid, - }, - }) + log.error('Error getting recent emails', error) return { errorCodes: [RecentEmailsErrorCode.BadRequest], @@ -60,22 +45,14 @@ export const markEmailAsItemResolver = authorized< MarkEmailAsItemSuccess, MarkEmailAsItemError, MutationMarkEmailAsItemArgs ->(async (_, { recentEmailId }, { claims, log }) => { - log.info('Marking email as item', { - recentEmailId, - labels: { - source: 'resolver', - resolver: 'markEmailAsItemResolver', - uid: claims.uid, - }, - }) - +>(async (_, { recentEmailId }, { authTrx, uid, log }) => { try { - const recentEmail = await getRepository(ReceivedEmail).findOneBy({ - id: recentEmailId, - user: { id: claims.uid }, - type: 'non-article', - }) + const recentEmail = await authTrx((t) => + t.getRepository(ReceivedEmail).findOneBy({ + id: recentEmailId, + type: 'non-article', + }) + ) if (!recentEmail) { log.info('no recent email', recentEmailId) @@ -84,13 +61,14 @@ export const markEmailAsItemResolver = authorized< } } - const newsletterEmail = await getRepository(NewsletterEmail).findOne({ - where: { - address: ILike(recentEmail.to), - user: { id: claims.uid }, - }, - relations: ['user'], - }) + const newsletterEmail = await authTrx((t) => + t.getRepository(NewsletterEmail).findOne({ + where: { + address: ILike(recentEmail.to), + }, + relations: ['user'], + }) + ) if (!newsletterEmail) { log.info('no newsletter email for', { id: recentEmail.id, @@ -127,7 +105,7 @@ export const markEmailAsItemResolver = authorized< await updateReceivedEmail(recentEmail.id, 'article') const text = `A recent email marked as a library item - by: ${claims.uid} + by: ${uid} from: ${recentEmail.from} subject: ${recentEmail.subject}` @@ -143,14 +121,7 @@ export const markEmailAsItemResolver = authorized< success, } } catch (error) { - log.error('Error marking email as item', { - error, - labels: { - source: 'resolver', - resolver: 'markEmailAsItemResolver', - uid: claims.uid, - }, - }) + log.error('Error marking email as item', error) return { errorCodes: [MarkEmailAsItemErrorCode.BadRequest], diff --git a/packages/api/src/resolvers/subscriptions/index.ts b/packages/api/src/resolvers/subscriptions/index.ts index d157278e7..e6a8800f0 100644 --- a/packages/api/src/resolvers/subscriptions/index.ts +++ b/packages/api/src/resolvers/subscriptions/index.ts @@ -63,15 +63,14 @@ export const subscriptionsResolver = authorized< sort?.by === SortBy.UpdatedTime ? 'lastFetchedAt' : 'createdAt' const sortOrder = sort?.order === SortOrder.Ascending ? 'ASC' : 'DESC' - const queryBuilder = await authTrx((t) => - t + const subscriptions = await authTrx(async (t) => { + const queryBuilder = t .getRepository(Subscription) .createQueryBuilder('subscription') .leftJoinAndSelect('subscription.newsletterEmail', 'newsletterEmail') .where({ user: { id: uid }, }) - ) if (type && type == SubscriptionType.Newsletter) { queryBuilder.andWhere({ @@ -95,9 +94,10 @@ export const subscriptionsResolver = authorized< ) } - const subscriptions = await queryBuilder - .orderBy('subscription.' + sortBy, sortOrder) - .getMany() + return queryBuilder + .orderBy('subscription.' + sortBy, sortOrder) + .getMany() + }) return { subscriptions, diff --git a/packages/api/src/resolvers/webhooks/index.ts b/packages/api/src/resolvers/webhooks/index.ts index 19379a72a..664a7c9ab 100644 --- a/packages/api/src/resolvers/webhooks/index.ts +++ b/packages/api/src/resolvers/webhooks/index.ts @@ -82,18 +82,7 @@ export const deleteWebhookResolver = authorized< MutationDeleteWebhookArgs >(async (_, { id }, { authTrx, uid, log }) => { try { - const deletedWebhook = await authTrx(async (t) => { - const webhook = await t.getRepository(Webhook).findOne({ - where: { id }, - relations: ['user'], - }) - - if (!webhook) { - throw new Error('Webhook not found') - } - - return t.getRepository(Webhook).remove(webhook) - }) + await authTrx(async (t) => t.getRepository(Webhook).delete(id)) analytics.track({ userId: uid, @@ -105,7 +94,16 @@ export const deleteWebhookResolver = authorized< }) return { - webhook: webhookDataToResponse(deletedWebhook), + webhook: { + id, + url: '', + eventTypes: [], + method: 'POST', + contentType: 'application/json', + enabled: false, + createdAt: new Date(), + updatedAt: new Date(), + }, } } catch (error) { log.error('Error deleting webhook', error) diff --git a/packages/api/src/services/groups.ts b/packages/api/src/services/groups.ts index b580385d7..7f8c70fe1 100644 --- a/packages/api/src/services/groups.ts +++ b/packages/api/src/services/groups.ts @@ -8,7 +8,7 @@ import { homePageURL } from '../env' import { RecommendationGroup, User as GraphqlUser } from '../generated/graphql' import { entityManager, getRepository } from '../repository' import { userDataToUser } from '../utils/helpers' -import { getLabelsAndCreateIfNotExist } from './labels' +import { findOrCreateLabels } from './labels' import { createRule } from './rules' export const createGroup = async (input: { @@ -226,10 +226,7 @@ export const createLabelAndRuleForGroup = async ( userId: string, groupName: string ) => { - const labels = await getLabelsAndCreateIfNotExist( - [{ name: groupName }], - userId - ) + const labels = await findOrCreateLabels([{ name: groupName }], userId) // create a rule to add the label to all pages in the group const addLabelPromise = createRule(userId, { diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index 99a9cffdb..d5db9c3ec 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -22,7 +22,7 @@ import { libraryItemRepository } from '../repository/library_item' // export const labelsLoader = new DataLoader(batchGetLabelsFromLinkIds) -export const getLabelsAndCreateIfNotExist = async ( +export const findOrCreateLabels = async ( labels: CreateLabelInput[], userId: string ): Promise => { diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 64f2949cd..bc872d380 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -285,7 +285,7 @@ export const findLibraryItemByUrl = async ( .leftJoinAndSelect('library_item.labels', 'labels') .leftJoinAndSelect('library_item.highlights', 'highlights') .where('library_item.user_id = :userId', { userId }) - .andWhere('library_item.url = :url', { url }) + .andWhere('library_item.original_url = :url', { url }) .getOne(), undefined, userId diff --git a/packages/api/src/services/newsletters.ts b/packages/api/src/services/newsletters.ts index 36257e3f9..d593a40ad 100644 --- a/packages/api/src/services/newsletters.ts +++ b/packages/api/src/services/newsletters.ts @@ -56,7 +56,7 @@ export const getNewsletterEmails = async ( status: SubscriptionStatus.Active, } ) - .where('newsletter_email.user_id = :userId', { userId }) + .where('newsletter_email.user = :userId', { userId }) .orderBy('newsletter_email.createdAt', 'DESC') .getMany() ) diff --git a/packages/api/src/services/save_email.ts b/packages/api/src/services/save_email.ts index 448db8a11..f4b7ac4cc 100644 --- a/packages/api/src/services/save_email.ts +++ b/packages/api/src/services/save_email.ts @@ -22,8 +22,10 @@ import { parsePreparedContent, parseUrlMetadata, } from '../utils/parser' +import { findOrCreateLabels } from './labels' import { createLibraryItem } from './library_item' import { updateReceivedEmail } from './received_emails' +import { saveSubscription } from './subscriptions' export type SaveEmailInput = { userId: string @@ -105,30 +107,30 @@ export const saveEmail = async ( publishedAt: validatedDate( parseResult.parsedContent?.publishedDate ?? undefined ), - subscription: { - name: input.author, - unsubscribeMailTo: input.unsubMailTo, - unsubscribeHttpUrl: input.unsubHttpUrl, - user: { id: input.userId }, - newsletterEmail: { id: input.newsletterEmailId }, - icon: siteIcon, - lastFetchedAt: new Date(), - }, state: LibraryItemState.Succeeded, siteIcon, siteName: parseResult.parsedContent?.siteName ?? undefined, wordCount: wordsCount(content), - labels: [ - { - ...newsletterLabel, - internal: true, - user: { id: input.userId }, - }, - ], }, input.userId ) + if (input.newsletterEmailId) { + await saveSubscription({ + userId: input.userId, + name: input.author, + unsubscribeMailTo: input.unsubMailTo, + unsubscribeHttpUrl: input.unsubHttpUrl, + icon: siteIcon, + newsletterEmailId: input.newsletterEmailId, + }) + } + + if (newsletterLabel) { + // add newsletter label + await findOrCreateLabels([newsletterLabel], input.userId) + } + await updateReceivedEmail(input.receivedEmailId, 'article') // create a task to update thumbnail and pre-cache all images diff --git a/packages/api/src/services/save_file.ts b/packages/api/src/services/save_file.ts index f1b33637d..15063ca17 100644 --- a/packages/api/src/services/save_file.ts +++ b/packages/api/src/services/save_file.ts @@ -8,7 +8,7 @@ import { } from '../generated/graphql' import { logger } from '../utils/logger' import { getStorageFileDetails } from '../utils/uploads' -import { getLabelsAndCreateIfNotExist } from './labels' +import { findOrCreateLabels } from './labels' import { updateLibraryItem } from './library_item' import { findUploadFileById, setFileUploadComplete } from './upload_file' @@ -40,7 +40,7 @@ export const saveFile = async ( input.state === ArticleSavingRequestStatus.Archived ? new Date() : null // add labels to page const labels = input.labels - ? await getLabelsAndCreateIfNotExist(input.labels, user.id) + ? await findOrCreateLabels(input.labels, user.id) : undefined if (input.state || input.labels) { const updated = await updateLibraryItem( diff --git a/packages/api/src/services/save_page.ts b/packages/api/src/services/save_page.ts index 20531d378..73af6dd13 100644 --- a/packages/api/src/services/save_page.ts +++ b/packages/api/src/services/save_page.ts @@ -30,7 +30,7 @@ import { logger } from '../utils/logger' import { parsePreparedContent } from '../utils/parser' import { createPageSaveRequest } from './create_page_save_request' import { saveHighlight } from './highlights' -import { getLabelsAndCreateIfNotExist } from './labels' +import { findOrCreateLabels } from './labels' import { createLibraryItem, updateLibraryItem } from './library_item' // where we can use APIs to fetch their underlying content. @@ -117,14 +117,14 @@ export const savePage = async ( input.state === ArticleSavingRequestStatus.Archived ? new Date() : null // add labels to page itemToSave.labels = input.labels - ? await getLabelsAndCreateIfNotExist(input.labels, user.id) + ? await findOrCreateLabels(input.labels, user.id) : undefined // check if the page already exists const existingLibraryItem = await authTrx((t) => t.getRepository(LibraryItem).findOne({ where: { user: { id: user.id }, originalUrl: itemToSave.originalUrl }, - relations: ['subscriptions'], + relations: ['subscription'], }) ) if (existingLibraryItem) { diff --git a/packages/api/src/services/subscriptions.ts b/packages/api/src/services/subscriptions.ts index 30771b657..41f3f547f 100644 --- a/packages/api/src/services/subscriptions.ts +++ b/packages/api/src/services/subscriptions.ts @@ -9,7 +9,7 @@ import { sendEmail } from '../utils/sendEmail' interface SaveSubscriptionInput { userId: string name: string - newsletterEmail: NewsletterEmail + newsletterEmailId: string unsubscribeMailTo?: string unsubscribeHttpUrl?: string icon?: string @@ -92,7 +92,7 @@ export const getSubscriptionByName = async ( export const saveSubscription = async ({ userId, name, - newsletterEmail, + newsletterEmailId, unsubscribeMailTo, unsubscribeHttpUrl, icon, @@ -121,7 +121,7 @@ export const saveSubscription = async ({ return tx.getRepository(Subscription).save({ ...subscriptionData, name, - newsletterEmail: { id: newsletterEmail.id }, + newsletterEmail: { id: newsletterEmailId }, user: { id: userId }, type: SubscriptionType.Newsletter, }) diff --git a/packages/db/migrations/0123.do.add_rls.sql b/packages/db/migrations/0123.do.add_rls.sql index 741c5a8f4..baa036a46 100755 --- a/packages/db/migrations/0123.do.add_rls.sql +++ b/packages/db/migrations/0123.do.add_rls.sql @@ -8,21 +8,25 @@ ALTER TABLE omnivore.features ENABLE ROW LEVEL SECURITY; CREATE POLICY features_policy on omnivore.features USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, UPDATE ON omnivore.features TO omnivore_user; ALTER TABLE omnivore.filters ENABLE ROW LEVEL SECURITY; CREATE POLICY filters_policy on omnivore.filters USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.filters TO omnivore_user; ALTER TABLE omnivore.integrations ENABLE ROW LEVEL SECURITY; CREATE POLICY integrations_policy on omnivore.integrations USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.integrations TO omnivore_user; ALTER TABLE omnivore.newsletter_emails ENABLE ROW LEVEL SECURITY; CREATE POLICY newsletter_emails_policy on omnivore.newsletter_emails USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, DELETE ON omnivore.newsletter_emails TO omnivore_user; CREATE POLICY labels_policy on omnivore.labels USING (user_id = omnivore.get_current_user_id()) @@ -32,25 +36,30 @@ ALTER TABLE omnivore.received_emails ENABLE ROW LEVEL SECURITY; CREATE POLICY received_emails_policy on omnivore.received_emails USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, UPDATE ON omnivore.received_emails TO omnivore_user; ALTER TABLE omnivore.rules ENABLE ROW LEVEL SECURITY; CREATE POLICY rules_policy on omnivore.rules USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.rules TO omnivore_user; ALTER TABLE omnivore.webhooks ENABLE ROW LEVEL SECURITY; CREATE POLICY webhooks_policy on omnivore.webhooks USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.webhooks TO omnivore_user; CREATE POLICY user_device_tokens_policy on omnivore.user_device_tokens USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, DELETE ON omnivore.user_device_tokens TO omnivore_user; ALTER TABLE omnivore.search_history ENABLE ROW LEVEL SECURITY; CREATE POLICY search_history_policy on omnivore.search_history USING (user_id = omnivore.get_current_user_id()) WITH CHECK (user_id = omnivore.get_current_user_id()); +GRANT SELECT, INSERT, DELETE ON omnivore.search_history TO omnivore_user; ALTER TABLE omnivore.abuse_report DROP COLUMN page_id; ALTER TABLE omnivore.abuse_report RENAME COLUMN elastic_page_id TO library_item_id;