Reference
Enrol an authenticator app for one account at any site, asking the owner only before a step that would reach his phone.
Skarbiec's credential enrol, and every skarbiec maintain pass for an account that needs a seed, asks the worker's credential admission for operation enrol, naming as provider the host of the page the account shows its two-step settings on. The worker starts google_authenticator_enrol at Google's account pages and generic_authenticator_enrol at any other site; the run waits at a gate for the owner's ready only before a step that would reach his phone, and writes the seed, the enrolment record and the recovery codes to the item that keeps the account's second factor.
Why it exists#
Every unattended sign-in to an account that asks for an authenticator code needs the account's seed in the vault. A site shows a setup key only inside a signed-in session and may tell the owner a sign-in method was added; Google, from a browser profile it has not seen, also sends a prompt to the account's phone. So the run asks the owner for nothing unless a step would reach his phone, waits for his ready before that step, and a prompt he never got ends the run by name instead of hanging.
How it is started#
Skarbiec starts it: skarbiec credential enrol role:<role>, and every skarbiec maintain pass for an account recorded to ask for an authenticator code that keeps no seed it accepts. The role names the account's identity (or a login that keeps its own factor). The site is the account's own data: the host of the page the item keeping its second factor names for its two-step settings (context.security_page, skarbiec login factor page), as skarbiec login factor observe reads it. Skarbiec POSTs /api/v1/credential-operations with operation enrol, mode submit, provider (that host), credential_id (the item that keeps the seed), login_item (the login signed in with) and expected_account. The worker answers with run, the action log id every later read and answer name.
The worker opens a gate before the run starts: a socket under ~/.weles/gates that only it listens at, named to the run as enrol_gate. Google's enrolment says it waits for ready only before a sign-in Google asks for on the way, since that sign-in may send Google's prompt to the phone; a run whose profile is signed in asks nothing, and the generic enrolment reaches no phone and asks nothing. skarbiec credential enrol role:<role> --answer ready passes the answer through mode answer; only then does the run sign in. If Google's prompt never reaches the phone, --answer not-received ends the run as prompt_not_received.
skarbiec credential enrol role:<role> --status --wait reads where the run stands through mode status with wait: it answers once the run waits for the owner or ends, woken by the run's own message at the gate or by its end, never by polling. The answer carries phase, waiting (ready, prompt_approval or null), stage and every answer given.
skarbiec credential enrol role:<role>
skarbiec credential enrol role:<role> --status --wait
skarbiec credential enrol role:<role> --answer ready
skarbiec credential enrol role:<role> --answer not-receivedWhat the run writes#
Once the site took the first code, the run writes in one conditional write to the item Skarbiec named: fields.totp_secret, context.enrolled (provider, the host of the page it enrolled at; at in seconds since the epoch; run) and no context.verification, since an earlier outcome spoke of another seed. It then writes the recovery codes the site shows as fields.recovery_codes, read back. Each later sign-in that submits a code records the site's answer with skarbiec totp verified --by weles: accepted, rejected or locked.
At any site#
generic_authenticator_enrol knows no site. In the account's own kept browser profile it opens the page the account names for its two-step settings; a page that sends the browser to a sign-in ends the run as sign_in_refused, and a password the site asks for again before a security setting is answered with the login's own. It reads the setup key from the page's structure: an otpauth address anywhere in its markup, the text of code-like elements, a read-only field, a value carried for copying, or a QR code decoded from the page's square images. Otherwise it presses the control the vision model finds that shows the key as text, or that sets up or adds an authenticator app; a page offering only to change an authenticator already set up is refused. The key is the one text Skarbiec's totp judge says computes codes: none or several end the run as key_not_read, and no wrong code is typed to find out which.
The model never sees a secret: before every question it is asked, every square image on the page (a QR code holds the seed whole) and every element holding the setup key or a recovery code is hidden, and the session records no video, response body or network dump. It types the current code into the page's code field, writes the seed once the field is gone and the page says nothing of a wrong code, reads the recovery codes from the code-like elements that follow, and presses on until the page shows two-factor authentication on. Its endings add page_unnamed, key_not_read and setup_not_finished (the seed is kept and the site took its code, but the setup did not show two-factor on) to those above.
Verifying the seed with a sign-in#
skarbiec credential enrol role:<role> --verify asks the admission for operation verify with the login and the item that keeps the seed. At Google's account pages the worker starts google_authenticator_verify, which opens the account's authenticator settings in the account's own browser profile, where Google asks the account to sign in again, and answers that sign-in from the vault; the code it submits is recorded as above. It adds and approves nothing and needs no order. Its endings: signed_in_with_code; not_asked (Google asked for no code, nothing proven); phone_required (Google asked for the phone and nothing was approved; within up to 7 days of a new method, support.google.com/accounts/answer/17137073, run it again after that window); sign_in_refused; login_unfit (the vault does not hold the login's factor, refused before any browser starts). A status read with wait answers once the run ends. At any other site the next sign-in through Weles that meets the code records it, and verify is refused as credentials.enrol-provider.
skarbiec credential enrol role:<role> --verifyWhat a sign-in records about the second factor#
Whether an account asks for a second step is not something a site offering one says: its owner turns two-step verification on and off at will. So a sign-in, at any site, that meets a second step after the password records it, on the item that keeps the account's second factor (the identity the login names, else the login), as context.second_factor with by weles and at in seconds since the epoch: totp for an authenticator code, push for a prompt or a code shown on a trusted device, sms, email or security-key. One shared reader does it for every site: it reads the page the sign-in stopped on and records only a step the page names in so many words. A sign-in with a surer signal passes it to the same recorder: Google's code field or prompt, and Apple's own answer naming the devices or phone numbers it sent the code to. Meeting again what Weles recorded moves at; meeting something else replaces it; a record the owner set with skarbiec login factor set stands while a sign-in meets the same.
A sign-in that met no second step records nothing: an account with two-step verification on asks nothing on a device it remembers, and Weles keeps a browser profile per account. none comes only from the account's security page, read without signing in (skarbiec login factor observe, and every skarbiec maintain pass for an account nothing records), or from the owner. Skarbiec's totp missing reads the record: a login whose account was seen to ask for a step and keeps no seed needs one; one seen to ask nothing needs none; one nothing has seen is unobserved, never needs.
Endings#
Every result names one ending and its remedy: enrolled; owner_not_ready (the worker stopped while the run waited for ready); prompt_not_received and prompt_declined (Google's phone prompt); sign_in_refused; already_enrolling; page_unnamed (the account names no two-step settings page); setup_not_reached; key_not_read; code_refused; seed_unconfirmed (the site enrolled, the vault did not confirm: remove the authenticator in the account and enrol again); backup_codes_unconfirmed (the seed is kept; store the codes by hand with skarbiec totp recovery role:<role> --replace); setup_not_finished. A stop no ending names is unnamed, with its own code, so a new way of stopping is never read as one of these. A run that ends while it waits for the owner is named credentials.enrol-unanswered in its reply.
Refusals#
credentials.enrol-field: the request lacks a field the enrolment reads (login_item, credential_id, answer, mode); run it as skarbiec credential enrol does. credentials.enrol-provider: a verification sign-in at a site other than Google's account pages, where Weles does not sign in to answer the code. credentials.enrol-unknown: this worker admitted no enrolment under that request id. credentials.enrol-not-waiting: the run is not waiting for that answer; read it with --status --wait first. credentials.enrol-gate-unmade: the worker could not listen under ~/.weles/gates, so nothing was started. weles errors show credentials lists each with its fix.
Tests#
tests/credentials/enrolment-failures.mjs meets the admission's refusals against the worker Stado placed, with a bearer file under build/. tests/trajectories/google-enrolment-endings.mjs meets the endings a run reports before anything opens. tests/trajectories/generic/authenticator-key.mjs holds which text of a setup page is taken for the seed. The run through a real Google account is Skarbiec's tests/credential/enrol_live.sh.
node tests/credentials/enrolment-failures.mjs --bearer-file build/<owner-only-bearer-file>
node --test tests/trajectories/google-enrolment-endings.mjs