mlab docs

Documents (S3)

Store the signed contract, annexes and amendments in your own S3 bucket - any S3-compatible service, files of any size.

Every contractual arrangement carries a Documents card: the signed agreement, its annexes and amendments. Files are stored in an S3 bucket you provide and never on the application server. Available on every tier.

What users see

On a contract page (/ct/detail/...), the Documents card lists each file with its size, who added it and when.

  • Upload one or several files at once; a progress bar follows each one.
  • Download a file: it is served as an attachment, never rendered in the page.
  • Delete a file, after confirmation. It is removed from the bucket too.
  • Deleting a contract deletes its documents.

Accepted types: PDF, Word, Excel, PowerPoint, OpenDocument text and spreadsheet, CSV, plain text, PNG, JPEG, ZIP, and emails (.eml, .msg). Anything else, or an empty file, is refused in the browser and again on the server.

Configure the bucket

Documents are off until the four required variables are set. Without them the platform works as before and the Documents card says storage is not configured.

.env
S3_ENDPOINT=https://s3.eu-west-3.amazonaws.com
S3_BUCKET=tprm-docs
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
# Optional
S3_REGION=eu-west-3
S3_PATH_STYLE=true
S3_PREFIX=
S3_MAX_UPLOAD_MB=2048
VariableRequiredDefaultDescription
S3_ENDPOINTYes-The S3 API URL: https://s3.<region>.amazonaws.com, https://s3.fr-par.scw.cloud, http://silo:9000...
S3_BUCKETYes-The bucket. It must exist: the platform does not create it.
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEYYes-Credentials allowed to put, get and delete objects in the bucket.
S3_REGIONNous-east-1The region used to sign requests. Most non-AWS services accept the default.
S3_PATH_STYLENotruetrue: <endpoint>/<bucket>/<key>, what MinIO, Silo and most self-hosted services expect. false: <bucket>.<host>/<key> (virtual-hosted).
S3_PREFIXNo-A folder inside the bucket, to share one bucket between instances.
S3_MAX_UPLOAD_MBNo2048Largest file accepted, in MB.

Requests are signed with AWS Signature Version 4, the scheme every S3-compatible service accepts (AWS S3, Scaleway, OVHcloud, MinIO...). The platform is tested against Silo.

Check the configuration

An administrator can prove the bucket and the keys work from the API: it writes a small test object, reads it back and deletes it.

curl -b "mlab_session=..." "https://tprm.example.com/api/v1/files/status?check=1"
# {"configured":true,"check":{"ok":true},"max_mb":2048,...}

When it fails, check.error carries the storage's own reason, such as SignatureDoesNotMatch (wrong secret) or NoSuchBucket.

Run a local bucket with Silo

Silo is an S3-compatible server that runs next to the platform in the same compose file. docker-compose.yml ships the service commented out:

docker-compose.yml
  silo:
    image: docker.io/pgsty/silo
    command: server /data
    environment:
      - MINIO_ROOT_USER=${S3_ACCESS_KEY_ID}
      - MINIO_ROOT_PASSWORD=${S3_SECRET_ACCESS_KEY}
    volumes:
      - silo_data:/data
    restart: unless-stopped

Uncomment it with the silo_data volume, set the variables, then create the bucket once:

.env
S3_ENDPOINT=http://silo:9000
S3_BUCKET=tprm-docs
S3_ACCESS_KEY_ID=tprm
S3_SECRET_ACCESS_KEY=<openssl rand -hex 24>
docker compose up -d silo app
docker compose exec silo sh -c 'mc alias set local http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" && mc mb local/tprm-docs'

Back up the silo_data volume with the database volumes: the documents live there.

Large files

Uploads are streamed to S3 in 8 MiB parts as they arrive (S3 multipart upload), so a 2 GB file costs the server no more memory than a small one; downloads are streamed the same way.

Reverse proxy. A proxy in front of the platform must accept request bodies as large as S3_MAX_UPLOAD_MB, or uploads fail with 413. For nginx, set client_max_body_size 2048m; (see Installation).

An upload that is refused, interrupted by the user or rejected by the storage is aborted: no partial object and no record are left. If the server itself stops in the middle of an upload, the parts already sent stay in the bucket as an incomplete multipart upload. MinIO and Silo remove those on their own (after 24 hours by default); on AWS S3, add a lifecycle rule that aborts incomplete multipart uploads after 1 day.

Security

  • Isolation. Documents of another organisation are never listed, downloaded or deleted: every request is checked against the signed-in user's organisation.
  • Storage keys. Objects are stored as <prefix>/<organisation>/contract/<contract>/<document>, made of identifiers only. The user's file name is kept in the database, cleaned of folders, control characters and quotes.
  • Downloads go through the platform, never straight from the bucket, so the bucket need not be reachable from browsers. They are served with Content-Disposition: attachment, the declared type and X-Content-Type-Options: nosniff.
  • Integrity. Every document keeps its SHA-256, computed while it is uploaded.
  • Audit. Uploads and deletions, with their author, are in the audit log.

API

EndpointMethodPurpose
/files/?entity_type=contract&entity_uuid=GETThe documents of a contract.
/files/uploadPOSTMultipart form: entity_type, entity_uuid, then file. The record fields must come before the file.
/files/{uuid}/downloadGETThe document, streamed.
/files/deleteDELETE{ "uuid": "..." }
/files/statusGETWhether storage is configured, the size limit and accepted extensions. ?check=1 (administrators) tests the bucket.

Uploads and downloads need a signed-in session; they are not available to API keys.

On this page