Limit asset access to owner
This commit is contained in:
@@ -43,6 +43,7 @@ export interface IAssetRepository {
|
|||||||
userId: string,
|
userId: string,
|
||||||
checkDuplicateAssetDto: CheckExistingAssetsDto,
|
checkDuplicateAssetDto: CheckExistingAssetsDto,
|
||||||
): Promise<CheckExistingAssetsResponseDto>;
|
): Promise<CheckExistingAssetsResponseDto>;
|
||||||
|
countByIdAndUser(assetId: string, userId: string): Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
||||||
@@ -343,4 +344,13 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
});
|
});
|
||||||
return new CheckExistingAssetsResponseDto(existingAssets.map((a) => a.deviceAssetId));
|
return new CheckExistingAssetsResponseDto(existingAssets.map((a) => a.deviceAssetId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async countByIdAndUser(assetId: string, userId: string): Promise<number> {
|
||||||
|
return await this.assetRepository.count({
|
||||||
|
where: {
|
||||||
|
id: assetId,
|
||||||
|
userId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
Header,
|
Header,
|
||||||
Put,
|
Put,
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||||
import { AssetService } from './asset.service';
|
import { AssetService } from './asset.service';
|
||||||
@@ -86,10 +88,12 @@ export class AssetController {
|
|||||||
|
|
||||||
@Get('/download/:assetId')
|
@Get('/download/:assetId')
|
||||||
async downloadFile(
|
async downloadFile(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
return this.assetService.downloadFile(query, assetId, res);
|
return this.assetService.downloadFile(query, assetId, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,21 +114,25 @@ export class AssetController {
|
|||||||
@Get('/file/:assetId')
|
@Get('/file/:assetId')
|
||||||
@Header('Cache-Control', 'max-age=300')
|
@Header('Cache-Control', 'max-age=300')
|
||||||
async serveFile(
|
async serveFile(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Headers() headers: Record<string, string>,
|
@Headers() headers: Record<string, string>,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
return this.assetService.serveFile(assetId, query, res, headers);
|
return this.assetService.serveFile(assetId, query, res, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/thumbnail/:assetId')
|
@Get('/thumbnail/:assetId')
|
||||||
@Header('Cache-Control', 'max-age=300')
|
@Header('Cache-Control', 'max-age=300')
|
||||||
async getAssetThumbnail(
|
async getAssetThumbnail(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
|
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
return this.assetService.getAssetThumbnail(assetId, query, res);
|
return this.assetService.getAssetThumbnail(assetId, query, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +203,8 @@ export class AssetController {
|
|||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<AssetResponseDto> {
|
): Promise<AssetResponseDto> {
|
||||||
return await this.assetService.getAssetById(authUser, assetId);
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
|
return await this.assetService.getAssetById(assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,7 +216,8 @@ export class AssetController {
|
|||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
@Body() dto: UpdateAssetDto,
|
@Body() dto: UpdateAssetDto,
|
||||||
): Promise<AssetResponseDto> {
|
): Promise<AssetResponseDto> {
|
||||||
return await this.assetService.updateAssetById(authUser, assetId, dto);
|
await this.assetService.checkAssetsAccess(authUser, [assetId], true);
|
||||||
|
return await this.assetService.updateAssetById(assetId, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('/')
|
@Delete('/')
|
||||||
@@ -215,17 +225,19 @@ export class AssetController {
|
|||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Body(ValidationPipe) assetIds: DeleteAssetDto,
|
@Body(ValidationPipe) assetIds: DeleteAssetDto,
|
||||||
): Promise<DeleteAssetResponseDto[]> {
|
): Promise<DeleteAssetResponseDto[]> {
|
||||||
|
await this.assetService.checkAssetsAccess(authUser, assetIds.ids, true);
|
||||||
|
|
||||||
const deleteAssetList: AssetResponseDto[] = [];
|
const deleteAssetList: AssetResponseDto[] = [];
|
||||||
|
|
||||||
for (const id of assetIds.ids) {
|
for (const id of assetIds.ids) {
|
||||||
const assets = await this.assetService.getAssetById(authUser, id);
|
const assets = await this.assetService.getAssetById(id);
|
||||||
if (!assets) {
|
if (!assets) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
deleteAssetList.push(assets);
|
deleteAssetList.push(assets);
|
||||||
|
|
||||||
if (assets.livePhotoVideoId) {
|
if (assets.livePhotoVideoId) {
|
||||||
const livePhotoVideo = await this.assetService.getAssetById(authUser, assets.livePhotoVideoId);
|
const livePhotoVideo = await this.assetService.getAssetById(assets.livePhotoVideoId);
|
||||||
if (livePhotoVideo) {
|
if (livePhotoVideo) {
|
||||||
deleteAssetList.push(livePhotoVideo);
|
deleteAssetList.push(livePhotoVideo);
|
||||||
assetIds.ids = [...assetIds.ids, livePhotoVideo.id];
|
assetIds.ids = [...assetIds.ids, livePhotoVideo.id];
|
||||||
@@ -233,7 +245,7 @@ export class AssetController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.assetService.deleteAssetById(authUser, assetIds);
|
const result = await this.assetService.deleteAssetById(assetIds);
|
||||||
|
|
||||||
result.forEach((res) => {
|
result.forEach((res) => {
|
||||||
deleteAssetList.filter((a) => a.id == res.id && res.status == DeleteAssetStatusEnum.SUCCESS);
|
deleteAssetList.filter((a) => a.id == res.id && res.status == DeleteAssetStatusEnum.SUCCESS);
|
||||||
|
|||||||
@@ -221,22 +221,18 @@ export class AssetService {
|
|||||||
return assets.map((asset) => mapAsset(asset));
|
return assets.map((asset) => mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
public async getAssetById(assetId: string): Promise<AssetResponseDto> {
|
||||||
const asset = await this._assetRepository.getById(assetId);
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
|
|
||||||
return mapAsset(asset);
|
return mapAsset(asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAssetById(authUser: AuthUserDto, assetId: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
public async updateAssetById(assetId: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||||
const asset = await this._assetRepository.getById(assetId);
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
throw new BadRequestException('Asset not found');
|
throw new BadRequestException('Asset not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authUser.id !== asset.userId) {
|
|
||||||
throw new ForbiddenException('Not the owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedAsset = await this._assetRepository.update(asset, dto);
|
const updatedAsset = await this._assetRepository.update(asset, dto);
|
||||||
|
|
||||||
return mapAsset(updatedAsset);
|
return mapAsset(updatedAsset);
|
||||||
@@ -485,14 +481,13 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteAssetById(authUser: AuthUserDto, assetIds: DeleteAssetDto): Promise<DeleteAssetResponseDto[]> {
|
public async deleteAssetById(assetIds: DeleteAssetDto): Promise<DeleteAssetResponseDto[]> {
|
||||||
const result: DeleteAssetResponseDto[] = [];
|
const result: DeleteAssetResponseDto[] = [];
|
||||||
|
|
||||||
const target = assetIds.ids;
|
const target = assetIds.ids;
|
||||||
for (const assetId of target) {
|
for (const assetId of target) {
|
||||||
const res = await this.assetRepository.delete({
|
const res = await this.assetRepository.delete({
|
||||||
id: assetId,
|
id: assetId,
|
||||||
userId: authUser.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.affected) {
|
if (res.affected) {
|
||||||
@@ -631,4 +626,20 @@ export class AssetService {
|
|||||||
getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||||
return this._assetRepository.getAssetCountByUserId(authUser.id);
|
return this._assetRepository.getAssetCountByUserId(authUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkAssetsAccess(authUser: AuthUserDto, assetIds: string[], mustBeOwner: boolean = false) {
|
||||||
|
for (let assetId of assetIds) {
|
||||||
|
// Step 1: Check if user owns asset
|
||||||
|
if (await this._assetRepository.countByIdAndUser(assetId, authUser.id) == 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid additional checks if ownership is required
|
||||||
|
if (!mustBeOwner) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user