improve password validation, add charset generator

This commit is contained in:
Sergey Chubaryan 2024-08-23 22:59:54 +03:00
parent 91476a29b2
commit df1596312d
5 changed files with 223 additions and 77 deletions

134
src/charsets/charsets.go Normal file
View File

@ -0,0 +1,134 @@
package charsets
import "strings"
type RandInt interface {
Int() int
}
type Charset interface {
TestRune(char rune) bool
RandomRune(r RandInt) rune
RandomString(r RandInt, size int) string
String() string
}
func NewCharsetFromASCII(offset, size int) Charset {
return charsetASCII{offset: offset, size: size}
}
type charsetASCII struct {
offset int
size int
}
func (c charsetASCII) TestRune(char rune) bool {
return int(char) >= c.offset && int(char) < c.offset+c.size
}
func (c charsetASCII) RandomRune(r RandInt) rune {
num := c.offset + r.Int()%(c.size-1)
return rune(num)
}
func (c charsetASCII) RandomString(r RandInt, size int) string {
builder := strings.Builder{}
for i := 0; i < size; i++ {
builder.WriteRune(c.RandomRune(r))
}
return builder.String()
}
func (c charsetASCII) String() string {
builder := strings.Builder{}
for i := 0; i < c.size; i++ {
builder.WriteRune(rune(c.offset + i))
}
return builder.String()
}
func NewCharsetFromString(s string) Charset {
charsArray := make([]rune, len(s))
charsMap := make(map[rune]bool, len(s))
for i, v := range s {
charsArray[i] = v
charsMap[v] = true
}
return charsetFromString{
charsArray: charsArray,
charsMap: charsMap,
}
}
type charsetFromString struct {
charsMap map[rune]bool
charsArray []rune
}
func (c charsetFromString) TestRune(char rune) bool {
return c.charsMap[char]
}
func (c charsetFromString) RandomRune(r RandInt) rune {
num := r.Int() % (len(c.charsArray) - 1)
return c.charsArray[num]
}
func (c charsetFromString) RandomString(r RandInt, size int) string {
builder := strings.Builder{}
for i := 0; i < size; i++ {
builder.WriteRune(c.RandomRune(r))
}
return builder.String()
}
func (c charsetFromString) String() string {
builder := strings.Builder{}
for _, v := range c.charsArray {
builder.WriteRune(v)
}
return builder.String()
}
func NewCharsetUnion(opts ...Charset) Charset {
charsets := []Charset{}
return charsetUnion{
charsets: append(charsets, opts...),
}
}
type charsetUnion struct {
charsets []Charset
}
func (c charsetUnion) TestRune(char rune) bool {
for _, charset := range c.charsets {
if charset.TestRune(char) {
return true
}
}
return false
}
func (c charsetUnion) RandomRune(r RandInt) rune {
index := r.Int() % (len(c.charsets) - 1)
charset := c.charsets[index]
return charset.RandomRune(r)
}
func (c charsetUnion) RandomString(r RandInt, size int) string {
builder := strings.Builder{}
for i := 0; i < size; i++ {
index := r.Int() % (len(c.charsets) - 1)
charset := c.charsets[index]
builder.WriteRune(charset.RandomRune(r))
}
return builder.String()
}
func (c charsetUnion) String() string {
return ""
}

36
src/charsets/enum.go Normal file
View File

@ -0,0 +1,36 @@
package charsets
type CharsetType int
const (
CharsetTypeAll CharsetType = iota
CharsetTypeLettersLower
CharsetTypeLettersUpper
CharsetTypeLetters
CharsetTypeNumeric
)
var (
charsetNumeric = NewCharsetFromASCII(0x30, 10)
charsetLettersLower = NewCharsetFromASCII(0x41, 26)
charsetLettersUpper = NewCharsetFromASCII(0x61, 26)
charsetLetters = NewCharsetUnion(charsetLettersLower, charsetLettersUpper)
charsetAll = NewCharsetUnion(charsetNumeric, charsetLettersLower, charsetLettersUpper)
)
func GetCharset(charsetType CharsetType) Charset {
switch charsetType {
case CharsetTypeNumeric:
return charsetNumeric
case CharsetTypeLettersLower:
return charsetLettersLower
case CharsetTypeLettersUpper:
return charsetLettersLower
case CharsetTypeLetters:
return charsetLetters
case CharsetTypeAll:
return charsetAll
default:
return nil
}
}

View File

@ -1,9 +1,11 @@
package services
import (
"backend/src/charsets"
"backend/src/core/repos"
"backend/src/core/utils"
"fmt"
"math/rand"
"time"
)
type ShortlinkService interface {
@ -18,18 +20,21 @@ type NewShortlinkServiceParams struct {
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
return &shortlinkService{
randomUtil: *utils.NewRand(),
cache: params.Cache,
}
}
type shortlinkService struct {
randomUtil utils.RandomUtil
cache repos.Cache[string, string]
}
func (s *shortlinkService) CreateLink(in string) (string, error) {
str := s.randomUtil.RandomID(10, utils.CharsetAll)
charset := charsets.GetCharset(charsets.CharsetTypeAll)
src := rand.NewSource(time.Now().UnixMicro())
randGen := rand.New(src)
str := charset.RandomString(randGen, 10)
s.cache.Set(str, in, 7*24*60*60)
return str, nil
}

View File

@ -1,6 +1,7 @@
package utils
import (
"backend/src/charsets"
"fmt"
"golang.org/x/crypto/bcrypt"
@ -13,10 +14,15 @@ type PasswordUtil interface {
}
func NewPasswordUtil() PasswordUtil {
return &passwordUtil{}
specialChars := `!@#$%^&*()_-+={[}]|\:;"'<,>.?/`
return &passwordUtil{
charsetSpecialChars: charsets.NewCharsetFromString(specialChars),
}
}
type passwordUtil struct{}
type passwordUtil struct {
charsetSpecialChars charsets.Charset
}
func (b *passwordUtil) Hash(password string) (string, error) {
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
@ -31,5 +37,39 @@ func (b *passwordUtil) Validate(password string) error {
if len(password) < 8 {
return fmt.Errorf("password must contain 8 or more characters")
}
charsetUpper := charsets.GetCharset(charsets.CharsetTypeLettersUpper)
charsetLower := charsets.GetCharset(charsets.CharsetTypeLettersLower)
lowercaseLettersCount := 0
uppercaseLettersCount := 0
specialCharsCount := 0
for _, v := range password {
if b.charsetSpecialChars.TestRune(v) {
specialCharsCount++
continue
}
if charsetUpper.TestRune(v) {
uppercaseLettersCount++
continue
}
if charsetLower.TestRune(v) {
lowercaseLettersCount++
continue
}
}
if lowercaseLettersCount == 0 {
return fmt.Errorf("password must contain at least 1 lowercase letter")
}
if uppercaseLettersCount == 0 {
return fmt.Errorf("password must contain at least 1 uppercase letter")
}
if specialCharsCount == 0 {
return fmt.Errorf("password must contain at least 1 special character")
}
return nil
}

View File

@ -1,69 +0,0 @@
package utils
import (
"math/rand"
"strings"
"time"
)
type Charset int
const (
CharsetAll Charset = iota
CharsetLettersLower
CharsetLettersUpper
CharsetLetters
CharsetNumeric
)
type charsetBlock struct {
Offset int
Size int
}
func NewRand() *RandomUtil {
charsetLettersLower := charsetBlock{
Offset: 0x41,
Size: 26,
}
charsetLettersUpper := charsetBlock{
Offset: 0x61,
Size: 26,
}
charsetNumeric := charsetBlock{
Offset: 0x30,
Size: 10,
}
return &RandomUtil{
charsets: map[Charset][]charsetBlock{
CharsetNumeric: {charsetNumeric},
CharsetLettersLower: {charsetLettersLower},
CharsetLettersUpper: {charsetLettersUpper},
CharsetLetters: {charsetLettersLower, charsetLettersUpper},
CharsetAll: {charsetLettersLower, charsetLettersUpper, charsetNumeric},
},
}
}
type RandomUtil struct {
charsets map[Charset][]charsetBlock
}
func (r *RandomUtil) RandomID(outputLenght int, charset Charset) string {
src := rand.NewSource(time.Now().UnixMicro())
randGen := rand.New(src)
charsetBlocks := r.charsets[charset]
builder := strings.Builder{}
for i := 0; i < outputLenght; i++ {
charsetBlock := charsetBlocks[randGen.Int()%len(charsetBlocks)]
byte := charsetBlock.Offset + (randGen.Int() % charsetBlock.Size)
builder.WriteRune(rune(byte))
}
return builder.String()
}