-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathtokens.go
149 lines (132 loc) · 4.06 KB
/
tokens.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
package passwordless
import (
"crypto/rand"
"errors"
"strings"
"context"
)
var (
crockfordBytes = []byte("0123456789abcdefghjkmnpqrstvwxyz")
)
// TokenGenerator defines an interface for generating and sanitising
// cryptographically-secure tokens.
type TokenGenerator interface {
// Generate should return a token and nil error on success, or an empty
// string and error on failure.
Generate(ctx context.Context) (string, error)
// Sanitize should take a user provided input and sanitize it such that
// it can be passed to a function that expects the same input as
// `Generate()`. Useful for cases where the token may be subject to
// minor transcription errors by a user. (e.g. 0 == O)
Sanitize(ctx context.Context, s string) (string, error)
}
// ByteGenerator generates random sequences of bytes from the specified set
// of the specified length.
type ByteGenerator struct {
Bytes []byte
Length int
}
// NewByteGenerator creates and returns a ByteGenerator.
func NewByteGenerator(b []byte, l int) *ByteGenerator {
return &ByteGenerator{
Bytes: b,
Length: l,
}
}
// Generate returns a string generated from random bytes of the configured
// set, of the given length. An error may be returned if there is insufficient
// entropy to generate a result.
func (g ByteGenerator) Generate(ctx context.Context) (string, error) {
if b, err := randBytes(g.Bytes, g.Length); err != nil {
return "", err
} else {
return string(b), nil
}
}
func (g ByteGenerator) Sanitize(ctx context.Context, s string) (string, error) {
return s, nil
}
// CrockfordGenerator generates random tokens using Douglas Crockford's base
// 32 alphabet which limits characters of similar appearances. The
// Sanitize method of this generator will deal with transcribing incorrect
// characters back to the correct value.
type CrockfordGenerator struct {
Length int
}
// NewCrockfordGenerator returns a new Crockford token generator that creates
// tokens of the specified length.
func NewCrockfordGenerator(l int) *CrockfordGenerator {
return &CrockfordGenerator{l}
}
func (g CrockfordGenerator) Generate(ctx context.Context) (string, error) {
if b, err := randBytes(crockfordBytes, g.Length); err != nil {
return "", err
} else {
return string(b), nil
}
}
// Sanitize attempts to translate strings back to the correct Crockford
// alphabet, in case of user transcribe errors.
func (g CrockfordGenerator) Sanitize(ctx context.Context, s string) (string, error) {
bs := []byte(strings.ToLower(s))
for i, b := range bs {
if b == 'i' || b == 'l' || b == '|' {
bs[i] = '1'
} else if b == 'o' {
bs[i] = '0'
}
}
return string(bs), nil
}
// PINGenerator generates numerical PINs of the specifeid length.
type PINGenerator struct {
Length int
}
// Generate returns a numerical PIN of the chosen length. If there is not
// enough random entropy, the returned string will be empty and an error
// value present.
func (g PINGenerator) Generate(ctx context.Context) (string, error) {
if b, err := randBytes([]byte("0123456789"), g.Length); err != nil {
return "", err
} else {
return string(b), nil
}
}
func (g PINGenerator) Sanitize(ctx context.Context, s string) (string, error) {
bs := []byte(strings.ToLower(s))
for i, b := range bs {
if b == 'i' || b == 'l' || b == '|' {
bs[i] = '1'
} else if b == 'o' {
bs[i] = '0'
} else if s[i] == 'B' {
bs[i] = '8'
} else if s[i] == 'b' {
bs[i] = '6'
} else if b == 's' {
bs[i] = '5'
}
}
return string(bs), nil
}
// randBytes returns a random array of bytes picked from `p` of length `n`.
func randBytes(p []byte, n int) ([]byte, error) {
if len(p) > 256 {
return nil, errors.New("randBytes requires a pool of <= 256 items")
}
c := len(p)
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return nil, err
}
// Pick items randomly out of `p`. Because it's possible that
// `len(p) < size(byte)`, use remainder in next iteration to ensure all
// bytes have an equal chance of being selected.
j := 0 // reservoir
for i := 0; i < n; i++ {
bb := int(b[i])
b[i] = p[(j+bb)%c]
j += (c + (c-bb)%c) % c
}
return b, nil
}