mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
fix: automatically link user without prompt (#8487)
# Which Problems Are Solved There were UX issue with the autolinking prompt page and users were not able to link their account or would not understand what to do. Since the trust to the IdP is already bound by the configuration, the user can directly be linked without any user input. # How the Problems Are Solved - remove the prompt page and directly link the user if possible - remove corresponding customization texts from the API and Console # Additional Changes None # Additional Context - relates to https://github.com/zitadel/zitadel/issues/7977 - discussed with customers - created as a `fix` to be able to backport --------- Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
@@ -455,9 +455,9 @@ func (l *Login) handleExternalUserAuthenticated(
|
||||
// checkAutoLinking checks if a user with the provided information (username or email) already exists within ZITADEL.
|
||||
// The decision, which information will be checked is based on the IdP template option.
|
||||
// The function returns a boolean whether a user was found or not.
|
||||
// If single a user was found, it will be automatically linked.
|
||||
func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) bool {
|
||||
queries := make([]query.SearchQuery, 0, 2)
|
||||
var user *query.NotifyUser
|
||||
switch provider.AutoLinking {
|
||||
case domain.AutoLinkingOptionUnspecified:
|
||||
// is auto linking is disable, we shouldn't even get here, but in case we do we can directly return
|
||||
@@ -472,7 +472,7 @@ func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
l.renderLinkingUserPrompt(w, r, authReq, user, nil)
|
||||
l.autoLinkUser(w, r, authReq, user)
|
||||
return true
|
||||
}
|
||||
// If a specific org has been requested, we'll check the provided username against usernames (of that org).
|
||||
@@ -501,10 +501,22 @@ func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
l.renderLinkingUserPrompt(w, r, authReq, user, nil)
|
||||
l.autoLinkUser(w, r, authReq, user)
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *Login) autoLinkUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, user *query.NotifyUser) {
|
||||
if err := l.authRepo.SelectUser(r.Context(), authReq.ID, user.ID, authReq.AgentID); err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if err := l.authRepo.LinkExternalUsers(r.Context(), authReq.ID, authReq.AgentID, domain.BrowserInfoFromRequest(r)); err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
||||
|
||||
// externalUserNotExisting is called if an externalAuthentication couldn't find a corresponding externalID
|
||||
// possible solutions are:
|
||||
//
|
||||
|
@@ -1,62 +0,0 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplLinkingUserPrompt = "link_user_prompt"
|
||||
)
|
||||
|
||||
type linkingUserPromptData struct {
|
||||
userData
|
||||
Username string
|
||||
Linking domain.AutoLinkingOption
|
||||
UserID string
|
||||
}
|
||||
|
||||
type linkingUserPromptFormData struct {
|
||||
OtherUser bool `schema:"other"`
|
||||
UserID string `schema:"userID"`
|
||||
}
|
||||
|
||||
func (l *Login) renderLinkingUserPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, user *query.NotifyUser, err error) {
|
||||
var errID, errMessage string
|
||||
if err != nil {
|
||||
errID, errMessage = l.getErrorMessage(r, err)
|
||||
}
|
||||
translator := l.getTranslator(r.Context(), authReq)
|
||||
identification := user.PreferredLoginName
|
||||
// hide the suffix in case the option is set and the auth request has been started with the primary domain scope
|
||||
if authReq.RequestedOrgDomain && authReq.LabelPolicy != nil && authReq.LabelPolicy.HideLoginNameSuffix {
|
||||
identification = user.Username
|
||||
}
|
||||
data := &linkingUserPromptData{
|
||||
Username: identification,
|
||||
UserID: user.ID,
|
||||
userData: l.getUserData(r, authReq, translator, "LinkingUserPrompt.Title", "LinkingUserPrompt.Description", errID, errMessage),
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplLinkingUserPrompt], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleLinkingUserPrompt(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(linkingUserPromptFormData)
|
||||
authReq, err := l.ensureAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if data.OtherUser {
|
||||
l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, nil)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.SelectUser(r.Context(), authReq.ID, data.UserID, authReq.AgentID)
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
@@ -83,7 +83,6 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName
|
||||
tmplLDAPLogin: "ldap_login.html",
|
||||
tmplDeviceAuthUserCode: "device_usercode.html",
|
||||
tmplDeviceAuthAction: "device_action.html",
|
||||
tmplLinkingUserPrompt: "link_user_prompt.html",
|
||||
}
|
||||
funcs := map[string]interface{}{
|
||||
"resourceUrl": func(file string) string {
|
||||
|
@@ -124,6 +124,5 @@ func CreateRouter(login *Login, interceptors ...mux.MiddlewareFunc) *mux.Router
|
||||
router.SkipClean(true).Handle("", http.RedirectHandler(HandlerPrefix+"/", http.StatusMovedPermanently))
|
||||
router.HandleFunc(EndpointDeviceAuth, login.handleDeviceAuthUserCode).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc(EndpointDeviceAuthAction, login.handleDeviceAuthAction).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc(EndpointLinkingUserPrompt, login.handleLinkingUserPrompt).Methods(http.MethodPost)
|
||||
return router
|
||||
}
|
||||
|
@@ -1,37 +0,0 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
<div class="lgn-head">
|
||||
<h1>{{t "LinkingUserPrompt.Title"}}</h1>
|
||||
<p>
|
||||
{{t "LinkingUserPrompt.Description"}}<br>
|
||||
{{.Username}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<form action="{{ linkingUserPromptUrl }}" method="POST">
|
||||
|
||||
{{ .CSRF }}
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="userID" value="{{ .UserID }}" />
|
||||
|
||||
{{template "error-message" .}}
|
||||
|
||||
<div class="lgn-actions lgn-reverse-order">
|
||||
<a class="lgn-icon-button lgn-left-action" id="back-button" href="#">
|
||||
<i class="lgn-icon-arrow-left-solid"></i>
|
||||
</a>
|
||||
<button class="lgn-raised-button lgn-primary lgn-initial-focus" id="submit-button" type="submit">{{t "LinkingUserPrompt.LinkButtonText"}}</button>
|
||||
<span class="fill-space"></span>
|
||||
<button class="lgn-stroked-button" name="other" value="true">{{t "LinkingUserPrompt.OtherButtonText"}}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
|
||||
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
|
||||
<script src="{{ resourceUrl "scripts/input_suffix_offset.js" }}"></script>
|
||||
<script src="{{ resourceUrl "scripts/go_back.js" }}"></script>
|
||||
|
||||
{{template "main-bottom" .}}
|
Reference in New Issue
Block a user