8 Commits

Author SHA1 Message Date
ignacionelson 7c9847981a Patch 8 pending security advisories in dependencies
league/commonmark 2.9.0 -> 2.10.0 fixes an XSS bypass and three DoS
issues; nanoid, qs, brace-expansion, and @humanfs/node bumped via
npm audit fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iQNYLu5a65foArRdE9zzx
2026-09-03 11:57:15 -03:00
ignacionelson daec0a877e Offer Google Cloud Storage as a storage backend
External storage meant S3 and nothing else, which is an odd hole for a
product whose users are as likely to be standing on Google Cloud as on
AWS — and paying to move bytes between two clouds to use this. The
Storage screen now asks which provider first, and the answer decides
which fields it shows, which it validates, and which driver the
files_external disk resolves to.

One disk, not two. files.disk is a stored column, so a third disk name
would fragment the data model and make every $file->disk consumer know
three names instead of two; the driver is swapped instead. A service
account key gets its own encrypted column rather than sharing `secret`,
because the two are validated, labelled and displayed differently and
one column meaning two things is how that goes wrong later.

Three things do not work by simply adding the adapter, and all three
fail quietly:

Laravel's temporaryUrl() looks for getTemporaryUrl() on the adapter,
while League's GCS adapter names it temporaryUrl(), so without the
registered callback every download and preview is a 500.

The two SDKs spell the signing options differently, and an unrecognised
one is dropped in silence — the symptom is a download named after the
storage key, not an exception. GoogleCloudStorageDriver translates, so
callers keep speaking one vocabulary, and the test asserts on the URL's
contents rather than on "a redirect happened", which is what would let
it regress.

That callback is also re-bound to the FilesystemAdapter before it runs,
so the translation is captured before registering rather than called as
$this->

`provider` is validated with 'sometimes', not 'required': absent means
S3, which is what every payload written before this choice meant, and
stops a browser holding a stale bundle from failing to save on a field
it cannot see.

Verified in a browser as well as in tests — which is how the null
provider on an unmigrated row was found, since the suite migrates and
never sees that state.
2026-08-24 16:38:13 -03:00
ignacionelson 2567d9f193 Depend on a version of community-modules, not a branch
projectsend/community-modules is published and tagged now, so the
dependency can name a version. It could not before: with nothing to point
at, composer.json carried "*" against a vcs entry, which meant every
install resolved the default branch and got whatever had been pushed to it.
`composer validate` has been saying so on every run — "unbound version
constraints (*) should be avoided" — and it is right, because a dependency
that tracks a branch cannot be reasoned about and cannot be rolled back.

So: "^1.0", and the vcs entry goes. It has to go rather than merely stop
being needed, for the same reason the migration tool's did — a repository
declared in composer.json is canonical and outranks packagist.org, so
leaving it would keep serving dev-main off the default branch and shadow
every release ever tagged. Silently, which is the part that matters.

The lock moves with it: dev-main 311883a becomes v1.0.0, sourced from
Packagist rather than a git clone of a moving branch. Same code — the tag
was cut from that commit — but now it is a code somebody can name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:00:49 -03:00
ignacionelson d32562e75c Let Packagist answer for the migration tool
projectsend/v1-migration-tool is published now, and tagged, so
`composer require projectsend/v1-migration-tool` resolves on its own —
on any installation, new or existing, with nothing added to composer.json.
That is what the guide always claimed and what a customer's Debian install
proved it was not.

The vcs entry has to go, not just stop being necessary. Repositories
declared in composer.json are canonical and outrank packagist.org, so
leaving it would keep serving dev-main off the default branch and silently
shadow every release ever tagged. No error, no warning, just the wrong code
indefinitely — the failure mode you find months later.

So the manual fallback goes too, from the guide and from the teardown note
that told people to keep the entry. Neither describes anything that exists
any more, and instructions for a state the reader is not in are how the
last round of this went wrong: the note about a private repository outlived
the repository being private, and got skipped for looking inapplicable
while still being required.

The changelog entry is rewritten rather than added to. It described
shipping a pointer, which was the previous fix and lasted a day; what
landed is that the tool is published and versioned, and an installation can
finally say which version of it ran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:44:17 -03:00
ignacionelson bba0d78499 Tell Composer where the migration tool lives
`composer require projectsend/v1-migration-tool` — the first command in the
migration guide, and the first thing anybody moving from Legacy types —
fails on a fresh installation with "Could not find a matching version of
package". The tool is not on Packagist, and nothing in composer.json said
where else to look, so Composer had no way to find it and said so.

The manifest already had the answer four lines up: community-modules, also
unpublished, resolves through a vcs entry. The migration tool now has the
same one, so the documented command works as documented.

The guide said to add that entry by hand, but behind "while the repository
is private" — which stopped being true when the repository went public,
and a note that reads as inapplicable gets skipped. The step it described
was still mandatory, because public and published are different things.
That paragraph is now a fallback for installations predating this commit,
which do still need it, keyed on the error text rather than on a condition
the reader cannot check.

The teardown step is inverted to match: it used to suggest removing the
repositories entry after the migration, which would now break a second
attempt. It says to leave it.

Changing repositories invalidates composer.lock's content-hash, hence the
one-line lock update.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:24:52 -03:00
ignacionelson 9192779ee4 Close four small gaps before the release goes out
Four unrelated one-liners, each already written down and none of them
worth a branch of its own.

The lock was still pinned to the community package's previous commit,
which is the one before it started shipping its own sixteen catalogues.
The mechanism that carries a package's translations to the browser landed
here last week; without this bump the release would have shipped that
mechanism with nothing to carry, and the Custom Assets screen would have
stayed half-English in every language.

The stock `local` disk had `serve` left on. Nothing in this application
writes to it, so the framework's /storage route was a door with nothing
behind it — but it was still a door, and closing it costs one word.

nginx evaluated `\.php$` before `/protected-files/`, so a protected path
ending in .php would have reached the PHP handler instead of streaming
under the sandbox headers that block sets. Not reachable on a default
install — the upload allowlist refuses php and X-Accel paths are UUIDs —
but the guarantee read stronger than it was. `^~` makes it true.

And `.release-build` is now ignored by eslint, so linting after building a
zip stops walking the vendored minified JS inside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 20:28:16 -03:00
ignacionelson 377dff7603 Point the lock at community-modules' current commit
The lock pinned a commit that no longer exists: that repository's
history was replaced with a single commit, so an install resolving from
the lock would have failed to fetch it.

Nothing about the package's contents changed — same code, same version
constraint, new commit id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:50:03 -03:00
ignacionelson 6e47d76ba6 ProjectSend 2.0.0
Client file sharing, rebuilt from the ground up: a private area per
client, resumable uploads, folders, groups and categories, sharing with
expiry dates and download limits, comments, file versions, an activity
log, a REST API, and sixteen languages.

This repository begins here. ProjectSend 2 was developed privately, and
that development history is not published — the previous generation
remains available, with its own history, at projectsend/legacy.

Free software under the GNU General Public License v2, or (at your
option) any later version.
2026-08-14 01:38:12 -03:00