mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-20 22:44:25 +00:00
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved - This adds rudimentary country code handling for LoginV1 SMS OTP - Previously defaulting to CH with no prompt to user causing e.g UK (+44) based numbers to be re-routed to CH. (+41) # Additional Context - Support for SMS OTP is not complete for LoginV2 (according to latest support email) Tested with Twilio on both UK + DE # . Appears to work. - Closes #8891 <img width="544" height="485" alt="image" src="https://github.com/user-attachments/assets/84d31bd8-8863-465f-8860-b51602439dfb" /> <img width="858" height="1298" alt="Screenshot 2026-01-16 at 11 23 22" src="https://github.com/user-attachments/assets/4bf16f0b-2fec-4d63-95b0-f0b73f3a131a" /> --------- Co-authored-by: Livio Spring <[email protected]> Co-authored-by: Silvan <[email protected]>
138 lines
4.5 KiB
Go
138 lines
4.5 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
|
|
CountryCode string
|
|
CountryCodes []domain.CountryCode
|
|
}
|
|
|
|
type smsInitFormData struct {
|
|
Edit bool `schema:"edit"`
|
|
Resend bool `schema:"resend"`
|
|
Phone string `schema:"phone"`
|
|
NewPhone string `schema:"newPhone"`
|
|
Code string `schema:"code"`
|
|
CountryCode string `schema:"countryCode"`
|
|
}
|
|
|
|
// 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
|
|
data.CountryCodes = domain.GetCountryCodes(translator.Lang(r))
|
|
if data.CountryCode == "" {
|
|
data.CountryCode = "CH" // default to Switzerland
|
|
}
|
|
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, CountryCode: formData.CountryCode}
|
|
|
|
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 {
|
|
// Normalize phone with selected country code before saving
|
|
normalizedPhone, normErr := domain.PhoneNumber(formData.NewPhone).NormalizeWithRegion(formData.CountryCode)
|
|
if normErr != nil {
|
|
data.Edit = true
|
|
l.renderRegisterSMS(w, r, authReq, data, normErr)
|
|
return
|
|
}
|
|
data.Phone = string(normalizedPhone)
|
|
_, err = l.command.ChangeUserPhone(ctx, authReq.UserID, string(normalizedPhone), 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)
|
|
}
|