feat(enable_banking): support MFA/decoupled banks and harden session handling (#2174)

Decoupled/MFA banks (e.g. VR Bank in Holstein) were hard-blocked because the
authorize flow aborted whenever auth_methods[0] was DECOUPLED. Enable Banking's
hosted /auth page actually coordinates decoupled SCA and redirects back with a
code, so route these banks through it instead:

- Provider#start_authorization accepts and forwards an auth_method param
- EnableBankingItem#select_auth_method picks the best method
  (REDIRECT > DECOUPLED > EMBEDDED), filtering by psu_type and skipping hidden
  methods
- Shared begin_authorization! re-fetches ASPSP metadata on each authorize and
  reauthorize, so the method is always re-derived (no persistence required)
- Remove the DECOUPLED block in the controller

Also stop the integration from constantly reporting "session expired":

- Only a session-level GET /sessions 401/404 flips the connection to
  requires_update; per-account 401/404 are retried and no longer kill the
  whole connection
- Reconcile session_expires_at from the API's access.valid_until on every sync
- Treat an expired session as a graceful requires_update state instead of
  raising a bare error

No schema changes. Adds covering tests.
This commit is contained in:
Tobias Rahloff
2026-06-04 19:53:52 +02:00
committed by GitHub
parent 6a89efb9c9
commit fe47c918bb
16 changed files with 393 additions and 69 deletions

View File

@@ -39,4 +39,28 @@ class EnableBankingItemsControllerTest < ActionDispatch::IntegrationTest
assert_includes haystack, "ing-diba ag",
"Expected the searchable data attribute to still include the bank name (existing name-search behavior)"
end
test "authorize no longer blocks decoupled banks and proceeds to the hosted auth page" do
Provider::EnableBanking.any_instance.stubs(:get_aspsps).returns(
aspsps: [
{
name: "VR Bank in Holstein",
country: "DE",
psu_types: [ "personal" ],
auth_methods: [ { name: "decoupled_app", approach: "DECOUPLED" } ]
}
]
)
Provider::EnableBanking.any_instance.stubs(:start_authorization).returns(
url: "https://api.enablebanking.com/auth/redirect/abc",
authorization_id: "auth_1"
)
post authorize_enable_banking_item_url(@item),
params: { aspsp_name: "VR Bank in Holstein", psu_type: "personal" }
assert_redirected_to "https://api.enablebanking.com/auth/redirect/abc"
assert_nil flash[:alert]
assert_equal "DECOUPLED", @item.reload.aspsp_auth_approach
end
end