feat(auth): add enforceTOTPIfRequired helper for OIDC flow

Extracts a TOTP gate that the OIDC callback will use to enforce 2FA
for users with TOTP enabled. Mirrors the local-login TOTP flow in
pkg/routes/api/v1/login.go. Not yet wired into HandleCallback.

Refs GHSA-8jvc-mcx6-r4cg
This commit is contained in:
kolaente 2026-04-09 13:14:41 +02:00 committed by kolaente
parent d291e3effe
commit c52b2a4f83
1 changed files with 33 additions and 0 deletions

View File

@ -32,6 +32,7 @@ import (
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/modules/avatar"
"code.vikunja.io/api/pkg/modules/avatar/upload"
"code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
@ -119,6 +120,38 @@ func (p *Provider) Issuer() (issuerURL string, err error) {
return iss.Issuer, nil
}
// enforceTOTPIfRequired mirrors the TOTP gate from pkg/routes/api/v1/login.go
// for the OIDC flow. Returns nil when the user does not have TOTP enabled.
// See GHSA-8jvc-mcx6-r4cg.
func enforceTOTPIfRequired(s *xorm.Session, u *user.User, totpPasscode string) error {
totpEnabled, err := user.TOTPEnabledForUser(s, u)
if err != nil {
return err
}
if !totpEnabled {
return nil
}
if totpPasscode == "" {
return user.ErrInvalidTOTPPasscode{}
}
_, err = user.ValidateTOTPPasscode(s, &user.TOTPPasscode{
User: u,
Passcode: totpPasscode,
})
if err != nil {
return err
}
// Reset the counter so old failed attempts don't trip a later lockout.
if err := keyvalue.Del(u.GetFailedTOTPAttemptsKey()); err != nil {
return err
}
return nil
}
// HandleCallback handles the auth request callback after redirecting from the provider with an auth code
// @Summary Authenticate a user with OpenID Connect
// @Description After a redirect from the OpenID Connect provider to the frontend has been made with the authentication `code`, this endpoint can be used to obtain a jwt token for that user and thus log them in.