Browse Source

fix(oauth2): reject link overwrite when user already has a linked identity

If a logged-in user already has an OAuth2 identity linked, reject
callbacks that would replace it with a different identity.
Frédéric Guillot 2 weeks ago
parent
commit
2b21269900

+ 4 - 0
internal/oauth2/google.go

@@ -77,6 +77,10 @@ func (g *googleProvider) PopulateUserWithProfileID(user *model.User, profile *Pr
 	user.GoogleID = profile.ID
 }
 
+func (g *googleProvider) GetUserProfileID(user *model.User) string {
+	return user.GoogleID
+}
+
 func (g *googleProvider) UnsetUserProfileID(user *model.User) {
 	user.GoogleID = ""
 }

+ 4 - 0
internal/oauth2/oidc.go

@@ -99,6 +99,10 @@ func (o *oidcProvider) PopulateUserWithProfileID(user *model.User, profile *Prof
 	user.OpenIDConnectID = profile.ID
 }
 
+func (o *oidcProvider) GetUserProfileID(user *model.User) string {
+	return user.OpenIDConnectID
+}
+
 func (o *oidcProvider) UnsetUserProfileID(user *model.User) {
 	user.OpenIDConnectID = ""
 }

+ 1 - 0
internal/oauth2/provider.go

@@ -18,5 +18,6 @@ type Provider interface {
 	GetProfile(ctx context.Context, code, codeVerifier string) (*Profile, error)
 	PopulateUserCreationWithProfileID(user *model.UserCreationRequest, profile *Profile)
 	PopulateUserWithProfileID(user *model.User, profile *Profile)
+	GetUserProfileID(user *model.User) string
 	UnsetUserProfileID(user *model.User)
 }

+ 13 - 0
internal/ui/oauth2_callback.go

@@ -84,6 +84,19 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
+		existingProfileID := authProvider.GetUserProfileID(loggedUser)
+		if existingProfileID != "" && existingProfileID != profile.ID {
+			slog.Error("Oauth2 user cannot be associated because this user is already linked to a different identity",
+				slog.Int64("user_id", loggedUser.ID),
+				slog.String("oauth2_provider", provider),
+				slog.String("existing_profile_id", existingProfileID),
+				slog.String("new_profile_id", profile.ID),
+			)
+			sess.NewFlashErrorMessage(printer.Print("error.duplicate_linked_account"))
+			response.HTMLRedirect(w, r, h.routePath("/settings"))
+			return
+		}
+
 		authProvider.PopulateUserWithProfileID(loggedUser, profile)
 		if err := h.store.UpdateUser(loggedUser); err != nil {
 			response.HTMLServerError(w, r, err)