package db import ( "context" "errors" "fmt" "log" "code.lila.network/adoralaura/go-urlsh/internal/misc" "code.lila.network/adoralaura/go-urlsh/models" "github.com/uptrace/bun" ) // UserHasMFA checks the DB if given models.User has MFA enabled. // Returns (true, nil) if User has MFA enabled, (false, nil) if not. // (false, error) if a DB error happened func UserHasMFA(user models.User) (bool, error) { numrows, err := models.DB.NewSelect().Model((*models.MFAConfig)(nil)).Where("username = ?", user.UserName).Where("active = ?", true).Count(context.Background()) if err != nil { return false, fmt.Errorf("[UserHasMFA] error getting MFA count from database: %q", err) } if numrows >= 1 { return true, nil } return false, nil } // scratchCodeUnique checks the database if the generated scratch code // is unique (not in the database yet) func scratchCodeIsUnique(db *bun.DB, scratchcode string) bool { var dbitem models.MFAScratchCode numrows, err := db.NewSelect().Model(&dbitem).Where("code = ?", scratchcode).Count(context.Background()) if err != nil { return false } if numrows != 0 { return false } return true } // RemoveMFAFromDB removes MFA entries for given models.User from the database. // Returns nil on success, error otherwise. func RemoveMFAFromDB(user models.User) error { hasMfa, err := UserHasMFA(user) if err != nil { return fmt.Errorf("[RemoveMFAFromDB] Error removing MFA from DB for user %v: %w", user.UserName, err) } if !hasMfa { return nil } _, err = models.DB.NewDelete().Model((*models.MFAConfig)(nil)).Where("username = ?", user.UserName).Exec(context.Background()) if err != nil { log.Println(err.Error()) return fmt.Errorf("[RemoveMFAFromDB] Error removing MFA Config from DB for user %v: %w", user.UserName, err) } _, err = models.DB.NewDelete().Model((*models.MFAScratchCode)(nil)).Where("username = ?", user.UserName).Exec(context.Background()) if err != nil { log.Println(err.Error()) return fmt.Errorf("[RemoveMFAFromDB] Error removing MFA scratch codes from DB for user %v: %w", user.UserName, err) } return nil } // UserHasFailedMFAAttempt checks the DB for failed MFA Setup attempts // e.g. inactive MFA config func UserHasFailedMFAAttempt(user models.User) (bool, error) { //var mfaconfig models.MFAConfig //numrows, err := models.DB.NewSelect().Model(&mfaconfig). // Where("username = ?", user.UserName).Where("active = ?", false). // Count(context.Background()) //if err != nil { // return false, err //} return true, nil } // GenerateNewScratchcodes generates new unique scratch codes, inserts them into the database // and returns the scratch codes as []models.MFAScratchCode, and nil if successful. Returns empty // []models.MFAScratchCode and an error if something failed func GenerateNewScratchcodes(db *bun.DB, user models.User) ([]models.MFAScratchCode, error) { scratchcodes := []models.MFAScratchCode{} scratchcodeFailed := 0 // generate four unique(!) scratch codes for the user for len(scratchcodes) != 4 { if scratchcodeFailed > 15 { return []models.MFAScratchCode{}, errors.New("failed to generate new scratch codes: maximum tries exceeded") } code := misc.RandomString(8) if scratchCodeIsUnique(db, code) { scratchcodes = append(scratchcodes, models.MFAScratchCode{ IsUsed: false, Code: code, UserName: user.UserName}) } else { scratchcodeFailed++ } } _, err := db.NewInsert().Model(&scratchcodes).Exec(context.Background()) if err != nil { return []models.MFAScratchCode{}, fmt.Errorf("error inserting new scratch codes to DB: %w", err) } return scratchcodes, nil }