github.com/rr250/mattermost-server@v5.11.1+incompatible/services/mfa/mfa.go (about)

     1  // Copyright (c) 2016 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/mattermost/mattermost-server/model"
    15  	"github.com/mattermost/mattermost-server/services/configservice"
    16  	"github.com/mattermost/mattermost-server/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 result := <-m.Store.User().UpdateMfaSecret(user.Id, secret); result.Err != nil {
    74  		return "", nil, model.NewAppError("GenerateQrCode", "mfa.generate_qr_code.save_secret.app_error", nil, result.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 result := <-m.Store.User().UpdateMfaActive(user.Id, true); result.Err != nil {
   103  		return model.NewAppError("Activate", "mfa.activate.save_active.app_error", nil, result.Err.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  	achan := m.Store.User().UpdateMfaActive(userId, false)
   115  	schan := m.Store.User().UpdateMfaSecret(userId, "")
   116  
   117  	if result := <-achan; result.Err != nil {
   118  		return model.NewAppError("Deactivate", "mfa.deactivate.save_active.app_error", nil, result.Err.Error(), http.StatusInternalServerError)
   119  	}
   120  
   121  	if result := <-schan; result.Err != nil {
   122  		return model.NewAppError("Deactivate", "mfa.deactivate.save_secret.app_error", nil, result.Err.Error(), http.StatusInternalServerError)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (m *Mfa) ValidateToken(secret, token string) (bool, *model.AppError) {
   129  	if err := m.checkConfig(); err != nil {
   130  		return false, err
   131  	}
   132  
   133  	otpConfig := &dgoogauth.OTPConfig{
   134  		Secret:      secret,
   135  		WindowSize:  3,
   136  		HotpCounter: 0,
   137  	}
   138  
   139  	trimmedToken := strings.TrimSpace(token)
   140  	ok, err := otpConfig.Authenticate(trimmedToken)
   141  	if err != nil {
   142  		return false, model.NewAppError("ValidateToken", "mfa.validate_token.authenticate.app_error", nil, err.Error(), http.StatusBadRequest)
   143  	}
   144  
   145  	return ok, nil
   146  }