Skip to content

AWS S3 storage

This project uses Amazon S3 for server-side file storage via @aws-sdk/client-s3. Uploads run in uploadToStorage (src/lib/s3.ts). Authenticated features call uploadFileToStorage and removeFileFromStorage (src/actions/file-actions.ts), which require a signed-in user.

Typical use in the app: profile / general settings image upload (src/components/forms/general-setting/use-general-setting-form.ts).

How it works in this codebase

ConcernBehavior
ClientSends a File to a Server Action (uploadFileToStorage).
AuthUnauthenticated requests return an error from file-actions.ts.
Object keyDefault prefix uploads/; filenames are sanitized and prefixed with a timestamp (src/lib/s3.ts).
Upload APIPutObjectCommand (no ACL set by default — see Object access).
Returned URLVirtual-hosted style: https://{BUCKET}.s3.{REGION}.amazonaws.com/{key}
DeleteDeleteObjectCommand; removeFromStorage only deletes if the URL contains the configured bucket name.

Environment variables

Values are read through src/config.tsappConfig.s3:

env
AWS_S3_REGION=
AWS_S3_BUCKET_NAME=
AWS_S3_ACCESS_KEY=
AWS_S3_SECRET_KEY=

All four should be set in any environment where uploads or deletes must run (! assertions are used when constructing the SDK client).

AWS setup

1. Create a bucket

  1. In AWS ConsoleS3Create bucket.
  2. Choose a globally unique name (example: nexusorbit-production-uploads).
  3. Pick a Region and use the same value as AWS_S3_REGION.

2. Create an IAM user (access keys)

  1. IAMUsers → create a user intended for application use (no console access required).
  2. Attach a policy that allows this app’s operations on your bucket only.

Minimal example (replace placeholders):

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "NexusOrbitS3BucketReadWriteDelete",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
    }
  ]
}

TIP

Prefer scoped policies (Resource limited to one bucket prefix) over s3:* on *.

3. Generate access keys

IAM → your user → Security credentialsCreate access key (use case: application running outside AWS, or per your org’s policy). Map them to AWS_S3_ACCESS_KEY and AWS_S3_SECRET_KEY.

Object access (public vs private)

The upload helper returns a plain HTTPS URL to the object. Browsers can only load or display that URL if the object is readable without signing.

Option A — Public read on objects (simplest for avatars/static files)

  • Use a bucket policy allowing s3:GetObject for Principal: "*" on arn:aws:s3:::YOUR_BUCKET_NAME/*, or enable ACL: "public-read" on PutObject only if your bucket/account still supports ACL-based public reads (modern defaults often enforce “Bucket owner enforced” and block ACLs).

Example bucket policy snippet (adjust bucket name):

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
    }
  ]
}

WARNING

Public GetObject makes every object URL guessable unless you restrict prefixes, use random keys only, or add WAF/other controls. Prefer least privilege for anything sensitive — this pattern fits non-sensitive public assets (e.g. profile images).

Option B — Private bucket (recommended for sensitive files)

  • Keep Block Public Access on and do not expose public GetObject.
  • You would need signed URLs (GetObjectCommand + presigner) or CloudFront with appropriate auth — that requires code changes beyond the current uploadToStorage return value.

There is an inline comment in src/lib/s3.ts for optional ACL: "public-read" on PutObject; use it only if it matches your bucket’s ACL settings and security model.

File map

RolePath
S3 client, upload/delete, URL buildingsrc/lib/s3.ts
Authenticated server actionssrc/actions/file-actions.ts
Config keyssrc/config.tsappConfig.s3
Example consumer (avatar upload)src/components/forms/general-setting/use-general-setting-form.ts

Troubleshooting

  • Failed to upload file to aws s3 — Check region matches the bucket, credentials are correct, and IAM policy includes s3:PutObject.
  • Upload succeeds but image does not load — Object likely private; add controlled public read policy, or implement signed URLs.
  • removeFromStorage returns false — URL string does not include the configured AWS_S3_BUCKET_NAME (src/lib/s3.ts).

Built with Nexus Orbit