-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
157 lines (142 loc) · 4.32 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"log"
"math/big"
"net/http"
"strconv"
"strings"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/sethvargo/go-password/password"
)
// characters in "vowels" are replaced as they
// appear in "leet" (e.g. a -> 4, e -> 3, ...)
const (
vowels = "aeioAEIO"
leet = "43104310"
)
func main() {
// use chi router for routing
r := chi.NewRouter()
// proposed base middleware stack
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// there is only one route which returns password candidates
r.Get("/passwords", getPasswords)
err := http.ListenAndServe(":3334", r)
if err != nil {
log.Print(err)
}
}
// response holding password candidates
type passwordsResponse struct {
Candidates []string `json:"candidates"`
}
// get password candidates
func getPasswords(w http.ResponseWriter, r *http.Request) {
// holds all invalid query param names
var invalidParams []string
// query param: length of the password
length, err := queryInt(r, "length", 8)
if err != nil {
invalidParams = append(invalidParams, "length")
}
// query param: number of digits of the password
numDigits, err := queryInt(r, "numDigits", 0)
if err != nil {
invalidParams = append(invalidParams, "numDigits")
}
// query param: number of symbols of the password
numSymbols, err := queryInt(r, "numSymbols", 0)
if err != nil {
invalidParams = append(invalidParams, "numSymbols")
}
// query param: number of password candidates to generate
numCandidates, err := queryInt(r, "numCandidates", 4)
if err != nil {
invalidParams = append(invalidParams, "numCandidates")
}
// query param: enabled replacing vowels randomly
replaceVowels, err := queryBool(r, "replaceVowels", false)
if err != nil {
invalidParams = append(invalidParams, "replaceVowels")
}
// if any invalid query params exists return error
if len(invalidParams) > 0 {
message := fmt.Sprintf("invalid parameter(s): %s", strings.Join(invalidParams, ","))
http.Error(w, message, 422)
return
}
var passwordsResponse passwordsResponse
// generate the desired number of password candidates and add them to the response
for i := 0; i < numCandidates; i++ {
candidate, err := generatePassword(length, numDigits, numSymbols, replaceVowels)
// if there is an error it most likely happend during password generation
// -> status code 422
if err != nil {
http.Error(w, err.Error(), 422)
return
}
passwordsResponse.Candidates = append(passwordsResponse.Candidates, candidate)
}
// serialize response (to json) and send it
response, _ := json.Marshal(passwordsResponse)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(response)
}
// replaces vowels in given string randomly and returns result
func randomReplaceVowels(s string) (string, error) {
// iterate over string rune by rune
for pos, runeValue := range s {
// check if rune at current position is vowel
if index := strings.IndexRune(vowels, runeValue); index > -1 {
// rand int from [0,1]
randInt, err := rand.Int(rand.Reader, big.NewInt(2))
if err != nil {
return "", nil
}
// 0 -> do not replace
// 1 -> replace
if randInt.Cmp(big.NewInt(1)) == 0 {
// replace vowel with number assuming "rune equals byte"
s = s[0:pos] + string(leet[index]) + s[pos+1:len(s)]
}
}
}
return s, nil
}
// returns int query param from request or default value if query param does not exists
func queryInt(r *http.Request, key string, defaultValue int) (int, error) {
value := r.URL.Query().Get(key)
if value == "" {
return defaultValue, nil
}
return strconv.Atoi(value)
}
// returns bool query param from request or default value if query param does not exists
func queryBool(r *http.Request, key string, defaultValue bool) (bool, error) {
value := r.URL.Query().Get(key)
if value == "" {
return defaultValue, nil
}
return strconv.ParseBool(value)
}
// generates and returns password
func generatePassword(length int, numDigits int, numSymbols int, replaceVowles bool) (string, error) {
// generate password according to parameters
res, err := password.Generate(length, numDigits, numSymbols, false, true)
if err != nil {
return "", err
}
// replace vowels (optionally)
if replaceVowles {
return randomReplaceVowels(res)
}
return res, nil
}