chore: password validation

This commit is contained in:
kekskurse 2025-03-13 21:12:16 +01:00
parent 3a0f0ced51
commit 7b5df08e45
26 changed files with 528 additions and 2 deletions

1
go.mod
View file

@ -4,6 +4,7 @@ go 1.24.1
require (
github.com/gin-gonic/gin v1.10.0
github.com/go-passwd/validator v0.0.0-20180902184246-0b4c967e436b
github.com/google/uuid v1.6.0
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0

2
go.sum
View file

@ -22,6 +22,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-passwd/validator v0.0.0-20180902184246-0b4c967e436b h1:XOkaXKVHqiFDTLzzHFkZ+VJkarlqnsSxIsuzcE75tk8=
github.com/go-passwd/validator v0.0.0-20180902184246-0b4c967e436b/go.mod h1:BOKCezpxxDZ5PLMqt+9MxZTCBeGcpUmDHDuYlkdPcI4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=

View file

@ -3,6 +3,7 @@ package miniauth
import "errors"
var (
ErrUserHasInvalideChars = errors.New("username has invalide chars")
ErrUsernameIsTaken = errors.New("username already takebn")
ErrUserHasInvalideChars = errors.New("username has invalide chars")
ErrUsernameIsTaken = errors.New("username already takebn")
ErrPasswordNotComplexEnough = errors.New("password not complex enough")
)

View file

@ -5,6 +5,7 @@ import (
"git.keks.cloud/kekskurse/miniauth/pkg/userstore"
"git.keks.cloud/kekskurse/miniauth/pkg/utils"
"github.com/go-passwd/validator"
"github.com/rs/zerolog"
)
@ -26,9 +27,24 @@ func (m Miniauth) RegisterUser(username string, mail string, password string) er
return utils.WrapError(ErrUserHasInvalideChars, err, log)
}
err = m.checkPasswordForRegistration(password)
if err != nil {
return utils.WrapError(ErrPasswordNotComplexEnough, err, log)
}
return nil
}
func (m Miniauth) checkPasswordForRegistration(password string) error {
passwordValidator := validator.New(
validator.MinLength(15, nil),
validator.ContainsAtLeast("abcdefghijklmnopqrstuvwxyz", 5, nil),
validator.ContainsAtLeast("0123456789", 3, nil),
validator.ContainsAtLeast(",;.:-_#+*?=}])[({/&%$§!<>|", 3, nil))
err := passwordValidator.Validate(password)
return err
}
func (m Miniauth) checkUsernameForRegistration(username string) error {
reg := regexp.MustCompile("^[a-z0-9]{4,25}$")
if !reg.MatchString(username) {

View file

@ -7,6 +7,47 @@ import (
"github.com/stretchr/testify/assert"
)
func TestPasswordCheck(t *testing.T) {
tts := []struct {
name string
password string
exptErrorString string
}{
{
name: "to short",
password: "abc",
exptErrorString: "Password length must be not lower that 15 chars",
},
{
name: "no number",
password: "abcdefghijklmnop",
exptErrorString: "Password must contains at least 3 chars from 0123456789",
},
{
name: "no special characters",
password: "abcd0e3fg5hijklmnop",
exptErrorString: "Password must contains at least 3 chars from ,;.:-_#+*?=}])[({/&%$§!<>|",
},
{
name: "no special characters",
password: "abcd0e,3.f-g5hijklmnop",
exptErrorString: "",
},
}
ma := getMiniAuth(t)
for _, tt := range tts {
t.Run(tt.name, func(t *testing.T) {
err := ma.checkPasswordForRegistration(tt.password)
if tt.exptErrorString != "" {
assert.Equal(t, tt.exptErrorString, err.Error())
return
}
assert.Nil(t, err)
})
}
}
func TestInvalideUsernames(t *testing.T) {
tts := []struct {
name string

15
vendor/github.com/go-passwd/validator/.editorconfig generated vendored Normal file
View file

@ -0,0 +1,15 @@
root = true
[*]
end_of_line = lf
insert_final_newline = false
trim_trailing_whitespace = true
charset = utf-8
[*.go]
indent_style = tab
indent_size = 4
[Makefile]
indent_style = tab
indent_size = 4

14
vendor/github.com/go-passwd/validator/.gitignore generated vendored Normal file
View file

@ -0,0 +1,14 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/

20
vendor/github.com/go-passwd/validator/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,20 @@
language: go
go: 1.10.x
matrix:
include:
- go: 1.9.x
install:
- go get -t ./...
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- go test -v -covermode=count -coverprofile=coverage.out ./...
- goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN
env:
global:
- secure: "Wmo0t7rIF6eFYXmD8qACjJGqSmfxMlxvX7gDW0ILqxbo6Ud9UuVGnaPyQbzEv0AZCcKHVfgUauURsb4xrdE3tEzjNMCaC0CbeuorGwKKU5oAJ1NXFVuCMfWssJkeSNgIaOHKBGbqA/v1dfYOiCiQznlZBjslKjQ9WE97JtIZjJtlmu4s4recO8+G6/JlvZwMslVgjfnTZunqMLUY0hob0459l7H6Cct9IClihMSMmLQLJCm0gfKGvKMCf/thvGVBD7V3xrYK2DwaYD3VOLjO+ouOHVwR0aAtXoJ9EufwHBAPyPVzW+/EwJ6i6aZ8ghqZQLF35Zm6ECzcwzLnZiNZB6wX56ZI4WQME0uKcX0XnSMcRQOAx5kJ7l3X8nZDqyth9DQ62uhiA6pDXU9Cdufvz3rMQjc/xdpIhiW9TpqNjFRbT3WoAAF2jPLO7jO8hfZ+geAn3gXanpN67x22JU2Y5PSQxgUX09YdmppxpcOP/ZqdvI3Z1wEjMEONZEWN3D7BqjUbdRHYSAss19LhvumUy6PBi7M5wM/NVCXQ+qZ9HSVSYNrQMag1RcLDADGHIRHZ9p0JBFZo/Lk5Vy8ZS/QjGvrFq/jMsyXFJ5gRrADk2cwqvta0+f4V/hlrILFVRg5wHTGTmhMlXk+Vbg4gOpSj6CG1//m94ijWppZ3zB0nJgM="

21
vendor/github.com/go-passwd/validator/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Tomasz Jakub Rup
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
vendor/github.com/go-passwd/validator/Makefile generated vendored Normal file
View file

@ -0,0 +1,3 @@
test:
go vet ./...
go test -cover ./...

122
vendor/github.com/go-passwd/validator/README.md generated vendored Normal file
View file

@ -0,0 +1,122 @@
# Password validator library for Go
[![Build Status](https://travis-ci.org/go-passwd/validator.svg?branch=master)](https://travis-ci.org/go-passwd/validator)
[![Coverage Status](https://coveralls.io/repos/github/go-passwd/validator/badge.svg?branch=master)](https://coveralls.io/github/go-passwd/validator?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-passwd/validator)](https://goreportcard.com/report/github.com/go-passwd/validator)
[![GoDoc](https://godoc.org/github.com/go-passwd/validator?status.svg)](https://godoc.org/github.com/go-passwd/validator)
## Installation
~~~sh
go get -u github.com/go-passwd/validator
~~~
## Usage
~~~go
import "github.com/go-passwd/validator"
passwordValidator := validator.New(validator.MinLength(5, nil), validator.MaxLength(10, nil))
err := passwordValidator.Validate(form.Password)
if err != nil {
panic(err)
}
~~~
You can pass to every validator functions ``customError`` parameter witch will be returned on error instead of default error.
~~~go
import "github.com/go-passwd/validator"
passwordValidator := validator.New(validator.MinLength(5, errors.New("too short")), validator.MaxLength(10, errors.New("too long")))
err := passwordValidator.Validate(form.Password)
if err != nil {
panic(err)
}
~~~
## Validators
### CommonPassword
Check if password is a common password.
Common password list is based on list created by Mark Burnett: https://xato.net/passwords/more-top-worst-passwords/
~~~go
passwordValidator := validator.New(validator.CommonPassword(nil))
~~~
### ContainsAtLeast
Count occurrences of a chars and compares it with required value.
~~~go
passwordValidator := validator.New(validator.ContainsAtLeast(5, "abcdefghijklmnopqrstuvwxyz", nil))
~~~
### ContainsOnly
Check if password contains only selected chars.
~~~go
passwordValidator := validator.New(validator.ContainsOnly("abcdefghijklmnopqrstuvwxyz", nil))
~~~
### MaxLength
Check if password length is not greater that defined length.
~~~go
passwordValidator := validator.New(validator.MaxLength(10, nil))
~~~
### MinLength
Check if password length is not lower that defined length.
~~~go
passwordValidator := validator.New(validator.MinLength(5, nil))
~~~
### Noop
Always return custom error.
~~~go
passwordValidator := validator.New(validator.Noop(nil))
~~~
### Regex
Check if password match regexp pattern.
~~~go
passwordValidator := validator.New(validator.Regex("^\\w+$", nil))
~~~
### Similarity
Check if password is sufficiently different from the attributes.
Attributes can be: user login, email, first name, last name, …
~~~go
passwordValidator := validator.New(validator.Similarity([]string{"username", "username@example.com"}, nil, nil))
~~~
### StartsWith
Check if password starts with one of letter.
~~~go
passwordValidator := validator.New(validator.StartsWith("abcdefghijklmnopqrstuvwxyz", nil))
~~~
### Unique
Check if password contains only unique chars.
~~~go
passwordValidator := validator.New(validator.Unique(nil))
~~~

1
vendor/github.com/go-passwd/validator/TODO.md generated vendored Normal file
View file

@ -0,0 +1 @@
* NotSequential

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
package validator
import (
"fmt"
"strings"
)
// ContainsAtLeast returns a ValidateFunc that count occurrences of a chars and compares it with required value
func ContainsAtLeast(chars string, occurrences int, customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
cnt := 0
for _, char := range strings.Split(chars, "") {
cnt += strings.Count(password, char)
}
if cnt < occurrences {
if customError != nil {
return customError
}
return fmt.Errorf("Password must contains at least %d chars from %s", occurrences, chars)
}
return nil
})
}

24
vendor/github.com/go-passwd/validator/contains_only.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
package validator
import (
"fmt"
"strings"
)
// ContainsOnly returns a ValidateFunc that check if password contains only selected chars
func ContainsOnly(chars string, customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
for _, char := range []rune(password) {
idx := strings.IndexFunc(chars, func(r rune) bool {
return r == char
})
if idx == -1 {
if customError != nil {
return customError
}
return fmt.Errorf("the password must contains only %s", chars)
}
}
return nil
})
}

2
vendor/github.com/go-passwd/validator/doc.go generated vendored Normal file
View file

@ -0,0 +1,2 @@
// Package validator is a set of functions to validate passwords
package validator

16
vendor/github.com/go-passwd/validator/max_length.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
package validator
import "fmt"
// MaxLength returns a ValidateFunc that check if password length is not greater that "length"
func MaxLength(length int, customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
if len(password) > length {
if customError != nil {
return customError
}
return fmt.Errorf("Password length must be not greater that %d chars", length)
}
return nil
})
}

16
vendor/github.com/go-passwd/validator/min_length.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
package validator
import "fmt"
// MinLength returns a ValidateFunc that check if password length is not lower that "length"
func MinLength(length int, customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
if len(password) < length {
if customError != nil {
return customError
}
return fmt.Errorf("Password length must be not lower that %d chars", length)
}
return nil
})
}

8
vendor/github.com/go-passwd/validator/noop.go generated vendored Normal file
View file

@ -0,0 +1,8 @@
package validator
// Noop returns a ValidateFunc that always return custom error
func Noop(customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
return customError
})
}

23
vendor/github.com/go-passwd/validator/regex.go generated vendored Normal file
View file

@ -0,0 +1,23 @@
package validator
import (
"fmt"
"regexp"
)
// Regex returns ValidateFunc that check if password match regexp pattern
func Regex(pattern string, customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
matched, err := regexp.MatchString(pattern, password)
if err != nil {
return err
}
if matched {
if customError != nil {
return customError
}
return fmt.Errorf("Password shouldn't match \"%s\" pattern", pattern)
}
return nil
})
}

47
vendor/github.com/go-passwd/validator/similarity.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
package validator
import (
"fmt"
"strings"
)
// Similarity returns ValidateFunc that validate whether the password is sufficiently different from the attributes
//
// Attributes can be: user login, email, first name, last name, …
func Similarity(attributes []string, maxSimilarity *float64, customError error) ValidateFunc {
if maxSimilarity == nil {
maxSimilarity = new(float64)
*maxSimilarity = 0.7
}
return ValidateFunc(func(password string) error {
for idx := range attributes {
if ratio(password, attributes[idx]) > *maxSimilarity {
if customError != nil {
return customError
}
return fmt.Errorf("The password is too similar to the %s", attributes[idx])
}
}
return nil
})
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if they have nothing in common.
func ratio(a, b string) float64 {
t := float64(len(a) + len(b))
m := 0.0
sa := strings.Split(a, "")
sb := strings.Split(b, "")
for idx := range sb {
aidx := strings.Index(a, sb[idx])
if aidx > -1 {
sa = append(sa[:aidx], sa[aidx+1:]...)
a = strings.Join(sa, "")
m = m + 1.0
}
}
return 2.0 * m / t
}

23
vendor/github.com/go-passwd/validator/starts_with.go generated vendored Normal file
View file

@ -0,0 +1,23 @@
package validator
import (
"fmt"
"strings"
)
// StartsWith returns ValidateFunc that validate whether the password is starts with one of letter
func StartsWith(letters string, customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
firstLetter := []rune(password)[0]
idx := strings.IndexFunc(letters, func(r rune) bool {
return r == firstLetter
})
if idx == -1 {
if customError != nil {
return customError
}
return fmt.Errorf("the password must starts with one of: %s", letters)
}
return nil
})
}

24
vendor/github.com/go-passwd/validator/unique.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
package validator
import (
"errors"
"strings"
)
// Unique returns ValidateFunc that validate whether the password has only unique chars
func Unique(customError error) ValidateFunc {
return ValidateFunc(func(password string) error {
runes := []rune(password)
for idx := range runes {
if strings.LastIndexFunc(password, func(r rune) bool {
return r == runes[idx]
}) != idx {
if customError != nil {
return customError
}
return errors.New("the password must contains unique chars")
}
}
return nil
})
}

View file

@ -0,0 +1,4 @@
package validator
// ValidateFunc defines a function to validate
type ValidateFunc func(password string) error

29
vendor/github.com/go-passwd/validator/validator.go generated vendored Normal file
View file

@ -0,0 +1,29 @@
package validator
import (
"errors"
)
// Validator represents set of password validators
type Validator []ValidateFunc
// New return new instance of Validator
func New(vfunc ...ValidateFunc) *Validator {
v := Validator{}
v = append(v, vfunc...)
return &v
}
// Validate the password
func (v *Validator) Validate(password string) error {
if len(*v) == 0 {
return errors.New("Validator must contains at least 1 validator function")
}
for _, passwordValidator := range *v {
err := passwordValidator(password)
if err != nil {
return err
}
}
return nil
}

3
vendor/modules.txt vendored
View file

@ -66,6 +66,9 @@ github.com/gin-gonic/gin/binding
github.com/gin-gonic/gin/internal/bytesconv
github.com/gin-gonic/gin/internal/json
github.com/gin-gonic/gin/render
# github.com/go-passwd/validator v0.0.0-20180902184246-0b4c967e436b
## explicit
github.com/go-passwd/validator
# github.com/go-playground/locales v0.14.1
## explicit; go 1.17
github.com/go-playground/locales