Skip to content

Conversation

@radon-at-beeper
Copy link
Contributor

@radon-at-beeper radon-at-beeper commented Jan 14, 2026

Add support for both TOTP and AFAD ("approve from another device") two-factor authentication flows. There are more, such as email OTP, but those flows do not appear by default, and show up only when certain security flags are tripped for an account. There is a pretty sophisticated framework in place for handling different traversals of the graph of different login screens, so we should be able to straightforwardly add support for such cases when we see them. All that should be necessary is enabling trace logs when experiencing the issue, and then the interpreter dev tooling can be used to craft the appropriate logic without needing real-time account access or credential sharing.

There are a fair number of framework improvements in this PR, which were needed to get to a place where MFA could be supported without the code becoming a tangled ball.

  • Login flow is now integrated with the bridgev2 login-step framework, and has the ability to hand control back and forth between user input and business logic. This is of course needed for MFA based flows.
  • To support that, there is a new Browser abstraction that implements a state machine transitioning between multiple Bloks pages and retaining global state. This wraps up a fair bit of duplicative and error-prone code repeatedly needing to instantiate new interpreters and have tons of big if conditionals.
    • The new way: everything boils down to a pair of control-flow blocks that hand off execution to each other, rather like co-routines, and mutually update the State field on the browser, which is an enum that models the graph of ways to traverse through the login flow.
    • The first control-flow block is a switch statement on the current state. This block handles states that are UI pages requiring some interaction from the user, for example tapping on buttons - as well as automatic states like executing an action that was returned from the server. It can also return a bridgev2.LoginStep to request user input. The block is called repeatedly in a loop by higher level code, until it reaches the Success state. So all it needs to do is perform at least one state transition, then the next run will take it from there.
    • The second control-flow block is a set of callbacks passed through to the interpreter bridge. These handle the events that are triggered while running the native code that is invoked from the other block. For example, if you are in the login page state and tap the login button, then the event handler for that button may trigger DoRPC with send_login_request, which the second block will handle by making the relevant Bloks request and setting up the newly rendered page and State to be processed by the next run through the first block.
    • Having all RPCs and page transitions handled in the same place means that repeated code for each of them can now be deduplicated, and I have done so.
  • The AFAD flow introduces a new Bloks standard library feature: timers! We now implement those, they are basically like setInterval, except that rather than doing the scheduling themselves, they call back to the interpreter bridge and provide a callback that will run the timer target, so that the delay can for example be translated into a bridgev2.LoginStepTypeDisplayAndWait.
  • AFAD has another novel dependency: the AsyncActionWithDataManifestV2 standard library call actually interprets some data nested inside one of its arguments as success and failure callbacks. The DoRPC interpreter bridge method now also gets passed a callback which can be invoked with the result of the RPC, which is necessary for the afad_state action to actually trigger the next phase of the login flow once the server indicates success.
  • We were getting more and more Bloks documents, and the original PoC code was getting duplicative, so I've stripped out the repetition and now we just have a single argument that you pass - the human name of the Bloks RPC to call. There is a map that is used to translate that into the internal Bloks ID. Now we don't have all those BloksDoc structs. It should be possible to autogenerate the Bloks client document IDs (notice that some of them are even the same between multiple RPCs, so clearly there is a pattern!), but I haven't decoded the scheme yet.
  • Bloks GraphQL errors are handled better, actually parsing out the error message and returning it to calling code.
  • We now handle local and global variables separately, and correctly carry over the appropriate variables between multiple screens. Also, there is a feature where variables can include a Bloks snippet to evaluate for their initial value - that's handled now.
  • More of the standard library implemented: setting global variables, creating refs, more screen transition functions, utilities from the mins namespace. Since several of the new functions require introspecting particular properties of tree.Make funcalls passed as arguments, that functionality has been split out to a common subroutine.
  • Brand-new Selenium-style library for traversing Bloks documents and interacting with elements: tapping buttons, filling in text fields, using pre-packaged element selectors, fetching script handlers from element attributes, etc. The login flow graph is also part of this library, although it could be split out.
  • BloksBundle JSON deserialization has been refactored to be one-and-done. Rather than unminification being a separate step, it's done automatically as part of the deserialization process using a custom unmarshaler.
  • The parent and container pointer system within Bloks trees was buggy and ad-hoc; it's been replaced by something much simpler, fixing issues uncovered when using FindDescendant, FindAncestor, FindCousin, etc.
  • Added some very nice and readable automatic logging to show how the interpreter is traversing through different login pages. This is registered at the debug level, with trace logs also including full Bloks request and response payloads which can be piped (after some preprocessing) into the included standalone Bloks debugger.
  • Handle blocked accounts (i.e. that redirect to a web checkpoint) with a proper error message.
  • Maybe(?????) handle the email OTP flow, since I saw it get triggered once but can't find a network trace for it, so I implemented the logic just by using my Selenium library and guessing about the structure of the page. We can revisit once somebody actually hits that branch and it predictably falls flat on its face.
    • I am pretty sure it will error out due to lack of a Bloks document ID right now, also the outgoing edge in the flow graph is not there, it will need an extra action state.

@radon-at-beeper radon-at-beeper marked this pull request as ready for review January 21, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants