Added test suites for all current models
This commit is contained in:
@@ -16,7 +16,7 @@ type Admin struct {
|
||||
|
||||
const adminJwtDuration = time.Hour * 2
|
||||
|
||||
var adminHmac = util.GenerateHmac()
|
||||
var AdminHmac = util.GenerateHmac()
|
||||
|
||||
func (a *Admin) GetJwt() (string, int) {
|
||||
j := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
@@ -26,7 +26,7 @@ func (a *Admin) GetJwt() (string, int) {
|
||||
"role": "admin",
|
||||
})
|
||||
|
||||
jstr, err := j.SignedString(adminHmac)
|
||||
jstr, err := j.SignedString(AdminHmac)
|
||||
if err != nil {
|
||||
// we should ALWAYS be able to build and sign a str
|
||||
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 (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -16,17 +18,29 @@ type Auth struct {
|
||||
}
|
||||
|
||||
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)
|
||||
a.PasswordHash = string(passHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return err, false
|
||||
}
|
||||
|
||||
if err := a.ValidateTwoFactor(tfCode, time.Now()); err != nil {
|
||||
if err := a.ValidateTwoFactor(tfCode, stamp); err != nil {
|
||||
return err, true
|
||||
}
|
||||
|
||||
@@ -51,7 +65,14 @@ func (a *Auth) ValidateTwoFactor(tfCode string, stamp time.Time) error {
|
||||
//TODO two factor
|
||||
if len(tfCode) == 6 {
|
||||
// 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 {
|
||||
// May be a renewal code
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user