improve password validation, add charset generator
This commit is contained in:
parent
91476a29b2
commit
df1596312d
134
src/charsets/charsets.go
Normal file
134
src/charsets/charsets.go
Normal 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
36
src/charsets/enum.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"backend/src/charsets"
|
||||||
"backend/src/core/repos"
|
"backend/src/core/repos"
|
||||||
"backend/src/core/utils"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShortlinkService interface {
|
type ShortlinkService interface {
|
||||||
@ -18,18 +20,21 @@ type NewShortlinkServiceParams struct {
|
|||||||
|
|
||||||
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
|
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
|
||||||
return &shortlinkService{
|
return &shortlinkService{
|
||||||
randomUtil: *utils.NewRand(),
|
|
||||||
cache: params.Cache,
|
cache: params.Cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type shortlinkService struct {
|
type shortlinkService struct {
|
||||||
randomUtil utils.RandomUtil
|
|
||||||
cache repos.Cache[string, string]
|
cache repos.Cache[string, string]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *shortlinkService) CreateLink(in string) (string, error) {
|
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)
|
s.cache.Set(str, in, 7*24*60*60)
|
||||||
return str, nil
|
return str, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"backend/src/charsets"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@ -13,10 +14,15 @@ type PasswordUtil interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewPasswordUtil() PasswordUtil {
|
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) {
|
func (b *passwordUtil) Hash(password string) (string, error) {
|
||||||
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
@ -31,5 +37,39 @@ func (b *passwordUtil) Validate(password string) error {
|
|||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
return fmt.Errorf("password must contain 8 or more characters")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user