Skip to main content

Media & CDN Strategy

This document details how Skin Club Pro handles media assets, including system images (logos, banners) and sensitive patient data (before/after photos).

1. Overview

The platform uses a centralized media strategy leveraging AWS S3 for storage and AWS CloudFront for content delivery. We distinguish between Public Assets and Private/Sensitive Assets.

2. Public Assets (System Assets)

Public assets are images used across the application that do not contain sensitive data.

  • Storage: Stored in the public/ prefix of the S3 bucket.
  • Access: Publicly readable via a CloudFront distribution.
  • Caching: Aggressively cached at the edge (TTL: 1 year) with Cache-Control: public, max-age=31536000.
  • Examples: Clinic logos, email header banners, marketing images.

Uploading System Assets

We use a dedicated script scripts/upload-system-assets.js to manage these.

node scripts/upload-system-assets.js --env=prod

3. Private Assets (Patient Data)

Sensitive assets like patient photos require strict access control.

  • Storage: Stored in tenant-specific prefixes: {tenantId}/{uuid}-{filename}.
  • Access: Restricted at the S3 bucket level. Access is only granted via Presigned URLs.
  • Expiration: Presigned URLs are generated with a short TTL (e.g., 1 hour) to ensure they cannot be shared or leaked.
  • Examples: Before/After treatment photos, scanned consent forms.

4. Infrastructure Details

S3 Bucket Configuration

  • Bucket Name: {env}-skinclub-pro-media
  • Encryption: AES-256 server-side encryption enabled.
  • CORS: Configured to allow requests from *.skinclubpro.com.

CloudFront Distribution

  • Origin: S3 bucket.
  • SSL: Managed via ACM certificate for media.skinclubpro.com.
  • Behaviors:
    • /public/*: Allowed GET and HEAD requests.
    • Default: Restricted access (requires signed URLs or internal service access).

5. Implementation in Services

Services interact with media via the MediaService in the CMS or Clinic services.

Generating a Presigned URL

async getSignedUrl(key: string): Promise<string> {
const command = new GetObjectCommand({
Bucket: this.bucket,
Key: key,
});
return getSignedUrl(this.s3Client, command, { expiresIn: 3600 });
}

Public Asset URL Fallback

If a CDN is not configured (e.g., in local development), the service falls back to direct S3 URLs or LocalStack endpoints.