Skip to content

feat(donations): contribute accounts via provider web authorization, earn New API credit - #246

Closed
Gaozx1 wants to merge 2 commits into
caigee-cmd:mainfrom
Gaozx1:feat/donations
Closed

Gaozx1 wants to merge 2 commits into
caigee-cmd:mainfrom
Gaozx1:feat/donations

Conversation

@Gaozx1

@Gaozx1 Gaozx1 commented Sep 27, 2026 •

Copy link
Copy Markdown

What this adds

A public donation flow: anyone can contribute a provider account (WorkBuddy, Qoder, Trae, Devin, Command Code) and receive New API credit for it, without a console key.

Reachable at /donations, backed by:

Route Purpose
GET /api/donations accepted formats, and whether each supports web auth
POST /api/donations start a web-authorization round
GET /api/donations/sessions/{id} poll a round; settles the reward on completion
DELETE /api/donations/sessions/{id} abandon a round
POST /api/donations/credential pasted-credential fallback

Web authorization

Contributions use each provider's own browser login, so a contributor never hands over a raw credential. POST /api/donations creates the account, opens the provider login through the existing LoginSessionProvider path, and returns the URL to visit. Polling it drives the round to completion.

Command Code has no browser login (it authenticates with a PAT), so it keeps the pasted-credential path. GET /api/donations reports web_auth per format and the page renders credential fields only for those formats — the frontend never hardcodes which provider supports what.

Why it is public

A contributor has no console key, and the reward goes to their own numeric New API user id. The route is therefore registered outside withConsoleKey, deliberately — the credential itself is the abuse control, because the account must authorize before anything is paid out.

The invariants that matter

Four properties are pinned by tests:

  1. No reward before authorization. A started round creates the account disabled and credits nothing. An unfinished round cannot carry traffic and cannot earn.
  2. Exactly one credit. The reward is issued once, when the provider login reports done. A repeat poll returns the stored outcome instead of crediting again.
  3. A credit failure keeps the account. If the authorization succeeds but the site rejects the credit, the response is credited:false plus credit_error, and the now-enabled account stays in the pool. Rolling back a valid contribution, or silently dropping the reward, is worse for both sides; an operator can credit manually without asking for a re-login.
  4. Abandoned rounds leave nothing behind. Pending sessions expire after 15 minutes and are swept along with their placeholder accounts; cancelling does the same immediately. A settled session survives the TTL so a finished reward stays reportable, and refuses cancellation.
POST /api/donations          { "format": "workbuddy-oauth-v1", "newapi_user_id": 400 }
201 → { "session_id": "...", "auth_url": "https://copilot.tencent.com/login?...", "status": "pending" }

GET /api/donations/sessions/{id}
200 → { "status": "credited", "credited": true, "credited_quota": 500000 }

Credit call

New API validates POST /api/user/manage with the user AccessToken in Authorization plus the matching numeric id in New-Api-User. The shape is asserted in a test:

{"id": 400, "action": "add_quota", "mode": "add", "value": 500000}

1 USD = 500000 quota, which is New API's default QuotaPerUnit.

Configuration

The site is set under System settings (donation_base_url, donation_token) and read per request, so it can be changed without a restart. The token is stored as a secret and is reported only as donation_configured; it is never echoed back. A test asserts that.

Validation

go test ./...                       # 22 packages pass (Linux)
go vet ./...                        # clean
gofmt -l <changed files>            # clean
npm run build                       # clean
npm run lint                        # 0 errors
python3 scripts/release-notes.py validate   # exit 0

Verified live end-to-end against a New API 1.2.0 site:

  • A web-auth round returned a real provider login URL, created the account disabled, and polling stayed pending with quota unchanged at 0 — no premature credit.
  • Cancelling removed the placeholder account (404 afterwards) and made the session unpollable.
  • The credit path took a contributor's quota 0 → 500000.
  • A deliberately unreachable site produced the expected credited:false + credit_error with the account retained.

Note on the console UI

/donations is public, so it is intentionally not in the authenticated nav. Link to it from wherever you advertise the program.

Gaozx1 added 2 commits September 27, 2026 14:20
Add a public donation flow: anyone can contribute a provider account and
receive New API credit for it, without a console key.

Backend
- internal/control/donations.go: Donations service. It resolves the target
  site from system settings, imports the contributed credential through the
  existing account import path, and only then credits the contributor via
  New API POST /api/user/manage.
- internal/console/donations.go: GET /api/donations advertises the accepted
  credential formats from the provider registry; POST submits a contribution.
- Registered on the public router, deliberately outside withConsoleKey: a
  contributor has no console key and the reward goes to their own numeric
  New API user id.
- System settings gain donation_base_url and donation_token. The token is
  stored as a secret and reported only as donation_configured, never echoed.

Ordering and failure handling
- Import first, credit second. A contribution that fails validation never
  reaches the credit call, so an unusable credential is never rewarded.
- A credit failure after a successful import keeps the account and returns
  credited=false with credit_error, rather than rolling back a valid
  contribution or silently dropping the reward.

Frontend
- New public /donations page (HeroUI) outside RequireAuth, with per-format
  fields: a JSON credential, or the Qoder auth blob plus machine id.
- api/donations.ts client, zh/en strings, and the rebuilt embedded assets.

Tests cover the quota conversion, format-to-provider mapping, the credit
request shape (Authorization + New-Api-User), site business errors, the
no-credit-before-import ordering, and that the stored token is never exposed.
Contributions now use each provider's own browser authorization instead of
asking the contributor to hand over a raw credential. Command Code has no
browser login (it uses a PAT), so the pasted-credential path is kept for it.

Backend
- Donations.StartSession creates the contributed account disabled, opens the
  provider login through the existing LoginSessionProvider path, and records a
  pending session. Nothing is credited here.
- Donations.PollSession reports progress and, once the provider login completes,
  enables the account and issues the reward exactly once. A repeat poll returns
  the stored outcome rather than crediting again.
- Donations.CancelSession abandons a pending round and removes its placeholder
  account; a settled round refuses cancellation.
- Pending sessions expire after 15 minutes and are swept with their placeholder
  accounts, so an abandoned round leaves nothing behind. A settled session
  survives the TTL so a finished reward stays reportable.
- Routes: POST /api/donations (start), GET|DELETE /api/donations/sessions/{id},
  POST /api/donations/credential (legacy). All remain public.

The two invariants from the original flow still hold, and are now asserted for
the web path too: no reward before the account is authorized, and a credit
failure after a successful authorization keeps the (now enabled) account while
reporting credited=false with credit_error.

Frontend
- The page leads with "Authorize in browser": it starts the round, opens the
  auth URL, and polls to completion, with a cancel action and a reopen link.
- Credential fields render only for formats that have no web login.
- GET /api/donations reports web_auth per format so the page never hardcodes it.

Tests cover the no-credit-before-authorization invariant, single-credit on
completion, keeping the account when the credit fails, placeholder cleanup on
login failure and cancel, refusing providers without a web login, and TTL
sweeping of pending but not settled sessions.
@Gaozx1 Gaozx1 changed the title feat(donations): accept contributed accounts for New API credit feat(donations): contribute accounts via provider web authorization, earn New API credit Sep 27, 2026
@Gaozx1 Gaozx1 closed this Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant