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 { TypeOrmModule } from '@nestjs/typeorm';
import cors from 'cors';
import { IncomingMessage, ServerResponse } from 'http';
import { EarlyConfigModule } from './config/early/early-config.module';
import { ServeStaticConfigService } from './config/early/serve-static.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 { 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({
imports: [
TypeOrmModule.forRootAsync({
@@ -25,4 +51,9 @@ import { PicsurRoutesModule } from './routes/routes.module';
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 { InjectRepository } from '@nestjs/typeorm';
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
import { LessThan, Repository } from 'typeorm';
import { ImageFileType } from '../../models/constants/image-file-types.const';
import { In, LessThan, Repository } from 'typeorm';
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.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(
imageId: string,
key: string,

View File

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

View File

@@ -1,15 +1,15 @@
import { Injectable, Logger } from '@nestjs/common';
import Crypto from 'crypto';
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 { 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 { IsQOI } from 'qoi-img';
import { ImageDBService } from '../../collections/image-db/image-db.service';
import { ImageFileDBService } from '../../collections/image-db/image-file-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 { EImageFileBackend } from '../../models/entities/image-file.entity';
import { EImageBackend } from '../../models/entities/image.entity';
@@ -157,6 +157,26 @@ export class ImageManagerService {
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 ==================================================================
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 { ImageFileType } from '../constants/image-file-types.const';
@Entity()
@Unique(['image_id', 'type'])

View File

@@ -9,7 +9,10 @@ import {
Res
} from '@nestjs/common';
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 { ImageFullIdParam } from '../../decorators/image-id/image-full-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');
}
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()
@Returns(ImageMetaResponse)
@Returns(ImageUploadResponse)
@RequiredPermissions(Permission.ImageUpload)
async uploadImage(
@MultiPart() multipart: ImageUploadDto,
@ReqUserID() userid: string,
): Promise<ImageMetaResponse> {
): Promise<ImageUploadResponse> {
const image = await this.imagesService.upload(
multipart.image.buffer,
userid,

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
</div>
<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 class="col-12">

View File

@@ -18,6 +18,7 @@ export class ViewComponent implements OnInit {
private utilService: UtilService
) {}
public previewLink = '';
public imageLinks = new ImageLinks();
async ngOnInit() {
@@ -32,7 +33,16 @@ export class ViewComponent implements OnInit {
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() {

View File

@@ -1,7 +1,10 @@
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 { 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 { Open } from 'picsur-shared/dist/types/failable';
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
@@ -14,7 +17,7 @@ export class ImageService {
public async UploadImage(image: File): AsyncFailable<string> {
const result = await this.api.postForm(
ImageMetaResponse,
ImageUploadResponse,
'/i',
new ImageUploadRequest(image)
);
@@ -22,14 +25,15 @@ export class ImageService {
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}`);
}
public GetImageURL(image: string): string {
public GetImageURL(image: string, mime: string | null): string {
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 {
@@ -42,7 +46,7 @@ export class ImageService {
};
}
public CreateImageLinksFromID(imageID: string, format: string): ImageLinks {
return this.CreateImageLinks(this.GetImageURL(imageID + '.' + format));
public CreateImageLinksFromID(imageID: string, mime: string | null): ImageLinks {
return this.CreateImageLinks(this.GetImageURL(imageID, mime));
}
}

View File

@@ -1,5 +1,18 @@
import { z } from 'zod';
import { EImageSchema } from '../../entities/image.entity';
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 const ImageUploadResponseSchema = EImageSchema;
export class ImageUploadResponse extends createZodDto(
ImageUploadResponseSchema,
) {}