mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-13 07:15:39 +01:00
Fix some cors issues
This commit is contained in:
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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'])
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const SettingsRoutes: PRoutes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
redirectTo: 'general',
|
redirectTo: 'general',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user