casdoor/object/mfa_totp.go
Yaodong Yu a60be2b2ab
feat: refactor MFA code and fix no-session bug (#2676)
* refactor: refactor mfa

* refactor: refactor mfa

* refactor: refactor mfa

* lint

* chore: reduce wait time
2024-02-06 20:17:59 +08:00

129 lines
2.7 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 (
"errors"
"time"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
const (
MfaTotpPeriodInSeconds = 30
)
type TotpMfa struct {
*MfaProps
period uint
secretSize uint
digits otp.Digits
}
func (mfa *TotpMfa) Initiate(userId string) (*MfaProps, error) {
//issuer := beego.AppConfig.String("appname")
//if issuer == "" {
// issuer = "casdoor"
//}
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
}
mfaProps := MfaProps{
MfaType: mfa.MfaType,
Secret: key.Secret(),
URL: key.URL(),
}
return &mfaProps, nil
}
func (mfa *TotpMfa) SetupVerify(passcode string) error {
result, err := totp.ValidateCustom(passcode, mfa.Secret, time.Now().UTC(), totp.ValidateOpts{
Period: MfaTotpPeriodInSeconds,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
if err != nil {
return err
}
if result {
return nil
} else {
return errors.New("totp passcode error")
}
}
func (mfa *TotpMfa) Enable(user *User) error {
columns := []string{"recovery_codes", "preferred_mfa_type", "totp_secret"}
user.RecoveryCodes = append(user.RecoveryCodes, mfa.RecoveryCodes...)
user.TotpSecret = mfa.Secret
if user.PreferredMfaType == "" {
user.PreferredMfaType = mfa.MfaType
}
_, err := updateUser(user.GetId(), user, columns)
if err != nil {
return err
}
return nil
}
func (mfa *TotpMfa) Verify(passcode string) error {
result, err := totp.ValidateCustom(passcode, mfa.Secret, time.Now().UTC(), totp.ValidateOpts{
Period: MfaTotpPeriodInSeconds,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
if err != nil {
return err
}
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{
MfaProps: config,
period: MfaTotpPeriodInSeconds,
secretSize: 20,
digits: otp.DigitsSix,
}
}