Fix some cors issues

This commit is contained in:
rubikscraft
2022-04-25 18:07:36 +02:00
parent 8fb95c8bdf
commit 86cbbdd5b4
13 changed files with 134 additions and 35 deletions

View File

@@ -1,6 +1,8 @@
import { Module } from '@nestjs/common'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ServeStaticModule } from '@nestjs/serve-static'; import { ServeStaticModule } from '@nestjs/serve-static';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import cors from 'cors';
import { IncomingMessage, ServerResponse } from 'http';
import { EarlyConfigModule } from './config/early/early-config.module'; import { EarlyConfigModule } from './config/early/early-config.module';
import { ServeStaticConfigService } from './config/early/serve-static.config.service'; import { ServeStaticConfigService } from './config/early/serve-static.config.service';
import { TypeOrmConfigService } from './config/early/type-orm.config.service'; import { TypeOrmConfigService } from './config/early/type-orm.config.service';
@@ -9,6 +11,30 @@ import { AuthManagerModule } from './managers/auth/auth.module';
import { DemoManagerModule } from './managers/demo/demo.module'; import { DemoManagerModule } from './managers/demo/demo.module';
import { PicsurRoutesModule } from './routes/routes.module'; import { PicsurRoutesModule } from './routes/routes.module';
const mainCorsConfig = cors({
origin: '<origin>',
});
const imageCorsConfig = cors({
origin: '*',
methods: ['GET', 'HEAD', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
exposedHeaders: ['Content-Type', 'Authorization', 'Accept'],
credentials: false,
// A month
maxAge: 30 * 24 * 60 * 60,
});
const imageCorpOverride = (
req: IncomingMessage,
res: ServerResponse,
next: Function,
) => {
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
next();
};
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forRootAsync({ TypeOrmModule.forRootAsync({
@@ -25,4 +51,9 @@ import { PicsurRoutesModule } from './routes/routes.module';
PicsurRoutesModule, PicsurRoutesModule,
], ],
}) })
export class AppModule {} export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(mainCorsConfig).exclude('/i').forRoutes('/');
consumer.apply(imageCorsConfig, imageCorpOverride).forRoutes('/i');
}
}

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
import { AsyncFailable, Fail } from 'picsur-shared/dist/types'; import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
import { LessThan, Repository } from 'typeorm'; import { In, LessThan, Repository } from 'typeorm';
import { ImageFileType } from '../../models/constants/image-file-types.const';
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity'; import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
import { EImageFileBackend } from '../../models/entities/image-file.entity'; import { EImageFileBackend } from '../../models/entities/image-file.entity';
@@ -74,6 +74,28 @@ export class ImageFileDBService {
} }
} }
public async getFileMimes(
imageId: string,
types: ImageFileType[],
): AsyncFailable<{ [key: string]: string | undefined }> {
try {
const found = await this.imageFileRepo.find({
where: { image_id: imageId, type: In(types) },
select: ['type', 'mime'],
});
if (!found) return Fail('Image not found');
return Object.fromEntries(
types.map((type) => [
type,
found.find((f) => f.type === type)?.mime,
]),
);
} catch (e) {
return Fail(e);
}
}
public async addDerivative( public async addDerivative(
imageId: string, imageId: string,
key: string, key: string,

View File

@@ -31,7 +31,6 @@ async function bootstrap() {
}, },
); );
// Configure nest app
app.useGlobalFilters(new MainExceptionFilter()); app.useGlobalFilters(new MainExceptionFilter());
app.useGlobalInterceptors(new SuccessInterceptor(app.get(Reflector))); app.useGlobalInterceptors(new SuccessInterceptor(app.get(Reflector)));
app.useGlobalPipes(new ZodValidationPipe()); app.useGlobalPipes(new ZodValidationPipe());

View File

@@ -1,15 +1,15 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import Crypto from 'crypto'; import Crypto from 'crypto';
import { fileTypeFromBuffer, FileTypeResult } from 'file-type'; import { fileTypeFromBuffer, FileTypeResult } from 'file-type';
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto'; import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
import { UsrPreference } from 'picsur-shared/dist/dto/usr-preferences.dto'; import { UsrPreference } from 'picsur-shared/dist/dto/usr-preferences.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types'; import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
import { ParseMime } from 'picsur-shared/dist/util/parse-mime'; import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
import { IsQOI } from 'qoi-img'; import { IsQOI } from 'qoi-img';
import { ImageDBService } from '../../collections/image-db/image-db.service'; import { ImageDBService } from '../../collections/image-db/image-db.service';
import { ImageFileDBService } from '../../collections/image-db/image-file-db.service'; import { ImageFileDBService } from '../../collections/image-db/image-file-db.service';
import { UsrPreferenceService } from '../../collections/preference-db/usr-preference-db.service'; import { UsrPreferenceService } from '../../collections/preference-db/usr-preference-db.service';
import { ImageFileType } from '../../models/constants/image-file-types.const';
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity'; import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
import { EImageFileBackend } from '../../models/entities/image-file.entity'; import { EImageFileBackend } from '../../models/entities/image-file.entity';
import { EImageBackend } from '../../models/entities/image.entity'; import { EImageBackend } from '../../models/entities/image.entity';
@@ -157,6 +157,26 @@ export class ImageManagerService {
return ParseMime(mime); return ParseMime(mime);
} }
public async getAllFileMimes(imageId: string): AsyncFailable<{
[ImageFileType.MASTER]: string;
[ImageFileType.ORIGINAL]: string | undefined;
}> {
const result = await this.imageFilesService.getFileMimes(imageId, [
ImageFileType.MASTER,
ImageFileType.ORIGINAL,
]);
if (HasFailed(result)) return result;
if (result[ImageFileType.MASTER] === undefined) {
return Fail('No master file found');
}
return {
[ImageFileType.MASTER]: result[ImageFileType.MASTER]!,
[ImageFileType.ORIGINAL]: result[ImageFileType.ORIGINAL],
};
}
// Util stuff ================================================================== // Util stuff ==================================================================
private async getFullMimeFromBuffer(image: Buffer): AsyncFailable<FullMime> { private async getFullMimeFromBuffer(image: Buffer): AsyncFailable<FullMime> {

View File

@@ -1,5 +1,5 @@
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
import { Column, Entity, Index, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { Column, Entity, Index, PrimaryGeneratedColumn, Unique } from 'typeorm';
import { ImageFileType } from '../constants/image-file-types.const';
@Entity() @Entity()
@Unique(['image_id', 'type']) @Unique(['image_id', 'type'])

View File

@@ -9,7 +9,10 @@ import {
Res Res
} from '@nestjs/common'; } from '@nestjs/common';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto'; import {
ImageMetaResponse,
ImageUploadResponse
} from 'picsur-shared/dist/dto/api/image.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { HasFailed } from 'picsur-shared/dist/types';
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator'; import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
import { ImageIdParam } from '../../decorators/image-id/image-id.decorator'; import { ImageIdParam } from '../../decorators/image-id/image-id.decorator';
@@ -88,16 +91,22 @@ export class ImageController {
throw new NotFoundException('Could not find image'); throw new NotFoundException('Could not find image');
} }
return image; const fileMimes = await this.imagesService.getAllFileMimes(id);
if (HasFailed(fileMimes)) {
this.logger.warn(fileMimes.getReason());
throw new InternalServerErrorException('Could not get image mime');
}
return { image, fileMimes };
} }
@Post() @Post()
@Returns(ImageMetaResponse) @Returns(ImageUploadResponse)
@RequiredPermissions(Permission.ImageUpload) @RequiredPermissions(Permission.ImageUpload)
async uploadImage( async uploadImage(
@MultiPart() multipart: ImageUploadDto, @MultiPart() multipart: ImageUploadDto,
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<ImageMetaResponse> { ): Promise<ImageUploadResponse> {
const image = await this.imagesService.upload( const image = await this.imagesService.upload(
multipart.image.buffer, multipart.image.buffer,
userid, userid,

View File

@@ -1,20 +1,10 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { Module } from '@nestjs/common';
import cors from 'cors';
import { DecoratorsModule } from '../../decorators/decorators.module'; import { DecoratorsModule } from '../../decorators/decorators.module';
import { ImageManagerModule } from '../../managers/image/image.module'; import { ImageManagerModule } from '../../managers/image/image.module';
import { ImageController } from './image.controller'; import { ImageController } from './image.controller';
const corsConfig = cors({
// 48 hours
maxAge: 1728000,
});
@Module({ @Module({
imports: [ImageManagerModule, DecoratorsModule], imports: [ImageManagerModule, DecoratorsModule],
controllers: [ImageController], controllers: [ImageController],
}) })
export class ImageModule implements NestModule { export class ImageModule {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(corsConfig).forRoutes('/i');
}
}

View File

@@ -16,6 +16,7 @@ const SettingsRoutes: PRoutes = [
children: [ children: [
{ {
path: '', path: '',
pathMatch: 'full',
redirectTo: 'general', redirectTo: 'general',
}, },
{ {

View File

@@ -5,7 +5,7 @@
</div> </div>
<div class="col-12 py-3"> <div class="col-12 py-3">
<picsur-img class="uploadedimage" [src]="imageLinks.source"></picsur-img> <picsur-img class="uploadedimage" [src]="previewLink"></picsur-img>
</div> </div>
<div class="col-12"> <div class="col-12">

View File

@@ -18,6 +18,7 @@ export class ViewComponent implements OnInit {
private utilService: UtilService private utilService: UtilService
) {} ) {}
public previewLink = '';
public imageLinks = new ImageLinks(); public imageLinks = new ImageLinks();
async ngOnInit() { async ngOnInit() {
@@ -32,7 +33,16 @@ export class ViewComponent implements OnInit {
return this.utilService.quitError(metadata.getReason()); return this.utilService.quitError(metadata.getReason());
} }
this.imageLinks = this.imageService.CreateImageLinksFromID(id, 'qoi'); const hasOriginal = metadata.fileMimes.original !== undefined;
this.previewLink = this.imageService.GetImageURL(
id,
metadata.fileMimes.master
);
this.imageLinks = this.imageService.CreateImageLinksFromID(
id,
hasOriginal ? null : metadata.fileMimes.master
);
} }
download() { download() {

View File

@@ -1,7 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto'; import {
ImageMetaResponse,
ImageUploadResponse
} from 'picsur-shared/dist/dto/api/image.dto';
import { ImageLinks } from 'picsur-shared/dist/dto/image-links.dto'; import { ImageLinks } from 'picsur-shared/dist/dto/image-links.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity'; import { Mime2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable } from 'picsur-shared/dist/types'; import { AsyncFailable } from 'picsur-shared/dist/types';
import { Open } from 'picsur-shared/dist/types/failable'; import { Open } from 'picsur-shared/dist/types/failable';
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto'; import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
@@ -14,7 +17,7 @@ export class ImageService {
public async UploadImage(image: File): AsyncFailable<string> { public async UploadImage(image: File): AsyncFailable<string> {
const result = await this.api.postForm( const result = await this.api.postForm(
ImageMetaResponse, ImageUploadResponse,
'/i', '/i',
new ImageUploadRequest(image) new ImageUploadRequest(image)
); );
@@ -22,14 +25,15 @@ export class ImageService {
return Open(result, 'id'); return Open(result, 'id');
} }
public async GetImageMeta(image: string): AsyncFailable<EImage> { public async GetImageMeta(image: string): AsyncFailable<ImageMetaResponse> {
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`); return await this.api.get(ImageMetaResponse, `/i/meta/${image}`);
} }
public GetImageURL(image: string): string { public GetImageURL(image: string, mime: string | null): string {
const baseURL = window.location.protocol + '//' + window.location.host; const baseURL = window.location.protocol + '//' + window.location.host;
const extension = mime !== null ? Mime2Ext(mime) ?? 'error' : null;
return `${baseURL}/i/${image}`; return `${baseURL}/i/${image}${extension !== null ? '.' + extension : ''}`;
} }
public CreateImageLinks(imageURL: string): ImageLinks { public CreateImageLinks(imageURL: string): ImageLinks {
@@ -42,7 +46,7 @@ export class ImageService {
}; };
} }
public CreateImageLinksFromID(imageID: string, format: string): ImageLinks { public CreateImageLinksFromID(imageID: string, mime: string | null): ImageLinks {
return this.CreateImageLinks(this.GetImageURL(imageID + '.' + format)); return this.CreateImageLinks(this.GetImageURL(imageID, mime));
} }
} }

View File

@@ -1,5 +1,18 @@
import { z } from 'zod';
import { EImageSchema } from '../../entities/image.entity'; import { EImageSchema } from '../../entities/image.entity';
import { createZodDto } from '../../util/create-zod-dto'; import { createZodDto } from '../../util/create-zod-dto';
import { ImageFileType } from '../image-file-types.dto';
export const ImageMetaResponseSchema = EImageSchema; export const ImageMetaResponseSchema = z.object({
image: EImageSchema,
fileMimes: z.object({
[ImageFileType.MASTER]: z.string(),
[ImageFileType.ORIGINAL]: z.union([z.string(), z.undefined()]),
}),
});
export class ImageMetaResponse extends createZodDto(ImageMetaResponseSchema) {} export class ImageMetaResponse extends createZodDto(ImageMetaResponseSchema) {}
export const ImageUploadResponseSchema = EImageSchema;
export class ImageUploadResponse extends createZodDto(
ImageUploadResponseSchema,
) {}