zitadel/internal/api/ui/login/mail_verify_handler.go
Livio Spring 0e99c8356a
fix: correctly check user auth methods and enable button (#8342)
# 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
2024-07-22 14:46:27 +02:00

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)
}