mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-04 00:11:04 +00:00 
			
		
		
		
	fix: do 2FA on OpenID connect
This commit is contained in:
		
					parent
					
						
							
								9828aca733
							
						
					
				
			
			
				commit
				
					
						90e974cd24
					
				
			
		
					 6 changed files with 79 additions and 16 deletions
				
			
		| 
						 | 
					@ -40,8 +40,8 @@ func GetUserOpenIDs(ctx context.Context, uid int64) ([]*UserOpenID, error) {
 | 
				
			||||||
	return openids, nil
 | 
						return openids, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// isOpenIDUsed returns true if the openid has been used.
 | 
					// IsOpenIDUsed returns true if the openid has been used.
 | 
				
			||||||
func isOpenIDUsed(ctx context.Context, uri string) (bool, error) {
 | 
					func IsOpenIDUsed(ctx context.Context, uri string) (bool, error) {
 | 
				
			||||||
	if len(uri) == 0 {
 | 
						if len(uri) == 0 {
 | 
				
			||||||
		return true, nil
 | 
							return true, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -71,7 +71,7 @@ func (err ErrOpenIDAlreadyUsed) Unwrap() error {
 | 
				
			||||||
// AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
 | 
					// AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
 | 
				
			||||||
// NOTE: make sure openid.URI is normalized already
 | 
					// NOTE: make sure openid.URI is normalized already
 | 
				
			||||||
func AddUserOpenID(ctx context.Context, openid *UserOpenID) error {
 | 
					func AddUserOpenID(ctx context.Context, openid *UserOpenID) error {
 | 
				
			||||||
	used, err := isOpenIDUsed(ctx, openid.URI)
 | 
						used, err := IsOpenIDUsed(ctx, openid.URI)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	} else if used {
 | 
						} else if used {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,6 +81,14 @@ func TwoFactorPost(ctx *context.Context) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Handle OpenID linking to user.
 | 
				
			||||||
 | 
							if oid, ok := ctx.Session.Get("twofaOpenID").(string); ok {
 | 
				
			||||||
 | 
								if err := user_model.AddUserOpenID(ctx, &user_model.UserOpenID{UID: u.ID, URI: oid}); err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("AddUserOpenID", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		twofa.LastUsedPasscode = form.Passcode
 | 
							twofa.LastUsedPasscode = form.Passcode
 | 
				
			||||||
		if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
 | 
							if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
 | 
				
			||||||
			ctx.ServerError("UserSignIn", err)
 | 
								ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
| 
						 | 
					@ -146,6 +154,14 @@ func TwoFactorScratchPost(ctx *context.Context) {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Handle OpenID linking to user.
 | 
				
			||||||
 | 
							if oid, ok := ctx.Session.Get("twofaOpenID").(string); ok {
 | 
				
			||||||
 | 
								if err := user_model.AddUserOpenID(ctx, &user_model.UserOpenID{UID: u.ID, URI: oid}); err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("AddUserOpenID", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		handleSignInFull(ctx, u, remember, false)
 | 
							handleSignInFull(ctx, u, remember, false)
 | 
				
			||||||
		if ctx.Written() {
 | 
							if ctx.Written() {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -310,6 +310,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
 | 
				
			||||||
		"openid_determined_username",
 | 
							"openid_determined_username",
 | 
				
			||||||
		"twofaUid",
 | 
							"twofaUid",
 | 
				
			||||||
		"twofaRemember",
 | 
							"twofaRemember",
 | 
				
			||||||
 | 
							"twofaOpenID",
 | 
				
			||||||
		"linkAccount",
 | 
							"linkAccount",
 | 
				
			||||||
	}, map[string]any{
 | 
						}, map[string]any{
 | 
				
			||||||
		"uid": u.ID,
 | 
							"uid": u.ID,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auth_model "forgejo.org/models/auth"
 | 
				
			||||||
	user_model "forgejo.org/models/user"
 | 
						user_model "forgejo.org/models/user"
 | 
				
			||||||
	"forgejo.org/modules/auth/openid"
 | 
						"forgejo.org/modules/auth/openid"
 | 
				
			||||||
	"forgejo.org/modules/base"
 | 
						"forgejo.org/modules/base"
 | 
				
			||||||
| 
						 | 
					@ -253,6 +254,7 @@ func ConnectOpenID(ctx *context.Context) {
 | 
				
			||||||
// ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account
 | 
					// ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account
 | 
				
			||||||
func ConnectOpenIDPost(ctx *context.Context) {
 | 
					func ConnectOpenIDPost(ctx *context.Context) {
 | 
				
			||||||
	form := web.GetForm(ctx).(*forms.ConnectOpenIDForm)
 | 
						form := web.GetForm(ctx).(*forms.ConnectOpenIDForm)
 | 
				
			||||||
 | 
						remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
 | 
				
			||||||
	oid, _ := ctx.Session.Get("openid_verified_uri").(string)
 | 
						oid, _ := ctx.Session.Get("openid_verified_uri").(string)
 | 
				
			||||||
	if oid == "" {
 | 
						if oid == "" {
 | 
				
			||||||
		ctx.Redirect(setting.AppSubURL + "/user/login/openid")
 | 
							ctx.Redirect(setting.AppSubURL + "/user/login/openid")
 | 
				
			||||||
| 
						 | 
					@ -264,28 +266,63 @@ func ConnectOpenIDPost(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
 | 
						ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
 | 
				
			||||||
	ctx.Data["OpenID"] = oid
 | 
						ctx.Data["OpenID"] = oid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	u, _, err := auth.UserSignIn(ctx, form.UserName, form.Password)
 | 
						u, source, err := auth.UserSignIn(ctx, form.UserName, form.Password)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err)
 | 
							handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// add OpenID for the user
 | 
						// Check if OID is already in use.
 | 
				
			||||||
	userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
 | 
						if used, err := user_model.IsOpenIDUsed(ctx, oid); err != nil {
 | 
				
			||||||
	if err = user_model.AddUserOpenID(ctx, userOID); err != nil {
 | 
							ctx.ServerError("IsOpenIDUsed", err)
 | 
				
			||||||
		if user_model.IsErrOpenIDAlreadyUsed(err) {
 | 
							return
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form)
 | 
						} else if used {
 | 
				
			||||||
			return
 | 
							ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form)
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		ctx.ServerError("AddUserOpenID", err)
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
 | 
						// Check if 2FA needs to be done.
 | 
				
			||||||
 | 
						has2FA, err := auth_model.HasTwoFactorByUID(ctx, u.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("HasTwoFactorByUID", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if skipper, ok := source.Cfg.(auth.LocalTwoFASkipper); !has2FA || (ok && skipper.IsSkipLocalTwoFA()) {
 | 
				
			||||||
 | 
							// Link this OID to the user.
 | 
				
			||||||
 | 
							if err := user_model.AddUserOpenID(ctx, &user_model.UserOpenID{UID: u.ID, URI: oid}); err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("AddUserOpenID", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
 | 
							ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
 | 
				
			||||||
	log.Trace("Session stored openid-remember: %t", remember)
 | 
							handleSignIn(ctx, u, remember)
 | 
				
			||||||
	handleSignIn(ctx, u, remember)
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if the user has webauthn registration.
 | 
				
			||||||
 | 
						hasWebAuthnTwofa, err := auth_model.HasWebAuthnRegistrationsByUID(ctx, u.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("HasWebAuthnRegistrationsByUID", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := updateSession(ctx, nil, map[string]any{
 | 
				
			||||||
 | 
							"twofaUid":      u.ID,
 | 
				
			||||||
 | 
							"twofaRemember": remember,
 | 
				
			||||||
 | 
							"twofaOpenID":   oid,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("Unable to update session", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If we have WebAuthn, redirect there first.
 | 
				
			||||||
 | 
						if hasWebAuthnTwofa {
 | 
				
			||||||
 | 
							ctx.Redirect(setting.AppSubURL + "/user/webauthn")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fallback to TOTP.
 | 
				
			||||||
 | 
						ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RegisterOpenID shows a form to create a new user authenticated via an OpenID URI
 | 
					// RegisterOpenID shows a form to create a new user authenticated via an OpenID URI
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,6 +166,14 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle OpenID linking to user.
 | 
				
			||||||
 | 
						if oid, ok := ctx.Session.Get("twofaOpenID").(string); ok {
 | 
				
			||||||
 | 
							if err := user_model.AddUserOpenID(ctx, &user_model.UserOpenID{UID: user.ID, URI: oid}); err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("AddUserOpenID", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	remember := ctx.Session.Get("twofaRemember").(bool)
 | 
						remember := ctx.Session.Get("twofaRemember").(bool)
 | 
				
			||||||
	redirect := handleSignInFull(ctx, user, remember, false)
 | 
						redirect := handleSignInFull(ctx, user, remember, false)
 | 
				
			||||||
	if redirect == "" {
 | 
						if redirect == "" {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,6 +77,7 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
 | 
				
			||||||
	_ = sess.Delete("openid_determined_username")
 | 
						_ = sess.Delete("openid_determined_username")
 | 
				
			||||||
	_ = sess.Delete("twofaUid")
 | 
						_ = sess.Delete("twofaUid")
 | 
				
			||||||
	_ = sess.Delete("twofaRemember")
 | 
						_ = sess.Delete("twofaRemember")
 | 
				
			||||||
 | 
						_ = sess.Delete("twofaOpenID")
 | 
				
			||||||
	_ = sess.Delete("webauthnAssertion")
 | 
						_ = sess.Delete("webauthnAssertion")
 | 
				
			||||||
	_ = sess.Delete("linkAccount")
 | 
						_ = sess.Delete("linkAccount")
 | 
				
			||||||
	err = sess.Set("uid", user.ID)
 | 
						err = sess.Set("uid", user.ID)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue