feat(login): add OTP (email and sms) (#6353)

* feat: login with otp

* fix(i18n): japanese translation

* add missing files

* fix provider change

* add event types translations to en

* add tests

* resourceOwner

* remove unused handler

* fix: secret generators and add comments

* add setup step

* rename

* linting

* fix setup

* improve otp handling

* fix autocomplete

* translations for login and notifications

* translations for event types

* changes from review

* check selected mfa type
This commit is contained in:
Livio Spring
2023-08-15 14:47:05 +02:00
committed by GitHub
parent faa9ed4de9
commit 7c494fd219
76 changed files with 3203 additions and 88 deletions

View File

@@ -149,6 +149,8 @@ type authMethod string
const (
authMethodPassword authMethod = "password"
authMethodOTP authMethod = "OTP"
authMethodOTPSMS authMethod = "OTP SMS"
authMethodOTPEmail authMethod = "OTP Email"
authMethodU2F authMethod = "U2F"
authMethodPasswordless authMethod = "passwordless"
)

View File

@@ -171,6 +171,14 @@ func setContext(ctx context.Context, resourceOwner string) context.Context {
return authz.SetCtxData(ctx, data)
}
func setUserContext(ctx context.Context, userID, resourceOwner string) context.Context {
data := authz.CtxData{
UserID: userID,
OrgID: resourceOwner,
}
return authz.SetCtxData(ctx, data)
}
func (l *Login) baseURL(ctx context.Context) string {
return http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure) + HandlerPrefix
}

View File

@@ -0,0 +1,125 @@
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, false)
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) {
var errID, errMessage string
if err != nil {
errID, errMessage = l.getErrorMessage(r, err)
}
data.baseData = l.getBaseData(r, authReq, "InitMFAOTP.Title", "InitMFAOTP.Description", errID, errMessage)
data.profileData = l.getProfileData(authReq)
data.MFAType = domain.MFATypeOTPSMS
l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), 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.getAuthRequestAndParseData(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, authReq.UserOrgID, 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, authReq.UserOrgID, 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)
}

View File

@@ -83,6 +83,12 @@ func (l *Login) handleMFACreation(w http.ResponseWriter, r *http.Request, authRe
case domain.MFATypeTOTP:
l.handleTOTPCreation(w, r, authReq, data)
return
case domain.MFATypeOTPSMS:
l.handleRegisterOTPSMS(w, r, authReq)
return
case domain.MFATypeOTPEmail:
l.handleRegisterOTPEmail(w, r, authReq)
return
case domain.MFATypeU2F:
l.renderRegisterU2F(w, r, authReq, nil)
return
@@ -103,3 +109,17 @@ func (l *Login) handleTOTPCreation(w http.ResponseWriter, r *http.Request, authR
}
l.renderMFAInitVerify(w, r, authReq, data, nil)
}
// handleRegisterOTPEmail will directly add OTP Email as 2FA.
// It will also add a successful OTP Email check to the auth request.
func (l *Login) handleRegisterOTPEmail(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
_, err := l.command.AddHumanOTPEmailWithCheckSucceeded(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.MFATypeOTPEmail,
}
l.renderMFAInitDone(w, r, authReq, done)
}

View File

@@ -84,6 +84,12 @@ func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request,
data.SelectedMFAProvider = domain.MFATypeTOTP
data.Title = translator.LocalizeWithoutArgs("VerifyMFAOTP.Title")
data.Description = translator.LocalizeWithoutArgs("VerifyMFAOTP.Description")
case domain.MFATypeOTPSMS:
l.handleOTPVerification(w, r, authReq, verificationStep.MFAProviders, domain.MFATypeOTPSMS, nil)
return
case domain.MFATypeOTPEmail:
l.handleOTPVerification(w, r, authReq, verificationStep.MFAProviders, domain.MFATypeOTPEmail, nil)
return
default:
l.renderError(w, r, authReq, err)
return

View File

@@ -0,0 +1,126 @@
package login
import (
"context"
"fmt"
"net/http"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
)
const (
tmplOTPVerification = "otpverification"
querySelectedProvider = "selectedProvider"
)
type mfaOTPData struct {
userData
MFAProviders []domain.MFAType
SelectedProvider domain.MFAType
}
type mfaOTPFormData struct {
Resend bool `schema:"resend"`
Code string `schema:"code"`
SelectedProvider domain.MFAType `schema:"selectedProvider"`
Provider domain.MFAType `schema:"provider"`
}
func OTPLink(origin, authRequestID, code string, provider domain.MFAType) string {
return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%d", externalLink(origin), EndpointMFAOTPVerify, QueryAuthRequestID, authRequestID, queryCode, code, querySelectedProvider, provider)
}
// renderOTPVerification renders the OTP verification for SMS and Email based on the passed MFAType.
// It will send a new code to either phone or email first.
func (l *Login) handleOTPVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, selectedProvider domain.MFAType, err error) {
if err != nil {
l.renderOTPVerification(w, r, authReq, providers, selectedProvider, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
var sendCode func(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) error
switch selectedProvider {
case domain.MFATypeOTPSMS:
sendCode = l.authRepo.SendMFAOTPSMS
case domain.MFATypeOTPEmail:
sendCode = l.authRepo.SendMFAOTPEmail
// another type should never be passed, but just making sure
case domain.MFATypeU2F,
domain.MFATypeTOTP,
domain.MFATypeU2FUserVerification:
l.renderError(w, r, authReq, err)
return
}
err = sendCode(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID)
l.renderOTPVerification(w, r, authReq, providers, selectedProvider, err)
}
func (l *Login) renderOTPVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, selectedProvider domain.MFAType, err error) {
var errID, errMessage string
if err != nil {
errID, errMessage = l.getErrorMessage(r, err)
}
data := &mfaOTPData{
userData: l.getUserData(r, authReq, "VerifyMFAU2F.Title", "VerifyMFAU2F.Description", errID, errMessage),
MFAProviders: removeSelectedProviderFromList(providers, selectedProvider),
SelectedProvider: selectedProvider,
}
l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), l.renderer.Templates[tmplOTPVerification], data, nil)
}
// handleOTPVerificationCheck handles form submissions of the OTP verification.
// On successful code verification, the check will be added to the auth request.
// A user is also able to request a code resend or choose another provider.
func (l *Login) handleOTPVerificationCheck(w http.ResponseWriter, r *http.Request) {
formData := new(mfaOTPFormData)
authReq, err := l.getAuthRequestAndParseData(r, formData)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
step, ok := authReq.PossibleSteps[0].(*domain.MFAVerificationStep)
if !ok {
l.renderError(w, r, authReq, err)
return
}
if formData.Resend {
l.handleOTPVerification(w, r, authReq, step.MFAProviders, formData.SelectedProvider, nil)
return
}
if formData.Code == "" {
l.renderMFAVerifySelected(w, r, authReq, step, formData.Provider, nil)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
var actionType authMethod
var verifyCode func(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) error
switch formData.SelectedProvider {
case domain.MFATypeOTPSMS:
actionType = authMethodOTPSMS
verifyCode = l.authRepo.VerifyMFAOTPSMS
case domain.MFATypeOTPEmail:
actionType = authMethodOTPEmail
verifyCode = l.authRepo.VerifyMFAOTPEmail
// another type should never be passed, but just making sure
case domain.MFATypeU2F,
domain.MFATypeTOTP,
domain.MFATypeU2FUserVerification:
l.renderOTPVerification(w, r, authReq, step.MFAProviders, formData.SelectedProvider, err)
return
}
err = verifyCode(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, formData.Code, authReq.ID, userAgentID, domain.BrowserInfoFromRequest(r))
metadata, actionErr := l.runPostInternalAuthenticationActions(authReq, r, actionType, err)
if err == nil && actionErr == nil && len(metadata) > 0 {
_, err = l.command.BulkSetUserMetadata(r.Context(), authReq.UserID, authReq.UserOrgID, metadata...)
} else if actionErr != nil && err == nil {
err = actionErr
}
if err != nil {
l.renderOTPVerification(w, r, authReq, step.MFAProviders, formData.SelectedProvider, err)
return
}
l.renderNextStep(w, r, authReq)
}

View File

@@ -54,9 +54,11 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
tmplPasswordlessRegistration: "passwordless_registration.html",
tmplPasswordlessRegistrationDone: "passwordless_registration_done.html",
tmplPasswordlessPrompt: "passwordless_prompt.html",
tmplMFAVerify: "mfa_verify_otp.html",
tmplMFAVerify: "mfa_verify_totp.html",
tmplMFAPrompt: "mfa_prompt.html",
tmplMFAInitVerify: "mfa_init_otp.html",
tmplMFASMSInit: "mfa_init_otp_sms.html",
tmplOTPVerification: "mfa_verify_otp.html",
tmplMFAU2FInit: "mfa_init_u2f.html",
tmplU2FVerification: "mfa_verification_u2f.html",
tmplMFAInitDone: "mfa_init_done.html",
@@ -170,6 +172,12 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
"mfaInitVerifyUrl": func() string {
return path.Join(r.pathPrefix, EndpointMFAInitVerify)
},
"mfaInitSMSVerifyUrl": func() string {
return path.Join(r.pathPrefix, EndpointMFASMSInitVerify)
},
"mfaOTPVerifyUrl": func() string {
return path.Join(r.pathPrefix, EndpointMFAOTPVerify)
},
"mfaInitU2FVerifyUrl": func() string {
return path.Join(r.pathPrefix, EndpointMFAInitU2FVerify)
},

View File

@@ -31,6 +31,8 @@ const (
EndpointMFAVerify = "/mfa/verify"
EndpointMFAPrompt = "/mfa/prompt"
EndpointMFAInitVerify = "/mfa/init/verify"
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
EndpointMFAOTPVerify = "/mfa/otp/verify"
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
EndpointU2FVerification = "/mfa/u2f/verify"
EndpointMailVerification = "/mail/verification"
@@ -89,6 +91,9 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPromptSelection).Methods(http.MethodGet)
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPrompt).Methods(http.MethodPost)
router.HandleFunc(EndpointMFAInitVerify, login.handleMFAInitVerify).Methods(http.MethodPost)
router.HandleFunc(EndpointMFASMSInitVerify, login.handleRegisterSMSCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointMFAOTPVerify, login.handleOTPVerificationCheck).Methods(http.MethodGet)
router.HandleFunc(EndpointMFAOTPVerify, login.handleOTPVerificationCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointMFAInitU2FVerify, login.handleRegisterU2F).Methods(http.MethodPost)
router.HandleFunc(EndpointU2FVerification, login.handleU2FVerification).Methods(http.MethodPost)
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)

View File

@@ -85,6 +85,8 @@ InitMFAPrompt:
потребителски акаунт.
Provider0: 'Приложение за удостоверяване (напр. Google/Microsoft Authenticator, Authy)'
Provider1: 'Зависи от устройството (напр. FaceID, Windows Hello, пръстов отпечатък)'
Provider3: OTP SMS
Provider4: OTP имейл
NextButtonText: следващия
SkipButtonText: пропуснете
InitMFAOTP:
@@ -98,6 +100,15 @@ InitMFAOTP:
CodeLabel: Код
NextButtonText: следващия
CancelButtonText: анулиране
InitMFAOTPSMS:
Title: 2-факторна проверка
DescriptionPhone: Създайте своя 2-фактор. Въведете телефонния си номер, за да го потвърдите.
DescriptionCode: Създайте своя 2-фактор. Въведете получения код, за да потвърдите своя телефонен номер.
PhoneLabel: Тайна
CodeLabel: Код
EditButtonText: редактиране
ResendButtonText: код за препращане
NextButtonText: следващия
InitMFAU2F:
Title: Добавете ключ за сигурност
Description: >-
@@ -118,6 +129,8 @@ InitMFADone:
MFAProvider:
Provider0: 'Приложение за удостоверяване (напр. Google/Microsoft Authenticator, Authy)'
Provider1: 'Зависи от устройството (напр. FaceID, Windows Hello, пръстов отпечатък)'
Provider3: OTP SMS
Provider4: OTP имейл
ChooseOther: или изберете друга опция
VerifyMFAOTP:
Title: Проверете 2-фактора

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: 2-Faktor-Authentifizierung gibt dir eine zusätzliche Sicherheit für dein Benutzerkonto. Damit stellst du sicher, dass nur du Zugriff auf deinen Account hast.
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
Provider3: OTP SMS
Provider4: OTP Email
NextButtonText: weiter
SkipButtonText: überspringen
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: weiter
CancelButtonText: abbrechen
InitMFAOTPSMS:
Title: 2-Faktor hinzufügen
DescriptionPhone: Erstelle deinen 2-Faktor. Gib deine Telefonnummer ein, um sie zu verifizieren.
DescriptionCode: Erstelle deinen 2-Faktor. Gib den erhaltenen Code ein um deinen Telefonnummer zu verifizieren.
PhoneLabel: Telefonnummer
CodeLabel: Code
EditButtonText: bearbeiten
ResendButtonText: Code erneut senden
NextButtonText: weiter
InitMFAU2F:
Title: Sicherheitsschlüssel hinzufügen
Description: Ein Sicherheitsschlüssel ist eine Verifizierungsmethode, die in Ihrem Telefon integriert sein kann, Bluetooth verwenden oder direkt an den USB-Anschluss Ihres Computers angeschlossen werden.
@@ -118,9 +130,18 @@ InitMFADone:
MFAProvider:
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
Provider3: OTP SMS
Provider4: OTP Email
ChooseOther: oder wähle eine andere Option aus
VerifyMFAOTP:
Title: 2-Faktor verifizieren
Description: Verifiziere deinen Zweitfaktor
CodeLabel: Code
ResendButtonText: Code erneut senden
NextButtonText: next
VerifyOTP:
Title: 2-Faktor verifizieren
Description: Verifiziere deinen Zweitfaktor
CodeLabel: Code

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: 2-factor authentication gives you an additional security for your user account. This ensures that only you have access to your account.
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
Provider3: OTP SMS
Provider4: OTP Email
NextButtonText: next
SkipButtonText: skip
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: next
CancelButtonText: cancel
InitMFAOTPSMS:
Title: 2-Factor Verification
DescriptionPhone: Create your 2-factor. Enter your phone number to verify it.
DescriptionCode: Create your 2-factor. Enter the received code to verify your phone number.
PhoneLabel: Phone
CodeLabel: Code
EditButtonText: edit
ResendButtonText: resend code
NextButtonText: next
InitMFAU2F:
Title: Add security key
Description: A security key is a verification method that can be built into your phone, use Bluetooth, or plug directly into your computer's USB port.
@@ -110,7 +122,7 @@ InitMFAU2F:
ErrorRetry: Retry, create a new challenge or choose a different method.
InitMFADone:
Title: Security key verified
Title: 2-factor verified
Description: Awesome! You just successfully set up your 2-factor and made your account way more secure. The Factor has to be entered on each login.
NextButtonText: next
CancelButtonText: cancel
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
Provider3: OTP SMS
Provider4: OTP Email
ChooseOther: or choose another option
VerifyMFAOTP:
@@ -126,6 +140,13 @@ VerifyMFAOTP:
CodeLabel: Code
NextButtonText: next
VerifyOTP:
Title: Verify 2-Factor
Description: Verify your second factor
CodeLabel: Code
ResendButtonText: resend code
NextButtonText: next
VerifyMFAU2F:
Title: 2-Factor Verification
Description: Verify your 2-Factor with the registered device (e.g FaceID, Windows Hello, Fingerprint)

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: La autenticación de doble factor te proporciona seguridad adicional para tu cuenta de usuario. Ésta asegura que solo tú tienes acceso a tu cuenta.
Provider0: App autenticadora (p.e Google/Microsoft Authenticator, Authy)
Provider1: Dependiente de un dispositivo (p.e FaceID, Windows Hello, Huella dactilar)
Provider3: OTP SMS
Provider4: OTP email
NextButtonText: siguiente
SkipButtonText: saltar
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: siguiente
CancelButtonText: cancelar
InitMFASMS:
Title: Verificación de doble factor
DescriptionPhone: Crea tu doble factor de autenticación. Introduce tu número de teléfono para verificarlo.
DescriptionCode: Crea tu doble factor de autenticación. Ingrese el código recibido para verificar su número de teléfono.
PhoneLabel: Número de teléfono
CodeLabel: Código
EditButtonText: editar
ResendButtonText: reenviar código
NextButtonText: siguiente
InitMFAU2F:
Title: Añadir clave de seguridad
Description: Una clave de seguridad es un método de verificación que puede integrarse en tu teléfono móvil, con Bluetooth, o conectándolo directamente en el puerto USB de tu ordenador.
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: App autenticadora (p.e Google/Microsoft Authenticator, Authy)
Provider1: Dependiente de un dispositivo (p.e FaceID, Windows Hello, Huella dactilar)
Provider3: OTP SMS
Provider4: OTP email
ChooseOther: o elige otra opción
VerifyMFAOTP:

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: L'authentification à deux facteurs vous offre une sécurité supplémentaire pour votre compte d'utilisateur. Vous êtes ainsi assuré d'être le seul à avoir accès à votre compte.
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
Provider3: OTP SMS
Provider4: OTP e-mail
NextButtonText: Suivant
SkipButtonText: Passer
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: Suivant
CancelButtonText: Annuler
InitMFASMS:
Title: Vérification à deux facteurs
DescriptionPhone: Créez votre 2-facteurs. Entrez votre numéro de téléphone pour le vérifier.
DescriptionCode: Créez votre 2-facteurs. Entrez le code reçu pour vérifier votre numéro de téléphone.
PhoneLabel: Numéro de téléphone
CodeLabel: Code
EditButtonText: Modifier
ResendButtonText: Renvoyer le code
NextButtonText: Suivant
InitMFAU2F:
Title: Ajouter une clé de sécurité
Description: Une clé de sécurité est une méthode de vérification qui peut être intégrée à votre téléphone, utiliser Bluetooth ou se brancher directement sur le port USB de votre ordinateur.
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
Provider3: OTP SMS
Provider4: OTP e-mail
ChooseOther: ou choisissez une autre option
VerifyMFAOTP:

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: L'autenticazione a due fattori offre un'ulteriore sicurezza al vostro account utente. Questo garantisce che solo voi possiate accedere al vostro account.
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
Provider3: OTP SMS
Provider4: OTP e-mail
NextButtonText: Avanti
SkipButtonText: salta
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: Avanti
CancelButtonText: annulla
InitMFASMS:
Title: Verificazione a 2 fattori
DescriptionPhone: Crea il tuo 2 fattori. Inserisci il tuo numero di telefono per verificarlo.
DescriptionCode: Crea il tuo 2 fattori. Inserisci il codice ricevuto per verificare il tuo numero di telefono.
PhoneLabel: Numero di telefono
CodeLabel: Codice
EditButtonText: Modifica
ResendButtonText: Reinvia codice
NextButtonText: Avanti
InitMFAU2F:
Title: Aggiungi chiave di sicurezza
Description: Una chiave di sicurezza è un metodo di verifica che può essere integrato nel telefono, utilizzare il Bluetooth o collegarlo direttamente alla porta USB del computer.
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
Provider3: OTP SMS
Provider4: OTP e-mail
ChooseOther: o scegli un'altra opzione
VerifyMFAOTP:

View File

@@ -82,6 +82,8 @@ InitMFAPrompt:
Description: 二要素認証でアカウントのセキュリティを強化します。
Provider0: 認証アプリGoogle/Microsoft Authenticator、Authyなど
Provider1: デバイス依存FaceID、Windows Hello、指紋など
Provider3: OTP SMS
Provider4: OTPメール
NextButtonText: 次へ
SkipButtonText: スキップ
@@ -94,6 +96,16 @@ InitMFAOTP:
NextButtonText: 次へ
CancelButtonText: キャンセル
InitMFASMS:
Title: 二要素認証
DescriptionPhone: 二要素認証を作成します。確認するには電話番号を入力してください。
DescriptionCode: 二要素認証を作成します。受信したコードを入力して電話番号を確認します。
PhoneLabel: 電話番号
CodeLabel: コード
EditButtonText: 編集
ResendButtonText: コードを再送信
NextButtonText: 次へ
InitMFAU2F:
Title: セキュリティキーの追加
Description: セキュリティキーは、携帯電話への組み込みや、Bluetoothの使用、パソコンのUSBポートに直接差し込むことなどで認証する方法です。
@@ -111,6 +123,8 @@ InitMFADone:
MFAProvider:
Provider0: AuthenticatorアプリGoogle/Microsoft Authenticator、Authyなど
Provider1: デバイス依存FaceID、Windows Hello、指紋など
Provider3: OTP SMS
Provider4: OTPメール
ChooseOther: または、他のオプションを選択
VerifyMFAOTP:

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: 2-факторската автентикација ви дава дополнителна безбедност за вашата корисничка сметка. Ова обезбедува само вие да имате пристап до вашата сметка.
Provider0: Апликација за автентикација (на пример Google/Microsoft Authenticator, Authy)
Provider1: Во зависност од вашиот уред (на пример FaceID, Windows Hello, отпечаток од прст)
Provider3: ОТП СМС
Provider4: ОТП е-пошта
NextButtonText: следно
SkipButtonText: прескокни
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: следно
CancelButtonText: откажи
InitMFASMS:
Title: Потврда на 2-факторска автентикација
DescriptionPhone: Направете двофакторна автентикација. Внесете го вашиот телефонски број за да го потврдите.
DescriptionCode: Направете двофакторна автентикација. Внесете го примениот код за да го потврдите вашиот телефонски број.
PhoneLabel: Телефонски број
CodeLabel: Код
EditButtonText: Уредување
ResendButtonText: повторно испрати код
NextButtonText: следно
InitMFAU2F:
Title: Додајте безбедносен клуч
Description: Безбедносниот клуч е метод на верификација кој може да се интегрира во вашиот телефон, да користи Bluetooth или директно да се поврзе во USB приклучокот на вашиот компјутер.
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: Апликација за автентикација (на пример Google/Microsoft Authenticator, Authy)
Provider1: Во зависност од вашиот уред (на пример FaceID, Windows Hello, отпечаток од прст)
Provider3: ОТП СМС
Provider4: ОТП е-пошта
ChooseOther: или изберете друга опција
VerifyMFAOTP:

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: 2-etapowe uwierzytelnianie daje Ci dodatkową ochronę dla Twojego konta użytkownika. Dzięki temu masz pewność, że tylko Ty masz dostęp do swojego konta.
Provider0: Aplikacja uwierzytelniająca (np. Google/Microsoft Authenticator, Authy)
Provider1: Zależny od urządzenia (np. FaceID, Windows Hello, Odcisk palca)
Provider3: OTP SMS
Provider4: OTP e-mail
NextButtonText: dalej
SkipButtonText: pomiń
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: dalej
CancelButtonText: anuluj
InitMFASMS:
Title: Weryfikacja 2-etapowa
DescriptionPhone: Utwórz uwierzytelnianie dwuskładnikowe. Wprowadź swój numer telefonu, aby go zweryfikować.
DescriptionCode: Utwórz uwierzytelnianie dwuskładnikowe. Wprowadź otrzymany kod, aby zweryfikować swój numer telefonu.
PhoneLabel: Numer telefonu
CodeLabel: Kod
EditButtonText: edytować
ResendButtonText: wyślij kod ponownie
NextButtonText: dalej
InitMFAU2F:
Title: Dodaj klucz zabezpieczeń
Description: Klucz zabezpieczeń to metoda weryfikacji, która może być zintegrowana z twoim telefonem, używająca Bluetooth lub podłączana bezpośrednio do portu USB komputera.
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: Aplikacja uwierzytelniająca (np. Google/Microsoft Authenticator, Authy)
Provider1: Zależny od urządzenia (np. FaceID, Windows Hello, Odcisk palca)
Provider3: OTP SMS
Provider4: OTP e-mail
ChooseOther: lub wybierz inną opcję
VerifyMFAOTP:

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: A autenticação de 2 fatores fornece uma segurança adicional para sua conta de usuário. Isso garante que apenas você tenha acesso à sua conta.
Provider0: Aplicativo de autenticação (por exemplo, Google/Microsoft Authenticator, Authy)
Provider1: Dependente do dispositivo (por exemplo, FaceID, Windows Hello, Impressão digital)
Provider3: OTP SMS
Provider4: OTP e-mail
NextButtonText: próximo
SkipButtonText: pular
@@ -101,6 +103,16 @@ InitMFAOTP:
NextButtonText: próximo
CancelButtonText: cancelar
InitMFASMS:
Title: Verificação de 2 fatores
DescriptionPhone: Crie sua verificação de 2 fatores. Digite seu número de telefone para verificá-lo.
DescriptionCode: Crie sua verificação de 2 fatores. Digite o código recebido para verificar seu número de telefone.
PhoneLabel: Número de telefone
CodeLabel: Código
EditButtonText: editar
ResendButtonText: reenviar código
NextButtonText: próximo
InitMFAU2F:
Title: Adicionar chave de segurança
Description: Uma chave de segurança é um método de verificação que pode ser incorporado ao seu telefone, usar Bluetooth ou conectar diretamente à porta USB do seu computador.
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: Aplicativo de autenticação (por exemplo, Google/Microsoft Authenticator, Authy)
Provider1: Dependente do dispositivo (por exemplo, FaceID, Windows Hello, Impressão digital)
Provider3: OTP SMS
Provider4: OTP e-mail
ChooseOther: ou escolha outra opção
VerifyMFAOTP:

View File

@@ -89,6 +89,8 @@ InitMFAPrompt:
Description: 两步验证为您的账户提供了额外的安全保障。这确保只有你能访问你的账户。
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹
Provider3: 一次性密码短信
Provider4: 一次性密码电子邮件
NextButtonText: 继续
SkipButtonText: 跳过
@@ -96,6 +98,16 @@ InitMFAOTP:
Title: 双因素验证
Description: 创建你的双因素。如果你还没有,请下载一个认证器应用程序。
OTPDescription: 使用您的身份验证器应用程序(例如 Google Authenticator扫描代码或复制密码并在下方插入生成的代码。
PhoneLabel: 电话号码
CodeLabel: 验证码
EditButtonText: 编辑
ResendButtonText: 重发代码
NextButtonText: 继续
InitMFASMS:
Title: 双因素验证
DescriptionPhone: 创建双因素身份验证。输入您的电话号码进行验证。
DescriptionCode: 创建双因素身份验证。输入收到的代码以验证您的电话号码。
SecretLabel: 秘钥
CodeLabel: 验证码
NextButtonText: 继续
@@ -118,6 +130,8 @@ InitMFADone:
MFAProvider:
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹
Provider3: 一次性密码短信
Provider4: 一次性密码电子邮件
ChooseOther: 或选择其他选项
VerifyMFAOTP:

View File

@@ -0,0 +1,6 @@
let form = document.getElementsByTagName('form')[0];
let editButton = document.getElementById('edit');
editButton.addEventListener('click', function () {
form.submit();
});

View File

@@ -1,15 +1,16 @@
@mixin lgn-mfa-base {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
margin: 2rem 0;
margin: 1rem 0;
.mfa {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
padding: 0 0.5rem;
padding: 1rem 0.5rem;
label {
display: flex;

View File

@@ -0,0 +1,63 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "InitMFAOTPSMS.Title"}}</h1>
{{ template "user-profile" . }}
{{if .Edit}}
<p>{{t "InitMFAOTPSMS.DescriptionPhone"}}</p>
{{else}}
<p>{{t "InitMFAOTPSMS.DescriptionCode"}}</p>
{{end}}
</div>
<form action="{{ mfaInitSMSVerifyUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="mfaType" value="{{ .MFAType }}" />
<input type="hidden" name="phone" value="{{ .Phone }}" />
<div class="fields">
{{if .Edit}}
<div class="field">
<label class="lgn-label" for="newPhone">{{t "InitMFAOTPSMS.PhoneLabel"}}</label>
<input class="lgn-input" type="tel" id="newPhone" name="newPhone" autocomplete="off" value="{{.Phone}}" autofocus required>
</div>
{{else}}
<div class="field lgn-actions">
<p>{{.Phone}}</p>
<span class="fill-space"></span>
<button type="button" id="edit" name="edit" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.EditButtonText"}}</button>
</div>
{{end}}
{{if and .Phone (not .Edit) }}
<div class="field">
<label class="lgn-label" for="code">{{t "InitMFAOTPSMS.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="one-time-code" autofocus required>
</div>
{{end}}
</div>
{{ template "error-message" .}}
<div class="lgn-actions lgn-reverse-order">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ mfaPromptChangeUrl .AuthReqID .MFAType }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "InitMFAOTPSMS.NextButtonText"}}</button>
{{if and .Phone (not .Edit) }}
<span class="fill-space"></span>
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.ResendButtonText"}}</button>
{{end}}
</div>
</form>
<script src="{{ resourceUrl "scripts/edit.js" }}"></script>
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}

View File

@@ -34,6 +34,16 @@
<img width="100px" height="100px" alt="OTP" src="{{ resourceUrl
"images/mfa/mfa-u2f.svg" }}" />
</div>
{{ end }} {{ if eq $provider 3 }}
<div class="mfa-img">
<img width="100px" height="100px" alt="OTP SMS" src="{{ resourceUrl
"images/mfa/mfa-u2f.svg" }}" /> // TODO: image
</div>
{{ end }}{{ if eq $provider 4 }}
<div class="mfa-img">
<img width="100px" height="100px" alt="OTP Email" src="{{ resourceUrl
"images/mfa/mfa-u2f.svg" }}" /> // TODO: image
</div>
{{ end }}
<span>{{ $providerName }} </span>
</label>

View File

@@ -1,34 +1,37 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "VerifyMFAOTP.Title"}}</h1>
<h1>{{t "VerifyOTP.Title"}}</h1>
{{ template "user-profile" . }}
<p>{{t "VerifyMFAOTP.Description"}}</p>
<p>{{t "VerifyOTP.Description"}}</p>
</div>
<form action="{{ mfaVerifyUrl }}" method="POST">
<form action="{{ mfaOTPVerifyUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="mfaType" value="{{ .SelectedMFAProvider }}" />
<input type="hidden" name="selectedProvider" value="{{ .SelectedProvider }}" />
<div class="fields">
<label class="lgn-label" for="code">{{t "VerifyMFAOTP.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="off" autofocus required>
<label class="lgn-label" for="code">{{t "VerifyOTP.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="one-time-code" autofocus required>
</div>
{{ template "error-message" .}}
<div class="lgn-actions">
<div class="lgn-actions lgn-reverse-order">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyOTP.NextButtonText"}}</button>
<span class="fill-space"></span>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyMFAOTP.NextButtonText"}}</button>
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "VerifyOTP.ResendButtonText"}}</button>
</div>
{{ if .MFAProviders }}
@@ -45,4 +48,4 @@
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@@ -0,0 +1,48 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "VerifyMFAOTP.Title"}}</h1>
{{ template "user-profile" . }}
<p>{{t "VerifyMFAOTP.Description"}}</p>
</div>
<form action="{{ mfaVerifyUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="mfaType" value="{{ .SelectedMFAProvider }}" />
<div class="fields">
<label class="lgn-label" for="code">{{t "VerifyMFAOTP.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="off" autofocus required>
</div>
{{ template "error-message" .}}
<div class="lgn-actions">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<span class="fill-space"></span>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyMFAOTP.NextButtonText"}}</button>
</div>
{{ if .MFAProviders }}
<div class="lgn-mfa-other">
<p>{{t "MFAProvider.ChooseOther"}}</p>
{{ range $provider := .MFAProviders}}
{{ $providerName := (t (printf "MFAProvider.Provider%v" $provider)) }}
<button class="lgn-stroked-button" type="submit" name="provider" value="{{$provider}}"
formnovalidate>{{$providerName}}</button>
{{ end }}
</div>
{{ end }}
</form>
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}