├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── cmd └── go-generate-password │ └── main.go ├── generator ├── generator.go └── generator_test.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | memprofile.out 3 | profile.out 4 | coverage.out -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - 1.18.x 7 | 8 | before_install: 9 | - go get golang.org/x/tools/cmd/cover 10 | - go get github.com/mattn/goveralls 11 | script: 12 | - make test 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.1.0] - 2019-11-14 4 | - Initial release 5 | 6 | [0.1.0]: https://github.com/m1/go-generate-password/tree/0.1.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Miles Croxford 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGES := $(shell go list ./... | grep -v cmd) 2 | 3 | .PHONY: test 4 | test: 5 | go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.out $(PACKAGES) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-generate-password 2 | 3 | [![GoDoc](https://godoc.org/github.com/m1/go-generate-password?status.svg)](https://godoc.org/github.com/m1/go-generate-password) 4 | [![Build Status](https://travis-ci.org/m1/go-generate-password.svg?branch=master)](https://travis-ci.org/m1/go-generate-password) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/m1/go-generate-password)](https://goreportcard.com/report/github.com/m1/go-generate-password) 6 | [![Release](https://img.shields.io/github/release/m1/go-generate-password.svg)](https://github.com/m1/go-generate-password/releases/latest) 7 | [![codecov](https://codecov.io/gh/m1/go-generate-password/branch/master/graph/badge.svg)](https://codecov.io/gh/m1/go-generate-password) 8 | 9 | __Password generator written in Go. Use as a [library](#library) or as a [CLI](#cli).__ 10 | 11 | ## Usage 12 | 13 | ### CLI 14 | 15 | go-generate-password can be used on the cli, just install using: `go get github.com/m1/go-generate-password/cmd/go-generate-password` 16 | 17 | To use: 18 | ``` 19 | ➜ go-generate-password --help 20 | go-generate-password is a password generating engine written in Go. 21 | 22 | Usage: 23 | go-generate-password [flags] 24 | 25 | Flags: 26 | --characters string Character set for the config 27 | --exclude-ambiguous Exclude ambiguous characters (default true) 28 | --exclude-similar Exclude similar characters (default true) 29 | -h, --help help for go-generate-password 30 | -l, --length int Length of the password (default 24) 31 | --lowercase Include lowercase letters (default true) 32 | --numbers Include numbers (default true) 33 | --symbols Include symbols (default true) 34 | -n, --times int How many passwords to generate (default 1) 35 | --uppercase Include uppercase letters (default true) 36 | ``` 37 | 38 | For example: 39 | ``` 40 | ➜ go-generate-password 41 | 5PU?rG-w9YkDus4?AbmKd+Z* 42 | ``` 43 | 44 | More detailed example: 45 | ``` 46 | ➜ go-generate-password -n 5 --length 16 --symbols=false 47 | X89R4HvATgg7HSKk 48 | YMwMPnXp7cnMTNdZ 49 | RZWKAvyxFxDWRB8u 50 | PvKb6uP4N7vAMVsD 51 | KHttvhevGrTYptM5 52 | ``` 53 | 54 | Example using custom character set: 55 | ```.env 56 | ➜ go-generate-password -n 5 --characters=abcdefg01 57 | 10cecfcfe0bea1fdcbb1afcf 58 | bfcgbgg0dccafdacdaa1de01 59 | gb0ggcffcefae0bb1ac0bbge 60 | abafbc1bbaff0cfbdgaee11d 61 | 1fge0fcbccabda0g0a01ffc0 62 | ``` 63 | 64 | ### Library 65 | To use as a library is pretty simple: 66 | 67 | ```go 68 | config := generator.Config{ 69 | Length: 16, 70 | IncludeSymbols: false, 71 | IncludeNumbers: true, 72 | IncludeLowercaseLetters: true, 73 | IncludeUppercaseLetters: true, 74 | ExcludeSimilarCharacters: true, 75 | ExcludeAmbiguousCharacters: true, 76 | } 77 | g, _ := generator.New(&config) 78 | 79 | pwd, _ := g.Generate() 80 | // pwd = 8hp43B2R7gaXrZUW 81 | 82 | pwds, _ := g.GenerateMany(5) 83 | // pwds = [ 84 | // dnPp2TW2e8wmkAwT, 85 | // XVYwWn25xuNwhUTy, 86 | // vQ8aSrustQzxQCkA, 87 | // AuT4fu5RU9TtxEUR, 88 | // muDwwBRpKpC5BcHr, 89 | // ] 90 | 91 | pwd, _ = g.GenerateWithLength(12) 92 | // pwd = HHhpzRGsmEWt 93 | 94 | pwds, _ := g.GenerateManyWithLength(5, 12) 95 | // pwds = [ 96 | // s5TKYPdgRzvZ 97 | // wZFgzs8PUvRg 98 | // tU73qZ9sPzEs 99 | // mMaYU6hkvxPQ 100 | // KBNZ2D7cVQS2 101 | // ] 102 | ``` 103 | 104 | The library also comes with some helper constants: 105 | ```go 106 | const ( 107 | // LengthWeak weak length password 108 | LengthWeak = 6 109 | 110 | // LengthOK ok length password 111 | LengthOK = 12 112 | 113 | // LengthStrong strong length password 114 | LengthStrong = 24 115 | 116 | // LengthVeryStrong very strong length password 117 | LengthVeryStrong = 36 118 | 119 | // DefaultLetterSet is the letter set that is defaulted to - just the 120 | // alphabet 121 | DefaultLetterSet = "abcdefghijklmnopqrstuvwxyz" 122 | 123 | // DefaultLetterAmbiguousSet are letters which are removed from the 124 | // chosen character set if removing similar characters 125 | DefaultLetterAmbiguousSet = "ijlo" 126 | 127 | // DefaultNumberSet the default symbol set if character set hasn't been 128 | // selected 129 | DefaultNumberSet = "0123456789" 130 | 131 | // DefaultNumberAmbiguousSet are the numbers which are removed from the 132 | // chosen character set if removing similar characters 133 | DefaultNumberAmbiguousSet = "01" 134 | 135 | // DefaultSymbolSet the default symbol set if character set hasn't been 136 | // selected 137 | DefaultSymbolSet = "!$%^&*()_+{}:@[];'#<>?,./|\\-=?" 138 | 139 | // DefaultSymbolAmbiguousSet are the symbols which are removed from the 140 | // chosen character set if removing ambiguous characters 141 | DefaultSymbolAmbiguousSet = "<>[](){}:;'/|\\," 142 | ) 143 | ``` 144 | -------------------------------------------------------------------------------- /cmd/go-generate-password/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/m1/go-generate-password/generator" 10 | ) 11 | 12 | var ( 13 | rootCmd *cobra.Command 14 | length uint 15 | characterSet string 16 | includeSymbols bool 17 | includeNumbers bool 18 | includeLowercaseLetters bool 19 | includeUppercaseLetters bool 20 | excludeSimilarCharacters bool 21 | excludeAmbiguousCharacters bool 22 | times uint 23 | ) 24 | 25 | func main() { 26 | rootCmd = &cobra.Command{ 27 | Run: generate, 28 | Use: "go-generate-password", 29 | Short: "go-generate-password is a password generating engine.", 30 | Long: "go-generate-password is a password generating engine written in Go.", 31 | } 32 | 33 | rootCmd.PersistentFlags().UintVarP(&length, "length", "l", generator.DefaultConfig.Length, "Length of the password") 34 | rootCmd.PersistentFlags().StringVar(&characterSet, "characters", generator.DefaultConfig.CharacterSet, "Character set for the config") 35 | rootCmd.PersistentFlags().BoolVar(&includeSymbols, "symbols", generator.DefaultConfig.IncludeSymbols, "Include symbols") 36 | rootCmd.PersistentFlags().BoolVar(&includeNumbers, "numbers", generator.DefaultConfig.IncludeNumbers, "Include numbers") 37 | rootCmd.PersistentFlags().BoolVar(&includeLowercaseLetters, "lowercase", generator.DefaultConfig.IncludeLowercaseLetters, "Include lowercase letters") 38 | rootCmd.PersistentFlags().BoolVar(&includeUppercaseLetters, "uppercase", generator.DefaultConfig.IncludeSymbols, "Include uppercase letters") 39 | rootCmd.PersistentFlags().BoolVar(&excludeSimilarCharacters, "exclude-similar", generator.DefaultConfig.ExcludeSimilarCharacters, "Exclude similar characters") 40 | rootCmd.PersistentFlags().BoolVar(&excludeAmbiguousCharacters, "exclude-ambiguous", generator.DefaultConfig.ExcludeAmbiguousCharacters, "Exclude ambiguous characters") 41 | rootCmd.PersistentFlags().UintVarP(×, "times", "n", 1, "How many passwords to generate") 42 | 43 | err := rootCmd.Execute() 44 | if err != nil { 45 | fmt.Println(err.Error()) 46 | os.Exit(1) 47 | } 48 | } 49 | 50 | func generate(_ *cobra.Command, args []string) { 51 | config := generator.Config{ 52 | Length: length, 53 | CharacterSet: characterSet, 54 | IncludeSymbols: includeSymbols, 55 | IncludeNumbers: includeNumbers, 56 | IncludeLowercaseLetters: includeLowercaseLetters, 57 | IncludeUppercaseLetters: includeUppercaseLetters, 58 | ExcludeSimilarCharacters: excludeSimilarCharacters, 59 | ExcludeAmbiguousCharacters: excludeAmbiguousCharacters, 60 | } 61 | g, err := generator.New(&config) 62 | if err != nil { 63 | fmt.Println(err.Error()) 64 | os.Exit(1) 65 | } 66 | 67 | pwds, err := g.GenerateMany(times) 68 | if err != nil { 69 | fmt.Println(err.Error()) 70 | os.Exit(1) 71 | } 72 | 73 | for _, pwd := range pwds { 74 | fmt.Println(pwd) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "math/big" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | // LengthWeak weak length password 12 | LengthWeak uint = 6 13 | 14 | // LengthOK ok length password 15 | LengthOK uint = 12 16 | 17 | // LengthStrong strong length password 18 | LengthStrong uint = 24 19 | 20 | // LengthVeryStrong very strong length password 21 | LengthVeryStrong uint = 36 22 | 23 | // DefaultLetterSet is the letter set that is defaulted to - just the 24 | // alphabet 25 | DefaultLetterSet = "abcdefghijklmnopqrstuvwxyz" 26 | 27 | // DefaultLetterAmbiguousSet are letters which are removed from the 28 | // chosen character set if removing similar characters 29 | DefaultLetterAmbiguousSet = "ijlo" 30 | 31 | // DefaultNumberSet the default symbol set if character set hasn't been 32 | // selected 33 | DefaultNumberSet = "0123456789" 34 | 35 | // DefaultNumberAmbiguousSet are the numbers which are removed from the 36 | // chosen character set if removing similar characters 37 | DefaultNumberAmbiguousSet = "01" 38 | 39 | // DefaultSymbolSet the default symbol set if character set hasn't been 40 | // selected 41 | DefaultSymbolSet = "!$%^&*()_+{}:@[];'#<>?,./|\\-=?" 42 | 43 | // DefaultSymbolAmbiguousSet are the symbols which are removed from the 44 | // chosen character set if removing ambiguous characters 45 | DefaultSymbolAmbiguousSet = "<>[](){}:;'/|\\," 46 | ) 47 | 48 | var ( 49 | // DefaultConfig is the default configuration, defaults to: 50 | // - length = 24 51 | // - Includes symbols, numbers, lowercase and uppercase letters. 52 | // - Excludes similar and ambiguous characters 53 | DefaultConfig = Config{ 54 | Length: LengthStrong, 55 | IncludeSymbols: true, 56 | IncludeNumbers: true, 57 | IncludeLowercaseLetters: true, 58 | IncludeUppercaseLetters: true, 59 | ExcludeSimilarCharacters: true, 60 | ExcludeAmbiguousCharacters: true, 61 | } 62 | 63 | // ErrConfigIsEmpty is the error if the config given is empty 64 | ErrConfigIsEmpty = errors.New("config is empty") 65 | ) 66 | 67 | // Generator is what generates the password 68 | type Generator struct { 69 | *Config 70 | } 71 | 72 | // Config is the config struct to hold the settings about 73 | // what type of password to generate 74 | type Config struct { 75 | // Length is the length of password to generate 76 | Length uint 77 | 78 | // CharacterSet is the setting to manually set the 79 | // character set 80 | CharacterSet string 81 | 82 | // IncludeSymbols is the setting to include symbols in 83 | // the character set 84 | // i.e. !"£* 85 | IncludeSymbols bool 86 | 87 | // IncludeNumbers is the setting to include number in 88 | // the character set 89 | // i.e. 1234 90 | IncludeNumbers bool 91 | 92 | // IncludeLowercaseLetters is the setting to include 93 | // lowercase letters in the character set 94 | // i.e. abcde 95 | IncludeLowercaseLetters bool 96 | 97 | // IncludeUppercaseLetters is the setting to include 98 | // uppercase letters in the character set 99 | // i.e. ABCD 100 | IncludeUppercaseLetters bool 101 | 102 | // ExcludeSimilarCharacters is the setting to exclude 103 | // characters that look the same in the character set 104 | // i.e. i1jIo0 105 | ExcludeSimilarCharacters bool 106 | 107 | // ExcludeAmbiguousCharacters is the setting to exclude 108 | // characters that can be hard to remember or symbols 109 | // that are rarely used 110 | // i.e. <>{}[]()/|\` 111 | ExcludeAmbiguousCharacters bool 112 | } 113 | 114 | // New returns a new generator 115 | func New(config *Config) (*Generator, error) { 116 | if config == nil { 117 | config = &DefaultConfig 118 | } 119 | 120 | if !config.IncludeSymbols && 121 | !config.IncludeUppercaseLetters && 122 | !config.IncludeLowercaseLetters && 123 | !config.IncludeNumbers && 124 | config.CharacterSet == "" { 125 | return nil, ErrConfigIsEmpty 126 | } 127 | 128 | if config.Length == 0 { 129 | config.Length = LengthStrong 130 | } 131 | 132 | if config.CharacterSet == "" { 133 | config.CharacterSet = buildCharacterSet(config) 134 | } 135 | 136 | return &Generator{Config: config}, nil 137 | } 138 | 139 | func buildCharacterSet(config *Config) string { 140 | var characterSet string 141 | if config.IncludeLowercaseLetters { 142 | characterSet += DefaultLetterSet 143 | if config.ExcludeSimilarCharacters { 144 | characterSet = removeCharacters(characterSet, DefaultLetterAmbiguousSet) 145 | } 146 | } 147 | 148 | if config.IncludeUppercaseLetters { 149 | characterSet += strings.ToUpper(DefaultLetterSet) 150 | if config.ExcludeSimilarCharacters { 151 | characterSet = removeCharacters(characterSet, strings.ToUpper(DefaultLetterAmbiguousSet)) 152 | } 153 | } 154 | 155 | if config.IncludeNumbers { 156 | characterSet += DefaultNumberSet 157 | if config.ExcludeSimilarCharacters { 158 | characterSet = removeCharacters(characterSet, DefaultNumberAmbiguousSet) 159 | } 160 | } 161 | 162 | if config.IncludeSymbols { 163 | characterSet += DefaultSymbolSet 164 | if config.ExcludeAmbiguousCharacters { 165 | characterSet = removeCharacters(characterSet, DefaultSymbolAmbiguousSet) 166 | } 167 | } 168 | 169 | return characterSet 170 | } 171 | 172 | func removeCharacters(str, characters string) string { 173 | return strings.Map(func(r rune) rune { 174 | if !strings.ContainsRune(characters, r) { 175 | return r 176 | } 177 | return -1 178 | }, str) 179 | } 180 | 181 | // NewWithDefault returns a new generator with the default 182 | // config 183 | func NewWithDefault() (*Generator, error) { 184 | return New(&DefaultConfig) 185 | } 186 | 187 | // Generate generates one password with length set in the 188 | // config 189 | func (g Generator) Generate() (*string, error) { 190 | var generated string 191 | characterSet := strings.Split(g.Config.CharacterSet, "") 192 | max := big.NewInt(int64(len(characterSet))) 193 | 194 | for i := uint(0); i < g.Config.Length; i++ { 195 | val, err := rand.Int(rand.Reader, max) 196 | if err != nil { 197 | return nil, err 198 | } 199 | generated += characterSet[val.Int64()] 200 | } 201 | return &generated, nil 202 | } 203 | 204 | // GenerateMany generates multiple passwords with length set 205 | // in the config 206 | func (g Generator) GenerateMany(amount uint) ([]string, error) { 207 | var generated []string 208 | for i := uint(0); i < amount; i++ { 209 | str, err := g.Generate() 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | generated = append(generated, *str) 215 | } 216 | return generated, nil 217 | } 218 | 219 | // GenerateWithLength generate one password with set length 220 | func (g Generator) GenerateWithLength(length uint) (*string, error) { 221 | var generated string 222 | characterSet := strings.Split(g.Config.CharacterSet, "") 223 | max := big.NewInt(int64(len(characterSet))) 224 | for i := uint(0); i < length; i++ { 225 | val, err := rand.Int(rand.Reader, max) 226 | if err != nil { 227 | return nil, err 228 | } 229 | generated += characterSet[val.Int64()] 230 | } 231 | return &generated, nil 232 | } 233 | 234 | // GenerateManyWithLength generates multiple passwords with set length 235 | func (g Generator) GenerateManyWithLength(amount, length uint) ([]string, error) { 236 | var generated []string 237 | for i := uint(0); i < amount; i++ { 238 | str, err := g.GenerateWithLength(length) 239 | if err != nil { 240 | return nil, err 241 | } 242 | generated = append(generated, *str) 243 | } 244 | return generated, nil 245 | } 246 | -------------------------------------------------------------------------------- /generator/generator_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestNew(t *testing.T) { 13 | type args struct { 14 | config *Config 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | want *Generator 20 | wantErr error 21 | }{ 22 | { 23 | name: "default config", 24 | args: args{nil}, 25 | want: func() *Generator { 26 | cfg := &DefaultConfig 27 | cfg.CharacterSet = buildCharacterSet(cfg) 28 | cfg.Length = LengthStrong 29 | return &Generator{cfg} 30 | }(), 31 | }, 32 | { 33 | name: "config is empty", 34 | args: args{&Config{}}, 35 | wantErr: ErrConfigIsEmpty, 36 | }, 37 | { 38 | name: "set config", 39 | args: args{&Config{ 40 | IncludeLowercaseLetters: true, 41 | }}, 42 | want: func() *Generator { 43 | cfg := Config{IncludeLowercaseLetters: true} 44 | cfg.CharacterSet = "abcdefghijklmnopqrstuvwxyz" 45 | cfg.Length = LengthStrong 46 | return &Generator{&cfg} 47 | }(), 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | got, err := New(tt.args.config) 53 | if (err != nil) != (tt.wantErr != nil) || err != tt.wantErr { 54 | t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) 55 | return 56 | } 57 | if !reflect.DeepEqual(got, tt.want) { 58 | t.Errorf("New() = %v, want %v", got, tt.want) 59 | } 60 | }) 61 | } 62 | } 63 | 64 | func TestNewWithDefault(t *testing.T) { 65 | tests := []struct { 66 | name string 67 | want *Generator 68 | wantErr error 69 | }{ 70 | { 71 | name: "default config", 72 | want: func() *Generator { 73 | cfg := &DefaultConfig 74 | cfg.CharacterSet = buildCharacterSet(cfg) 75 | return &Generator{cfg} 76 | }(), 77 | }, 78 | } 79 | for _, tt := range tests { 80 | t.Run(tt.name, func(t *testing.T) { 81 | got, err := NewWithDefault() 82 | if (err != nil) != (tt.wantErr != nil) || err != tt.wantErr { 83 | t.Errorf("NewWithDefault() error = %v, wantErr %v", err, tt.wantErr) 84 | return 85 | } 86 | if !reflect.DeepEqual(got, tt.want) { 87 | t.Errorf("NewWithDefault() = %v, want %v", got, tt.want) 88 | } 89 | }) 90 | } 91 | } 92 | 93 | func Test_buildCharacterSet(t *testing.T) { 94 | type args struct { 95 | config *Config 96 | } 97 | tests := []struct { 98 | name string 99 | args args 100 | want string 101 | }{ 102 | { 103 | name: "exclude similar characters", 104 | args: args{ 105 | config: &Config{ 106 | IncludeLowercaseLetters: true, 107 | IncludeSymbols: true, 108 | IncludeNumbers: true, 109 | ExcludeSimilarCharacters: true, 110 | ExcludeAmbiguousCharacters: true, 111 | }, 112 | }, 113 | want: "abcdefghkmnpqrstuvwxyz23456789!$%^&*_+@#?.-=?", 114 | }, 115 | { 116 | name: "exclude numbers", 117 | args: args{ 118 | config: &Config{ 119 | IncludeLowercaseLetters: true, 120 | IncludeSymbols: true, 121 | IncludeNumbers: false, 122 | ExcludeSimilarCharacters: true, 123 | ExcludeAmbiguousCharacters: true, 124 | }, 125 | }, 126 | want: "abcdefghkmnpqrstuvwxyz!$%^&*_+@#?.-=?", 127 | }, 128 | { 129 | name: "full list", 130 | args: args{ 131 | config: &Config{ 132 | IncludeLowercaseLetters: true, 133 | IncludeSymbols: true, 134 | IncludeNumbers: true, 135 | ExcludeSimilarCharacters: false, 136 | ExcludeAmbiguousCharacters: false, 137 | }, 138 | }, 139 | want: "abcdefghijklmnopqrstuvwxyz0123456789!$%^&*()_+{}:@[];'#<>?,./|\\-=?", 140 | }, 141 | } 142 | for _, tt := range tests { 143 | t.Run(tt.name, func(t *testing.T) { 144 | if got := buildCharacterSet(tt.args.config); got != tt.want { 145 | t.Errorf("buildCharacterSet() = %v, want %v", got, tt.want) 146 | } 147 | }) 148 | } 149 | } 150 | 151 | func TestGenerator_Generate(t *testing.T) { 152 | type fields struct { 153 | Config *Config 154 | } 155 | tests := []struct { 156 | name string 157 | fields fields 158 | test func(*string, string) 159 | wantErr error 160 | }{ 161 | { 162 | name: "valid", 163 | fields: fields{&DefaultConfig}, 164 | test: func(pwd *string, characterSet string) { 165 | assert.Len(t, *pwd, int(DefaultConfig.Length)) 166 | err := stringMatchesCharacters(*pwd, characterSet) 167 | if err != nil { 168 | t.Errorf("Generate() error = %v", err) 169 | return 170 | } 171 | }, 172 | }, 173 | } 174 | for _, tt := range tests { 175 | t.Run(tt.name, func(t *testing.T) { 176 | g, _ := New(tt.fields.Config) 177 | got, err := g.Generate() 178 | if (err != nil) != (tt.wantErr != nil) || err != tt.wantErr { 179 | t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) 180 | return 181 | } 182 | tt.test(got, g.CharacterSet) 183 | }) 184 | } 185 | } 186 | 187 | func TestGenerator_GenerateMany(t *testing.T) { 188 | type fields struct { 189 | Config *Config 190 | } 191 | type args struct { 192 | amount uint 193 | } 194 | tests := []struct { 195 | name string 196 | args args 197 | fields fields 198 | test func([]string, string) 199 | wantErr error 200 | }{ 201 | { 202 | name: "valid", 203 | args: args{amount: 5}, 204 | fields: fields{Config: &DefaultConfig}, 205 | test: func(pwds []string, characterSet string) { 206 | assert.Len(t, pwds, 5) 207 | 208 | for _, pwd := range pwds { 209 | err := stringMatchesCharacters(pwd, characterSet) 210 | if err != nil { 211 | t.Errorf("Generate() error = %v", err) 212 | return 213 | } 214 | } 215 | }, 216 | }, 217 | } 218 | for _, tt := range tests { 219 | t.Run(tt.name, func(t *testing.T) { 220 | g, _ := New(tt.fields.Config) 221 | got, err := g.GenerateMany(tt.args.amount) 222 | if (err != nil) != (tt.wantErr != nil) || err != tt.wantErr { 223 | t.Errorf("GenerateMany() error = %v, wantErr %v", err, tt.wantErr) 224 | return 225 | } 226 | 227 | tt.test(got, g.CharacterSet) 228 | }) 229 | } 230 | } 231 | 232 | func stringMatchesCharacters(str, characters string) error { 233 | set := strings.Split(characters, "") 234 | strSet := strings.Split(str, "") 235 | 236 | for _, strChr := range strSet { 237 | found := false 238 | for _, setChr := range set { 239 | if strChr == setChr { 240 | found = true 241 | } 242 | } 243 | if !found { 244 | return fmt.Errorf("%v should not be in the str", strChr) 245 | } 246 | } 247 | 248 | return nil 249 | } 250 | 251 | func TestGenerator_GenerateWithLength(t *testing.T) { 252 | type fields struct { 253 | Config *Config 254 | } 255 | type args struct { 256 | length uint 257 | } 258 | tests := []struct { 259 | name string 260 | fields fields 261 | args args 262 | test func(*string, string) 263 | wantErr error 264 | }{ 265 | { 266 | name: "valid", 267 | fields: fields{&DefaultConfig}, 268 | args: args{length: 5}, 269 | test: func(pwd *string, characterSet string) { 270 | assert.Len(t, *pwd, 5) 271 | err := stringMatchesCharacters(*pwd, characterSet) 272 | if err != nil { 273 | t.Errorf("Generate() error = %v", err) 274 | return 275 | } 276 | }, 277 | }, 278 | } 279 | for _, tt := range tests { 280 | t.Run(tt.name, func(t *testing.T) { 281 | g, _ := New(tt.fields.Config) 282 | got, err := g.GenerateWithLength(tt.args.length) 283 | if (err != nil) != (tt.wantErr != nil) || err != tt.wantErr { 284 | t.Errorf("GenerateWithLength() error = %v, wantErr %v", err, tt.wantErr) 285 | return 286 | } 287 | tt.test(got, g.CharacterSet) 288 | }) 289 | } 290 | } 291 | 292 | func TestGenerator_GenerateManyWithLength(t *testing.T) { 293 | type fields struct { 294 | Config *Config 295 | } 296 | type args struct { 297 | amount uint 298 | length uint 299 | } 300 | tests := []struct { 301 | name string 302 | args args 303 | fields fields 304 | test func([]string, string) 305 | wantErr error 306 | }{ 307 | { 308 | name: "valid", 309 | args: args{amount: 5, length: 5}, 310 | fields: fields{Config: &DefaultConfig}, 311 | test: func(pwds []string, characterSet string) { 312 | assert.Len(t, pwds, 5) 313 | 314 | for _, pwd := range pwds { 315 | assert.Len(t, pwd, 5) 316 | err := stringMatchesCharacters(pwd, characterSet) 317 | if err != nil { 318 | t.Errorf("Generate() error = %v", err) 319 | return 320 | } 321 | } 322 | }, 323 | }, 324 | } 325 | for _, tt := range tests { 326 | t.Run(tt.name, func(t *testing.T) { 327 | g, _ := New(tt.fields.Config) 328 | 329 | got, err := g.GenerateManyWithLength(tt.args.amount, tt.args.length) 330 | if (err != nil) != (tt.wantErr != nil) || err != tt.wantErr { 331 | t.Errorf("GenerateManyWithLength() error = %v, wantErr %v", err, tt.wantErr) 332 | return 333 | } 334 | 335 | tt.test(got, g.CharacterSet) 336 | }) 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/m1/go-generate-password 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/spf13/cobra v1.4.0 7 | github.com/stretchr/testify v1.7.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | github.com/spf13/pflag v1.0.5 // indirect 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 6 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 10 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 11 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 12 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 13 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 16 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | --------------------------------------------------------------------------------