diff --git a/server/src/domain/access/access.core.ts b/server/src/domain/access/access.core.ts index 894dcc54af..0922abbae2 100644 --- a/server/src/domain/access/access.core.ts +++ b/server/src/domain/access/access.core.ts @@ -26,6 +26,7 @@ export enum Permission { LIBRARY_CREATE = 'library.create', LIBRARY_READ = 'library.read', + LIBRARY_WRITE = 'library.write', LIBRARY_UPDATE = 'library.update', LIBRARY_DELETE = 'library.delete', LIBRARY_DOWNLOAD = 'library.download', @@ -183,6 +184,9 @@ export class AccessCore { (await this.repository.library.hasPartnerAccess(authUser.id, id)) ); + case Permission.LIBRARY_WRITE: + return this.repository.library.hasOwnerAccess(authUser.id, id); + case Permission.LIBRARY_UPDATE: return this.repository.library.hasOwnerAccess(authUser.id, id); diff --git a/server/src/immich/api-v1/asset/asset.service.spec.ts b/server/src/immich/api-v1/asset/asset.service.spec.ts index 02eb5ece42..c6c1f2888e 100644 --- a/server/src/immich/api-v1/asset/asset.service.spec.ts +++ b/server/src/immich/api-v1/asset/asset.service.spec.ts @@ -139,6 +139,7 @@ describe('AssetService', () => { const dto = _getCreateAssetDto(); assetRepositoryMock.create.mockResolvedValue(assetEntity); + accessMock.library.hasOwnerAccess.mockResolvedValue(true); await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: false, id: 'id_1' }); @@ -158,6 +159,7 @@ describe('AssetService', () => { assetRepositoryMock.create.mockRejectedValue(error); assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([_getAsset_1()]); + accessMock.library.hasOwnerAccess.mockResolvedValue(true); await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: true, id: 'id_1' }); @@ -175,6 +177,7 @@ describe('AssetService', () => { assetRepositoryMock.create.mockResolvedValueOnce(assetStub.livePhotoMotionAsset); assetRepositoryMock.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset); + accessMock.library.hasOwnerAccess.mockResolvedValue(true); await expect( sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion), @@ -367,6 +370,7 @@ describe('AssetService', () => { it('should handle a file import', async () => { assetRepositoryMock.create.mockResolvedValue(assetStub.image); storageMock.checkFileExists.mockResolvedValue(true); + accessMock.library.hasOwnerAccess.mockResolvedValue(true); await expect( sut.importFile(authStub.external1, { @@ -387,6 +391,7 @@ describe('AssetService', () => { assetRepositoryMock.create.mockRejectedValue(error); assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([assetStub.image]); storageMock.checkFileExists.mockResolvedValue(true); + accessMock.library.hasOwnerAccess.mockResolvedValue(true); cryptoMock.hashFile.mockResolvedValue(Buffer.from('file hash', 'utf8')); await expect( diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index a80d7675b4..30aeec55cb 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -91,6 +91,7 @@ export class AssetService { try { const libraryId = await this.getLibraryId(authUser, dto.libraryId); + await this.access.requirePermission(authUser, Permission.LIBRARY_WRITE, libraryId); if (livePhotoFile) { const livePhotoDto = { ...dto, assetType: AssetType.VIDEO, isVisible: false, libraryId }; livePhotoAsset = await this.assetCore.create(authUser, livePhotoDto, livePhotoFile); @@ -162,6 +163,7 @@ export class AssetService { try { const libraryId = await this.getLibraryId(authUser, dto.libraryId); + await this.access.requirePermission(authUser, Permission.LIBRARY_WRITE, libraryId); const asset = await this.assetCore.create(authUser, { ...dto, libraryId }, assetFile, undefined, dto.sidecarPath); return { id: asset.id, duplicate: false }; } catch (error: QueryFailedError | Error | any) {