github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/services/mfa/mfa.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package mfa
     5  
     6  import (
     7  	b32 "encoding/base32"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  
    13  	"github.com/dgryski/dgoogauth"
    14  	"github.com/vnforks/kid/v5/model"
    15  	"github.com/vnforks/kid/v5/services/configservice"
    16  	"github.com/vnforks/kid/v5/store"
    17  	"github.com/mattermost/rsc/qr"
    18  )
    19  
    20  const (
    21  	MFA_SECRET_SIZE = 20
    22  )
    23  
    24  type Mfa struct {
    25  	ConfigService configservice.ConfigService
    26  	Store         store.Store
    27  }
    28  
    29  func New(configService configservice.ConfigService, store store.Store) Mfa {
    30  	return Mfa{configService, store}
    31  }
    32  
    33  func (m *Mfa) checkConfig() *model.AppError {
    34  	if !*m.ConfigService.Config().ServiceSettings.EnableMultifactorAuthentication {
    35  		return model.NewAppError("checkConfig", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
    36  	}
    37  
    38  	return nil
    39  }
    40  
    41  func getIssuerFromUrl(uri string) string {
    42  	issuer := "Mattermost"
    43  	siteUrl := strings.TrimSpace(uri)
    44  
    45  	if len(siteUrl) > 0 {
    46  		siteUrl = strings.TrimPrefix(siteUrl, "https://")
    47  		siteUrl = strings.TrimPrefix(siteUrl, "http://")
    48  		issuer = strings.TrimPrefix(siteUrl, "www.")
    49  	}
    50  
    51  	return url.QueryEscape(issuer)
    52  }
    53  
    54  func (m *Mfa) GenerateSecret(user *model.User) (string, []byte, *model.AppError) {
    55  	if err := m.checkConfig(); err != nil {
    56  		return "", nil, err
    57  	}
    58  
    59  	issuer := getIssuerFromUrl(*m.ConfigService.Config().ServiceSettings.SiteURL)
    60  
    61  	secret := b32.StdEncoding.EncodeToString([]byte(model.NewRandomString(MFA_SECRET_SIZE)))
    62  
    63  	authLink := fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s", issuer, user.Email, secret, issuer)
    64  
    65  	code, err := qr.Encode(authLink, qr.H)
    66  
    67  	if err != nil {
    68  		return "", nil, model.NewAppError("GenerateQrCode", "mfa.generate_qr_code.create_code.app_error", nil, err.Error(), http.StatusInternalServerError)
    69  	}
    70  
    71  	img := code.PNG()
    72  
    73  	if err := m.Store.User().UpdateMfaSecret(user.Id, secret); err != nil {
    74  		return "", nil, model.NewAppError("GenerateQrCode", "mfa.generate_qr_code.save_secret.app_error", nil, err.Error(), http.StatusInternalServerError)
    75  	}
    76  
    77  	return secret, img, nil
    78  }
    79  
    80  func (m *Mfa) Activate(user *model.User, token string) *model.AppError {
    81  	if err := m.checkConfig(); err != nil {
    82  		return err
    83  	}
    84  
    85  	otpConfig := &dgoogauth.OTPConfig{
    86  		Secret:      user.MfaSecret,
    87  		WindowSize:  3,
    88  		HotpCounter: 0,
    89  	}
    90  
    91  	trimmedToken := strings.TrimSpace(token)
    92  
    93  	ok, err := otpConfig.Authenticate(trimmedToken)
    94  	if err != nil {
    95  		return model.NewAppError("Activate", "mfa.activate.authenticate.app_error", nil, err.Error(), http.StatusInternalServerError)
    96  	}
    97  
    98  	if !ok {
    99  		return model.NewAppError("Activate", "mfa.activate.bad_token.app_error", nil, "", http.StatusUnauthorized)
   100  	}
   101  
   102  	if appErr := m.Store.User().UpdateMfaActive(user.Id, true); appErr != nil {
   103  		return model.NewAppError("Activate", "mfa.activate.save_active.app_error", nil, appErr.Error(), http.StatusInternalServerError)
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func (m *Mfa) Deactivate(userId string) *model.AppError {
   110  	if err := m.checkConfig(); err != nil {
   111  		return err
   112  	}
   113  
   114  	schan := make(chan *model.AppError, 1)
   115  	go func() {
   116  		schan <- m.Store.User().UpdateMfaSecret(userId, "")
   117  		close(schan)
   118  	}()
   119  
   120  	if err := m.Store.User().UpdateMfaActive(userId, false); err != nil {
   121  		return model.NewAppError("Deactivate", "mfa.deactivate.save_active.app_error", nil, err.Error(), http.StatusInternalServerError)
   122  	}
   123  
   124  	if err := <-schan; err != nil {
   125  		return model.NewAppError("Deactivate", "mfa.deactivate.save_secret.app_error", nil, err.Error(), http.StatusInternalServerError)
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func (m *Mfa) ValidateToken(secret, token string) (bool, *model.AppError) {
   132  	if err := m.checkConfig(); err != nil {
   133  		return false, err
   134  	}
   135  
   136  	otpConfig := &dgoogauth.OTPConfig{
   137  		Secret:      secret,
   138  		WindowSize:  3,
   139  		HotpCounter: 0,
   140  	}
   141  
   142  	trimmedToken := strings.TrimSpace(token)
   143  	ok, err := otpConfig.Authenticate(trimmedToken)
   144  	if err != nil {
   145  		return false, model.NewAppError("ValidateToken", "mfa.validate_token.authenticate.app_error", nil, err.Error(), http.StatusBadRequest)
   146  	}
   147  
   148  	return ok, nil
   149  }