mirror of
https://github.com/zitadel/zitadel.git
synced 2025-05-06 08:06:47 +00:00

# Which Problems Are Solved The current login will always prefer external authentication (through an IdP) over local authentication. So as soon as either the user had connected to an IdP or even when the login policy was just set up to have an IdP allowed, users would be redirected to that IdP for (re)authentication. This could lead to problems, where the IdP was not available or any other error occurred in the process (such as secret expired for EntraID). Even when local authentication (passkeys or password) was allowed for the corresponding user, they would always be redirected to the IdP again, preventing any authentication. If admins were affected, they might not even be able to update the client secret of the IdP. # How the Problems Are Solved Errors during the external IdP flow are handled in an `externalAuthFailed` function, which will check if the organisation allows local authentication and if the user has set up such. If either password or passkeys is set up, the corresponding login page will be presented to the user. As already with local auth passkeys is preferred over password authentication. The user is informed that the external login failed and fail back to local auth as an error on the corresponding page in a focused mode. Any interaction or after 5 second the focus mode is disabled. # Additional Changes None. # Additional Context closes #6466
123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package login
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
)
|
|
|
|
const (
|
|
tmplMFASMSInit = "mfainitsms"
|
|
)
|
|
|
|
type smsInitData struct {
|
|
userData
|
|
Edit bool
|
|
MFAType domain.MFAType
|
|
Phone string
|
|
}
|
|
|
|
type smsInitFormData struct {
|
|
Edit bool `schema:"edit"`
|
|
Resend bool `schema:"resend"`
|
|
Phone string `schema:"phone"`
|
|
NewPhone string `schema:"newPhone"`
|
|
Code string `schema:"code"`
|
|
}
|
|
|
|
// handleRegisterOTPSMS checks if the user has a verified phone number and will directly add OTP SMS as 2FA.
|
|
// It will also add a successful OTP SMS check to the auth request.
|
|
// If there's no verified phone number, the potential last phone number will be used to render the registration page
|
|
func (l *Login) handleRegisterOTPSMS(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
|
|
user, err := l.query.GetNotifyUserByID(r.Context(), true, authReq.UserID)
|
|
if err != nil {
|
|
l.renderError(w, r, authReq, err)
|
|
return
|
|
}
|
|
if user.VerifiedPhone == "" {
|
|
data := new(smsInitData)
|
|
data.Phone = user.LastPhone
|
|
data.Edit = user.LastPhone == ""
|
|
l.renderRegisterSMS(w, r, authReq, data, nil)
|
|
return
|
|
}
|
|
_, err = l.command.AddHumanOTPSMSWithCheckSucceeded(setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq)
|
|
if err != nil {
|
|
l.renderError(w, r, authReq, err)
|
|
return
|
|
}
|
|
done := &mfaDoneData{
|
|
MFAType: domain.MFATypeOTPSMS,
|
|
}
|
|
l.renderMFAInitDone(w, r, authReq, done)
|
|
}
|
|
|
|
func (l *Login) renderRegisterSMS(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *smsInitData, err error) {
|
|
translator := l.getTranslator(r.Context(), authReq)
|
|
data.baseData = l.getBaseData(r, authReq, translator, "InitMFAOTP.Title", "InitMFAOTP.Description", err)
|
|
data.profileData = l.getProfileData(authReq)
|
|
data.MFAType = domain.MFATypeOTPSMS
|
|
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplMFASMSInit], data, nil)
|
|
}
|
|
|
|
// handleRegisterSMSCheck handles form submissions of the SMS registration.
|
|
// The user can be either in edit mode, where a phone number can be entered / changed.
|
|
// If a phone was set, the user can either switch to edit mode, have a resend of the code or verify the code by entering it.
|
|
// On successful code verification, the phone will be added to the user as well as his MFA
|
|
// and a successful OTP SMS check will be added to the auth request.
|
|
func (l *Login) handleRegisterSMSCheck(w http.ResponseWriter, r *http.Request) {
|
|
formData := new(smsInitFormData)
|
|
authReq, err := l.ensureAuthRequestAndParseData(r, formData)
|
|
if err != nil {
|
|
l.renderError(w, r, authReq, err)
|
|
return
|
|
}
|
|
|
|
ctx := setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID)
|
|
// save the current state
|
|
data := &smsInitData{Phone: formData.Phone}
|
|
|
|
if formData.Edit {
|
|
data.Edit = true
|
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
|
return
|
|
}
|
|
|
|
if formData.Resend {
|
|
_, err = l.command.CreateHumanPhoneVerificationCode(ctx, authReq.UserID, authReq.UserOrgID)
|
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
|
return
|
|
}
|
|
|
|
// if the user is currently in edit mode,
|
|
// he can either change the phone number
|
|
// or just return to the code verification again
|
|
if formData.Code == "" {
|
|
data.Phone = formData.NewPhone
|
|
if formData.NewPhone != formData.Phone {
|
|
_, err = l.command.ChangeUserPhone(ctx, authReq.UserID, formData.NewPhone, l.userCodeAlg)
|
|
if err != nil {
|
|
// stay in edit more
|
|
data.Edit = true
|
|
}
|
|
}
|
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
|
return
|
|
}
|
|
|
|
_, err = l.command.VerifyUserPhone(ctx, authReq.UserID, formData.Code, l.userCodeAlg)
|
|
if err != nil {
|
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
|
return
|
|
}
|
|
_, err = l.command.AddHumanOTPSMSWithCheckSucceeded(ctx, authReq.UserID, authReq.UserOrgID, authReq)
|
|
if err != nil {
|
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
|
return
|
|
}
|
|
done := &mfaDoneData{
|
|
MFAType: domain.MFATypeOTPSMS,
|
|
}
|
|
l.renderMFAInitDone(w, r, authReq, done)
|
|
}
|