import type { Core } from '@strapi/strapi'; /** * Media types the Media Library accepts. * * The site publishes reports, guidance notes and event material, so office * documents and PDFs are allowed next to the usual image/video/audio types. */ const allowedMediaTypes = [ 'image/*', 'video/*', 'audio/*', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.*', 'text/plain', 'text/csv', ]; /** * Types that are never accepted, even if they slip past `allowedMediaTypes`. * SVG is denied on purpose: it can carry script and is served from the same * origin as the API. */ const deniedTypes = [ 'image/svg+xml', 'application/vnd.microsoft.portable-executable', 'application/x-msdownload', 'application/x-msdos-program', 'application/x-executable', 'application/x-dosexec', 'application/x-sh', 'text/x-shellscript', 'application/x-mach-binary', ]; /** * The locales the site ships with. * * `ar` is the default (RTL) and `en` is the left-to-right translation. The web * app mirrors these codes in its URL prefixes (`/en/...`, `/ar/...`), so the * codes here are part of the public contract - do not rename them. * * The i18n plugin itself does not read this key; it is our own declaration and * is read back at boot by `src/bootstrap/locales.ts`, which creates any missing * locale and pins the default one. Adding a locale is therefore a one-line edit * here plus a restart. */ const locales = [ { code: 'ar', name: 'Arabic (ar)', isDefault: true }, { code: 'en', name: 'English (en)' }, ]; const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin => ({ /** * Internationalisation - core plugin, listed explicitly so the two supported * locales live in version control rather than only in the database. */ i18n: { enabled: true, config: { defaultLocale: 'ar', locales, }, }, 'users-permissions': { config: { jwtSecret: env('JWT_SECRET'), /** * "refresh" uses the SessionManager (short-lived access token + refresh * token) instead of the legacy long-lived plugin JWT. */ jwtManagement: 'refresh', sessions: { // Refresh token is stored in a cookie that JavaScript cannot read. httpOnly: true, }, /** * Rate limit on the auth endpoints (login, forgot-password, ...). * Tightened from the Strapi default of 10 requests per minute. */ ratelimit: { interval: env.int('AUTH_RATE_LIMIT_INTERVAL', 60_000), max: env.int('AUTH_RATE_LIMIT_MAX', 5), }, }, }, /** * Uploads stay on the default `local` provider: files land in * `apps/cms/public/uploads` and are served by `strapi::public`. * * TO SWITCH TO S3 IN PRODUCTION * ----------------------------- * 1. Add the provider package to apps/cms/package.json: * "@strapi/provider-upload-aws-s3": "5.53.0" * 2. Replace the `provider` / `providerOptions` keys below with: * * provider: 'aws-s3', * providerOptions: { * baseUrl: env('CDN_URL'), // optional CloudFront/CDN origin * rootPath: env('S3_ROOT_PATH'), // optional key prefix * s3Options: { * credentials: { * accessKeyId: env('AWS_ACCESS_KEY_ID'), * secretAccessKey: env('AWS_ACCESS_SECRET'), * }, * region: env('AWS_REGION'), * params: { * ACL: env('AWS_ACL', 'private'), * signedUrlExpires: env.int('AWS_SIGNED_URL_EXPIRES', 15 * 60), * Bucket: env('AWS_BUCKET'), * }, * }, * }, * actionOptions: { upload: {}, uploadStream: {}, delete: {} }, * * 3. Add the bucket/CDN host to the Content Security Policy in * `config/middlewares.ts` (`img-src` and `media-src`), otherwise the admin * panel shows broken thumbnails. * 4. Set the AWS_* / CDN_URL variables listed in `.env.example`. */ upload: { config: { provider: 'local', providerOptions: { // Cache-Control max-age (seconds) sent for locally served uploads. localServer: { maxage: env.int('UPLOAD_LOCAL_MAX_AGE', 300_000), }, }, // Hard ceiling per file, in bytes. Defaults to 50 MB. sizeLimit: env.int('UPLOAD_MAX_FILE_SIZE', 50 * 1024 * 1024), security: { allowedTypes: allowedMediaTypes, deniedTypes, }, }, }, email: { config: { provider: "@strapi/provider-email-nodemailer", providerOptions: { host: env("SMTP_HOST", "mail.sharper.ly"), port: env("SMTP_PORT", 587), secure: true, auth: { user: env("SMTP_USERNAME"), pass: env("SMTP_PASSWORD"), }, }, settings: { defaultFrom: "SHARP-ER ", defaultReplyTo: "United Efforts " } }, } }); export default config;