fix: link Tink Link to the user created in Step 2
All checks were successful
Build and Deploy / deploy (push) Successful in 22s

Previously Step 2 (create user) and Step 3 (Tink Link) were disconnected —
the bank connection went to an anonymous new user, not the one just created.

Fix: Step 3 now calls /api/v1/oauth/authorization-grant/delegate with the
user_id from session to get an authorization_code, which is injected into
the Tink Link URL. This binds the bank connection to the correct customer.

Also stores user_market in session so Step 3 uses the same market as Step 2.
Shows a note confirming which user Tink Link is bound to.
This commit is contained in:
Henrik Jess Nielsen
2026-05-22 21:45:58 +02:00
parent 5e14a219b1
commit 179424a78f

View File

@@ -172,6 +172,7 @@ async def step2_post(request: Request,
result = await client.create_user(app_token, external_user_id, market=market) result = await client.create_user(app_token, external_user_id, market=market)
sess["user_id"] = result.get("user_id", "") sess["user_id"] = result.get("user_id", "")
sess["external_user_id"] = external_user_id sess["external_user_id"] = external_user_id
sess["user_market"] = market
except Exception as e: except Exception as e:
error = str(e) error = str(e)
@@ -201,17 +202,41 @@ CONSOLE_CALLBACK = "https://console.tink.com/callback"
async def step3_get(request: Request): async def step3_get(request: Request):
sess = _session(request) sess = _session(request)
s = get_settings() s = get_settings()
client = _client() client = _client(log_cb=_logger(sess))
tink_link_url = client.get_tink_link_url(market="DK")
dev_tink_link_url = client.get_tink_link_url(
market="DK", redirect_uri_override=CONSOLE_CALLBACK
)
error = None error = None
credentials = None credentials = None
auth_code_error = None
cb_success = request.query_params.get("cb_success") cb_success = request.query_params.get("cb_success")
cb_error = request.query_params.get("cb_error") cb_error = request.query_params.get("cb_error")
# If we have a user from Step 2, generate an authorization_code for them
# so Tink Link connects the bank to THAT specific user (not a new anonymous one)
user_id = sess.get("user_id", "")
app_token = sess.get("app_token", "")
authorization_code = None
if user_id and app_token and not sess.get("user_token"):
try:
grant = await client.get_authorization_grant_token(
app_token=app_token,
user_id=user_id,
scope="accounts:read,transactions:read,credentials:read",
)
authorization_code = grant.get("code", "")
except Exception as e:
auth_code_error = str(e)
tink_link_url = client.get_tink_link_url(
market=sess.get("user_market", "DK"),
authorization_code=authorization_code or None,
)
dev_tink_link_url = client.get_tink_link_url(
market=sess.get("user_market", "DK"),
authorization_code=authorization_code or None,
redirect_uri_override=CONSOLE_CALLBACK,
)
# Demo mode: auto-mark as connected with mock data # Demo mode: auto-mark as connected with mock data
if s.demo_mode and not sess.get("user_token"): if s.demo_mode and not sess.get("user_token"):
sess["user_token"] = "demo-mode-token" sess["user_token"] = "demo-mode-token"
@@ -227,35 +252,46 @@ async def step3_get(request: Request):
try: try:
credentials = await client.list_credentials(user_token) credentials = await client.list_credentials(user_token)
except Exception as e: except Exception as e:
# credentials:read may not be granted in simple Tink Link flow — not fatal
if "403" in str(e): if "403" in str(e):
credentials = {"note": "credentials:read kræver authorization-grant flow"} credentials = {"note": "credentials:read kræver authorization-grant flow"}
else: else:
error = str(e) error = str(e)
external_user_id = sess.get("external_user_id", "")
uid_display = user_id or "$USER_ID"
curl_example = ( curl_example = (
"# Simpelt Tink Link flow — ingen pre-oprettet bruger nødvendig\n" "# Step 1: Hent authorization_code for den specifikke bruger\n"
f"# Redirect brugeren direkte til:\n" "curl -X POST https://api.tink.com/api/v1/oauth/authorization-grant/delegate \\\n"
f"https://link.tink.com/1.0/transactions/connect-accounts\n" " -H 'Authorization: Bearer $APP_TOKEN' \\\n"
f" ?client_id=$CLIENT_ID\n" " -d 'actor_client_id=$CLIENT_ID' \\\n"
f" &redirect_uri=$REDIRECT_URI\n" f" -d 'user_id={uid_display}' \\\n"
f" &market=DK\n" " -d 'scope=accounts:read,transactions:read'\n\n"
f" &scope=accounts:read,transactions:read,credentials:read\n\n" "# Step 2: Send authorization_code med i Tink Link URL\n"
"# Tink Link redirecter tilbage med ?code=... som du exchangeer for user token:\n" "https://link.tink.com/1.0/transactions/connect-accounts\n"
" ?client_id=$CLIENT_ID\n"
" &redirect_uri=$REDIRECT_URI\n"
" &authorization_code=$CODE ← binder til din bruger!\n\n"
"# Step 3: Exchange callback code for user token\n"
"curl -X POST https://api.tink.com/api/v1/oauth/token \\\n" "curl -X POST https://api.tink.com/api/v1/oauth/token \\\n"
" -d 'grant_type=authorization_code' \\\n" " -d 'grant_type=authorization_code' \\\n"
" -d 'client_id=$CLIENT_ID' \\\n" " -d 'client_id=$CLIENT_ID' \\\n"
" -d 'client_secret=$CLIENT_SECRET' \\\n" " -d 'client_secret=$CLIENT_SECRET' \\\n"
" -d 'code=$CODE'" " -d 'code=$CALLBACK_CODE'"
) )
linked_user_note = None
if authorization_code:
linked_user_note = f"Tink Link er bundet til bruger <code class='text-violet-300 font-mono'>{external_user_id}</code> via authorization_code"
elif user_id:
linked_user_note = f"⚠️ Kunne ikke hente authorization_code: {auth_code_error or 'mangler app token'} — Tink Link opretter en ny anonym bruger"
return templates.TemplateResponse("step.html", _ctx(request, { return templates.TemplateResponse("step.html", _ctx(request, {
"step": 3, "step": 3,
"title": "Tilslut Bank", "title": "Tilslut Bank",
"subtitle": "Tink Link — Bank Connection", "subtitle": "Tink Link — Bank Connection",
"endpoint": "Tink Link /1.0/transactions/connect-accounts", "endpoint": "GET /api/v1/oauth/authorization-grant/delegate",
"api_version": "Link v1", "api_version": "v1",
"description": "Brugeren åbner Tink Link, vælger Tink Demo Bank og logger ind med test-credentials fra Console. Tink redirecter tilbage med en authorization_code som automatisk exchang'es til et user token.", "description": linked_user_note or "Åbn Tink Link, vælg Tink Demo Bank og log ind med test-credentials.",
"curl_example": curl_example, "curl_example": curl_example,
"result": credentials, "result": credentials,
"tink_link_url": tink_link_url, "tink_link_url": tink_link_url,