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 }