Storage Locations

Technical reference for Certivant's per-file storage locations — how ID documents, certificates, capture photos and call recordings keep pointing at the right bucket or storage account after the platform storage settings or a tenant's storage region change.

Every stored file records the location it was written to, next to its storage key. A location is one concrete place files are kept — an S3 bucket, an Azure storage account and container, or a local folder — identified by a stable id that contains no secrets:

ProviderLocation id
AWS S3 / S3-compatibles3:aws:ca-central-1/kyc-documents, s3:minio.example.com:ca-central-1/kyc
Azure Blob (each region of a multi-region setup is its own location)azure:certivantca/kyc
Local disklocal:./storage

Because reads always go back to the recorded location, these changes never strand old files:

  • the super admin changes Settings → Storage (new provider, new bucket, new account);
  • a tenant is moved to another storage region (multi-region Azure);
  • a storage key is rotated (the location keeps its id; the new key is used).

Where it is recorded

TableColumn
kyc_documentstorageLocation
kyc_certificate_of_verificationstorageLocation
kyc_capture_tokenstorageLocation
kyc_verification_conferencerecordingStorageLocation (the chunks and the assembled recording)

Each location's connection settings are kept in their own encrypted SystemSetting row, storage.location:<id>. Rows are never removed — files may still point at them. Saving the storage settings remembers the old settings' locations first, then the new ones.

In code

packages/kyc-engine/src/services/storage-location.service.ts:

  • Write — storageForNewFile(organizationId) returns { storage, location }: the tenant's region when the platform uses multi-region storage, otherwise the configured place. Save location with the key.
  • Read / delete — storageForFile(row.storageLocation), or the shortcuts downloadStoredFile(row), deleteStoredFile(row) and signedUrlForStoredFile(row) for any row with storageKey + storageLocation.
  • A row without a location (saved before locations were recorded) resolves to where it was written then: the platform default place — or, for certificates, the tenant's place (storageForFile(location, organizationId)).
  • An unknown location throws instead of reading from the wrong place.
  • Upload now, save later (public forms upload each file before the applicant submits) — locateUploadedFile(organizationId, key) checks the tenant's current place first (fileExists, a metadata check, no download); if the storage settings or the tenant's region changed while the applicant was filling the form, it checks every remembered place and returns the one that has the file. Submit and the upload preflight both use it, so the recorded location is always where the file really is.

Existing data

scripts/backfill-storage-locations.ts records the location of every file saved before this change (documents, capture photos and recordings → the default place; certificates → the tenant's place) and remembers the current locations. It is idempotent — dry run by default, --apply to write:

npx dotenv -c -e .env -- packages/database/node_modules/.bin/tsx scripts/backfill-storage-locations.ts --apply

Run it on each environment before changing the storage settings or a tenant's region for the first time.