feat: storage template file move hardening (#5917)

* fix: pgvecto.rs extension breaks typeorm schema:drop command

* fix: parse postgres bigints to javascript number types when selecting data

* feat: verify file size is the same as original asset after copying file for storage template job

* feat: allow disabling of storage template job, defaults to disabled for new instances

* fix: don't allow setting concurrency for storage template migration, can cause race conditions above 1

* feat: add checksum verification when file is copied for storage template job

* fix: extract metadata for assets that aren't visible on timeline
This commit is contained in:
Zack Pollard
2023-12-29 18:41:33 +00:00
committed by GitHub
parent 5f6bd4ae7e
commit 2e38fa73bf
36 changed files with 686 additions and 225 deletions
@@ -10,6 +10,7 @@ import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE } from '../job';
import {
IAlbumRepository,
IAssetRepository,
ICryptoRepository,
IMoveRepository,
IPersonRepository,
IStorageRepository,
@@ -61,6 +62,7 @@ export class StorageTemplateService {
@Inject(IPersonRepository) personRepository: IPersonRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(IUserRepository) private userRepository: IUserRepository,
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
) {
this.template = this.compile(config.storageTemplate.template);
this.configCore = SystemConfigCore.create(configRepository);
@@ -70,10 +72,22 @@ export class StorageTemplateService {
this.logger.debug(`Received config, compiling storage template: ${template}`);
this.template = this.compile(template);
});
this.storageCore = StorageCore.create(assetRepository, moveRepository, personRepository, storageRepository);
this.storageCore = StorageCore.create(
assetRepository,
moveRepository,
personRepository,
cryptoRepository,
configRepository,
storageRepository,
);
}
async handleMigrationSingle({ id }: IEntityJob) {
const storageTemplateEnabled = (await this.configCore.getConfig()).storageTemplate.enabled;
if (!storageTemplateEnabled) {
return true;
}
const [asset] = await this.assetRepository.getByIds([id]);
const user = await this.userRepository.get(asset.ownerId, {});
@@ -93,6 +107,11 @@ export class StorageTemplateService {
async handleMigration() {
this.logger.log('Starting storage template migration');
const storageTemplateEnabled = (await this.configCore.getConfig()).storageTemplate.enabled;
if (!storageTemplateEnabled) {
this.logger.log('Storage template migration disabled, skipping');
return true;
}
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getAll(pagination),
);
@@ -123,12 +142,23 @@ export class StorageTemplateService {
return;
}
const { id, sidecarPath, originalPath } = asset;
const { id, sidecarPath, originalPath, exifInfo } = asset;
const oldPath = originalPath;
const newPath = await this.getTemplatePath(asset, metadata);
if (!exifInfo || !exifInfo.fileSizeInByte) {
this.logger.error(`Asset ${id} missing exif info, skipping storage template migration`);
return;
}
try {
await this.storageCore.moveFile({ entityId: id, pathType: AssetPathType.ORIGINAL, oldPath, newPath });
await this.storageCore.moveFile({
entityId: id,
pathType: AssetPathType.ORIGINAL,
oldPath,
newPath,
assetInfo: { sizeInBytes: exifInfo.fileSizeInByte, checksum: asset.checksum },
});
if (sidecarPath) {
await this.storageCore.moveFile({
entityId: id,