mirror of
https://github.com/strapi/strapi.git
synced 2025-12-25 06:04:29 +00:00
feat: Private s3 buckets (#20861)
* feat: sign media files * fix: bootstrap test
This commit is contained in:
parent
2403a57adf
commit
ae299ee651
@ -56,7 +56,7 @@ describe('Upload plugin bootstrap function', () => {
|
||||
registerCron() {},
|
||||
},
|
||||
extensions: {
|
||||
contentManager: { entityManager: { addSignedFileUrlsToAdmin: jest.fn() } },
|
||||
signFileUrlsOnDocumentService: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -41,11 +41,7 @@ export async function bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
||||
await getService('weeklyMetrics').registerCron();
|
||||
getService('metrics').sendUploadPluginMetrics();
|
||||
|
||||
if (strapi.config.get('plugin::upload.signAdminURLsOnly', false)) {
|
||||
getService('extensions').contentManager.entityManager.addSignedFileUrlsToAdmin();
|
||||
} else {
|
||||
getService('extensions').core.entityService.addSignedFileUrlsToEntityService();
|
||||
}
|
||||
getService('extensions').signFileUrlsOnDocumentService();
|
||||
}
|
||||
|
||||
const registerWebhookEvents = async () =>
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
import { async, traverseEntity } from '@strapi/utils';
|
||||
|
||||
import type { Schema, UID } from '@strapi/types';
|
||||
|
||||
import { getService } from '../../../utils';
|
||||
|
||||
import type { File } from '../../../types';
|
||||
|
||||
type SignEntityMediaVisitor = (
|
||||
args: {
|
||||
key: string;
|
||||
value: unknown;
|
||||
attribute: Schema.Attribute.AnyAttribute;
|
||||
},
|
||||
utils: {
|
||||
set: (key: string, value: unknown) => void;
|
||||
}
|
||||
) => Promise<void>;
|
||||
|
||||
function isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {
|
||||
if (!value || attribute.type !== 'media') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const signEntityMediaVisitor: SignEntityMediaVisitor = async (
|
||||
{ key, value, attribute },
|
||||
{ set }
|
||||
) => {
|
||||
const { signFileUrls } = getService('file');
|
||||
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attribute.type !== 'media') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFile(value, attribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the attribute is repeatable sign each file
|
||||
if (attribute.multiple) {
|
||||
const signedFiles = await async.map(value, signFileUrls);
|
||||
set(key, signedFiles);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the attribute is not repeatable only sign a single file
|
||||
const signedFile = await signFileUrls(value);
|
||||
set(key, signedFile);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Iterate through an entity manager result
|
||||
* Check which modelAttributes are media and pre sign the image URLs
|
||||
* if they are from the current upload provider
|
||||
*
|
||||
* @param {Object} entity
|
||||
* @param {Object} modelAttributes
|
||||
* @returns
|
||||
*/
|
||||
const signEntityMedia = async (entity: any, uid: UID.Schema) => {
|
||||
const model = strapi.getModel(uid);
|
||||
return traverseEntity(
|
||||
// @ts-expect-error - FIXME: fix traverseEntity types
|
||||
signEntityMediaVisitor,
|
||||
{ schema: model, getModel: strapi.getModel.bind(strapi) },
|
||||
entity
|
||||
);
|
||||
};
|
||||
|
||||
const addSignedFileUrlsToAdmin = async () => {
|
||||
// FIXME: This is not working. Replace with doc service middlewares or wrap the CM somewhow
|
||||
// const { provider } = strapi.plugins.upload;
|
||||
// const isPrivate = await provider.isPrivate();
|
||||
// // We only need to sign the file urls if the provider is private
|
||||
// if (!isPrivate) {
|
||||
// return;
|
||||
// }
|
||||
// strapi.get('services').extend('plugin::content-manager.entity-manager', (entityManager: any) => {
|
||||
// /**
|
||||
// * Map entity manager responses to sign private media URLs
|
||||
// * @param {Object} entity
|
||||
// * @param {string} uid
|
||||
// * @returns
|
||||
// */
|
||||
// const mapEntity = async (entity, uid) => {
|
||||
// const mappedEntity = await entityManager.mapEntity(entity, uid);
|
||||
// return signEntityMedia(mappedEntity, uid);
|
||||
// };
|
||||
// return { ...entityManager, mapEntity };
|
||||
// });
|
||||
};
|
||||
|
||||
export { addSignedFileUrlsToAdmin, signEntityMedia };
|
||||
@ -1,3 +0,0 @@
|
||||
import { addSignedFileUrlsToAdmin } from './entity-manager';
|
||||
|
||||
export const entityManager = { addSignedFileUrlsToAdmin };
|
||||
@ -1,29 +0,0 @@
|
||||
import { signEntityMedia } from '../utils';
|
||||
|
||||
const addSignedFileUrlsToEntityService = async () => {
|
||||
// FIXME: This is not working. Replace with doc service middlewares
|
||||
// const { provider } = strapi.plugins.upload;
|
||||
// const isPrivate = await provider.isPrivate();
|
||||
// // We only need to sign the file urls if the provider is private
|
||||
// if (!isPrivate) {
|
||||
// return;
|
||||
// }
|
||||
// const decorator = (service) => ({
|
||||
// async wrapResult(result, options) {
|
||||
// const wrappedResult = await service.wrapResult.call(this, result, options);
|
||||
// // Load returns only the attribute of the entity, not the entity itself,
|
||||
// if (options.action === 'load') {
|
||||
// const entity = { [options.field]: result };
|
||||
// const signedEntity = await signEntityMedia(entity, options.uid);
|
||||
// return signedEntity[options.field];
|
||||
// }
|
||||
// if (Array.isArray(wrappedResult)) {
|
||||
// return Promise.all(wrappedResult.map((entity) => signEntityMedia(entity, options.uid)));
|
||||
// }
|
||||
// return signEntityMedia(wrappedResult, options.uid);
|
||||
// },
|
||||
// });
|
||||
// strapi.entityService.decorate(decorator);
|
||||
};
|
||||
|
||||
export { addSignedFileUrlsToEntityService, signEntityMedia };
|
||||
@ -1,3 +0,0 @@
|
||||
import { addSignedFileUrlsToEntityService } from './entity-service';
|
||||
|
||||
export const entityService = { addSignedFileUrlsToEntityService };
|
||||
@ -1,7 +1,53 @@
|
||||
import * as contentManager from './content-manager';
|
||||
import * as core from './core';
|
||||
import { async } from '@strapi/utils';
|
||||
import { signEntityMedia } from './utils';
|
||||
|
||||
const signFileUrlsOnDocumentService = async () => {
|
||||
const { provider } = strapi.plugins.upload;
|
||||
const isPrivate = await provider.isPrivate();
|
||||
|
||||
// We only need to sign the file urls if the provider is private
|
||||
if (!isPrivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
strapi.documents.use(async (ctx, next) => {
|
||||
const uid = ctx.uid;
|
||||
const result: any = await next();
|
||||
|
||||
if (ctx.action === 'findMany') {
|
||||
// Shape: [ entry ]
|
||||
return async.map(result, (entry: any) => signEntityMedia(entry, uid));
|
||||
}
|
||||
|
||||
if (
|
||||
ctx.action === 'findFirst' ||
|
||||
ctx.action === 'findOne' ||
|
||||
ctx.action === 'create' ||
|
||||
ctx.action === 'update'
|
||||
) {
|
||||
// Shape: entry
|
||||
return signEntityMedia(result, uid);
|
||||
}
|
||||
|
||||
if (
|
||||
ctx.action === 'delete' ||
|
||||
ctx.action === 'clone' ||
|
||||
ctx.action === 'publish' ||
|
||||
ctx.action === 'unpublish' ||
|
||||
ctx.action === 'discardDraft'
|
||||
) {
|
||||
// Shape: { entries: [ entry ] }
|
||||
// ...
|
||||
return {
|
||||
...result,
|
||||
entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
contentManager,
|
||||
core,
|
||||
signFileUrlsOnDocumentService,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user