mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-13 16:22:39 +10:00
feat: make internal objectbackend methods private
This commit is contained in:
@ -3,9 +3,7 @@ import prisma from "~/server/internal/db/database";
|
|||||||
import objectHandler from "~/server/internal/objects";
|
import objectHandler from "~/server/internal/objects";
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
const allowed = await aclManager.allowSystemACL(h3, [
|
const allowed = await aclManager.allowSystemACL(h3, ["game:image:delete"]);
|
||||||
"game:image:delete",
|
|
||||||
]);
|
|
||||||
if (!allowed) throw createError({ statusCode: 403 });
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
const body = await readBody(h3);
|
const body = await readBody(h3);
|
||||||
@ -37,8 +35,8 @@ export default defineEventHandler(async (h3) => {
|
|||||||
throw createError({ statusCode: 400, statusMessage: "Image not found" });
|
throw createError({ statusCode: 400, statusMessage: "Image not found" });
|
||||||
|
|
||||||
game.mImageLibrary.splice(imageIndex, 1);
|
game.mImageLibrary.splice(imageIndex, 1);
|
||||||
await objectHandler.delete(imageId);
|
await objectHandler.deleteWithPermission(imageId);
|
||||||
|
|
||||||
if (game.mBannerId === imageId) {
|
if (game.mBannerId === imageId) {
|
||||||
game.mBannerId = game.mImageLibrary[0];
|
game.mBannerId = game.mImageLibrary[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,10 @@ export default defineEventHandler(async (h3) => {
|
|||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
|
||||||
const etagRequestValue = h3.headers.get("If-None-Match");
|
const etagRequestValue = h3.headers.get("If-None-Match");
|
||||||
const etagActualValue = await objectHandler.fetchHash(id);
|
const etagActualValue = await objectHandler.fetchHashWithWithPermissions(
|
||||||
|
id,
|
||||||
|
userId
|
||||||
|
);
|
||||||
if (etagRequestValue !== null && etagActualValue === etagRequestValue) {
|
if (etagRequestValue !== null && etagActualValue === etagRequestValue) {
|
||||||
// would compare if etag is valid, but objects should never change
|
// would compare if etag is valid, but objects should never change
|
||||||
setResponseStatus(h3, 304);
|
setResponseStatus(h3, 304);
|
||||||
|
|||||||
@ -14,7 +14,10 @@ export default defineEventHandler(async (h3) => {
|
|||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
|
||||||
const etagRequestValue = h3.headers.get("If-None-Match");
|
const etagRequestValue = h3.headers.get("If-None-Match");
|
||||||
const etagActualValue = await objectHandler.fetchHash(id);
|
const etagActualValue = await objectHandler.fetchHashWithWithPermissions(
|
||||||
|
id,
|
||||||
|
userId
|
||||||
|
);
|
||||||
if (etagRequestValue !== null && etagActualValue === etagRequestValue) {
|
if (etagRequestValue !== null && etagActualValue === etagRequestValue) {
|
||||||
// would compare if etag is valid, but objects should never change
|
// would compare if etag is valid, but objects should never change
|
||||||
setResponseStatus(h3, 304);
|
setResponseStatus(h3, 304);
|
||||||
|
|||||||
@ -129,7 +129,7 @@ class NewsManager {
|
|||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
if (article.image) {
|
if (article.image) {
|
||||||
return await objectHandler.delete(article.image);
|
return await objectHandler.deleteWithPermission(article.image);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
Object,
|
|
||||||
ObjectBackend,
|
ObjectBackend,
|
||||||
ObjectMetadata,
|
ObjectMetadata,
|
||||||
ObjectReference,
|
ObjectReference,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
import { FsObjectBackend } from "./fsBackend";
|
import { FsObjectBackend } from "./fsBackend";
|
||||||
export const objectHandler = new FsObjectBackend();
|
import { ObjectHandler } from "./objectHandler";
|
||||||
export default objectHandler
|
|
||||||
|
export const objectHandler = new ObjectHandler(new FsObjectBackend());
|
||||||
|
export default objectHandler;
|
||||||
|
|||||||
@ -49,21 +49,29 @@ export abstract class ObjectBackend {
|
|||||||
abstract create(
|
abstract create(
|
||||||
id: string,
|
id: string,
|
||||||
source: Source,
|
source: Source,
|
||||||
metadata: ObjectMetadata,
|
metadata: ObjectMetadata
|
||||||
): Promise<ObjectReference | undefined>;
|
): Promise<ObjectReference | undefined>;
|
||||||
abstract createWithWriteStream(
|
abstract createWithWriteStream(
|
||||||
id: string,
|
id: string,
|
||||||
metadata: ObjectMetadata,
|
metadata: ObjectMetadata
|
||||||
): Promise<Writable | undefined>;
|
): Promise<Writable | undefined>;
|
||||||
abstract delete(id: ObjectReference): Promise<boolean>;
|
abstract delete(id: ObjectReference): Promise<boolean>;
|
||||||
abstract fetchMetadata(
|
abstract fetchMetadata(
|
||||||
id: ObjectReference,
|
id: ObjectReference
|
||||||
): Promise<ObjectMetadata | undefined>;
|
): Promise<ObjectMetadata | undefined>;
|
||||||
abstract writeMetadata(
|
abstract writeMetadata(
|
||||||
id: ObjectReference,
|
id: ObjectReference,
|
||||||
metadata: ObjectMetadata,
|
metadata: ObjectMetadata
|
||||||
): Promise<boolean>;
|
): Promise<boolean>;
|
||||||
abstract fetchHash(id: ObjectReference): Promise<string | undefined>;
|
abstract fetchHash(id: ObjectReference): Promise<string | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ObjectHandler {
|
||||||
|
private backend: ObjectBackend;
|
||||||
|
|
||||||
|
constructor(backend: ObjectBackend) {
|
||||||
|
this.backend = backend;
|
||||||
|
}
|
||||||
|
|
||||||
private async fetchMimeType(source: Source) {
|
private async fetchMimeType(source: Source) {
|
||||||
if (source instanceof ReadableStream) {
|
if (source instanceof ReadableStream) {
|
||||||
@ -87,13 +95,13 @@ export abstract class ObjectBackend {
|
|||||||
id: string,
|
id: string,
|
||||||
sourceFetcher: () => Promise<Source>,
|
sourceFetcher: () => Promise<Source>,
|
||||||
metadata: { [key: string]: string },
|
metadata: { [key: string]: string },
|
||||||
permissions: Array<string>,
|
permissions: Array<string>
|
||||||
) {
|
) {
|
||||||
const { source, mime } = await this.fetchMimeType(await sourceFetcher());
|
const { source, mime } = await this.fetchMimeType(await sourceFetcher());
|
||||||
if (!mime)
|
if (!mime)
|
||||||
throw new Error("Unable to calculate MIME type - is the source empty?");
|
throw new Error("Unable to calculate MIME type - is the source empty?");
|
||||||
|
|
||||||
await this.create(id, source, {
|
await this.backend.create(id, source, {
|
||||||
permissions,
|
permissions,
|
||||||
userMetadata: metadata,
|
userMetadata: metadata,
|
||||||
mime,
|
mime,
|
||||||
@ -103,9 +111,9 @@ export abstract class ObjectBackend {
|
|||||||
async createWithStream(
|
async createWithStream(
|
||||||
id: string,
|
id: string,
|
||||||
metadata: { [key: string]: string },
|
metadata: { [key: string]: string },
|
||||||
permissions: Array<string>,
|
permissions: Array<string>
|
||||||
) {
|
) {
|
||||||
return this.createWithWriteStream(id, {
|
return this.backend.createWithWriteStream(id, {
|
||||||
permissions,
|
permissions,
|
||||||
userMetadata: metadata,
|
userMetadata: metadata,
|
||||||
mime: "application/octet-stream",
|
mime: "application/octet-stream",
|
||||||
@ -113,13 +121,13 @@ export abstract class ObjectBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches object, but also checks if user has perms to access it
|
* Fetches object, but also checks if user has perms to access it
|
||||||
* @param id
|
* @param id
|
||||||
* @param userId
|
* @param userId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async fetchWithPermissions(id: ObjectReference, userId?: string) {
|
async fetchWithPermissions(id: ObjectReference, userId?: string) {
|
||||||
const metadata = await this.fetchMetadata(id);
|
const metadata = await this.backend.fetchMetadata(id);
|
||||||
if (!metadata) return;
|
if (!metadata) return;
|
||||||
|
|
||||||
// We only need one permission, so find instead of filter is faster
|
// We only need one permission, so find instead of filter is faster
|
||||||
@ -137,7 +145,7 @@ export abstract class ObjectBackend {
|
|||||||
|
|
||||||
// Because any permission can be read or up, we automatically know we can read this object
|
// Because any permission can be read or up, we automatically know we can read this object
|
||||||
// So just straight return the object
|
// So just straight return the object
|
||||||
const source = await this.fetch(id);
|
const source = await this.backend.fetch(id);
|
||||||
if (!source) return undefined;
|
if (!source) return undefined;
|
||||||
const object: Object = {
|
const object: Object = {
|
||||||
data: source,
|
data: source,
|
||||||
@ -146,6 +154,28 @@ export abstract class ObjectBackend {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchHashWithWithPermissions(id: ObjectReference, userId?: string) {
|
||||||
|
const metadata = await this.backend.fetchMetadata(id);
|
||||||
|
if (!metadata) return;
|
||||||
|
|
||||||
|
// We only need one permission, so find instead of filter is faster
|
||||||
|
const myPermissions = metadata.permissions.find((e) => {
|
||||||
|
if (userId !== undefined && e.startsWith(userId)) return true;
|
||||||
|
if (userId !== undefined && e.startsWith("internal")) return true;
|
||||||
|
if (e.startsWith("anonymous")) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!myPermissions) {
|
||||||
|
// We do not have access to this object
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because any permission can be read or up, we automatically know we can read this object
|
||||||
|
// So just straight return the object
|
||||||
|
return await this.backend.fetchHash(id);
|
||||||
|
}
|
||||||
|
|
||||||
// If we need to fetch a remote resource, it doesn't make sense
|
// If we need to fetch a remote resource, it doesn't make sense
|
||||||
// to immediately fetch the object, *then* check permissions.
|
// to immediately fetch the object, *then* check permissions.
|
||||||
// Instead the caller can pass a simple anonymous funciton, like
|
// Instead the caller can pass a simple anonymous funciton, like
|
||||||
@ -154,9 +184,9 @@ export abstract class ObjectBackend {
|
|||||||
async writeWithPermissions(
|
async writeWithPermissions(
|
||||||
id: ObjectReference,
|
id: ObjectReference,
|
||||||
sourceFetcher: () => Promise<Source>,
|
sourceFetcher: () => Promise<Source>,
|
||||||
userId?: string,
|
userId?: string
|
||||||
) {
|
) {
|
||||||
const metadata = await this.fetchMetadata(id);
|
const metadata = await this.backend.fetchMetadata(id);
|
||||||
if (!metadata) return false;
|
if (!metadata) return false;
|
||||||
|
|
||||||
const myPermissions = metadata.permissions
|
const myPermissions = metadata.permissions
|
||||||
@ -178,13 +208,13 @@ export abstract class ObjectBackend {
|
|||||||
if (!hasPermission) return false;
|
if (!hasPermission) return false;
|
||||||
|
|
||||||
const source = await sourceFetcher();
|
const source = await sourceFetcher();
|
||||||
const result = await this.write(id, source);
|
const result = await this.backend.write(id, source);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWithPermission(id: ObjectReference, userId?: string) {
|
async deleteWithPermission(id: ObjectReference, userId?: string) {
|
||||||
const metadata = await this.fetchMetadata(id);
|
const metadata = await this.backend.fetchMetadata(id);
|
||||||
if (!metadata) return false;
|
if (!metadata) return false;
|
||||||
|
|
||||||
const myPermissions = metadata.permissions
|
const myPermissions = metadata.permissions
|
||||||
@ -205,7 +235,7 @@ export abstract class ObjectBackend {
|
|||||||
|
|
||||||
if (!hasPermission) return false;
|
if (!hasPermission) return false;
|
||||||
|
|
||||||
const result = await this.delete(id);
|
const result = await this.backend.delete(id);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class SaveManager {
|
|||||||
index: number,
|
index: number,
|
||||||
objectId: string
|
objectId: string
|
||||||
) {
|
) {
|
||||||
await objectHandler.delete(objectId);
|
await objectHandler.deleteWithPermission(objectId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async pushSave(
|
async pushSave(
|
||||||
@ -62,7 +62,7 @@ class SaveManager {
|
|||||||
await Promise.all([hashPromise, uploadStream]);
|
await Promise.all([hashPromise, uploadStream]);
|
||||||
|
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
await objectHandler.delete(newSaveObjectId);
|
await objectHandler.deleteWithPermission(newSaveObjectId, userId);
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: "Hash failed to generate",
|
statusMessage: "Hash failed to generate",
|
||||||
|
|||||||
Reference in New Issue
Block a user