mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-24 00:46:42 +00:00
# Which Problems Are Solved
User enumeration was possible on the select account page by passing any userID as part of the form POST. Existing users could be selected even if they never authenticated on the same user agent (browser).
# How the Problems Are Solved
A check for an existing session on the same user agent was added to the select user function, resp. only required for the account selection page, since in other cases there doesn't have to be an existing session and the user agent integrity is already checked.
# Additional Changes
None
# Additional Context
None
(cherry picked from commit 7abe759c95)
221 lines
6.7 KiB
Go
221 lines
6.7 KiB
Go
package login
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
const (
|
|
tmplRegister = "register"
|
|
)
|
|
|
|
type registerFormData struct {
|
|
Email domain.EmailAddress `schema:"email"`
|
|
Username string `schema:"username"`
|
|
Firstname string `schema:"firstname"`
|
|
Lastname string `schema:"lastname"`
|
|
Language string `schema:"language"`
|
|
Password string `schema:"register-password"`
|
|
Password2 string `schema:"register-password-confirmation"`
|
|
TermsConfirm bool `schema:"terms-confirm"`
|
|
}
|
|
|
|
type registerData struct {
|
|
baseData
|
|
registerFormData
|
|
MinLength uint64
|
|
HasUppercase string
|
|
HasLowercase string
|
|
HasNumber string
|
|
HasSymbol string
|
|
ShowUsername bool
|
|
ShowUsernameSuffix bool
|
|
OrgRegister bool
|
|
}
|
|
|
|
func determineResourceOwner(ctx context.Context, authRequest *domain.AuthRequest) string {
|
|
if authRequest != nil && authRequest.RequestedOrgID != "" {
|
|
return authRequest.RequestedOrgID
|
|
}
|
|
return authz.GetInstance(ctx).DefaultOrganisationID()
|
|
}
|
|
|
|
func (l *Login) handleRegister(w http.ResponseWriter, r *http.Request) {
|
|
data := new(registerFormData)
|
|
authRequest, err := l.getAuthRequestAndParseData(r, data)
|
|
if err != nil {
|
|
l.renderError(w, r, authRequest, err)
|
|
return
|
|
}
|
|
if err := l.checkRegistrationAllowed(r, determineResourceOwner(r.Context(), authRequest), authRequest); err != nil {
|
|
l.renderError(w, r, authRequest, err)
|
|
return
|
|
}
|
|
l.renderRegister(w, r, authRequest, data, nil)
|
|
}
|
|
|
|
func (l *Login) checkRegistrationAllowed(r *http.Request, orgID string, authReq *domain.AuthRequest) error {
|
|
if authReq != nil {
|
|
if registrationAllowed(authReq) {
|
|
return nil
|
|
}
|
|
return zerrors.ThrowPreconditionFailed(nil, "VIEW-RRGRXz4kGw", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
|
|
}
|
|
loginPolicy, err := l.getLoginPolicy(r, orgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if loginPolicy.AllowRegister && loginPolicy.AllowUsernamePassword {
|
|
return nil
|
|
}
|
|
return zerrors.ThrowPreconditionFailed(nil, "VIEW-Vq3bduAacD", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
|
|
}
|
|
|
|
func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
|
data := new(registerFormData)
|
|
authRequest, err := l.getAuthRequestAndParseData(r, data)
|
|
if err != nil {
|
|
l.renderError(w, r, authRequest, err)
|
|
return
|
|
}
|
|
resourceOwner := determineResourceOwner(r.Context(), authRequest)
|
|
if err := l.checkRegistrationAllowed(r, resourceOwner, authRequest); err != nil {
|
|
l.renderError(w, r, authRequest, err)
|
|
return
|
|
}
|
|
if data.Password != data.Password2 {
|
|
err := zerrors.ThrowInvalidArgument(nil, "VIEW-KaGue", "Errors.User.Password.ConfirmationWrong")
|
|
l.renderRegister(w, r, authRequest, data, err)
|
|
return
|
|
}
|
|
// For consistency with the external authentication flow,
|
|
// the setMetadata() function is provided on the pre creation hook, for now,
|
|
// like for the ExternalAuthentication flow.
|
|
// If there is a need for additional context after registration,
|
|
// we could provide that method in the PostCreation trigger too,
|
|
// without breaking existing actions.
|
|
// Also, if that field is needed, we probably also should provide it
|
|
// for ExternalAuthentication.
|
|
user, metadatas, err := l.runPreCreationActions(authRequest, r, data.toHumanDomain(), make([]*domain.Metadata, 0), resourceOwner, domain.FlowTypeInternalAuthentication)
|
|
if err != nil {
|
|
l.renderRegister(w, r, authRequest, data, err)
|
|
return
|
|
}
|
|
|
|
human := command.AddHumanFromDomain(user, metadatas, authRequest, nil)
|
|
err = l.command.AddUserHuman(setContext(r.Context(), resourceOwner), resourceOwner, human, true, l.userCodeAlg)
|
|
if err != nil {
|
|
l.renderRegister(w, r, authRequest, data, err)
|
|
return
|
|
}
|
|
userGrants, err := l.runPostCreationActions(human.ID, authRequest, r, resourceOwner, domain.FlowTypeInternalAuthentication)
|
|
if err != nil {
|
|
l.renderError(w, r, authRequest, err)
|
|
return
|
|
}
|
|
|
|
err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
|
|
if err != nil {
|
|
l.renderError(w, r, authRequest, err)
|
|
return
|
|
}
|
|
|
|
if authRequest == nil {
|
|
l.defaultRedirect(w, r)
|
|
return
|
|
}
|
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
|
err = l.authRepo.SelectUser(r.Context(), authRequest.ID, human.ID, userAgentID, false)
|
|
if err != nil {
|
|
l.renderRegister(w, r, authRequest, data, err)
|
|
return
|
|
}
|
|
l.renderNextStep(w, r, authRequest)
|
|
}
|
|
|
|
func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authRequest *domain.AuthRequest, formData *registerFormData, err error) {
|
|
translator := l.getTranslator(r.Context(), authRequest)
|
|
if formData == nil {
|
|
formData = new(registerFormData)
|
|
}
|
|
if formData.Language == "" {
|
|
formData.Language = l.renderer.ReqLang(translator, r).String()
|
|
}
|
|
|
|
resourceOwner := determineResourceOwner(r.Context(), authRequest)
|
|
data := registerData{
|
|
baseData: l.getBaseData(r, authRequest, translator, "RegistrationUser.Title", "RegistrationUser.Description", err),
|
|
registerFormData: *formData,
|
|
}
|
|
|
|
pwPolicy := l.getPasswordComplexityPolicy(r, resourceOwner)
|
|
if pwPolicy != nil {
|
|
data.MinLength = pwPolicy.MinLength
|
|
if pwPolicy.HasUppercase {
|
|
data.HasUppercase = UpperCaseRegex
|
|
}
|
|
if pwPolicy.HasLowercase {
|
|
data.HasLowercase = LowerCaseRegex
|
|
}
|
|
if pwPolicy.HasSymbol {
|
|
data.HasSymbol = SymbolRegex
|
|
}
|
|
if pwPolicy.HasNumber {
|
|
data.HasNumber = NumberRegex
|
|
}
|
|
}
|
|
|
|
orgIAMPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
|
|
if err != nil {
|
|
l.renderRegister(w, r, authRequest, formData, err)
|
|
return
|
|
}
|
|
data.ShowUsername = orgIAMPolicy.UserLoginMustBeDomain
|
|
data.OrgRegister = orgIAMPolicy.UserLoginMustBeDomain
|
|
|
|
labelPolicy, err := l.getLabelPolicy(r, resourceOwner)
|
|
if err != nil {
|
|
l.renderRegister(w, r, authRequest, formData, err)
|
|
return
|
|
}
|
|
data.ShowUsernameSuffix = !labelPolicy.HideLoginNameSuffix
|
|
|
|
funcs := map[string]interface{}{
|
|
"selectedLanguage": func(l string) bool {
|
|
if formData == nil {
|
|
return false
|
|
}
|
|
return formData.Language == l
|
|
},
|
|
}
|
|
if authRequest == nil {
|
|
l.customTexts(r.Context(), translator, resourceOwner)
|
|
}
|
|
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplRegister], data, funcs)
|
|
}
|
|
|
|
func (d registerFormData) toHumanDomain() *domain.Human {
|
|
return &domain.Human{
|
|
Username: d.Username,
|
|
Profile: &domain.Profile{
|
|
FirstName: d.Firstname,
|
|
LastName: d.Lastname,
|
|
PreferredLanguage: language.Make(d.Language),
|
|
},
|
|
Password: &domain.Password{
|
|
SecretString: d.Password,
|
|
},
|
|
Email: &domain.Email{
|
|
EmailAddress: d.Email,
|
|
},
|
|
}
|
|
}
|