Gitea Version
1.26.2
What happened?
Hey there, I encountered the following bug:
When [oauth2_client].ACCOUNT_LINKING = auto is enabled and an OIDC user logs in whose email/username matches an existing local Gitea account, Gitea enters the automatic account-linking path. If that existing account has local Gitea 2FA/WebAuthn configured, Gitea redirects to the 2FA flow.
After entering a valid TOTP code, the login fails with HTTP 500.
The relevant log line is:
routers/web/auth/2fa.go:79:TwoFactorPost() [E] UserSignIn: not in LinkAccount session
Server-side session inspection showed the session contained:
linkAccount
twofaUid
twofaRemember
webauthn
but did not contain:
linkAccountData
Switching ACCOUNT_LINKING from auto to login makes the same user able to link and log in successfully.
Configuration
[oauth2]
ENABLED = true
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
ACCOUNT_LINKING = auto
The OAuth auth source also has “skip local 2FA” enabled, but the account-linking path appears to intentionally ignore that setting.
Session provider:
[session]
PROVIDER = file
PROVIDER_CONFIG = /var/lib/gitea/data/sessions
Steps to Reproduce
- Configure an OIDC provider.
- Enable auto registration and automatic account linking:
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
ACCOUNT_LINKING = auto
- Ensure an existing local Gitea user has the same username/email as the OIDC identity.
- Enable local Gitea 2FA or WebAuthn for that user.
- Log in via OIDC.
- Complete the local 2FA prompt with a valid TOTP code.
Expected Behavior
Gitea should complete the 2FA challenge, link the OIDC identity to the existing account, and log the user in.
Actual Behavior
Gitea returns HTTP 500 after the correct 2FA code is submitted.
Log excerpt:
HTTPRequest [I] router: completed GET /user/oauth2/keycloak/callback?... 303 See Other
HTTPRequest [I] router: completed GET /user/webauthn ... 200 OK
HTTPRequest [I] router: completed GET /user/two_factor ... 200 OK
routers/web/auth/2fa.go:79:TwoFactorPost() [E] UserSignIn: not in LinkAccount session
HTTPRequest [I] router: completed POST /user/two_factor ... 500 Internal Server Error
Suspected Cause
In the ACCOUNT_LINKING=auto path, createUserInContext calls oauth2LinkAccount(ctx, user, possibleLinkAccountData, true) directly.
possibleLinkAccountData exists only in memory during the callback request. If the existing user has 2FA enabled, oauth2LinkAccount stores only:
"twofaUid"
"twofaRemember"
"linkAccount"
in the session before redirecting to /user/two_factor.
It does not persist:
"linkAccountData"
When TwoFactorPost later sees linkAccount, it calls linkAccountFromContext, which expects linkAccountData to exist in the session. Since it is missing, Gitea returns not in LinkAccount session.
The manual ACCOUNT_LINKING=login path works because showLinkingLogin calls Oauth2SetLinkAccountData before redirecting to /user/link_account.
Possible Fix
Persist linkAccountData in oauth2LinkAccount when setting linkAccount=true, for example:
if err := updateSession(ctx, nil, map[string]any{
"twofaUid": u.ID,
"twofaRemember": remember,
"linkAccount": true,
"linkAccountData": *linkAccountData,
}); err != nil {
ctx.ServerError("RegenerateSession", err)
return
}
This should allow the 2FA/WebAuthn follow-up request to complete account linking in the auto-link flow.
How are you running Gitea?
binary
Gitea Version
1.26.2
What happened?
Hey there, I encountered the following bug:
When
[oauth2_client].ACCOUNT_LINKING = autois enabled and an OIDC user logs in whose email/username matches an existing local Gitea account, Gitea enters the automatic account-linking path. If that existing account has local Gitea 2FA/WebAuthn configured, Gitea redirects to the 2FA flow.After entering a valid TOTP code, the login fails with HTTP 500.
The relevant log line is:
routers/web/auth/2fa.go:79:TwoFactorPost() [E] UserSignIn: not in LinkAccount sessionServer-side session inspection showed the session contained:
linkAccount
twofaUid
twofaRemember
webauthn
but did not contain:
linkAccountData
Switching ACCOUNT_LINKING from auto to login makes the same user able to link and log in successfully.
Configuration
The OAuth auth source also has “skip local 2FA” enabled, but the account-linking path appears to intentionally ignore that setting.
Session provider:
Steps to Reproduce
Expected Behavior
Gitea should complete the 2FA challenge, link the OIDC identity to the existing account, and log the user in.
Actual Behavior
Gitea returns HTTP 500 after the correct 2FA code is submitted.
Log excerpt:
HTTPRequest [I] router: completed GET /user/oauth2/keycloak/callback?... 303 See Other
HTTPRequest [I] router: completed GET /user/webauthn ... 200 OK
HTTPRequest [I] router: completed GET /user/two_factor ... 200 OK
routers/web/auth/2fa.go:79:TwoFactorPost() [E] UserSignIn: not in LinkAccount session
HTTPRequest [I] router: completed POST /user/two_factor ... 500 Internal Server Error
Suspected Cause
In the ACCOUNT_LINKING=auto path, createUserInContext calls oauth2LinkAccount(ctx, user, possibleLinkAccountData, true) directly.
possibleLinkAccountData exists only in memory during the callback request. If the existing user has 2FA enabled, oauth2LinkAccount stores only:
"twofaUid"
"twofaRemember"
"linkAccount"
in the session before redirecting to /user/two_factor.
It does not persist:
"linkAccountData"
When TwoFactorPost later sees linkAccount, it calls linkAccountFromContext, which expects linkAccountData to exist in the session. Since it is missing, Gitea returns not in LinkAccount session.
The manual ACCOUNT_LINKING=login path works because showLinkingLogin calls Oauth2SetLinkAccountData before redirecting to /user/link_account.
Possible Fix
Persist linkAccountData in oauth2LinkAccount when setting linkAccount=true, for example:
This should allow the 2FA/WebAuthn follow-up request to complete account linking in the auto-link flow.
How are you running Gitea?
binary