mirror of
https://github.com/strapi/strapi.git
synced 2025-08-15 04:08:04 +00:00

* Fix typo of query key from 'providers' to 'get-providers' * Closing #19644 fix filters type * fix patreon oauth 400 error from lack of user-agent header * test: backport cli tests (#20433) * fix(chore): increase specificity of the Lightning icon color (#20467) * test: temporarily disable broken edit ctb tests on CI (#20481) * feat(cli): cloud cli commands (v4) (#20119) * feat(cli): add cloud commands Co-authored-by: Gonzalo Garcia <nouvellegon@gmail.com> Co-authored-by: nathan-pichon <nathan.pichon@strapi.io> Co-authored-by: Abdallah M <55534657+abdallahmz@users.noreply.github.com> * v4.25.0 (#20500) * NPS: Update frequency of the NPS (#20492) * enhancement(admin): change postFirstDismissal and display delays * enhancement(admin): change the display delay * fix: prevent use of local ips on webhooks (#20487) * chore: mask error on webhook manual trigger * feat: prevent using local ips * feat: display webhook edit error * chore: pr suggestion * chore: api tests * chore: allow local ips on development * chore: only run check on production * feat: include internationalized urls * fix: prettier * Add: Strapi deploy command to README files * v4.25.1 * Update @aws-sdk/client-s3 package * feat(cli): add browser logout step (#20502) * feat(cli): add browser log out step * handle error whiile fetching the config * Update LICENSE packing for packages for v4 (#20576) * feat(create-strapi-app): remove the cloud project creation part (#20561) * feat(create-strapi-app): remove the cloud project creation part * fix(create-strapi-app): adding new parameter to cloud service instantiation * Update Yarn to 4.3.1 Signed-off-by: Sora Morimoto <sora@morimoto.io> * feat(cli): trigger login sequence when token is missing or invalid (#20572) * feat(cli): launch login when auth fails * fix(cli): abstract create project fn * fix(cli): guidelines * fix(cli): rebase --------- Co-authored-by: Gonzalo Andres Garcia <nouvellegon@gmail.com> * fix clone entity, #20509 (#20531) * chore: bump glob from 7.2.0 to 9.0.0 * chore: bump glob to v10.4.2 in core/strapi * update yarn.lock * removed redundant packages/core/strapi/src/load/glob.ts file * v4.25.2 (#20675) * fix: support string array * v4.25.2 --------- Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com> Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com> * Merge branch 'develop' into v5/main * fix: ts errors * fix: typescript issues * fix: remove unused import * Update packages/cli/create-strapi-app/src/create-strapi-app.ts Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * Update packages/cli/create-strapi-app/src/create-strapi-app.ts Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * Update packages/generators/app/src/resources/files/js/README.md Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * Update packages/generators/app/src/resources/files/ts/README.md Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> * feat(cloud-cli): enable cloud-cli --------- Signed-off-by: Sora Morimoto <sora@morimoto.io> Co-authored-by: smoothdvd <madfxgao@gmail.com> Co-authored-by: Micah Riggan <micahriggan@gmail.com> Co-authored-by: Tewson Seeoun <tewson.seeoun@gmail.com> Co-authored-by: Ben Irvin <ben.irvin@strapi.io> Co-authored-by: Simone <startae14@gmail.com> Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io> Co-authored-by: Gonzalo Garcia <nouvellegon@gmail.com> Co-authored-by: Abdallah M <55534657+abdallahmz@users.noreply.github.com> Co-authored-by: Maxime Castres <mcastres@student.42.fr> Co-authored-by: Convly <jean-sebastien.herbaux@epitech.eu> Co-authored-by: Alex Supkay <asupkay1124@gmail.com> Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com> Co-authored-by: Sora Morimoto <sora@morimoto.io> Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com> Co-authored-by: Kirill Verevkin <kira795@yandex.ru> Co-authored-by: chrismuiruriz <chrismuiruri007@gmail.com>
172 lines
5.0 KiB
TypeScript
172 lines
5.0 KiB
TypeScript
import type { ReadStream } from 'node:fs';
|
|
import { getOr } from 'lodash/fp';
|
|
import {
|
|
S3Client,
|
|
GetObjectCommand,
|
|
DeleteObjectCommand,
|
|
DeleteObjectCommandOutput,
|
|
PutObjectCommandInput,
|
|
CompleteMultipartUploadCommandOutput,
|
|
AbortMultipartUploadCommandOutput,
|
|
S3ClientConfig,
|
|
ObjectCannedACL,
|
|
} from '@aws-sdk/client-s3';
|
|
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
import { Upload } from '@aws-sdk/lib-storage';
|
|
import { extractCredentials, isUrlFromBucket } from './utils';
|
|
|
|
export interface File {
|
|
name: string;
|
|
alternativeText?: string;
|
|
caption?: string;
|
|
width?: number;
|
|
height?: number;
|
|
formats?: Record<string, unknown>;
|
|
hash: string;
|
|
ext?: string;
|
|
mime: string;
|
|
size: number;
|
|
sizeInBytes: number;
|
|
url: string;
|
|
previewUrl?: string;
|
|
path?: string;
|
|
provider?: string;
|
|
provider_metadata?: Record<string, unknown>;
|
|
stream?: ReadStream;
|
|
buffer?: Buffer;
|
|
}
|
|
|
|
export type UploadCommandOutput = (
|
|
| CompleteMultipartUploadCommandOutput
|
|
| AbortMultipartUploadCommandOutput
|
|
) & {
|
|
Location: string;
|
|
};
|
|
|
|
export interface AWSParams {
|
|
Bucket: string; // making it required
|
|
ACL?: ObjectCannedACL;
|
|
signedUrlExpires?: number;
|
|
}
|
|
|
|
export interface DefaultOptions extends S3ClientConfig {
|
|
// TODO Remove this in V5
|
|
accessKeyId?: AwsCredentialIdentity['accessKeyId'];
|
|
secretAccessKey?: AwsCredentialIdentity['secretAccessKey'];
|
|
// Keep this for V5
|
|
credentials?: AwsCredentialIdentity;
|
|
params?: AWSParams;
|
|
[k: string]: any;
|
|
}
|
|
|
|
export type InitOptions = (DefaultOptions | { s3Options: DefaultOptions }) & {
|
|
baseUrl?: string;
|
|
rootPath?: string;
|
|
[k: string]: any;
|
|
};
|
|
|
|
const assertUrlProtocol = (url: string) => {
|
|
// Regex to test protocol like "http://", "https://"
|
|
return /^\w*:\/\//.test(url);
|
|
};
|
|
|
|
const getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) => {
|
|
if (Object.keys(legacyS3Options).length > 0) {
|
|
process.emitWarning(
|
|
"S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property."
|
|
);
|
|
}
|
|
const credentials = extractCredentials({ s3Options, ...legacyS3Options });
|
|
const config = {
|
|
...s3Options,
|
|
...legacyS3Options,
|
|
...(credentials ? { credentials } : {}),
|
|
};
|
|
|
|
config.params.ACL = getOr(ObjectCannedACL.public_read, ['params', 'ACL'], config);
|
|
|
|
return config;
|
|
};
|
|
|
|
export default {
|
|
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) {
|
|
// TODO V5 change config structure to avoid having to do this
|
|
const config = getConfig({ baseUrl, rootPath, s3Options, ...legacyS3Options });
|
|
const s3Client = new S3Client(config);
|
|
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';
|
|
|
|
const getFileKey = (file: File) => {
|
|
const path = file.path ? `${file.path}/` : '';
|
|
return `${filePrefix}${path}${file.hash}${file.ext}`;
|
|
};
|
|
|
|
const upload = async (file: File, customParams: Partial<PutObjectCommandInput> = {}) => {
|
|
const fileKey = getFileKey(file);
|
|
const uploadObj = new Upload({
|
|
client: s3Client,
|
|
params: {
|
|
Bucket: config.params.Bucket,
|
|
Key: fileKey,
|
|
Body: file.stream || Buffer.from(file.buffer as any, 'binary'),
|
|
ACL: config.params.ACL,
|
|
ContentType: file.mime,
|
|
...customParams,
|
|
},
|
|
});
|
|
|
|
const upload = (await uploadObj.done()) as UploadCommandOutput;
|
|
|
|
if (assertUrlProtocol(upload.Location)) {
|
|
file.url = baseUrl ? `${baseUrl}/${fileKey}` : upload.Location;
|
|
} else {
|
|
// Default protocol to https protocol
|
|
file.url = `https://${upload.Location}`;
|
|
}
|
|
};
|
|
|
|
return {
|
|
isPrivate() {
|
|
return config.params.ACL === 'private';
|
|
},
|
|
|
|
async getSignedUrl(file: File, customParams: any): Promise<{ url: string }> {
|
|
// Do not sign the url if it does not come from the same bucket.
|
|
if (!isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {
|
|
return { url: file.url };
|
|
}
|
|
const fileKey = getFileKey(file);
|
|
|
|
const url = await getSignedUrl(
|
|
// @ts-expect-error - TODO fix client type
|
|
s3Client,
|
|
new GetObjectCommand({
|
|
Bucket: config.params.Bucket,
|
|
Key: fileKey,
|
|
...customParams,
|
|
}),
|
|
{
|
|
expiresIn: getOr(15 * 60, ['params', 'signedUrlExpires'], config),
|
|
}
|
|
);
|
|
|
|
return { url };
|
|
},
|
|
uploadStream(file: File, customParams = {}) {
|
|
return upload(file, customParams);
|
|
},
|
|
upload(file: File, customParams = {}) {
|
|
return upload(file, customParams);
|
|
},
|
|
delete(file: File, customParams = {}): Promise<DeleteObjectCommandOutput> {
|
|
const command = new DeleteObjectCommand({
|
|
Bucket: config.params.Bucket,
|
|
Key: getFileKey(file),
|
|
...customParams,
|
|
});
|
|
return s3Client.send(command);
|
|
},
|
|
};
|
|
},
|
|
};
|