Reference
Point an OAuth client at a new callback address, without opening the provider's console.
weles oauth-callback signs one Skarbiec login in on the managed executor and makes a Google OAuth client accept a stated callback URL, then reads the client back to prove it holds.
Why it exists#
A sign-in server that moves to a new address breaks every provider sign-in until each provider accepts the new callback: GitHub sends the person back only to the OAuth app's Authorization callback URL, and Google only to one of the OAuth client's Authorized redirect URIs. Neither offers an API that edits them for an ordinary OAuth app or client; the only way is the owner's settings page in a signed-in browser. This action is that browser step, so a move of sign-in is finished by a command rather than by a person editing a form.
Commands and execution#
Start with weles oauth-callback --provider google --login-role <skarbiec-role> --client-role <skarbiec-role> --callback <url>, or select the exact login with --login-item <skarbiec-item> instead of --login-role and the client with --client-item <skarbiec-item> instead of --client-role. The client item holds the OAuth client's client_id; --callback is the https URL the provider must accept. The managed executor starts a detached google_oauth_callback run and returns its identifier; weles oauth-callback --run <run-id> reads it back, with the run's report as its result once it ended. Configure WELES_WORKER_API_BASE and WELES_WORKER_TOKEN as for weles security. Neither command starts a browser on the caller's machine. Weles Desktop's OAuth callback screen (Work) starts the same run by role and reads it back.
Google: the run signs the login in to the Google account, opens the OAuth client in the Google Auth Platform of the project its client_id names (the number before the first dash), adds --callback to Authorized redirect URIs when it is not there and saves, then opens the client again and reports every accepted redirect URI. Google keeps every other redirect URI; an address already accepted is reported unchanged (changed false).
GitHub is not edited yet: --provider github is refused with the usage status ('Weles edits google'). A GitHub OAuth app keeps one callback URL, edited only on its owner's settings page, and the owner's GitHub login is not in Skarbiec to sign in with.
Admission resolves each selector once and retains login_item, client_item and callback in both the run's parameters and the saved request. Blank, conflicting or unknown inputs, a callback that is not an https URL with a host, and a provider other than google are refused before contacting the executor; invalid API input returns HTTP 400.
From Weles Web#
Weles Web's OAuth callback screen (/oauth-callback) starts the same run and reads it back through /api/oauth-callback. Only an administrator of the organization the executor is bound to (WISENT_ORGANIZATION_ID on the server) may use either; the client secret never crosses the route.
POST /api/oauth-callback takes a JSON object whose only key is params (login and client selectors and callback, read as the command reads them) and starts the run detached on the managed executor, answering HTTP 202 with ok true and the run's row, status running. GET /api/oauth-callback?run_id=<uuid> reads that run's result back, answering HTTP 200 with the row, its status and the result once the run has written it; the screen polls it.
Refusals: HTTP 400 for a body with any other key, params the parser refuses, or a run_id that is missing or not a UUID; HTTP 403 when the signed-in organization is not the one the executor serves (and the authorization's own status when the caller is no administrator); HTTP 404 when the executor has no result for that run_id; HTTP 502 when the executor refuses the request, answers another action or answers a report that is not weles.oauth-callback.v1 for the stated callback; HTTP 503 when WISENT_ORGANIZATION_ID binds no organization or WELES_WORKER_API_BASE and WELES_WORKER_TOKEN do not name a usable executor (HTTPS, or managed loopback, without credentials, a query or a fragment). Each refusal's error names the operation and the executor endpoint it was asking.
Result contract#
A finished run answers weles.oauth-callback.v1: ok, provider, login_item, client_item, client_id, callback, previous (the callbacks the page held before, or null when it was not read), accepted (the callbacks the page holds when read back after saving), changed (false when the callback was already there and nothing was saved), checked_at and source_revision. ok is true only when the read-back page holds callback. The client secret is never read, written to a file, a log or the result.
Refusals and diagnosis#
Every stop is a blocked code in the result with what was observed: login_item_required, client_item_required, callback_required, client_item_unreadable (the item holds no client_id), google_sign_in_required (Google still asks for a sign-in after the run signed in once at each address it was sent to), sign_in_refused (the sign-in's own blocked code when it names one, else this), client_not_found (the project does not show the client_id: the login is not its owner, or the client was deleted), callback_field_missing (the page holds no redirect URI field the run recognises; the page's address and title are named), save_refused (Google's error banner), callback_not_accepted (the client read back after saving does not hold the callback) and google_oauth_callback_error (the run itself failed, with its error). Each ends the run failed, and weles oauth-callback --run exits non-zero on it.
A run that could not be dispatched names the executor's refusal, as weles security does: an unreachable executor, a token it refuses, or an action its allowlist does not hold (oauth_callback must be in the executor's action allowlist).
The CLI refuses with the usage status before the executor is asked anything, and every refusal prints what failed, why, what it observed, the fix and its cause id (operation oauth-callback.app on the errors page): options-unknown (a word or option the command does not take), value-blank ('--<option> needs a value that is not blank'), run-takes-no-start-options (a --run read given any option but --json), run-id-invalid (a --run value that is no run id), provider-unsupported (no --provider, or one Weles does not edit: 'oauth-callback needs --provider naming whose OAuth client it edits; Weles edits google'), login-selector-invalid (both or neither of --login-role and --login-item), client-selector-invalid (both or neither of --client-role and --client-item) and callback-invalid (a --callback that is not an https URL with a host). POST /run refuses a google_oauth_callback request without exactly one of client_role or client_item, or without an https callback, as run.request cause oauth-client-unnamed (HTTP 400, error invalid_oauth_callback); an unresolvable login is the login-unnamed cause every Google run shares. An answer that does not start the run, a record of another run, or one bound to another login, client item or callback is refused as executor.run's run-not-started, run-unrelated or run-rebound. tests/security/oauth-callback-failures.mjs provokes every CLI cause through the built CLI, and tests/runs/request-failures.mjs the API's; tests/security/oauth-callback.mjs, when WELES_TEST_OAUTH_LOGIN_ITEM, WELES_TEST_OAUTH_CLIENT_ROLE and WELES_TEST_OAUTH_CALLBACK name a real login, client role and callback, starts the run and reads it back until the callback is among the client's accepted redirect URIs; without them the live start is reported as not run.