Browse Source

feat(go): allow resetting mfa;

Vishal Dalwadi 6 months ago
parent
commit
ecbb1fce6a
4 changed files with 52 additions and 15 deletions
  1. 23 12
      controllers/user.go
  2. 23 0
      logic/jwts.go
  3. 3 2
      models/structs.go
  4. 3 1
      models/user_mgmt.go

+ 23 - 12
controllers/user.go

@@ -10,6 +10,7 @@ import (
 	"github.com/gravitl/netmaker/db"
 	"image/png"
 	"net/http"
+	"net/url"
 	"reflect"
 	"time"
 
@@ -470,15 +471,6 @@ func initiateTOTPSetup(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	user.TOTPSecret = key.Secret()
-	err = logic.UpsertUser(*user)
-	if err != nil {
-		err = fmt.Errorf("error upserting user: %v", err)
-		logger.Log(0, err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-
 	qrCodeImg, err := key.Image(200, 200)
 	if err != nil {
 		err = fmt.Errorf("failed to generate totp key: %v", err)
@@ -499,8 +491,9 @@ func initiateTOTPSetup(w http.ResponseWriter, r *http.Request) {
 	qrCode := "data:image/png;base64," + base64.StdEncoding.EncodeToString(qrCodePng.Bytes())
 
 	logic.ReturnSuccessResponseWithJson(w, r, models.TOTPInitiateResponse{
-		OTPAuthURL: key.URL(),
-		QRCode:     qrCode,
+		OTPAuthURL:          key.URL(),
+		OTPAuthURLSignature: logic.GenerateOTPAuthURLSignature(key.URL()),
+		QRCode:              qrCode,
 	}, "totp setup initiated")
 }
 
@@ -523,6 +516,13 @@ func completeTOTPSetup(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if !logic.VerifyOTPAuthURL(req.OTPAuthURL, req.OTPAuthURLSignature) {
+		err = fmt.Errorf("otp auth url signature mismatch")
+		logger.Log(0, err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
 	user, err := logic.GetUser(username)
 	if err != nil {
 		logger.Log(0, "failed to get user: ", err.Error())
@@ -538,8 +538,19 @@ func completeTOTPSetup(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if totp.Validate(req.TOTP, user.TOTPSecret) {
+	otpAuthURL, err := url.Parse(req.OTPAuthURL)
+	if err != nil {
+		err = fmt.Errorf("error parsing otp auth url: %v", err)
+		logger.Log(0, err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	totpSecret := otpAuthURL.Query().Get("secret")
+
+	if totp.Validate(req.TOTP, totpSecret) {
 		user.IsMFAEnabled = true
+		user.TOTPSecret = totpSecret
 		err = logic.UpsertUser(*user)
 		if err != nil {
 			err = fmt.Errorf("error upserting user: %v", err)

+ 23 - 0
logic/jwts.go

@@ -2,6 +2,9 @@ package logic
 
 import (
 	"context"
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"strings"
@@ -105,6 +108,9 @@ func CreateUserJWT(username string, role models.UserRoleID) (response string, er
 	return "", err
 }
 
+// CreatePreAuthToken generate a jwt token to be used as intermediate
+// token after primary-factor authentication but before secondary-factor
+// authentication.
 func CreatePreAuthToken(username string) (string, error) {
 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
 		Issuer:    "Netmaker",
@@ -117,6 +123,23 @@ func CreatePreAuthToken(username string) (string, error) {
 	return token.SignedString(jwtSecretKey)
 }
 
+func GenerateOTPAuthURLSignature(url string) string {
+	signer := hmac.New(sha256.New, jwtSecretKey)
+	signer.Write([]byte(url))
+	return base64.StdEncoding.EncodeToString(signer.Sum(nil))
+}
+
+func VerifyOTPAuthURL(url, signature string) bool {
+	signer := hmac.New(sha256.New, jwtSecretKey)
+	signer.Write([]byte(url))
+	signatureBytes, err := base64.StdEncoding.DecodeString(string(signer.Sum(nil)))
+	if err != nil {
+		return false
+	}
+
+	return signature == string(signatureBytes)
+}
+
 func GetUserNameFromToken(authtoken string) (username string, err error) {
 	claims := &models.UserClaims{}
 	var tokenSplit = strings.Split(authtoken, " ")

+ 3 - 2
models/structs.go

@@ -81,8 +81,9 @@ type PartialUserLoginResponse struct {
 }
 
 type TOTPInitiateResponse struct {
-	OTPAuthURL string `json:"otp_auth_url"`
-	QRCode     string `json:"qr_code"`
+	OTPAuthURL          string `json:"otp_auth_url"`
+	OTPAuthURLSignature string `json:"otp_auth_url_signature"`
+	QRCode              string `json:"qr_code"`
 }
 
 // Claims is  a struct that will be encoded to a JWT.

+ 3 - 1
models/user_mgmt.go

@@ -203,7 +203,9 @@ type UserAuthParams struct {
 }
 
 type UserTOTPVerificationParams struct {
-	TOTP string `json:"totp"`
+	OTPAuthURL          string `json:"otp_auth_url"`
+	OTPAuthURLSignature string `json:"otp_auth_url_signature"`
+	TOTP                string `json:"totp"`
 }
 
 // UserClaims - user claims struct