casdoor/object/mfa.go
Yaodong Yu eb39e9e044
feat: add multi-factor authentication (MFA) feature (#1800)
* feat: add two-factor authentication interface and api

* merge

* feat: add Two-factor authentication accountItem and two-factor api in frontend

* feat: add basic 2fa setup UI

* rebase

* feat: finish the two-factor authentication

* rebase

* feat: support recover code

* chore: fix eslint error

* feat: support multiple sms account

* fix: client application login

* fix: lint

* Update authz.go

* Update mfa.go

* fix: support phone

* fix: i18n

* fix: i18n

* fix: support preferred mfa methods

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-05 21:23:59 +08:00

108 lines
2.6 KiB
Go

// 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 (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/beego/beego/context"
)
type MfaSessionData struct {
UserId string
}
type MfaProps struct {
Id string `json:"id"`
IsPreferred bool `json:"isPreferred"`
AuthType string `json:"type" form:"type"`
Secret string `json:"secret,omitempty"`
CountryCode string `json:"countryCode,omitempty"`
URL string `json:"url,omitempty"`
RecoveryCodes []string `json:"recoveryCodes,omitempty"`
}
type MfaInterface interface {
SetupVerify(ctx *context.Context, passCode string) error
Verify(passCode string) error
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
Enable(ctx *context.Context, user *User) error
}
const (
SmsType = "sms"
TotpType = "app"
)
const (
MfaSessionUserId = "MfaSessionUserId"
NextMfa = "NextMfa"
)
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
switch providerType {
case SmsType:
return NewSmsTwoFactor(config)
case TotpType:
return nil
}
return nil
}
func RecoverTfs(user *User, recoveryCode string) error {
hit := false
twoFactor := user.GetPreferMfa(false)
if len(twoFactor.RecoveryCodes) == 0 {
return fmt.Errorf("do not have recovery codes")
}
for _, code := range twoFactor.RecoveryCodes {
if code == recoveryCode {
hit = true
break
}
}
if !hit {
return fmt.Errorf("recovery code not found")
}
affected := UpdateUser(user.GetId(), user, []string{"two_factor_auth"}, user.IsAdminUser())
if !affected {
return fmt.Errorf("")
}
return nil
}
func GetMaskedProps(props *MfaProps) *MfaProps {
maskedProps := &MfaProps{
AuthType: props.AuthType,
Id: props.Id,
IsPreferred: props.IsPreferred,
}
if props.AuthType == SmsType {
if !util.IsEmailValid(props.Secret) {
maskedProps.Secret = util.GetMaskedPhone(props.Secret)
} else {
maskedProps.Secret = util.GetMaskedEmail(props.Secret)
}
}
return maskedProps
}