Skip to content

Commit

Permalink
refactor(server): add base methods for access checks (immich-app#13349)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 authored Oct 10, 2024
1 parent 97edf90 commit 8daa807
Show file tree
Hide file tree
Showing 17 changed files with 84 additions and 90 deletions.
9 changes: 4 additions & 5 deletions server/src/services/activity.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { ActivityEntity } from 'src/entities/activity.entity';
import { Permission } from 'src/enum';
import { BaseService } from 'src/services/base.service';
import { requireAccess } from 'src/utils/access';

@Injectable()
export class ActivityService extends BaseService {
async getAll(auth: AuthDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
const activities = await this.activityRepository.search({
userId: dto.userId,
albumId: dto.albumId,
Expand All @@ -31,12 +30,12 @@ export class ActivityService extends BaseService {
}

async getStatistics(auth: AuthDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
return { comments: await this.activityRepository.getStatistics(dto.assetId, dto.albumId) };
}

async create(auth: AuthDto, dto: ActivityCreateDto): Promise<MaybeDuplicate<ActivityResponseDto>> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ACTIVITY_CREATE, ids: [dto.albumId] });
await this.requireAccess({ auth, permission: Permission.ACTIVITY_CREATE, ids: [dto.albumId] });

const common = {
userId: auth.user.id,
Expand Down Expand Up @@ -70,7 +69,7 @@ export class ActivityService extends BaseService {
}

async delete(auth: AuthDto, id: string): Promise<void> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ACTIVITY_DELETE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ACTIVITY_DELETE, ids: [id] });
await this.activityRepository.delete(id);
}
}
19 changes: 9 additions & 10 deletions server/src/services/album.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { AssetEntity } from 'src/entities/asset.entity';
import { Permission } from 'src/enum';
import { AlbumAssetCount, AlbumInfoOptions } from 'src/interfaces/album.interface';
import { BaseService } from 'src/services/base.service';
import { checkAccess, requireAccess } from 'src/utils/access';
import { addAssets, removeAssets } from 'src/utils/asset.util';

@Injectable()
Expand Down Expand Up @@ -82,7 +81,7 @@ export class AlbumService extends BaseService {
}

async get(auth: AuthDto, id: string, dto: AlbumInfoDto): Promise<AlbumResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [id] });
await this.albumRepository.updateThumbnails();
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
const album = await this.findOrFail(id, { withAssets });
Expand All @@ -106,7 +105,7 @@ export class AlbumService extends BaseService {
}
}

const allowedAssetIdsSet = await checkAccess(this.accessRepository, {
const allowedAssetIdsSet = await this.checkAccess({
auth,
permission: Permission.ASSET_SHARE,
ids: dto.assetIds || [],
Expand All @@ -130,7 +129,7 @@ export class AlbumService extends BaseService {
}

async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_UPDATE, ids: [id] });

const album = await this.findOrFail(id, { withAssets: true });

Expand All @@ -153,13 +152,13 @@ export class AlbumService extends BaseService {
}

async delete(auth: AuthDto, id: string): Promise<void> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_DELETE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_DELETE, ids: [id] });
await this.albumRepository.delete(id);
}

async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
const album = await this.findOrFail(id, { withAssets: false });
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_ADD_ASSET, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_ADD_ASSET, ids: [id] });

const results = await addAssets(
auth,
Expand All @@ -182,7 +181,7 @@ export class AlbumService extends BaseService {
}

async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_REMOVE_ASSET, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_REMOVE_ASSET, ids: [id] });

const album = await this.findOrFail(id, { withAssets: false });
const results = await removeAssets(
Expand All @@ -203,7 +202,7 @@ export class AlbumService extends BaseService {
}

async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise<AlbumResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });

const album = await this.findOrFail(id, { withAssets: false });

Expand Down Expand Up @@ -247,14 +246,14 @@ export class AlbumService extends BaseService {

// non-admin can remove themselves
if (auth.user.id !== userId) {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
}

await this.albumUserRepository.delete({ albumId: id, userId });
}

async updateUser(auth: AuthDto, id: string, userId: string, dto: Partial<AlbumUserEntity>): Promise<void> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role });
}

Expand Down
12 changes: 6 additions & 6 deletions server/src/services/asset-media.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entit
import { AssetStatus, AssetType, CacheControl, Permission, StorageFolder } from 'src/enum';
import { JobName } from 'src/interfaces/job.interface';
import { BaseService } from 'src/services/base.service';
import { requireAccess, requireUploadAccess } from 'src/utils/access';
import { requireUploadAccess } from 'src/utils/access';
import { getAssetFiles, onBeforeLink } from 'src/utils/asset.util';
import { ImmichFileResponse } from 'src/utils/file';
import { mimeTypes } from 'src/utils/mime-types';
Expand Down Expand Up @@ -125,7 +125,7 @@ export class AssetMediaService extends BaseService {
sidecarFile?: UploadFile,
): Promise<AssetMediaResponseDto> {
try {
await requireAccess(this.accessRepository, {
await this.requireAccess({
auth,
permission: Permission.ASSET_UPLOAD,
// do not need an id here, but the interface requires it
Expand Down Expand Up @@ -159,7 +159,7 @@ export class AssetMediaService extends BaseService {
sidecarFile?: UploadFile,
): Promise<AssetMediaResponseDto> {
try {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: [id] });
const asset = (await this.assetRepository.getById(id)) as AssetEntity;

this.requireQuota(auth, file.size);
Expand All @@ -182,7 +182,7 @@ export class AssetMediaService extends BaseService {
}

async downloadOriginal(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_DOWNLOAD, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: [id] });

const asset = await this.findOrFail(id);

Expand All @@ -194,7 +194,7 @@ export class AssetMediaService extends BaseService {
}

async viewThumbnail(auth: AuthDto, id: string, dto: AssetMediaOptionsDto): Promise<ImmichFileResponse> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_VIEW, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ASSET_VIEW, ids: [id] });

const asset = await this.findOrFail(id);
const size = dto.size ?? AssetMediaSize.THUMBNAIL;
Expand All @@ -217,7 +217,7 @@ export class AssetMediaService extends BaseService {
}

async playbackVideo(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_VIEW, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ASSET_VIEW, ids: [id] });

const asset = await this.findOrFail(id);

Expand Down
11 changes: 5 additions & 6 deletions server/src/services/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
JobStatus,
} from 'src/interfaces/job.interface';
import { BaseService } from 'src/services/base.service';
import { requireAccess } from 'src/utils/access';
import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util';
import { usePagination } from 'src/utils/pagination';

Expand Down Expand Up @@ -86,7 +85,7 @@ export class AssetService extends BaseService {
}

async get(auth: AuthDto, id: string): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ASSET_READ, ids: [id] });

const asset = await this.assetRepository.getById(
id,
Expand Down Expand Up @@ -135,7 +134,7 @@ export class AssetService extends BaseService {
}

async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: [id] });

const { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto;
const repos = { asset: this.assetRepository, event: this.eventRepository };
Expand Down Expand Up @@ -178,7 +177,7 @@ export class AssetService extends BaseService {

async updateAll(auth: AuthDto, dto: AssetBulkUpdateDto): Promise<void> {
const { ids, dateTimeOriginal, latitude, longitude, ...options } = dto;
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_UPDATE, ids });
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids });

for (const id of ids) {
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude });
Expand Down Expand Up @@ -275,7 +274,7 @@ export class AssetService extends BaseService {
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
const { ids, force } = dto;

await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_DELETE, ids });
await this.requireAccess({ auth, permission: Permission.ASSET_DELETE, ids });
await this.assetRepository.updateAll(ids, {
deletedAt: new Date(),
status: force ? AssetStatus.DELETED : AssetStatus.TRASHED,
Expand All @@ -284,7 +283,7 @@ export class AssetService extends BaseService {
}

async run(auth: AuthDto, dto: AssetJobsDto) {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_UPDATE, ids: dto.assetIds });
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: dto.assetIds });

const jobs: JobItem[] = [];

Expand Down
3 changes: 1 addition & 2 deletions server/src/services/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
} from 'src/enum';
import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
import { BaseService } from 'src/services/base.service';
import { requireAccess } from 'src/utils/access';
import { getAssetFiles } from 'src/utils/asset.util';
import { usePagination } from 'src/utils/pagination';

Expand All @@ -36,7 +35,7 @@ export class AuditService extends BaseService {

async getDeletes(auth: AuthDto, dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
const userId = dto.userId || auth.user.id;
await requireAccess(this.accessRepository, { auth, permission: Permission.TIMELINE_READ, ids: [userId] });
await this.requireAccess({ auth, permission: Permission.TIMELINE_READ, ids: [userId] });

const audits = await this.auditRepository.getAfter(dto.after, {
userIds: [userId],
Expand Down
15 changes: 12 additions & 3 deletions server/src/services/base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { ITrashRepository } from 'src/interfaces/trash.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
import { IViewRepository } from 'src/interfaces/view.interface';
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
import { getConfig, updateConfig } from 'src/utils/config';

export class BaseService {
Expand Down Expand Up @@ -95,7 +96,7 @@ export class BaseService {
);
}

private get repos() {
private get configRepos() {
return {
configRepo: this.configRepository,
metadataRepo: this.systemMetadataRepository,
Expand All @@ -104,10 +105,18 @@ export class BaseService {
}

getConfig(options: { withCache: boolean }) {
return getConfig(this.repos, options);
return getConfig(this.configRepos, options);
}

updateConfig(newConfig: SystemConfig) {
return updateConfig(this.repos, newConfig);
return updateConfig(this.configRepos, newConfig);
}

requireAccess(request: AccessRequest) {
return requireAccess(this.accessRepository, request);
}

checkAccess(request: AccessRequest) {
return checkAccess(this.accessRepository, request);
}
}
9 changes: 4 additions & 5 deletions server/src/services/download.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { AssetEntity } from 'src/entities/asset.entity';
import { Permission } from 'src/enum';
import { ImmichReadStream } from 'src/interfaces/storage.interface';
import { BaseService } from 'src/services/base.service';
import { requireAccess } from 'src/utils/access';
import { HumanReadableSize } from 'src/utils/bytes';
import { usePagination } from 'src/utils/pagination';
import { getPreferences } from 'src/utils/preferences';
Expand Down Expand Up @@ -62,7 +61,7 @@ export class DownloadService extends BaseService {
}

async downloadArchive(auth: AuthDto, dto: AssetIdsDto): Promise<ImmichReadStream> {
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_DOWNLOAD, ids: dto.assetIds });
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: dto.assetIds });

const zip = this.storageRepository.createZipStream();
const assets = await this.assetRepository.getByIds(dto.assetIds);
Expand Down Expand Up @@ -105,20 +104,20 @@ export class DownloadService extends BaseService {

if (dto.assetIds) {
const assetIds = dto.assetIds;
await requireAccess(this.accessRepository, { auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds });
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds });
const assets = await this.assetRepository.getByIds(assetIds, { exifInfo: true });
return usePagination(PAGINATION_SIZE, () => ({ hasNextPage: false, items: assets }));
}

if (dto.albumId) {
const albumId = dto.albumId;
await requireAccess(this.accessRepository, { auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] });
await this.requireAccess({ auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] });
return usePagination(PAGINATION_SIZE, (pagination) => this.assetRepository.getByAlbumId(pagination, albumId));
}

if (dto.userId) {
const userId = dto.userId;
await requireAccess(this.accessRepository, { auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] });
await this.requireAccess({ auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] });
return usePagination(PAGINATION_SIZE, (pagination) =>
this.assetRepository.getByUserId(pagination, userId, { isVisible: true }),
);
Expand Down
13 changes: 6 additions & 7 deletions server/src/services/memory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from '
import { AssetEntity } from 'src/entities/asset.entity';
import { Permission } from 'src/enum';
import { BaseService } from 'src/services/base.service';
import { checkAccess, requireAccess } from 'src/utils/access';
import { addAssets, removeAssets } from 'src/utils/asset.util';

@Injectable()
Expand All @@ -16,7 +15,7 @@ export class MemoryService extends BaseService {
}

async get(auth: AuthDto, id: string): Promise<MemoryResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.MEMORY_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.MEMORY_READ, ids: [id] });
const memory = await this.findOrFail(id);
return mapMemory(memory);
}
Expand All @@ -25,7 +24,7 @@ export class MemoryService extends BaseService {
// TODO validate type/data combination

const assetIds = dto.assetIds || [];
const allowedAssetIds = await checkAccess(this.accessRepository, {
const allowedAssetIds = await this.checkAccess({
auth,
permission: Permission.ASSET_SHARE,
ids: assetIds,
Expand All @@ -44,7 +43,7 @@ export class MemoryService extends BaseService {
}

async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise<MemoryResponseDto> {
await requireAccess(this.accessRepository, { auth, permission: Permission.MEMORY_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] });

const memory = await this.memoryRepository.update({
id,
Expand All @@ -57,12 +56,12 @@ export class MemoryService extends BaseService {
}

async remove(auth: AuthDto, id: string): Promise<void> {
await requireAccess(this.accessRepository, { auth, permission: Permission.MEMORY_DELETE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.MEMORY_DELETE, ids: [id] });
await this.memoryRepository.delete(id);
}

async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
await requireAccess(this.accessRepository, { auth, permission: Permission.MEMORY_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.MEMORY_READ, ids: [id] });

const repos = { access: this.accessRepository, bulk: this.memoryRepository };
const results = await addAssets(auth, repos, { parentId: id, assetIds: dto.ids });
Expand All @@ -76,7 +75,7 @@ export class MemoryService extends BaseService {
}

async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
await requireAccess(this.accessRepository, { auth, permission: Permission.MEMORY_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] });

const repos = { access: this.accessRepository, bulk: this.memoryRepository };
const results = await removeAssets(auth, repos, {
Expand Down
Loading

0 comments on commit 8daa807

Please sign in to comment.