fix(auth): preserve first-party deletion on refresh - #331
Conversation
|
@/private/tmp/pr331-comment.md |
54584c0 to
50d9e74
Compare
|
@realmeylisdev I took over the branch and replaced the NIP-98 deletion path with the safer fix we discussed:
Verification:
Local DB-backed tests could not run here because Docker/Postgres was unavailable ( Please review the takeover changes and merge when you are comfortable with them. |
NotThatKindOfDrLiz
left a comment
There was a problem hiding this comment.
Approved after takeover. The PR now preserves first-party deletion authority through refresh-token rotation without adding a NIP-98 deletion bypass, and CI is green on the current head.
The refresh grant is the path issue #323 actually fixes, but nothing exercised it. The two tests added with the fix cover the UCAN builder and the repository column independently, so both still pass if the grant stops reading `oauth_auth.is_first_party`. Drive the real `/oauth/token` route with `grant_type=refresh_token` against a seeded authorization and assert the `first_party` fact on the rotated access token: present for a first-party authorization, absent for a third-party one. Verified the positive case fails when the grant is reverted to a hardcoded `false`.
|
@NotThatKindOfDrLiz Reviewed the takeover — the persisted-flag approach is the right shape and I'm happy with it. I traced every site it touches: both explicit-column selects into I pushed one test-only commit on top (b78b5c8), so CI will re-run against a new head. Why: the refresh grant is the path this PR actually fixes, but nothing exercised it.
Docker was available here, so I also ran the DB-backed tests that were blocked on your side:
Two things I'd like your read on before I merge:
|
|
@realmeylisdev I took over the remaining auth-boundary issues and pushed two commits on top of the branch:
Verification:
Please review the takeover changes and merge when you are ready. |
NotThatKindOfDrLiz
left a comment
There was a problem hiding this comment.
Final pass is clean after the takeover commits.
The first-party authorization boundary now matches the PR description: third-party OAuth bearers cannot use /api/headless/authorize to mint durable deletion authority, refresh rotation preserves only persisted first-party authorizations, and silent re-auth replacement keeps the first-party flag from the authorization it replaces. GitHub CI is green on the current head.
|
@realmeylisdev one more merge-gate reminder: because this is a security-sensitive Keycast auth change, the PR should ultimately be approved for merge by @dcadenas before it lands. |
Summary
oauth_authorizations.is_first_partyflag so the refresh grant can mint replacement UCANs withfirst_party: trueonly for authorizations that originally came from a first-party flow./api/headless/authorizeso a third-party OAuth bearer token cannot mint a first-party authorization.DELETE /user/accounton the existing UCAN authorization path; it no longer gains a NIP-98 bypass.Motivation
Authorization for account deletion depends on a UCAN fact (
first_party) that was previously present only on the original headless-flow access token. Clients refresh immediately before deletion, so a returning first-party user could lose the fact and be denied after the client had already published an irreversible NIP-62 vanish.The safer fix is to persist first-party status on the OAuth authorization and preserve it during refresh-token rotation while keeping third-party OAuth apps outside the account-deletion boundary.
Related Issue
Review notes
is_first_party = false; users with old sessions may need to re-authenticate once to get a first-party authorization carrying the new persisted flag. There is no safe server-side backfill, becauseclient_idandredirect_uriare caller-supplied at/api/headless/login.first_partynow persists for the life of eligible first-party authorizations rather than for a single access token, so deletion authority is renewable via refresh where it previously expired with the access token.cloudbuild.yamlruns migrations before the deploy step.Testing
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warnings -A deprecatedcargo check -p keycast_api --tests --features integration-testscargo test -p keycast_api server_signed_ucan_includes_first_party_only_when_requestedPoolTimedOut):cargo test -p keycast_api --features integration-tests --test headless_auth_test headless_authorizecargo test -p keycast_api --features integration-tests --test oauth_refresh_first_party_testcargo test -p keycast_core --features integration-tests --lib repositories::oauth_authorization40ae627.Visuals