Secure Uploads - v1.0.0
Secure Storage
SecureFileStorageService (bound to SecureFileStorageInterface) is the entry point for storing and retrieving files. Most callers go through HasSecureFiles::attachSecureFile() rather than touching the service directly.
Store
use ArtisanPackUI\SecureUploads\Contracts\SecureFileStorageInterface;
$storage = app(SecureFileStorageInterface::class);
$stored = $storage->store($request->file('attachment'), [
'disk' => 's3-private', // optional — default config('filesystems.default')
'path' => 'posts/' . $post->id, // optional — default 'secure-files'
'owner' => $post, // optional — links to a model via morph
'metadata' => ['source' => 'cms'], // optional — stored as JSON
]);
$stored->identifier; // string — opaque ID used in signed URLs
$stored->disk; // string
$stored->path; // string — disk-relative path
$stored->filename; // string — sanitized original filename
$stored->mimeType; // string — detected MIME
$stored->size; // int — bytes
Retrieve
$file = $storage->retrieve($identifier); // ?StoredFile
$file->stream(); // resource — for streaming responses
Or read the bytes directly:
$contents = $storage->getContents($identifier); // ?string
For Eloquent access:
$model = $storage->getModel($identifier); // ?SecureUploadedFile
Signed URLs
$url = $storage->generateSecureUrl($identifier, expirationMinutes: 15);
Or via the helper:
$url = secure_uploads()->generateSecureUrl($identifier);
The URL points at the bundled secure-file.show route. Pass the file URL to your view / API response — anyone with the URL can fetch the file until expiration.
For force-download links, hit the secure-file.download route instead:
$url = URL::signedRoute('secure-file.download', ['identifier' => $identifier]);
The download route sets Content-Disposition: attachment so browsers save rather than render.
Delete
$storage->delete($identifier); // bool — removes disk + DB row
HasSecureFiles::detachSecureFile() is a thin wrapper that also removes the morph relationship row.
Quarantine
$storage->quarantine($identifier); // moves the file from its disk to the quarantine path
Called automatically by the async scanning flow when a file fails its scan. You can also call it manually when reviewing flagged uploads.
Pending-scan inventory
$pending = $storage->getPendingScanFiles(limit: 100);
Returns a Collection<SecureUploadedFile> of files awaiting async scan. security:scan-quarantine uses this internally.
Disk + path conventions
- Disk: by default the package writes to the application's default disk. Use a dedicated private disk in production — never store secure files on a public disk.
- Path: default
'secure-files'. Use a per-owner path ('posts/123','users/42/avatars') when you want filesystem-level inspection to be readable. - Filename: always sanitized via
FileValidationService::sanitizeFilename(). Stored with a hash prefix to avoid collisions; the original (sanitized) filename is preserved in the DB for download.