diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index c03f7bacaa..bd50ab3083 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -75,7 +75,7 @@ export class SearchService extends BaseService { const page = dto.page ?? 1; const size = dto.size || 250; - const userIds = await this.getUserIdsToSearch(auth); + const userIds = await this.getUserIdsToSearch(auth, dto.visibility); const { hasNextPage, items } = await this.searchRepository.searchMetadata( { page, size }, { @@ -103,7 +103,7 @@ export class SearchService extends BaseService { requireElevatedPermission(auth); } - const userIds = await this.getUserIdsToSearch(auth); + const userIds = await this.getUserIdsToSearch(auth, dto.visibility); const items = await this.searchRepository.searchRandom(dto.size || 250, { ...dto, userIds }); return items.map((item) => mapAsset(item, { auth })); } @@ -113,7 +113,7 @@ export class SearchService extends BaseService { requireElevatedPermission(auth); } - const userIds = await this.getUserIdsToSearch(auth); + const userIds = await this.getUserIdsToSearch(auth, dto.visibility); const items = await this.searchRepository.searchLargeAssets(dto.size || 250, { ...dto, userIds }); return items.map((item) => mapAsset(item, { auth })); } @@ -128,7 +128,7 @@ export class SearchService extends BaseService { throw new BadRequestException('Smart search is not enabled'); } - const userIds = this.getUserIdsToSearch(auth); + const userIds = this.getUserIdsToSearch(auth, dto.visibility); let embedding; if (dto.query) { const key = machineLearning.clip.modelName + dto.query + dto.language; @@ -202,7 +202,11 @@ export class SearchService extends BaseService { } } - private async getUserIdsToSearch(auth: AuthDto): Promise { + private async getUserIdsToSearch(auth: AuthDto, visibility?: AssetVisibility): Promise { + // Locked assets are personal. Never include partner IDs, regardless of A's elevated session. + if (visibility === AssetVisibility.Locked) { + return [auth.user.id]; + } const partnerIds = await getMyPartnerIds({ userId: auth.user.id, repository: this.partnerRepository, diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts index 4f447f6c3d..ca668b6068 100644 --- a/server/src/services/timeline.service.spec.ts +++ b/server/src/services/timeline.service.spec.ts @@ -204,5 +204,16 @@ describe(TimelineService.name, () => { }), ).rejects.toThrow(BadRequestException); }); + + it('should throw an error if withPartners is true and visibility is locked', async () => { + await expect( + sut.getTimeBucket(authStub.adminWithElevatedPermission, { + timeBucket: 'bucket', + visibility: AssetVisibility.Locked, + withPartners: true, + userId: authStub.adminWithElevatedPermission.user.id, + }), + ).rejects.toThrow(BadRequestException); + }); }); }); diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index 7e9013dc01..b7ecbad9a9 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -71,13 +71,14 @@ export class TimelineService extends BaseService { } if (dto.withPartners) { + const requestedLocked = dto.visibility === AssetVisibility.Locked; const requestedArchived = dto.visibility === AssetVisibility.Archive || dto.visibility === undefined; const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false; const requestedTrash = dto.isTrashed === true; - if (requestedArchived || requestedFavorite || requestedTrash) { + if (requestedLocked || requestedArchived || requestedFavorite || requestedTrash) { throw new BadRequestException( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); } } diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts index 85d52f14a1..88a2278898 100644 --- a/server/test/fixtures/auth.stub.ts +++ b/server/test/fixtures/auth.stub.ts @@ -41,6 +41,13 @@ export const authStub = { id: 'token-id', } as AuthSession, }), + adminWithElevatedPermission: Object.freeze({ + user: authUser.admin, + session: { + id: 'token-id-elevated', + hasElevatedPermission: true, + } as AuthSession, + }), adminSharedLink: Object.freeze({ user: authUser.admin, sharedLink: { diff --git a/server/test/medium/specs/services/timeline.service.spec.ts b/server/test/medium/specs/services/timeline.service.spec.ts index cb696366d0..d7013b84be 100644 --- a/server/test/medium/specs/services/timeline.service.spec.ts +++ b/server/test/medium/specs/services/timeline.service.spec.ts @@ -51,13 +51,13 @@ describe(TimelineService.name, () => { const response1 = sut.getTimeBuckets(auth, { withPartners: true, visibility: AssetVisibility.Archive }); await expect(response1).rejects.toBeInstanceOf(BadRequestException); await expect(response1).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); const response2 = sut.getTimeBuckets(auth, { withPartners: true }); await expect(response2).rejects.toBeInstanceOf(BadRequestException); await expect(response2).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); }); @@ -67,13 +67,13 @@ describe(TimelineService.name, () => { const response1 = sut.getTimeBuckets(auth, { withPartners: true, isFavorite: false }); await expect(response1).rejects.toBeInstanceOf(BadRequestException); await expect(response1).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); const response2 = sut.getTimeBuckets(auth, { withPartners: true, isFavorite: true }); await expect(response2).rejects.toBeInstanceOf(BadRequestException); await expect(response2).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); }); @@ -83,7 +83,7 @@ describe(TimelineService.name, () => { const response = sut.getTimeBuckets(auth, { withPartners: true, isTrashed: true }); await expect(response).rejects.toBeInstanceOf(BadRequestException); await expect(response).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); });