97 Commits
Author SHA1 Message Date
Johanna LarssonandTangled 9f05ea5f31 Introduce job to repair missing blobs
My last PR introduced a migration to blobs, changing them from being unique per cid to being unique per cid+user. This is because two users can upload the same blob, get the same cid, and then previously in tranquil the second user would never actually get their ownership recorded, meaning listBlobs was missing the blob, and a migration _off_ of tranquil would leave them sans blob https://tangled.org/tranquil.farm/tranquil-pds/pulls/261/round/3

This PR attempts to add a job to repair existing instances where blobs have been deduplicated and not attributed to each owning user. It does this by pulling all records for each user, and then in batches crawling the records to find blob references. For each blob reference, it checks whether that user has its ownership recorded, and if not, repairs it by adding the entry.

To avoid re-running this potentially heavy job on every startup, I've used the config table to store a little record of whether it has been run.
2026-09-17 12:35:55 +00:00
Johanna LarssonandTangled 2088f59197 Stop deleting logo blob when switching
This also means we can clean up the last blob operations that were per cid rather than user+cid 🪓

We should be setting ourselves up to be able to go garbage collect blobs safely so deleting the logo blobs manually won't matter anyway.
2026-09-12 15:34:46 +00:00
Johanna LarssonandTangled 877b587481 Ensure listing missing blobs always checks per user blobs 2026-09-12 15:34:46 +00:00
Johanna LarssonandTangled 695a7d981c Allow blobs to be owned by multiple accounts
The current tranquil database design only allows each blob to be owned by one account. This means that if a second account also has that blob, tranquil skips associated the blob with the account. That works fine a lot of the time, since blobs are looked up by cid and the blob exists. However, it can lead to loss of data under certain scenarios.

One example is where I upload a blob, the blob already exists in my instance so insertion is skipped (postgres requires cid to be unique in blobs, fjall only allows one owner per blob cid), I then decide to migrate off tranquil, the blob does not come with me since it is not mine.

Another example is where an account is deleted. If a blob was uploaded for account a, then account b uploads the same blob tranquil skips storing it since it exists. Then I delete account a, now account b's blob is missing.

I accidentally stumbled upon this when I migrated my account to my own tranuil instance and list blobs now lists 2 fewer blobs than before, two images that had been uploaded by accounts already on the PDS.

ps I found record_blobs a bit confusing, at first it looked like a blob ownership table, but then it turns out to just be used for migrations!

This PR makes the blob primary key be cid+user for postgres, and updates the queries to account for there being multiple "blobs" with the same cid. For queries that just care about the blob existing, it doesn't matter "whose" blob it is, so limit 1.

Most of the work is on the metastore side. Adds ref_count to track how many are referencing the blob since we can't just check for other rows. Instead of storing blobs directly, we now store a per account cid, and the blob reference itself is shared and keyed by cid only. This means some of these operations now require updating two places, so they're done in `batch`es.

With the new layout get_blob_value becomes simpler, all blob data is a single "table" or whatever it's called, so we just grab it using cid instead of looking it up for the user.

Migrates blobs rather than maintaining two different versions of the tables, although it seems like that could be supported.

I removed a test that asserted the old behavior, and added a reasonable (?) set of new tests that assert the new behavior, including a parity test.
2026-09-12 15:34:46 +00:00
LewisandTangled d00d72895a store: record-by-cid reverse index
Lewis: May this revision serve well! <lu5a@proton.me>
2026-07-25 08:27:40 +03:00
LewisandTangled 4f37ac26cd store: typed revs thru metastore keys & requests
Lewis: May this revision serve well! <lu5a@proton.me>
2026-07-25 08:27:39 +03:00
LewisandTangled 932b0c07d4 db: make stored handle optional when it no longer parses
Lewis: May this revision serve well! <lu5a@proton.me>
2026-07-25 08:27:39 +03:00
Lewis 6ec4484cad sqlx: add missing file
Lewis: May this revision serve well! <lu5a@proton.me>
2026-06-29 08:31:30 +03:00
Lewis aab1a945c2 session: deletes scope to did, route muts by did
Lewis: May this revision serve well! <lu5a@proton.me>
2026-06-28 09:55:11 +03:00
aveandTangled 9dc184ee33 bsky(auth): add grace period to legacy session refresh
Concurrent or retried com.atproto.server.refreshSession calls presenting the
same refresh token hit the reuse-detection path, which deleted the session and
returned "Refresh token has been revoked due to suspected compromise" —
logging users out at random. The legacy flow had no grace period, unlike OAuth.

Mirror the reference atproto PDS: every rotated refresh token gets a 2h grace
window measured from its own rotation time (used_refresh_tokens.used_at in
postgres; a rotated_at_ms field appended to the metastore used-marker, with
old-format markers decoding as outside the window). A refresh presenting a
recently-rotated token is served the session's current tokens, re-minted on
the fly with the same jti/expiry — signed JWTs are never persisted. Reuse
outside the window still revokes the session.

The grace lookup returns the session's encrypted signing key so the handler
verifies the presented token's signature before minting replacement tokens or
revoking a session; a forged token bearing a known jti gets a generic
rejection with no side effects.

Integration tests asserting the old replay-gets-401 behavior are reworked to
the new contract and now also cover forged-signature replays and
out-of-window revocation.
2026-06-27 23:54:22 +03:00
LewisandTangled 39a2e40b35 invite codes: dedup consumption, iron out kinks
Lewis: May this revision serve well! <lu5a@proton.me>
2026-06-26 13:28:49 +03:00
LewisandTangled e9dc57d6f4 fix(firehose): lost events if seq commits out of order
Lewis: May this revision serve well! <lu5a@proton.me>
2026-05-30 21:10:38 +03:00
LewisandTangled 4d2c7d4723 feat(auth): verification-gate override, inbound-migration bypass, store deleter improvement
Lewis: May this revision serve well! <lu5a@proton.me>
2026-05-23 23:02:43 +03:00
LewisandTangled a13343e1de fix(oauth): gc tokens in pg in the right order, more exposure of dpop err
Lewis: May this revision serve well! <lu5a@proton.me>
2026-05-14 16:51:53 +03:00
LewisandTangled eee6fb9ff4 feat(comms): EmailSender, permanent/transient routing
Lewis: May this revision serve well! <lu5a@proton.me>
2026-05-02 22:28:59 +03:00
LewisandTangled 34f050a1f0 feat(oauth): discoverable passkey authentication
Lewis: May this revision serve well! <lu5a@proton.me>
2026-04-12 11:16:58 +00:00
Lewis 05246aec58 feat(tranquil-store): gc and hardening
Lewis: May this revision serve well! <lu5a@proton.me>
2026-04-10 13:57:47 +03:00
Lewis 8d0e0c6810 feat(tranquil-store): whole test suite working
Lewis: May this revision serve well! <lu5a@proton.me>
2026-04-10 13:57:44 +03:00
Lewis a5c68a3506 feat(tranquil-store): metastore
Lewis: May this revision serve well! <lu5a@proton.me>
2026-04-10 13:57:43 +03:00
LewisandTangled 19b0ea19c6 refactor(tranquil-pds): consolidate repos, rate limits, validation, and auth helpers 2026-03-22 12:51:44 +00:00
LewisandTangled e6c8a44a42 feat(signal): add postgres-backed signal protocol store 2026-03-22 07:14:19 +00:00
LewisandTangled ebf5af4922 fix(backups): remove useless backups concept 2026-03-18 12:17:33 +00:00
LewisandTangled 7926c798c6 feat: cross-pds delegation 2026-03-17 19:22:34 +00:00
lewisandTangled 28ca66624a fix: ability to send more verifications 2026-02-24 10:13:11 +00:00
lewisandTangled cd7400bc8b fix: improved discord & signal comms 2026-02-05 16:13:45 +00:00
lewisandTangled b104dc938a fix: telegram comms ux improvements 2026-02-04 19:08:44 +00:00
lewisandTangled 442ca1434f fix: better dashboard UX 2026-02-02 19:29:03 +00:00
lewisandTangled 190f1a3430 feat: legacy 2fa impl 2026-01-28 18:40:08 +00:00
lewisandTangled d7b96773fd fix: better type-safety 2026-01-26 17:15:51 +00:00
lewisandTangled 8af0cfe0af fix: oauth consolidation, include-scope improvements 2026-01-25 13:07:32 +00:00
lewisandTangled a18be8c6fd feat: filesystem blob storage 2026-01-21 17:53:59 +00:00
lewisandTangled 12a189cb2b feat: oauth prompt=create, other frontend fixes 2026-01-19 17:44:24 +00:00
lewis b3ec7feb96 sso signup & login 2026-01-18 01:15:13 +02:00
lewis 4e29861990 migration improvements 2026-01-15 21:17:35 +02:00
lewis e09567f5cf sqlx cache regenerated for new query locations 2026-01-14 22:23:03 +02:00
lewis 0bad085ead delayed loading spinner in frontend, more functional-style code 2026-01-12 08:46:52 +02:00
lewis 16fb4dbd03 oauth error msg improvement, general code quality 2026-01-11 22:33:41 +02:00
lewis 7985f76c0d Streamplace oauth compatibility, mock plc dir in tests 2026-01-11 17:05:38 +02:00
lewis 8f595b5ffb Separate crates for separate concerns 2026-01-10 21:13:41 +02:00
lewis fbc2477771 Passkey-only accounts can opt into a password 2026-01-10 11:10:17 +02:00
lewis 9a516a012d Remove old user blocks 2026-01-06 19:49:42 +02:00
lewis 3954189c22 Functional typesafe backend 2026-01-04 15:18:26 +02:00
lewis 0f32cc3faa Performance improvements 2026-01-03 00:58:28 +02:00
lewis df2135b5e1 Backups, adversarial migrations 2026-01-02 00:24:32 +02:00
lewis a46d2d6f8d Age assurance override env var 2025-12-31 17:01:20 +02:00
lewis 4d6e21b00d pds-hosted did migrates away 2025-12-30 21:16:49 +02:00
lewis ea55590b6c Misc fixes for blobs and invites 2025-12-30 18:46:31 +02:00
lewis 7be60ea2d1 Fixed migration problem 2025-12-30 23:45:44 +02:00
lewis 70fce2f1f9 misc genesis fixes 2025-12-30 22:46:26 +02:00
lewis 6027e9517a Admin endoints vs ref 2025-12-29 20:58:04 +02:00