Added test suites for all current models
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/yxzzy-wtf/gin-gonic-prepack/database"
|
"github.com/yxzzy-wtf/gin-gonic-prepack/database"
|
||||||
"github.com/yxzzy-wtf/gin-gonic-prepack/models"
|
"github.com/yxzzy-wtf/gin-gonic-prepack/models"
|
||||||
@@ -103,7 +102,7 @@ func UserVerify() gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
verifyJwt, _ := c.GetQuery("verify")
|
verifyJwt, _ := c.GetQuery("verify")
|
||||||
|
|
||||||
claims, err := parseJwt(verifyJwt, models.UserHmac)
|
claims, err := util.ParseJwt(verifyJwt, models.UserHmac)
|
||||||
if err != nil || claims["role"] != "verify" {
|
if err != nil || claims["role"] != "verify" {
|
||||||
fmt.Println("bad claim or role not 'verify'", err)
|
fmt.Println("bad claim or role not 'verify'", err)
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
@@ -175,7 +174,7 @@ func UserResetForgottenPassword() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := parseJwt(resetVals.Token, models.UserHmac)
|
claims, err := util.ParseJwt(resetVals.Token, models.UserHmac)
|
||||||
if err != nil || claims["role"] != "reset" {
|
if err != nil || claims["role"] != "reset" {
|
||||||
fmt.Println("bad claim or role not 'reset'", err)
|
fmt.Println("bad claim or role not 'reset'", err)
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
@@ -245,7 +244,7 @@ func AdminLogin() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func genericAuth(expectedRole string) gin.HandlerFunc {
|
func genericAuth(expectedRole string, hmac []byte) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
tokenStr := c.GetHeader(JwtHeader)
|
tokenStr := c.GetHeader(JwtHeader)
|
||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
@@ -253,7 +252,7 @@ func genericAuth(expectedRole string) gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := parseJwt(tokenStr, models.UserHmac)
|
claims, err := util.ParseJwt(tokenStr, hmac)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "token ") || err.Error() == "signature is invalid" {
|
if strings.HasPrefix(err.Error(), "token ") || err.Error() == "signature is invalid" {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()})
|
||||||
@@ -279,27 +278,11 @@ func genericAuth(expectedRole string) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserAuth() gin.HandlerFunc {
|
func UserAuth() gin.HandlerFunc {
|
||||||
return genericAuth("user")
|
return genericAuth("user", models.UserHmac)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AdminAuth() gin.HandlerFunc {
|
func AdminAuth() gin.HandlerFunc {
|
||||||
return genericAuth("admin")
|
return genericAuth("admin", models.AdminHmac)
|
||||||
}
|
|
||||||
|
|
||||||
func parseJwt(tokenStr string, hmac []byte) (jwt.MapClaims, error) {
|
|
||||||
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
||||||
return nil, fmt.Errorf("bad signing method %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
|
|
||||||
return hmac, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
|
||||||
return claims, nil
|
|
||||||
} else {
|
|
||||||
return jwt.MapClaims{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Doot() gin.HandlerFunc {
|
func Doot() gin.HandlerFunc {
|
||||||
|
|||||||
7
controllers/core/core_test.go
Normal file
7
controllers/core/core_test.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUserSignup(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.18
|
|||||||
require github.com/gin-gonic/gin v1.7.7
|
require github.com/gin-gonic/gin v1.7.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
@@ -20,6 +21,7 @@ require (
|
|||||||
github.com/mattn/go-sqlite3 v1.14.12 // indirect
|
github.com/mattn/go-sqlite3 v1.14.12 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pquerna/otp v1.3.0 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
|
||||||
golang.org/x/sys v0.0.0-20220429121018-84afa8d3f7b3 // indirect
|
golang.org/x/sys v0.0.0-20220429121018-84afa8d3f7b3 // indirect
|
||||||
|
|||||||
5
go.sum
5
go.sum
@@ -1,3 +1,6 @@
|
|||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
|
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||||
|
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -55,6 +58,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||||
|
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type Admin struct {
|
|||||||
|
|
||||||
const adminJwtDuration = time.Hour * 2
|
const adminJwtDuration = time.Hour * 2
|
||||||
|
|
||||||
var adminHmac = util.GenerateHmac()
|
var AdminHmac = util.GenerateHmac()
|
||||||
|
|
||||||
func (a *Admin) GetJwt() (string, int) {
|
func (a *Admin) GetJwt() (string, int) {
|
||||||
j := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
j := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
@@ -26,7 +26,7 @@ func (a *Admin) GetJwt() (string, int) {
|
|||||||
"role": "admin",
|
"role": "admin",
|
||||||
})
|
})
|
||||||
|
|
||||||
jstr, err := j.SignedString(adminHmac)
|
jstr, err := j.SignedString(AdminHmac)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we should ALWAYS be able to build and sign a str
|
// we should ALWAYS be able to build and sign a str
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
41
models/admin_test.go
Normal file
41
models/admin_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdminGetJwt(t *testing.T) {
|
||||||
|
a := Admin{}
|
||||||
|
a.Uid = uuid.New()
|
||||||
|
|
||||||
|
jwtToken, maxAge := a.GetJwt()
|
||||||
|
if maxAge != int(time.Hour.Seconds()*2) {
|
||||||
|
t.Errorf("issued token with incorrect max age, expected %vs but was %vs", time.Hour.Seconds()*2, maxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
testClaims, err := util.ParseJwt(jwtToken, AdminHmac)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("tried to parse valid token but got error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["sub"] != a.Uid.String() {
|
||||||
|
t.Errorf("`sub` value of %v does not match expected of %v", testClaims["sub"], a.Uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["role"] != "admin" {
|
||||||
|
t.Errorf("`role` value of %v does not match expected of `admin`", testClaims["role"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["iat"]; !exists {
|
||||||
|
t.Errorf("`iat` does not exist in jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["exp"]; !exists {
|
||||||
|
t.Errorf("`exp` does not exist in jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,17 +18,29 @@ type Auth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) SetPassword(pass string) error {
|
func (a *Auth) SetPassword(pass string) error {
|
||||||
|
if len(pass) < 12 {
|
||||||
|
return errors.New("password too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(strings.ToLower(pass), "password") {
|
||||||
|
return errors.New("contains phrase 'password'")
|
||||||
|
}
|
||||||
|
|
||||||
passHash, _ := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
|
passHash, _ := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
|
||||||
a.PasswordHash = string(passHash)
|
a.PasswordHash = string(passHash)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) Login(pass string, tfCode string) (error, bool) {
|
func (a *Auth) Login(pass string, tfCode string) (error, bool) {
|
||||||
|
return a.login(pass, tfCode, time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) login(pass string, tfCode string, stamp time.Time) (error, bool) {
|
||||||
if err := a.CheckPassword(pass); err != nil {
|
if err := a.CheckPassword(pass); err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.ValidateTwoFactor(tfCode, time.Now()); err != nil {
|
if err := a.ValidateTwoFactor(tfCode, stamp); err != nil {
|
||||||
return err, true
|
return err, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +65,14 @@ func (a *Auth) ValidateTwoFactor(tfCode string, stamp time.Time) error {
|
|||||||
//TODO two factor
|
//TODO two factor
|
||||||
if len(tfCode) == 6 {
|
if len(tfCode) == 6 {
|
||||||
// Test 2FA
|
// Test 2FA
|
||||||
return errors.New("2FA invalid")
|
expect, err := totp.GenerateCode(a.TwoFactorSecret, stamp)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("could not process 2fa")
|
||||||
|
}
|
||||||
|
if expect == tfCode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("2fa invalid")
|
||||||
} else {
|
} else {
|
||||||
// May be a renewal code
|
// May be a renewal code
|
||||||
return errors.New("unlock invalid")
|
return errors.New("unlock invalid")
|
||||||
|
|||||||
137
models/auth_test.go
Normal file
137
models/auth_test.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBadPasswords(t *testing.T) {
|
||||||
|
a := Auth{}
|
||||||
|
|
||||||
|
if err := a.SetPassword("short"); err.Error() != "password too short" {
|
||||||
|
t.Errorf("allowed short password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.SetPassword("tqr9wyfPassword9k8rwcd"); err.Error() != "contains phrase 'password'" {
|
||||||
|
t.Errorf("allowed password containing the word 'password'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.SetPassword("qc2q2fn34dqifqu23j7dp0"); err != nil {
|
||||||
|
t.Errorf("rejected acceptable password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettingPassword(t *testing.T) {
|
||||||
|
a := Auth{}
|
||||||
|
|
||||||
|
if a.PasswordHash != "" {
|
||||||
|
t.Errorf("passwordhash comes with default value")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SetPassword("This-q2o37rcfy2ij34tgjwi374f3w")
|
||||||
|
ph := a.PasswordHash
|
||||||
|
if ph == "" {
|
||||||
|
t.Errorf("passwordhash was not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SetPassword("Different-q2o37rcfy2ij34tgjwi374f3w")
|
||||||
|
if ph == a.PasswordHash {
|
||||||
|
t.Errorf("password hashes are the same across different passwords")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordFlow(t *testing.T) {
|
||||||
|
a := Auth{}
|
||||||
|
|
||||||
|
a.SetPassword("Base-w894t7yw9xj8fxh834dr32")
|
||||||
|
if err := a.CheckPassword("Incorrect-w894t7yw9xj8fxh834dr32"); err == nil {
|
||||||
|
t.Errorf("did not fail when provided the wrong password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.CheckPassword("Base-w894t7yw9xj8fxh834dr32"); err != nil {
|
||||||
|
t.Errorf("failed when provided the right password")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SetPassword("Secondary-w894t7yw9xj8fxh834dr32")
|
||||||
|
if err := a.CheckPassword("Base-w894t7yw9xj8fxh834dr32"); err == nil {
|
||||||
|
t.Errorf("did not fail when provided the original password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.CheckPassword("Secondary-w894t7yw9xj8fxh834dr32"); err != nil {
|
||||||
|
t.Errorf("failed when provided the correct updated password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTwoFactorWhenNotSet(t *testing.T) {
|
||||||
|
a := Auth{}
|
||||||
|
if err := a.ValidateTwoFactor("ZZZZZZ", time.Now()); err == nil {
|
||||||
|
t.Errorf("no 2fa set up but code provided, should get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.ValidateTwoFactor("", time.Now()); err != nil {
|
||||||
|
t.Errorf("no code give but no 2fa set up, should not have errored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTwoFactor(t *testing.T) {
|
||||||
|
a := Auth{}
|
||||||
|
a.TwoFactorSecret = "AAAAAAAAAAAAAAAA"
|
||||||
|
|
||||||
|
testTime := time.Date(2022, 1, 5, 18, 0, 0, 0, time.UTC)
|
||||||
|
expected := "566833"
|
||||||
|
|
||||||
|
if err := a.ValidateTwoFactor("000000", testTime); err == nil {
|
||||||
|
t.Errorf("accepted invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.ValidateTwoFactor(expected, testTime); err != nil {
|
||||||
|
t.Errorf("rejected expected token at T0 of period")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.ValidateTwoFactor(expected, testTime.Add(29*time.Second)); err != nil {
|
||||||
|
t.Errorf("rejected expected token at T29 of period")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.ValidateTwoFactor(expected, testTime.Add(35*time.Second)); err == nil {
|
||||||
|
t.Errorf("accepted valid token at T35 of period (token is from last period)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombinedLogin(t *testing.T) {
|
||||||
|
a := Auth{}
|
||||||
|
a.SetPassword("q2ricy2rqi3c4r23rcou")
|
||||||
|
a.TwoFactorSecret = "AAAAAAAAAAAAAAAA"
|
||||||
|
testTime := time.Date(2022, 1, 5, 18, 0, 0, 0, time.UTC)
|
||||||
|
expected := "566833"
|
||||||
|
|
||||||
|
err, show := a.login("q2ricy2rqi3c4r23rcou", expected, testTime)
|
||||||
|
if err == nil || err.Error() != "not yet verified" {
|
||||||
|
t.Errorf("validated login of unverified user")
|
||||||
|
}
|
||||||
|
if !show {
|
||||||
|
t.Errorf("unverified is an acceptable message to show, did not indicate true")
|
||||||
|
}
|
||||||
|
|
||||||
|
err, show = a.login("q2ricy2rqi3c4r23rcou", "000000", testTime)
|
||||||
|
if err == nil || err.Error() != "2fa invalid" {
|
||||||
|
t.Errorf("validated incorrect 2fa code")
|
||||||
|
}
|
||||||
|
if !show {
|
||||||
|
t.Errorf("bad 2fa is an acceptable message to show, did not indicate true")
|
||||||
|
}
|
||||||
|
|
||||||
|
err, show = a.login("bad", "000000", testTime)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("validated bad password")
|
||||||
|
}
|
||||||
|
if show {
|
||||||
|
t.Errorf("bad passwrd not an acceptable message to show, but indicated true")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Verified = true
|
||||||
|
err, _ = a.login("q2ricy2rqi3c4r23rcou", expected, testTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed good login")
|
||||||
|
}
|
||||||
|
}
|
||||||
54
models/base_test.go
Normal file
54
models/base_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOnCreateNewUUID(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
b := Base{}
|
||||||
|
b.BeforeCreate(nil)
|
||||||
|
|
||||||
|
if b.Uid == uuid.Nil {
|
||||||
|
t.Errorf("did not generate uuid BeforeCreate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Created.IsZero() {
|
||||||
|
t.Errorf("did not set created time")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.Created.After(now) {
|
||||||
|
t.Errorf("created date should be after %v, was %v", now, b.Created)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.Updated.IsZero() {
|
||||||
|
t.Errorf("updated date already set to %v", b.Updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.Deleted.IsZero() {
|
||||||
|
t.Errorf("deleted date already set to %v", b.Updated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnSaveUpdateDate(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
b := Base{}
|
||||||
|
b.BeforeSave(nil)
|
||||||
|
|
||||||
|
if !b.Updated.After(now) {
|
||||||
|
t.Errorf("updated date should be updated to after %v, is %v", now, b.Updated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteSetsTime(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
b := Base{}
|
||||||
|
b.Delete()
|
||||||
|
|
||||||
|
if !b.Deleted.After(now) {
|
||||||
|
t.Errorf("updated date should be updated to after %v, is %v", now, b.Updated)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
models/tenanted_test.go
Normal file
11
models/tenanted_test.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCannotCreateUntenanted(t *testing.T) {
|
||||||
|
tnt := Tenanted{}
|
||||||
|
|
||||||
|
if err := tnt.BeforeCreate(nil); err == nil {
|
||||||
|
t.Errorf("allowed creation of Tenanted model without Tenant value")
|
||||||
|
}
|
||||||
|
}
|
||||||
95
models/user_test.go
Normal file
95
models/user_test.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserGetJwt(t *testing.T) {
|
||||||
|
u := User{}
|
||||||
|
u.Uid = uuid.New()
|
||||||
|
|
||||||
|
jwtToken, maxAge := u.GetJwt()
|
||||||
|
if maxAge != int(time.Hour.Seconds()*24) {
|
||||||
|
t.Errorf("issued token with incorrect max age, expected %vs but was %vs", time.Hour.Seconds()*24, maxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
testClaims, err := util.ParseJwt(jwtToken, UserHmac)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("tried to parse valid token but got error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["sub"] != u.Uid.String() {
|
||||||
|
t.Errorf("`sub` value of %v does not match expected of %v", testClaims["sub"], u.Uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["role"] != "user" {
|
||||||
|
t.Errorf("`role` value of %v does not match expected of `user`", testClaims["role"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["iat"]; !exists {
|
||||||
|
t.Errorf("`iat` does not exist in jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["exp"]; !exists {
|
||||||
|
t.Errorf("`exp` does not exist in jwt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestUserGetVerifyJwt(t *testing.T) {
|
||||||
|
u := User{}
|
||||||
|
u.Uid = uuid.New()
|
||||||
|
|
||||||
|
jwtToken := u.GetVerificationJwt()
|
||||||
|
|
||||||
|
testClaims, err := util.ParseJwt(jwtToken, UserHmac)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("tried to parse valid token but got error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["sub"] != u.Uid.String() {
|
||||||
|
t.Errorf("`sub` value of %v does not match expected of %v", testClaims["sub"], u.Uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["role"] != "verify" {
|
||||||
|
t.Errorf("`role` value of %v does not match expected of `verify`", testClaims["role"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["iat"]; !exists {
|
||||||
|
t.Errorf("`iat` does not exist in jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["exp"]; !exists {
|
||||||
|
t.Errorf("`exp` does not exist in jwt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGetResetJwt(t *testing.T) {
|
||||||
|
u := User{}
|
||||||
|
u.Uid = uuid.New()
|
||||||
|
|
||||||
|
jwtToken := u.GetResetPasswordJwt()
|
||||||
|
|
||||||
|
testClaims, err := util.ParseJwt(jwtToken, UserHmac)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("tried to parse valid token but got error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["sub"] != u.Uid.String() {
|
||||||
|
t.Errorf("`sub` value of %v does not match expected of %v", testClaims["sub"], u.Uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testClaims["role"] != "reset" {
|
||||||
|
t.Errorf("`role` value of %v does not match expected of `reset`", testClaims["role"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["iat"]; !exists {
|
||||||
|
t.Errorf("`iat` does not exist in jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := testClaims["exp"]; !exists {
|
||||||
|
t.Errorf("`exp` does not exist in jwt")
|
||||||
|
}
|
||||||
|
}
|
||||||
17
util/util.go
17
util/util.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,3 +34,19 @@ func SendEmail(title string, body string, recipient string) {
|
|||||||
//TODO
|
//TODO
|
||||||
fmt.Println("Send", title, body, "to", recipient)
|
fmt.Println("Send", title, body, "to", recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseJwt(tokenStr string, hmac []byte) (jwt.MapClaims, error) {
|
||||||
|
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("bad signing method %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return hmac, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
} else {
|
||||||
|
return jwt.MapClaims{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user