mirror of
https://github.com/zitadel/zitadel.git
synced 2025-04-23 23:01:31 +00:00

# Which Problems Are Solved #8291 added backwards compatibilty for users who were created through the user V2 API and want to sign in to the login UI. There were however to issues, where users might be prompted to set a password even if they already had one set or they would not be able to submit the email verification code. # How the Problems Are Solved - Replaced `SearchUserAuthMethods `with `ListUserAuthMethodTypes` to check for set up auth methods. - Fixed page / javascript to disable submit button. # Additional Changes - Changed `ListActiveUserAuthMethodTypes ` to `ListUserAuthMethodTypes` and a `activeOnly` boolean parameter # Additional Context - relates to #8291 - noticed internally on QA
192 lines
6.2 KiB
Go
192 lines
6.2 KiB
Go
package login
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
"slices"
|
|
|
|
"github.com/zitadel/logging"
|
|
|
|
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
const (
|
|
queryCode = "code"
|
|
queryUserID = "userID"
|
|
|
|
tmplMailVerification = "mail_verification"
|
|
tmplMailVerified = "mail_verified"
|
|
)
|
|
|
|
type mailVerificationFormData struct {
|
|
Code string `schema:"code"`
|
|
UserID string `schema:"userID"`
|
|
Resend bool `schema:"resend"`
|
|
PasswordInit bool `schema:"passwordInit"`
|
|
Password string `schema:"password"`
|
|
PasswordConfirm string `schema:"passwordconfirm"`
|
|
}
|
|
|
|
type mailVerificationData struct {
|
|
baseData
|
|
profileData
|
|
UserID string
|
|
Code string
|
|
PasswordInit bool
|
|
MinLength uint64
|
|
HasUppercase string
|
|
HasLowercase string
|
|
HasNumber string
|
|
HasSymbol string
|
|
}
|
|
|
|
func MailVerificationLink(origin, userID, code, orgID, authRequestID string) string {
|
|
v := url.Values{}
|
|
v.Set(queryUserID, userID)
|
|
v.Set(queryCode, code)
|
|
v.Set(queryOrgID, orgID)
|
|
v.Set(QueryAuthRequestID, authRequestID)
|
|
return externalLink(origin) + EndpointMailVerification + "?" + v.Encode()
|
|
}
|
|
|
|
func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) {
|
|
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
|
|
userID := r.FormValue(queryUserID)
|
|
code := r.FormValue(queryCode)
|
|
if userID == "" && authReq == nil {
|
|
l.renderError(w, r, authReq, nil)
|
|
return
|
|
}
|
|
if userID == "" {
|
|
userID = authReq.UserID
|
|
}
|
|
passwordInit := l.checkUserNoFirstFactor(r.Context(), userID)
|
|
if code != "" && !passwordInit {
|
|
l.checkMailCode(w, r, authReq, userID, code, "")
|
|
return
|
|
}
|
|
l.renderMailVerification(w, r, authReq, userID, code, passwordInit, nil)
|
|
}
|
|
|
|
func (l *Login) checkUserNoFirstFactor(ctx context.Context, userID string) bool {
|
|
authMethods, err := l.query.ListUserAuthMethodTypes(setUserContext(ctx, userID, ""), userID, false)
|
|
if err != nil {
|
|
logging.WithFields("userID", userID).OnError(err).Warn("unable to load user's auth methods for mail verification")
|
|
return false
|
|
}
|
|
return !slices.ContainsFunc(authMethods.AuthMethodTypes, func(m domain.UserAuthMethodType) bool {
|
|
return m == domain.UserAuthMethodTypeIDP ||
|
|
m == domain.UserAuthMethodTypePassword ||
|
|
m == domain.UserAuthMethodTypePasswordless
|
|
})
|
|
}
|
|
|
|
func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Request) {
|
|
data := new(mailVerificationFormData)
|
|
authReq, err := l.getAuthRequestAndParseData(r, data)
|
|
if err != nil {
|
|
l.renderError(w, r, authReq, err)
|
|
return
|
|
}
|
|
if !data.Resend {
|
|
if data.PasswordInit && data.Password != data.PasswordConfirm {
|
|
err := zerrors.ThrowInvalidArgument(nil, "VIEW-fsdfd", "Errors.User.Password.ConfirmationWrong")
|
|
l.renderMailVerification(w, r, authReq, data.UserID, data.Code, data.PasswordInit, err)
|
|
return
|
|
}
|
|
l.checkMailCode(w, r, authReq, data.UserID, data.Code, data.Password)
|
|
return
|
|
}
|
|
var userOrg, authReqID string
|
|
if authReq != nil {
|
|
userOrg = authReq.UserOrgID
|
|
authReqID = authReq.ID
|
|
}
|
|
emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg)
|
|
if err != nil {
|
|
l.renderMailVerification(w, r, authReq, data.UserID, "", data.PasswordInit, err)
|
|
return
|
|
}
|
|
_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator, authReqID)
|
|
l.renderMailVerification(w, r, authReq, data.UserID, "", data.PasswordInit, err)
|
|
}
|
|
|
|
func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code, password string) {
|
|
userOrg := ""
|
|
if authReq != nil {
|
|
userID = authReq.UserID
|
|
userOrg = authReq.UserOrgID
|
|
}
|
|
emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg)
|
|
if err != nil {
|
|
l.renderMailVerification(w, r, authReq, userID, "", password != "", err)
|
|
return
|
|
}
|
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
|
_, err = l.command.VerifyHumanEmail(setContext(r.Context(), userOrg), userID, code, userOrg, password, userAgentID, emailCodeGenerator)
|
|
if err != nil {
|
|
l.renderMailVerification(w, r, authReq, userID, "", password != "", err)
|
|
return
|
|
}
|
|
l.renderMailVerified(w, r, authReq, userOrg)
|
|
}
|
|
|
|
func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, passwordInit bool, err error) {
|
|
var errID, errMessage string
|
|
if err != nil {
|
|
errID, errMessage = l.getErrorMessage(r, err)
|
|
}
|
|
if userID == "" && authReq != nil {
|
|
userID = authReq.UserID
|
|
}
|
|
|
|
translator := l.getTranslator(r.Context(), authReq)
|
|
data := mailVerificationData{
|
|
baseData: l.getBaseData(r, authReq, translator, "EmailVerification.Title", "EmailVerification.Description", errID, errMessage),
|
|
UserID: userID,
|
|
profileData: l.getProfileData(authReq),
|
|
Code: code,
|
|
PasswordInit: passwordInit,
|
|
}
|
|
if passwordInit {
|
|
policy := l.getPasswordComplexityPolicyByUserID(r, userID)
|
|
if policy != nil {
|
|
data.MinLength = policy.MinLength
|
|
if policy.HasUppercase {
|
|
data.HasUppercase = UpperCaseRegex
|
|
}
|
|
if policy.HasLowercase {
|
|
data.HasLowercase = LowerCaseRegex
|
|
}
|
|
if policy.HasSymbol {
|
|
data.HasSymbol = SymbolRegex
|
|
}
|
|
if policy.HasNumber {
|
|
data.HasNumber = NumberRegex
|
|
}
|
|
}
|
|
}
|
|
if authReq == nil {
|
|
user, err := l.query.GetUserByID(r.Context(), false, userID)
|
|
if err == nil {
|
|
l.customTexts(r.Context(), translator, user.ResourceOwner)
|
|
}
|
|
}
|
|
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplMailVerification], data, nil)
|
|
}
|
|
|
|
func (l *Login) renderMailVerified(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgID string) {
|
|
translator := l.getTranslator(r.Context(), authReq)
|
|
data := mailVerificationData{
|
|
baseData: l.getBaseData(r, authReq, translator, "EmailVerificationDone.Title", "EmailVerificationDone.Description", "", ""),
|
|
profileData: l.getProfileData(authReq),
|
|
}
|
|
if authReq == nil {
|
|
l.customTexts(r.Context(), translator, orgID)
|
|
}
|
|
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplMailVerified], data, nil)
|
|
}
|