diff --git a/packages/api/src/repository/label.ts b/packages/api/src/repository/label.ts index 8a1e615f0..adb90457a 100644 --- a/packages/api/src/repository/label.ts +++ b/packages/api/src/repository/label.ts @@ -49,11 +49,12 @@ export const labelRepository = entityManager.getRepository(Label).extend({ .getOne() }, - findByNames(names: string[]) { + findByNames(names: string[], userId: string) { return this.createQueryBuilder() .where('LOWER(name) IN (:...names)', { names: names.map((n) => n.toLowerCase()), }) + .andWhere('user_id = :userId', { userId }) .getMany() }, diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index cfc3898cd..7e4f52aee 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -794,7 +794,7 @@ export const bulkActionResolver = authorized< return { errorCodes: [BulkActionErrorCode.BadRequest] } } - labels = await findLabelsByIds(labelIds) + labels = await findLabelsByIds(labelIds, uid) } // parse query diff --git a/packages/api/src/resolvers/labels/index.ts b/packages/api/src/resolvers/labels/index.ts index 5ca48a3ee..ef0687c04 100644 --- a/packages/api/src/resolvers/labels/index.ts +++ b/packages/api/src/resolvers/labels/index.ts @@ -50,6 +50,9 @@ export const labelsResolver = authorized( const labels = await authTrx(async (tx) => { return tx.withRepository(labelRepository).find({ + where: { + user: { id: uid }, + }, order: { position: 'ASC', }, diff --git a/packages/api/src/resolvers/recent_emails/index.ts b/packages/api/src/resolvers/recent_emails/index.ts index d61731a3e..8b91143d0 100644 --- a/packages/api/src/resolvers/recent_emails/index.ts +++ b/packages/api/src/resolvers/recent_emails/index.ts @@ -20,10 +20,13 @@ import { sendEmail } from '../../utils/sendEmail' export const recentEmailsResolver = authorized< RecentEmailsSuccess, RecentEmailsError ->(async (_, __, { authTrx, log }) => { +>(async (_, __, { authTrx, log, uid }) => { try { const recentEmails = await authTrx((t) => t.getRepository(ReceivedEmail).find({ + where: { + user: { id: uid }, + }, order: { createdAt: 'DESC' }, take: 20, }) @@ -50,6 +53,7 @@ export const markEmailAsItemResolver = authorized< const recentEmail = await authTrx((t) => t.getRepository(ReceivedEmail).findOneBy({ id: recentEmailId, + user: { id: uid }, type: 'non-article', }) ) @@ -64,6 +68,7 @@ export const markEmailAsItemResolver = authorized< const newsletterEmail = await authTrx((t) => t.getRepository(NewsletterEmail).findOne({ where: { + user: { id: uid }, address: ILike(recentEmail.to), }, relations: ['user'], diff --git a/packages/api/src/resolvers/user_device_tokens/index.ts b/packages/api/src/resolvers/user_device_tokens/index.ts index 483a917f0..661762f48 100644 --- a/packages/api/src/resolvers/user_device_tokens/index.ts +++ b/packages/api/src/resolvers/user_device_tokens/index.ts @@ -28,13 +28,11 @@ export const setDeviceTokenResolver = authorized< SetDeviceTokenSuccess, SetDeviceTokenError, MutationSetDeviceTokenArgs ->(async (_parent, { input }, { claims: { uid }, log }) => { - log.info('setDeviceTokenResolver', input) - +>(async (_parent, { input }, { uid, log }) => { const { id, token } = input if (!id && !token) { - log.info('id or token is required') + log.error('id or token is required') return { errorCodes: [SetDeviceTokenErrorCode.BadRequest], @@ -44,7 +42,7 @@ export const setDeviceTokenResolver = authorized< try { // when token is null, we are deleting it if (!token && id) { - const deviceToken = await findDeviceTokenById(id) + const deviceToken = await findDeviceTokenById(id, uid) if (!deviceToken) { log.error('device token not found', id) @@ -107,7 +105,7 @@ export const setDeviceTokenResolver = authorized< token ) { // duplicate token - const deviceToken = await findDeviceTokenByToken(token) + const deviceToken = await findDeviceTokenByToken(token, uid) if (!deviceToken) { return { diff --git a/packages/api/src/services/api_key.ts b/packages/api/src/services/api_key.ts index 8f004a748..95b613a2b 100644 --- a/packages/api/src/services/api_key.ts +++ b/packages/api/src/services/api_key.ts @@ -18,7 +18,10 @@ export const findApiKeys = async ( 'createdAt', 'usedAt', ], - where, + where: { + ...where, + user: { id: userId }, + }, order: { usedAt: { direction: 'DESC', nulls: 'last' }, createdAt: 'DESC', diff --git a/packages/api/src/services/integrations/index.ts b/packages/api/src/services/integrations/index.ts index 281f75530..5927af9ac 100644 --- a/packages/api/src/services/integrations/index.ts +++ b/packages/api/src/services/integrations/index.ts @@ -45,7 +45,11 @@ export const findIntegration = async ( userId: string ) => { return authTrx( - async (t) => t.getRepository(Integration).findOneBy(where), + async (t) => + t.getRepository(Integration).findOneBy({ + ...where, + user: { id: userId }, + }), undefined, userId ) @@ -56,7 +60,11 @@ export const findIntegrations = async ( where?: FindOptionsWhere | FindOptionsWhere[] ) => { return authTrx( - async (t) => t.getRepository(Integration).find({ where }), + async (t) => + t.getRepository(Integration).findBy({ + ...where, + user: { id: userId }, + }), undefined, userId ) diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index 25172110f..bfb109db7 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -31,7 +31,8 @@ export const findOrCreateLabels = async ( const labelRepo = tx.withRepository(labelRepository) // find existing labels const labelEntities = await labelRepo.findByNames( - labels.map((l) => l.name) + labels.map((l) => l.name), + userId ) const existingLabelsInLowerCase = labelEntities.map((l) => @@ -99,7 +100,7 @@ export const addLabelsToLibraryItem = async ( async (tx) => { const libraryItem = await tx .withRepository(libraryItemRepository) - .findOneByOrFail({ id: libraryItemId }) + .findOneByOrFail({ id: libraryItemId, user: { id: userId } }) if (libraryItem.labels) { labels.push(...libraryItem.labels) @@ -156,12 +157,20 @@ export const saveLabelsInHighlight = async ( ) } -export const findLabelsByIds = async (ids: string[]): Promise => { - return authTrx(async (tx) => { - return tx.withRepository(labelRepository).findBy({ - id: In(ids), - }) - }) +export const findLabelsByIds = async ( + ids: string[], + userId: string +): Promise => { + return authTrx( + async (tx) => { + return tx.withRepository(labelRepository).findBy({ + id: In(ids), + user: { id: userId }, + }) + }, + undefined, + userId + ) } export const createLabel = async ( @@ -219,7 +228,10 @@ export const findLabelsByUserId = async (userId: string): Promise => { export const findLabelById = async (id: string, userId: string) => { return authTrx( - async (tx) => tx.withRepository(labelRepository).findOneBy({ id }), + async (tx) => + tx + .withRepository(labelRepository) + .findOneBy({ id, user: { id: userId } }), undefined, userId ) diff --git a/packages/api/src/services/received_emails.ts b/packages/api/src/services/received_emails.ts index 0551a28b1..b1ea031af 100644 --- a/packages/api/src/services/received_emails.ts +++ b/packages/api/src/services/received_emails.ts @@ -32,23 +32,27 @@ export const updateReceivedEmail = async ( userId: string ) => { return authTrx( - (t) => t.getRepository(ReceivedEmail).update(id, { type }), + (t) => + t + .getRepository(ReceivedEmail) + .update({ id, user: { id: userId } }, { type }), undefined, userId ) } -export const deleteReceivedEmail = async (id: string, userId?: string) => { +export const deleteReceivedEmail = async (id: string, userId: string) => { return authTrx( - (t) => t.getRepository(ReceivedEmail).delete(id), + (t) => t.getRepository(ReceivedEmail).delete({ id, user: { id: userId } }), undefined, userId ) } -export const findReceivedEmailById = async (id: string, userId?: string) => { +export const findReceivedEmailById = async (id: string, userId: string) => { return authTrx( - (t) => t.getRepository(ReceivedEmail).findOneBy({ id }), + (t) => + t.getRepository(ReceivedEmail).findOneBy({ id, user: { id: userId } }), undefined, userId ) diff --git a/packages/api/src/services/rules.ts b/packages/api/src/services/rules.ts index 4153fe493..01f86c8ce 100644 --- a/packages/api/src/services/rules.ts +++ b/packages/api/src/services/rules.ts @@ -13,6 +13,7 @@ export const createRule = async ( ): Promise => { const existingRule = await authTrx((t) => t.getRepository(Rule).findOneBy({ + user: { id: userId }, name: ILike(rule.name), }) ) @@ -32,11 +33,11 @@ export const createRule = async ( ) } -export const deleteRule = async (id: string, userId?: string) => { +export const deleteRule = async (id: string, userId: string) => { return authTrx( async (t) => { const repo = t.getRepository(Rule) - const rule = await repo.findOneByOrFail({ id }) + const rule = await repo.findOneByOrFail({ id, user: { id: userId } }) await repo.delete(id) return rule }, diff --git a/packages/api/src/services/user_device_tokens.ts b/packages/api/src/services/user_device_tokens.ts index 19c64e45c..180fca7cf 100644 --- a/packages/api/src/services/user_device_tokens.ts +++ b/packages/api/src/services/user_device_tokens.ts @@ -6,19 +6,28 @@ import { analytics } from '../utils/analytics' export const findDeviceTokenById = async ( id: string, - userId?: string + userId: string ): Promise => { return authTrx( - (t) => t.getRepository(UserDeviceToken).findOneBy({ id }), + (t) => + t.getRepository(UserDeviceToken).findOneBy({ id, user: { id: userId } }), undefined, userId ) } export const findDeviceTokenByToken = async ( - token: string + token: string, + userId: string ): Promise => { - return authTrx((t) => t.getRepository(UserDeviceToken).findOneBy({ token })) + return authTrx( + (t) => + t + .getRepository(UserDeviceToken) + .findOneBy({ token, user: { id: userId } }), + undefined, + userId + ) } export const findDeviceTokensByUserId = async ( @@ -70,7 +79,9 @@ export const deleteDeviceToken = async ( }) return authTrx(async (t) => { - const result = await t.getRepository(UserDeviceToken).delete(id) + const result = await t + .getRepository(UserDeviceToken) + .delete({ id, user: { id: userId } }) return !!result.affected }) diff --git a/packages/api/src/services/webhook.ts b/packages/api/src/services/webhook.ts index 4747d225d..4414f44a1 100644 --- a/packages/api/src/services/webhook.ts +++ b/packages/api/src/services/webhook.ts @@ -26,7 +26,7 @@ export const createWebhook = async ( ) } -export const findWebhooks = async (userId?: string) => { +export const findWebhooks = async (userId: string) => { return authTrx( (tx) => tx.getRepository(Webhook).findBy({ user: { id: userId } }), undefined, @@ -34,19 +34,19 @@ export const findWebhooks = async (userId?: string) => { ) } -export const findWebhookById = async (id: string, userId?: string) => { +export const findWebhookById = async (id: string, userId: string) => { return authTrx( - (tx) => tx.getRepository(Webhook).findOneBy({ id }), + (tx) => tx.getRepository(Webhook).findOneBy({ id, user: { id: userId } }), undefined, userId ) } -export const deleteWebhook = async (id: string, userId?: string) => { +export const deleteWebhook = async (id: string, userId: string) => { return authTrx( async (tx) => { const repo = tx.getRepository(Webhook) - const webhook = await repo.findOneByOrFail({ id }) + const webhook = await repo.findOneByOrFail({ id, user: { id: userId } }) await repo.delete(id) return webhook }, diff --git a/packages/api/test/resolvers/user_device_tokens.test.ts b/packages/api/test/resolvers/user_device_tokens.test.ts index 1f22179e2..aeb38ef83 100644 --- a/packages/api/test/resolvers/user_device_tokens.test.ts +++ b/packages/api/test/resolvers/user_device_tokens.test.ts @@ -3,9 +3,12 @@ import 'mocha' import { User } from '../../src/entity/user' import { UserDeviceToken } from '../../src/entity/user_device_tokens' import { SetDeviceTokenErrorCode } from '../../src/generated/graphql' -import { getRepository } from '../../src/repository' import { deleteUser } from '../../src/services/user' -import { createDeviceToken, deleteDeviceTokens, findDeviceTokenById } from '../../src/services/user_device_tokens' +import { + createDeviceToken, + deleteDeviceTokens, + findDeviceTokenById, +} from '../../src/services/user_device_tokens' import { createTestDeviceToken, createTestUser } from '../db' import { generateFakeUuid, graphqlRequest, request } from '../util' @@ -76,7 +79,8 @@ describe('Device tokens API', () => { it('responds with status code 200 and deletes the token', async () => { const response = await graphqlRequest(query, authToken).expect(200) const deviceToken = await findDeviceTokenById( - response.body.data.setDeviceToken.deviceToken.id + response.body.data.setDeviceToken.deviceToken.id, + user.id ) expect(deviceToken).to.be.null }) diff --git a/packages/db/migrations/0126.do.revert_rls.sql b/packages/db/migrations/0126.do.revert_rls.sql new file mode 100755 index 000000000..8c778d9d2 --- /dev/null +++ b/packages/db/migrations/0126.do.revert_rls.sql @@ -0,0 +1,38 @@ +-- Type: DO +-- Name: revert_rls +-- Description: Revert rls on rows + +BEGIN; + +ALTER TABLE omnivore.filters DISABLE ROW LEVEL SECURITY; +DROP POLICY filters_policy on omnivore.filters; + +ALTER TABLE omnivore.integrations DISABLE ROW LEVEL SECURITY; +DROP POLICY integrations_policy on omnivore.integrations; + +DROP POLICY labels_policy on omnivore.labels; +CREATE POLICY read_labels on omnivore.labels + FOR SELECT TO omnivore_user + USING (true); +CREATE POLICY create_labels on omnivore.labels + FOR INSERT TO omnivore_user + WITH CHECK (true); + +ALTER TABLE omnivore.received_emails DISABLE ROW LEVEL SECURITY; +DROP POLICY received_emails_policy on omnivore.received_emails; + +ALTER TABLE omnivore.rules DISABLE ROW LEVEL SECURITY; +DROP POLICY rules_policy on omnivore.rules; + +ALTER TABLE omnivore.webhooks DISABLE ROW LEVEL SECURITY; +DROP POLICY webhooks_policy on omnivore.webhooks; + +DROP POLICY user_device_tokens_policy on omnivore.user_device_tokens; +CREATE POLICY read_user_device_tokens on omnivore.user_device_tokens + FOR SELECT TO omnivore_user + USING (true); +CREATE POLICY create_user_device_tokens on omnivore.user_device_tokens + FOR INSERT TO omnivore_user + WITH CHECK (true); + +COMMIT; diff --git a/packages/db/migrations/0126.undo.revert_rls.sql b/packages/db/migrations/0126.undo.revert_rls.sql new file mode 100755 index 000000000..ea04b6cd0 --- /dev/null +++ b/packages/db/migrations/0126.undo.revert_rls.sql @@ -0,0 +1,50 @@ +-- Type: UNDO +-- Name: revert_rls +-- Description: Revert rls on rows + +BEGIN; + +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; + +DROP POLICY read_labels ON omnivore.labels; +DROP POLICY create_labels ON omnivore.labels; +CREATE POLICY labels_policy on omnivore.labels + USING (user_id = omnivore.get_current_user_id()) + WITH CHECK (user_id = omnivore.get_current_user_id()); + +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; + +DROP POLICY read_user_device_tokens ON omnivore.user_device_tokens; +DROP POLICY create_user_device_tokens ON omnivore.user_device_tokens; +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; + +COMMIT;