Skip to content

feat(locallibrary): ask a service for more scope when a plugin needs it, and handle a refused token - #339

Merged
botandbang merged 19 commits into
mainfrom
bnb/account-escalation
Nov 2, 2026
Merged

botandbang merged 19 commits into
mainfrom
bnb/account-escalation

Conversation

@botandbang

Copy link
Copy Markdown
Contributor

What and why

Stacked on #335. With accounts shared between plugins, one plugin can need more of an account than another already got, and a service can refuse a token mid-publish. This makes locallibrary handle both instead of each plugin.

Added:

  • An account type's params, sent with every sign-in; google-account sends access_type=offline so a web-type client gets a refresh token too. Only login_hint, prompt, hd and access_type are allowed, and a key can't be in both params and one_account/another_account.
  • Signing in again for one account asks for every scope its accesses use plus what's added, sends one_account's params, and keeps the old refresh token when the service sends none. A signed-out account that still has accesses can be signed in again by name.
  • A plugin gets only the scopes the service actually granted; the rest are listed as declined and remembered on the access until plugin access --give gives them. A sign-in again that comes back as a different person keeps that account but gives the plugin no access.
  • A "sign in again as NAME" action on access and scope errors (only scopes the manifest asks for), accounts.access.give and plugin access PLUGIN --give ACCOUNT --scope S.
  • Before a plugin destination starts, and again before a publish is applied, its accounts are checked: an access, signed in, the scopes given and granted, and a token that won't run out mid-upload (an hour, plus the recording's size at the slowest assumed upload speed). The terminal offers to fix each problem once.
  • A token a service refuses with 401 is refreshed once and the request retried; the account is signed out only when the retry's challenge says invalid_token. A 401/403 with WWW-Authenticate: ... error="insufficient_scope" or Google's ACCESS_TOKEN_SCOPE_INSUFFICIENT body becomes kind scope with needs, and the account stops counting those scopes as granted.

Changed: youtube, mixcloud and soundcloud no longer handle a refused token themselves; a 401 that still reaches them is a plain refusal.

How it was tested

  • Hegel properties for each path: what signing in again asks for and keeps, what a plugin is given from a partial grant, a destination starting only when its accounts will last (fails with the expiry check or the re-check before apply removed), a refused token refreshed once then given up (reply sequences of 401, invalid_token, header and body scope refusals, plain 403, 200), and the challenge parser. Each new test was broken once to see it fail. Tests use a mock account plugin and mock OAuth service only.
  • Locally: fmt, clippy (workspace and the CLI's CI forms), ast-grep, docs/rpc current, full nextest 4087/4088 (the one failure is fixed by test(locallibrary): leave blank names out of the edit model's playlist lookups #334). Hegel coverage 95.56 / 94.05 / 93.64.

Checklist

  • Tests cover the change
  • For locallibrary: apps/locallibrary/CHANGELOG.md has a line under Unreleased
  • The READMEs, docs/ and --help still say what the code does (apps/locallibrary/docs/rpc/ too)

@botandbang
botandbang requested a review from bnb as a code owner October 30, 2026 22:05
@github-actions github-actions Bot added documentation Improvements or additions to documentation locallibrary locallibrary and the loclib-* crates locallibrary: cli The locallibrary command line locallibrary: player The terminal player and its waveforms locallibrary: sidecar The JSON-RPC sidecar and the api module it serves locallibrary: core The core crate, the data model and rekordbox's formats locallibrary: plugins The plugin protocol, its host and the built-in plugins locallibrary: publishing The Atmosphere account, and the sets and profile published to it tests Tests, fixtures and test harnesses labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Size

Compared with main. KiB are 1,024 bytes.

This pull request main Change
Client JavaScript 718.3 KiB 718.3 KiB 0 B (0%)
Client JavaScript, brotli 241.1 KiB 241.1 KiB 0 B (0%)
Client CSS 90.3 KiB 90.3 KiB 0 B (0%)
Client CSS, brotli 12.9 KiB 12.9 KiB 0 B (0%)

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Rust coverage

From run 130. Both are held to the thresholds in .cargo/config.toml.

Tests Lines Functions Regions
All 97.35% (86428/88779) 96.45% (13955/14469) 95.6% (190730/199507)
Hegel property tests alone 95.36% (84661/88779) 94.04% (13606/14469) 93.44% (186413/199507)

bnb
bnb previously approved these changes Oct 30, 2026
rustdoc denies the private accounts::ready link under -D warnings; link Plugins::unready instead.
# Conflicts:
#	apps/locallibrary/src/account_cmd/access.rs
#	apps/locallibrary/src/account_cmd/accounts.rs
#	crates/locallibrary/src/plugin/net.rs
bnb
bnb previously approved these changes Nov 1, 2026
# Conflicts:
#	crates/locallibrary/src/plugin/accounts/access.rs
#	crates/locallibrary/src/plugin/accounts/shared.rs
bnb
bnb previously approved these changes Nov 1, 2026
bnb
bnb previously approved these changes Nov 1, 2026
Base automatically changed from bnb/shared-accounts to main November 1, 2026 22:43
@botandbang
botandbang dismissed bnb’s stale review November 1, 2026 22:43

The merge-base changed after approval.

@botandbang

Copy link
Copy Markdown
Contributor Author

Closing and reopening so CI runs against current main (with #353's CodeQL fix). Branch unchanged.

@botandbang botandbang closed this Nov 1, 2026
@botandbang botandbang reopened this Nov 1, 2026
@botandbang botandbang closed this Nov 1, 2026
@botandbang botandbang reopened this Nov 1, 2026
bnb
bnb previously approved these changes Nov 1, 2026
prepare used to check where a plugin's request goes, fill in its secrets and accounts' tokens,
and return the address with the query's filled in. Now address() parses the address and checks
the plugin may go there, with no secret in it, and prepare returns what's sent: its headers,
body and accounts, and its query with everything filled in, kept apart. follow sends the first
hop to the address without its query and adds the filled pairs with RequestBuilder::query, which
encodes them the same way, so the first request on the wire doesn't change. The address follow
keeps and resolves each redirect against never holds a token, so a redirect back to the same
address (an empty or fragment-only Location) no longer sends one again.

Mixcloud still takes its token in the query, since its API reads it nowhere else
(https://www.mixcloud.com/developers/).
@botandbang
botandbang added this pull request to the merge queue Nov 2, 2026
@botandbang
botandbang removed this pull request from the merge queue due to a manual request Nov 2, 2026
@botandbang
botandbang added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 3a5f676 Nov 2, 2026
35 checks passed
@botandbang
botandbang deleted the bnb/account-escalation branch November 2, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation locallibrary: cli The locallibrary command line locallibrary: core The core crate, the data model and rekordbox's formats locallibrary: player The terminal player and its waveforms locallibrary: plugins The plugin protocol, its host and the built-in plugins locallibrary: publishing The Atmosphere account, and the sets and profile published to it locallibrary: sidecar The JSON-RPC sidecar and the api module it serves locallibrary locallibrary and the loclib-* crates tests Tests, fixtures and test harnesses

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants