mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 09:23:40 +08:00
feat: add TOTP multi-factor authentication (#2014)
* feat: add totp multi-factor authentication * feat: add license * feat:i18n and update yarn.lock * feat:i18n * fix: i18n
This commit is contained in:
@ -17,7 +17,6 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/beego/beego"
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -58,10 +57,7 @@ func (c *ApiController) MfaSetupInitiate() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer := beego.AppConfig.String("appname")
|
mfaProps, err := MfaUtil.Initiate(c.Ctx, user.GetId())
|
||||||
accountName := user.GetId()
|
|
||||||
|
|
||||||
mfaProps, err := MfaUtil.Initiate(c.Ctx, issuer, accountName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
4
go.mod
4
go.mod
@ -42,7 +42,7 @@ require (
|
|||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1
|
github.com/pkoukk/tiktoken-go v0.1.1
|
||||||
github.com/plutov/paypal/v4 v4.7.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/prometheus/client_golang v1.11.1
|
github.com/prometheus/client_golang v1.11.1
|
||||||
github.com/prometheus/client_model v0.2.0
|
github.com/prometheus/client_model v0.2.0
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
@ -59,7 +59,7 @@ require (
|
|||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||||
github.com/xorm-io/builder v0.3.13 // indirect
|
github.com/xorm-io/builder v0.3.13
|
||||||
github.com/xorm-io/core v0.7.4
|
github.com/xorm-io/core v0.7.4
|
||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
|
7
go.sum
7
go.sum
@ -105,6 +105,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||||
@ -495,10 +497,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
|
github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw=
|
github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw=
|
||||||
github.com/plutov/paypal/v4 v4.7.0 h1:6TRvYD4ny6yQfHaABeStNf43GFM1wpW5jU/XEDGQmq0=
|
|
||||||
github.com/plutov/paypal/v4 v4.7.0/go.mod h1:D56boafCRGcF/fEM0w282kj0fCDKIyrwOPX/Te1jCmw=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
|
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
@ -595,7 +597,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MfaRecoveryCodesSession = "mfa_recovery_codes"
|
||||||
|
|
||||||
type MfaSessionData struct {
|
type MfaSessionData struct {
|
||||||
UserId string
|
UserId string
|
||||||
}
|
}
|
||||||
@ -37,10 +39,10 @@ type MfaProps struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MfaInterface interface {
|
type MfaInterface interface {
|
||||||
SetupVerify(ctx *context.Context, passCode string) error
|
Initiate(ctx *context.Context, userId string) (*MfaProps, error)
|
||||||
Verify(passCode string) error
|
SetupVerify(ctx *context.Context, passcode string) error
|
||||||
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
|
|
||||||
Enable(ctx *context.Context, user *User) error
|
Enable(ctx *context.Context, user *User) error
|
||||||
|
Verify(passcode string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -58,11 +60,11 @@ const (
|
|||||||
func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface {
|
func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface {
|
||||||
switch mfaType {
|
switch mfaType {
|
||||||
case SmsType:
|
case SmsType:
|
||||||
return NewSmsTwoFactor(config)
|
return NewSmsMfaUtil(config)
|
||||||
case EmailType:
|
case EmailType:
|
||||||
return NewEmailTwoFactor(config)
|
return NewEmailMfaUtil(config)
|
||||||
case TotpType:
|
case TotpType:
|
||||||
return nil
|
return NewTotpMfaUtil(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -97,23 +99,9 @@ func MfaRecover(user *User, recoveryCode string) error {
|
|||||||
func GetAllMfaProps(user *User, masked bool) []*MfaProps {
|
func GetAllMfaProps(user *User, masked bool) []*MfaProps {
|
||||||
mfaProps := []*MfaProps{}
|
mfaProps := []*MfaProps{}
|
||||||
|
|
||||||
if user.MfaPhoneEnabled {
|
for _, mfaType := range []string{SmsType, EmailType, TotpType} {
|
||||||
mfaProps = append(mfaProps, user.GetMfaProps(SmsType, masked))
|
mfaProps = append(mfaProps, user.GetMfaProps(mfaType, masked))
|
||||||
} else {
|
|
||||||
mfaProps = append(mfaProps, &MfaProps{
|
|
||||||
Enabled: false,
|
|
||||||
MfaType: SmsType,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if user.MfaEmailEnabled {
|
|
||||||
mfaProps = append(mfaProps, user.GetMfaProps(EmailType, masked))
|
|
||||||
} else {
|
|
||||||
mfaProps = append(mfaProps, &MfaProps{
|
|
||||||
Enabled: false,
|
|
||||||
MfaType: EmailType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return mfaProps
|
return mfaProps
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +109,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
|
|||||||
mfaProps := &MfaProps{}
|
mfaProps := &MfaProps{}
|
||||||
|
|
||||||
if mfaType == SmsType {
|
if mfaType == SmsType {
|
||||||
|
if !user.MfaPhoneEnabled {
|
||||||
|
return &MfaProps{
|
||||||
|
Enabled: false,
|
||||||
|
MfaType: mfaType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mfaProps = &MfaProps{
|
mfaProps = &MfaProps{
|
||||||
Enabled: user.MfaPhoneEnabled,
|
Enabled: user.MfaPhoneEnabled,
|
||||||
MfaType: mfaType,
|
MfaType: mfaType,
|
||||||
@ -132,6 +127,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
|
|||||||
mfaProps.Secret = user.Phone
|
mfaProps.Secret = user.Phone
|
||||||
}
|
}
|
||||||
} else if mfaType == EmailType {
|
} else if mfaType == EmailType {
|
||||||
|
if !user.MfaEmailEnabled {
|
||||||
|
return &MfaProps{
|
||||||
|
Enabled: false,
|
||||||
|
MfaType: mfaType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mfaProps = &MfaProps{
|
mfaProps = &MfaProps{
|
||||||
Enabled: user.MfaEmailEnabled,
|
Enabled: user.MfaEmailEnabled,
|
||||||
MfaType: mfaType,
|
MfaType: mfaType,
|
||||||
@ -142,9 +144,22 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
|
|||||||
mfaProps.Secret = user.Email
|
mfaProps.Secret = user.Email
|
||||||
}
|
}
|
||||||
} else if mfaType == TotpType {
|
} else if mfaType == TotpType {
|
||||||
|
if user.TotpSecret == "" {
|
||||||
|
return &MfaProps{
|
||||||
|
Enabled: false,
|
||||||
|
MfaType: mfaType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mfaProps = &MfaProps{
|
mfaProps = &MfaProps{
|
||||||
|
Enabled: true,
|
||||||
MfaType: mfaType,
|
MfaType: mfaType,
|
||||||
}
|
}
|
||||||
|
if masked {
|
||||||
|
mfaProps.Secret = ""
|
||||||
|
} else {
|
||||||
|
mfaProps.Secret = user.TotpSecret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.PreferredMfaType == mfaType {
|
if user.PreferredMfaType == mfaType {
|
||||||
@ -158,8 +173,9 @@ func DisabledMultiFactorAuth(user *User) error {
|
|||||||
user.RecoveryCodes = []string{}
|
user.RecoveryCodes = []string{}
|
||||||
user.MfaPhoneEnabled = false
|
user.MfaPhoneEnabled = false
|
||||||
user.MfaEmailEnabled = false
|
user.MfaEmailEnabled = false
|
||||||
|
user.TotpSecret = ""
|
||||||
|
|
||||||
_, err := UpdateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled"}, user.IsAdminUser())
|
_, err := updateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled", "totp_secret"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -18,26 +18,24 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MfaSmsCountryCodeSession = "mfa_country_code"
|
MfaSmsCountryCodeSession = "mfa_country_code"
|
||||||
MfaSmsDestSession = "mfa_dest"
|
MfaSmsDestSession = "mfa_dest"
|
||||||
MfaSmsRecoveryCodesSession = "mfa_recovery_codes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SmsMfa struct {
|
type SmsMfa struct {
|
||||||
Config *MfaProps
|
Config *MfaProps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mfa *SmsMfa) Initiate(ctx *context.Context, name string, secret string) (*MfaProps, error) {
|
func (mfa *SmsMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
||||||
recoveryCode := uuid.NewString()
|
recoveryCode := uuid.NewString()
|
||||||
|
|
||||||
err := ctx.Input.CruSession.Set(MfaSmsRecoveryCodesSession, []string{recoveryCode})
|
err := ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -63,9 +61,9 @@ func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
|
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
|
||||||
recoveryCodes := ctx.Input.CruSession.Get(MfaSmsRecoveryCodesSession).([]string)
|
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
|
||||||
if len(recoveryCodes) == 0 {
|
if len(recoveryCodes) == 0 {
|
||||||
return fmt.Errorf("recovery codes is empty")
|
return fmt.Errorf("recovery codes is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
columns := []string{"recovery_codes", "preferred_mfa_type"}
|
columns := []string{"recovery_codes", "preferred_mfa_type"}
|
||||||
@ -111,7 +109,7 @@ func (mfa *SmsMfa) Verify(passCode string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
|
func NewSmsMfaUtil(config *MfaProps) *SmsMfa {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = &MfaProps{
|
config = &MfaProps{
|
||||||
MfaType: SmsType,
|
MfaType: SmsType,
|
||||||
@ -122,7 +120,7 @@ func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmailTwoFactor(config *MfaProps) *SmsMfa {
|
func NewEmailMfaUtil(config *MfaProps) *SmsMfa {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = &MfaProps{
|
config = &MfaProps{
|
||||||
MfaType: EmailType,
|
MfaType: EmailType,
|
||||||
|
133
object/mfa_totp.go
Normal file
133
object/mfa_totp.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/beego/beego"
|
||||||
|
"github.com/beego/beego/context"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pquerna/otp"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MfaTotpSecretSession = "mfa_totp_secret"
|
||||||
|
|
||||||
|
type TotpMfa struct {
|
||||||
|
Config *MfaProps
|
||||||
|
period uint
|
||||||
|
secretSize uint
|
||||||
|
digits otp.Digits
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
||||||
|
issuer := beego.AppConfig.String("appname")
|
||||||
|
if issuer == "" {
|
||||||
|
issuer = "casdoor"
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := totp.Generate(totp.GenerateOpts{
|
||||||
|
Issuer: issuer,
|
||||||
|
AccountName: userId,
|
||||||
|
Period: mfa.period,
|
||||||
|
SecretSize: mfa.secretSize,
|
||||||
|
Digits: mfa.digits,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.Input.CruSession.Set(MfaTotpSecretSession, key.Secret())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveryCode := uuid.NewString()
|
||||||
|
err = ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mfaProps := MfaProps{
|
||||||
|
MfaType: mfa.Config.MfaType,
|
||||||
|
RecoveryCodes: []string{recoveryCode},
|
||||||
|
Secret: key.Secret(),
|
||||||
|
URL: key.URL(),
|
||||||
|
}
|
||||||
|
return &mfaProps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
|
||||||
|
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession).(string)
|
||||||
|
result := totp.Validate(passcode, secret)
|
||||||
|
|
||||||
|
if result {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("totp passcode error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
|
||||||
|
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
|
||||||
|
if len(recoveryCodes) == 0 {
|
||||||
|
return fmt.Errorf("recovery codes is missing")
|
||||||
|
}
|
||||||
|
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession).(string)
|
||||||
|
if secret == "" {
|
||||||
|
return fmt.Errorf("totp secret is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := []string{"recovery_codes", "preferred_mfa_type", "totp_secret"}
|
||||||
|
|
||||||
|
user.RecoveryCodes = append(user.RecoveryCodes, recoveryCodes...)
|
||||||
|
user.TotpSecret = secret
|
||||||
|
if user.PreferredMfaType == "" {
|
||||||
|
user.PreferredMfaType = mfa.Config.MfaType
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := updateUser(user.GetId(), user, columns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mfa *TotpMfa) Verify(passcode string) error {
|
||||||
|
result := totp.Validate(passcode, mfa.Config.Secret)
|
||||||
|
|
||||||
|
if result {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("totp passcode error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTotpMfaUtil(config *MfaProps) *TotpMfa {
|
||||||
|
if config == nil {
|
||||||
|
config = &MfaProps{
|
||||||
|
MfaType: TotpType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TotpMfa{
|
||||||
|
Config: config,
|
||||||
|
period: 30,
|
||||||
|
secretSize: 20,
|
||||||
|
digits: otp.DigitsSix,
|
||||||
|
}
|
||||||
|
}
|
@ -161,6 +161,7 @@ type User struct {
|
|||||||
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||||
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
|
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
|
||||||
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"`
|
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"`
|
||||||
|
TotpSecret string `xorm:"varchar(100)" json:"totpSecret,omitempty"`
|
||||||
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
|
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
|
||||||
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
|
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
|
||||||
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
|
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
|
||||||
@ -432,16 +433,19 @@ func GetMaskedUser(user *User, errs ...error) (*User, error) {
|
|||||||
if user.AccessSecret != "" {
|
if user.AccessSecret != "" {
|
||||||
user.AccessSecret = "***"
|
user.AccessSecret = "***"
|
||||||
}
|
}
|
||||||
if user.RecoveryCodes != nil {
|
|
||||||
user.RecoveryCodes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.ManagedAccounts != nil {
|
if user.ManagedAccounts != nil {
|
||||||
for _, manageAccount := range user.ManagedAccounts {
|
for _, manageAccount := range user.ManagedAccounts {
|
||||||
manageAccount.Password = "***"
|
manageAccount.Password = "***"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.TotpSecret != "" {
|
||||||
|
user.TotpSecret = ""
|
||||||
|
}
|
||||||
|
if user.RecoveryCodes != nil {
|
||||||
|
user.RecoveryCodes = nil
|
||||||
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +24,6 @@
|
|||||||
"i18next": "^19.8.9",
|
"i18next": "^19.8.9",
|
||||||
"libphonenumber-js": "^1.10.19",
|
"libphonenumber-js": "^1.10.19",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"qrcode.react": "^3.1.0",
|
|
||||||
"qs": "^6.10.2",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-app-polyfill": "^3.0.0",
|
"react-app-polyfill": "^3.0.0",
|
||||||
"react-codemirror2": "^7.2.1",
|
"react-codemirror2": "^7.2.1",
|
||||||
|
@ -16,8 +16,8 @@ import React, {useState} from "react";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Button, Input} from "antd";
|
import {Button, Input} from "antd";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import {EmailMfaType, SmsMfaType} from "./MfaSetupPage";
|
import {EmailMfaType, RecoveryMfaType, SmsMfaType} from "./MfaSetupPage";
|
||||||
import {MfaSmsVerifyForm, mfaAuth} from "./MfaVerifyForm";
|
import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaAuth} from "./MfaVerifyForm";
|
||||||
|
|
||||||
export const NextMfa = "NextMfa";
|
export const NextMfa = "NextMfa";
|
||||||
export const RequiredMfa = "RequiredMfa";
|
export const RequiredMfa = "RequiredMfa";
|
||||||
@ -60,7 +60,7 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mfaType === SmsMfaType || mfaType === EmailMfaType) {
|
if (mfaType !== RecoveryMfaType) {
|
||||||
return (
|
return (
|
||||||
<div style={{width: 300, height: 350}}>
|
<div style={{width: 300, height: 350}}>
|
||||||
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
||||||
@ -69,12 +69,18 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
|||||||
<div style={{marginBottom: 24}}>
|
<div style={{marginBottom: 24}}>
|
||||||
{i18next.t("mfa:Multi-factor authentication description")}
|
{i18next.t("mfa:Multi-factor authentication description")}
|
||||||
</div>
|
</div>
|
||||||
<MfaSmsVerifyForm
|
{mfaType === SmsMfaType || mfaType === EmailMfaType ? (
|
||||||
mfaProps={mfaProps}
|
<MfaSmsVerifyForm
|
||||||
method={mfaAuth}
|
mfaProps={mfaProps}
|
||||||
onFinish={verify}
|
method={mfaAuth}
|
||||||
application={application}
|
onFinish={verify}
|
||||||
/>
|
application={application}
|
||||||
|
/>) : (
|
||||||
|
<MfaTotpVerifyForm
|
||||||
|
mfaProps={mfaProps}
|
||||||
|
onFinish={verify}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<span style={{float: "right"}}>
|
<span style={{float: "right"}}>
|
||||||
{i18next.t("mfa:Have problems?")}
|
{i18next.t("mfa:Have problems?")}
|
||||||
<a onClick={() => {
|
<a onClick={() => {
|
||||||
@ -85,7 +91,7 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (mfaType === "recovery") {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div style={{width: 300, height: 350}}>
|
<div style={{width: 300, height: 350}}>
|
||||||
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {Button, Col, Form, Input, Result, Row, Steps} from "antd";
|
import {Button, Col, Form, Input, Result, Row, Steps} from "antd";
|
||||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
@ -26,6 +26,7 @@ import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaSetup} from "./MfaVerifyForm";
|
|||||||
export const EmailMfaType = "email";
|
export const EmailMfaType = "email";
|
||||||
export const SmsMfaType = "sms";
|
export const SmsMfaType = "sms";
|
||||||
export const TotpMfaType = "app";
|
export const TotpMfaType = "app";
|
||||||
|
export const RecoveryMfaType = "recovery";
|
||||||
|
|
||||||
function CheckPasswordForm({user, onSuccess, onFail}) {
|
function CheckPasswordForm({user, onSuccess, onFail}) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@ -76,29 +77,11 @@ function CheckPasswordForm({user, onSuccess, onFail}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MfaVerifyForm({mfaType, application, user, onSuccess, onFail}) {
|
export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [mfaProps, setMfaProps] = useState({mfaType: mfaType});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (mfaType === SmsMfaType) {
|
|
||||||
setMfaProps({
|
|
||||||
mfaType: mfaType,
|
|
||||||
secret: user.phone,
|
|
||||||
countryCode: user.countryCode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mfaType === EmailMfaType) {
|
|
||||||
setMfaProps({
|
|
||||||
mfaType: mfaType,
|
|
||||||
secret: user.email,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [mfaType]);
|
|
||||||
|
|
||||||
const onFinish = ({passcode}) => {
|
const onFinish = ({passcode}) => {
|
||||||
const data = {passcode, mfaType: mfaType, ...user};
|
const data = {passcode, mfaType: mfaProps.mfaType, ...user};
|
||||||
MfaBackend.MfaSetupVerify(data)
|
MfaBackend.MfaSetupVerify(data)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
@ -115,14 +98,14 @@ export function MfaVerifyForm({mfaType, application, user, onSuccess, onFail}) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mfaType === null || mfaType === undefined || mfaProps.secret === undefined) {
|
if (mfaProps === undefined || mfaProps === null) {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mfaType === SmsMfaType || mfaType === EmailMfaType) {
|
if (mfaProps.mfaType === SmsMfaType || mfaProps.mfaType === EmailMfaType) {
|
||||||
return <MfaSmsVerifyForm onFinish={onFinish} application={application} method={mfaSetup} mfaProps={mfaProps} />;
|
return <MfaSmsVerifyForm mfaProps={mfaProps} onFinish={onFinish} application={application} method={mfaSetup} user={user} />;
|
||||||
} else if (mfaType === TotpMfaType) {
|
} else if (mfaProps.mfaType === TotpMfaType) {
|
||||||
return <MfaTotpVerifyForm onFinish={onFinish} />;
|
return <MfaTotpVerifyForm mfaProps={mfaProps} onFinish={onFinish} />;
|
||||||
} else {
|
} else {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
@ -183,7 +166,7 @@ class MfaSetupPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
if (this.state.isAuthenticated === true && this.state.mfaProps === null) {
|
if (this.state.isAuthenticated === true && (this.state.mfaProps === null || this.state.mfaType !== prevState.mfaType)) {
|
||||||
MfaBackend.MfaSetupInitiate({
|
MfaBackend.MfaSetupInitiate({
|
||||||
mfaType: this.state.mfaType,
|
mfaType: this.state.mfaType,
|
||||||
...this.getUser(),
|
...this.getUser(),
|
||||||
@ -226,18 +209,20 @@ class MfaSetupPage extends React.Component {
|
|||||||
renderStep() {
|
renderStep() {
|
||||||
switch (this.state.current) {
|
switch (this.state.current) {
|
||||||
case 0:
|
case 0:
|
||||||
return <CheckPasswordForm
|
return (
|
||||||
user={this.getUser()}
|
<CheckPasswordForm
|
||||||
onSuccess={() => {
|
user={this.getUser()}
|
||||||
this.setState({
|
onSuccess={() => {
|
||||||
current: this.state.current + 1,
|
this.setState({
|
||||||
isAuthenticated: true,
|
current: this.state.current + 1,
|
||||||
});
|
isAuthenticated: true,
|
||||||
}}
|
});
|
||||||
onFail={(res) => {
|
}}
|
||||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
onFail={(res) => {
|
||||||
}}
|
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||||
/>;
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 1:
|
case 1:
|
||||||
if (!this.state.isAuthenticated) {
|
if (!this.state.isAuthenticated) {
|
||||||
return null;
|
return null;
|
||||||
@ -246,7 +231,7 @@ class MfaSetupPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<MfaVerifyForm
|
<MfaVerifyForm
|
||||||
mfaType={this.state.mfaType}
|
mfaProps={this.state.mfaProps}
|
||||||
application={this.state.application}
|
application={this.state.application}
|
||||||
user={this.props.account}
|
user={this.props.account}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
@ -261,11 +246,6 @@ class MfaSetupPage extends React.Component {
|
|||||||
<Col span={24} style={{display: "flex", justifyContent: "left"}}>
|
<Col span={24} style={{display: "flex", justifyContent: "left"}}>
|
||||||
{(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null :
|
{(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null :
|
||||||
<Button type={"link"} onClick={() => {
|
<Button type={"link"} onClick={() => {
|
||||||
if (this.state.isPromptPage) {
|
|
||||||
this.props.history.push(`/prompt/${this.state.application.name}?promptType=mfa&mfaType=${EmailMfaType}`);
|
|
||||||
} else {
|
|
||||||
this.props.history.push(`/mfa-authentication/setup?mfaType=${EmailMfaType}`);
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
mfaType: EmailMfaType,
|
mfaType: EmailMfaType,
|
||||||
});
|
});
|
||||||
@ -275,17 +255,21 @@ class MfaSetupPage extends React.Component {
|
|||||||
{
|
{
|
||||||
(this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null :
|
(this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null :
|
||||||
<Button type={"link"} onClick={() => {
|
<Button type={"link"} onClick={() => {
|
||||||
if (this.state.isPromptPage) {
|
|
||||||
this.props.history.push(`/prompt/${this.state.application.name}?promptType=mfa&mfaType=${SmsMfaType}`);
|
|
||||||
} else {
|
|
||||||
this.props.history.push(`/mfa-authentication/setup?mfaType=${SmsMfaType}`);
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
mfaType: SmsMfaType,
|
mfaType: SmsMfaType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}>{i18next.t("mfa:Use SMS")}</Button>
|
}>{i18next.t("mfa:Use SMS")}</Button>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
(this.state.mfaType === TotpMfaType) ? null :
|
||||||
|
<Button type={"link"} onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
mfaType: TotpMfaType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}>{i18next.t("mfa:Use Authenticator App")}</Button>
|
||||||
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -294,18 +278,20 @@ class MfaSetupPage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
|
return (
|
||||||
onSuccess={() => {
|
<EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
|
||||||
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
onSuccess={() => {
|
||||||
if (this.state.isPromptPage && this.state.redirectUri) {
|
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
||||||
Setting.goToLink(this.state.redirectUri);
|
if (this.state.isPromptPage && this.state.redirectUri) {
|
||||||
} else {
|
Setting.goToLink(this.state.redirectUri);
|
||||||
Setting.goToLink("/account");
|
} else {
|
||||||
}
|
Setting.goToLink("/account");
|
||||||
}}
|
}
|
||||||
onFail={(res) => {
|
}}
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
onFail={(res) => {
|
||||||
}} />;
|
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
||||||
|
}} />
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -328,24 +314,20 @@ class MfaSetupPage extends React.Component {
|
|||||||
<Col span={24} style={{justifyContent: "center"}}>
|
<Col span={24} style={{justifyContent: "center"}}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<div style={{textAlign: "center", fontSize: "28px"}}>
|
<p style={{textAlign: "center", fontSize: "28px"}}>
|
||||||
{i18next.t("mfa:Protect your account with Multi-factor authentication")}</div>
|
{i18next.t("mfa:Protect your account with Multi-factor authentication")}</p>
|
||||||
<div style={{textAlign: "center", fontSize: "16px", marginTop: "10px"}}>{i18next.t("mfa:Each time you sign in to your Account, you'll need your password and a authentication code")}</div>
|
<p style={{textAlign: "center", fontSize: "16px", marginTop: "10px"}}>{i18next.t("mfa:Each time you sign in to your Account, you'll need your password and a authentication code")}</p>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Col span={24}>
|
|
||||||
<Steps current={this.state.current}
|
|
||||||
items={[
|
|
||||||
{title: i18next.t("mfa:Verify Password"), icon: <UserOutlined />},
|
|
||||||
{title: i18next.t("mfa:Verify Code"), icon: <KeyOutlined />},
|
|
||||||
{title: i18next.t("general:Enable"), icon: <CheckOutlined />},
|
|
||||||
]}
|
|
||||||
style={{width: "90%", maxWidth: "500px", margin: "auto", marginTop: "80px",
|
|
||||||
}} >
|
|
||||||
</Steps>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Steps current={this.state.current}
|
||||||
|
items={[
|
||||||
|
{title: i18next.t("mfa:Verify Password"), icon: <UserOutlined />},
|
||||||
|
{title: i18next.t("mfa:Verify Code"), icon: <KeyOutlined />},
|
||||||
|
{title: i18next.t("general:Enable"), icon: <CheckOutlined />},
|
||||||
|
]}
|
||||||
|
style={{width: "90%", maxWidth: "500px", margin: "auto", marginTop: "50px",
|
||||||
|
}} >
|
||||||
|
</Steps>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||||
<div style={{marginTop: "10px", textAlign: "center"}}>
|
<div style={{marginTop: "10px", textAlign: "center"}}>
|
||||||
|
@ -12,23 +12,33 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import {Button, Col, Form, Input, Row} from "antd";
|
import {Button, Col, Form, Input, QRCode, Space} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {CopyOutlined, UserOutlined} from "@ant-design/icons";
|
import {CopyOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
import {SendCodeInput} from "../common/SendCodeInput";
|
import {SendCodeInput} from "../common/SendCodeInput";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import React from "react";
|
import React, {useEffect} from "react";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
|
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
|
||||||
import {EmailMfaType} from "./MfaSetupPage";
|
import {EmailMfaType, SmsMfaType} from "./MfaSetupPage";
|
||||||
|
|
||||||
export const mfaAuth = "mfaAuth";
|
export const mfaAuth = "mfaAuth";
|
||||||
export const mfaSetup = "mfaSetup";
|
export const mfaSetup = "mfaSetup";
|
||||||
|
|
||||||
export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
|
export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method, user}) => {
|
||||||
const [dest, setDest] = React.useState(mfaProps.secret ?? "");
|
const [dest, setDest] = React.useState(mfaProps.secret ?? "");
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mfaProps.mfaType === SmsMfaType) {
|
||||||
|
setDest(user.phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mfaProps.mfaType === EmailMfaType) {
|
||||||
|
setDest(user.email);
|
||||||
|
}
|
||||||
|
}, [mfaProps.mfaType]);
|
||||||
|
|
||||||
const isEmail = () => {
|
const isEmail = () => {
|
||||||
return mfaProps.mfaType === EmailMfaType;
|
return mfaProps.mfaType === EmailMfaType;
|
||||||
};
|
};
|
||||||
@ -42,9 +52,9 @@ export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
|
|||||||
countryCode: mfaProps.countryCode,
|
countryCode: mfaProps.countryCode,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mfaProps.secret !== "" ?
|
{dest !== "" ?
|
||||||
<div style={{marginBottom: 20, textAlign: "left", gap: 8}}>
|
<div style={{marginBottom: 20, textAlign: "left", gap: 8}}>
|
||||||
{isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {mfaProps.secret}
|
{isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {dest}
|
||||||
</div> :
|
</div> :
|
||||||
(<React.Fragment>
|
(<React.Fragment>
|
||||||
<p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") :
|
<p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") :
|
||||||
@ -114,44 +124,49 @@ export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
|
|||||||
export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => {
|
export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const renderSecret = () => {
|
||||||
|
if (!mfaProps.secret) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||||
|
<QRCode
|
||||||
|
errorLevel="H"
|
||||||
|
value={mfaProps.url}
|
||||||
|
icon={"https://cdn.casdoor.com/static/favicon.png"}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<p style={{textAlign: "center"}}>{i18next.t("mfa:Scan the QR code with your Authenticator App")}</p>
|
||||||
|
<p style={{textAlign: "center"}}>{i18next.t("mfa:Or copy the secret to your Authenticator App")}</p>
|
||||||
|
<Col span={24}>
|
||||||
|
<Space>
|
||||||
|
<Input value={mfaProps.secret} />
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
shape="round"
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
copy(`${mfaProps.secret}`);
|
||||||
|
Setting.showMessage(
|
||||||
|
"success",
|
||||||
|
i18next.t("mfa:Multi-factor secret to clipboard successfully")
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
style={{width: "300px"}}
|
style={{width: "300px"}}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
>
|
>
|
||||||
<Row type="flex" justify="center" align="middle">
|
{renderSecret()}
|
||||||
<Col>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row type="flex" justify="center" align="middle">
|
|
||||||
<Col>
|
|
||||||
{Setting.getLabel(
|
|
||||||
i18next.t("mfa:Multi-factor secret"),
|
|
||||||
i18next.t("mfa:Multi-factor secret - Tooltip")
|
|
||||||
)}
|
|
||||||
:
|
|
||||||
</Col>
|
|
||||||
<Col>
|
|
||||||
<Input value={mfaProps.secret} />
|
|
||||||
</Col>
|
|
||||||
<Col>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
shape="round"
|
|
||||||
icon={<CopyOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
copy(`${mfaProps.secret}`);
|
|
||||||
Setting.showMessage(
|
|
||||||
"success",
|
|
||||||
i18next.t("mfa:Multi-factor secret to clipboard successfully")
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="passcode"
|
name="passcode"
|
||||||
rules={[{required: true, message: "Please input your passcode"}]}
|
rules={[{required: true, message: "Please input your passcode"}]}
|
||||||
@ -162,7 +177,6 @@ export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => {
|
|||||||
placeholder={i18next.t("mfa:Passcode")}
|
placeholder={i18next.t("mfa:Passcode")}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
style={{marginTop: 24}}
|
style={{marginTop: 24}}
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "If you are unable to access your device, enter your recovery code to verify your identity",
|
"Multi-factor recover description": "If you are unable to access your device, enter your recovery code to verify your identity",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Métodos de vários fatores",
|
"Multi-factor methods": "Métodos de vários fatores",
|
||||||
"Multi-factor recover": "Recuperação de vários fatores",
|
"Multi-factor recover": "Recuperação de vários fatores",
|
||||||
"Multi-factor recover description": "Se você não conseguir acessar seu dispositivo, insira seu código de recuperação para verificar sua identidade",
|
"Multi-factor recover description": "Se você não conseguir acessar seu dispositivo, insira seu código de recuperação para verificar sua identidade",
|
||||||
"Multi-factor secret": "Segredo de vários fatores",
|
|
||||||
"Multi-factor secret - Tooltip": "Segredo de vários fatores - Dica de ferramenta",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Segredo de vários fatores copiado para a área de transferência com sucesso",
|
"Multi-factor secret to clipboard successfully": "Segredo de vários fatores copiado para a área de transferência com sucesso",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Código de acesso",
|
"Passcode": "Código de acesso",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Guarde este código de recuperação. Quando o seu dispositivo não puder fornecer um código de autenticação, você poderá redefinir a autenticação mfa usando este código de recuperação",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Guarde este código de recuperação. Quando o seu dispositivo não puder fornecer um código de autenticação, você poderá redefinir a autenticação mfa usando este código de recuperação",
|
||||||
"Protect your account with Multi-factor authentication": "Proteja sua conta com autenticação de vários fatores",
|
"Protect your account with Multi-factor authentication": "Proteja sua conta com autenticação de vários fatores",
|
||||||
"Recovery code": "Código de recuperação",
|
"Recovery code": "Código de recuperação",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Definir preferido",
|
"Set preferred": "Definir preferido",
|
||||||
"Setup": "Configuração",
|
"Setup": "Configuração",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Usar código de verificação SMS",
|
"Use SMS verification code": "Usar código de verificação SMS",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "Multi-factor methods",
|
"Multi-factor methods": "Multi-factor methods",
|
||||||
"Multi-factor recover": "Multi-factor recover",
|
"Multi-factor recover": "Multi-factor recover",
|
||||||
"Multi-factor recover description": "Multi-factor recover description",
|
"Multi-factor recover description": "Multi-factor recover description",
|
||||||
"Multi-factor secret": "Multi-factor secret",
|
|
||||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
|
||||||
|
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||||
"Passcode": "Passcode",
|
"Passcode": "Passcode",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
|
||||||
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
|
||||||
"Recovery code": "Recovery code",
|
"Recovery code": "Recovery code",
|
||||||
|
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
|
||||||
"Set preferred": "Set preferred",
|
"Set preferred": "Set preferred",
|
||||||
"Setup": "Setup",
|
"Setup": "Setup",
|
||||||
|
"Use Authenticator App": "Use Authenticator App",
|
||||||
"Use Email": "Use Email",
|
"Use Email": "Use Email",
|
||||||
"Use SMS": "Use SMS",
|
"Use SMS": "Use SMS",
|
||||||
"Use SMS verification code": "Use SMS verification code",
|
"Use SMS verification code": "Use SMS verification code",
|
||||||
|
@ -434,17 +434,18 @@
|
|||||||
"Multi-factor methods": "多因素认证方式",
|
"Multi-factor methods": "多因素认证方式",
|
||||||
"Multi-factor recover": "重置多因素认证",
|
"Multi-factor recover": "重置多因素认证",
|
||||||
"Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份",
|
"Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份",
|
||||||
"Multi-factor secret": "多因素密钥",
|
|
||||||
"Multi-factor secret - Tooltip": "多因素密钥 - Tooltip",
|
|
||||||
"Multi-factor secret to clipboard successfully": "多因素密钥已复制到剪贴板",
|
"Multi-factor secret to clipboard successfully": "多因素密钥已复制到剪贴板",
|
||||||
|
"Or copy the secret to your Authenticator App": "或者将这个密钥复制到你的身份验证应用中",
|
||||||
"Passcode": "认证码",
|
"Passcode": "认证码",
|
||||||
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式",
|
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式",
|
||||||
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式",
|
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式",
|
||||||
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证",
|
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证",
|
||||||
"Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户",
|
"Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户",
|
||||||
"Recovery code": "恢复码",
|
"Recovery code": "恢复码",
|
||||||
|
"Scan the QR code with your Authenticator App": "用你的身份验证应用扫描二维码",
|
||||||
"Set preferred": "设为首选",
|
"Set preferred": "设为首选",
|
||||||
"Setup": "设置",
|
"Setup": "设置",
|
||||||
|
"Use Authenticator App": "使用身份验证应用",
|
||||||
"Use Email": "使用电子邮件",
|
"Use Email": "使用电子邮件",
|
||||||
"Use SMS": "使用短信",
|
"Use SMS": "使用短信",
|
||||||
"Use SMS verification code": "使用手机或电子邮件发送验证码认证",
|
"Use SMS verification code": "使用手机或电子邮件发送验证码认证",
|
||||||
|
3021
web/yarn.lock
3021
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user