Skip to content

Commit 529496a

Browse files
cstocktonChris Stockton
andauthored
chore: add unit tests for GenerateOtp (#2281)
This change locks in the current behavior of GenerateOtp with unit tests. This required a small change to allow passing an io.Reader. In addition I added a TODO comment about a minor defensive change to include bounds checks, error propagation and the correct way to support larger digits. This is in response to #2272 --------- Co-authored-by: Chris Stockton <[email protected]>
1 parent 2a38668 commit 529496a

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

internal/crypto/crypto.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,20 @@ import (
2020

2121
// GenerateOtp generates a random n digit otp
2222
func GenerateOtp(digits int) string {
23+
return generateOtp(rand.Reader, digits)
24+
}
25+
26+
func generateOtp(r io.Reader, digits int) string {
27+
// TODO(cstockton): Change the code to be below and propagate errors so we
28+
// can have non-panicing bounds checks. This is just a defensive change so
29+
// if someone changes OTP length in the future we don't end up with an
30+
// overflowed float64 / panic.
31+
//
32+
// upper := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(digits)), nil)
33+
// val := must(rand.Int(r, upper))
34+
//
2335
upper := math.Pow10(digits)
24-
val := must(rand.Int(rand.Reader, big.NewInt(int64(upper))))
36+
val := must(rand.Int(r, big.NewInt(int64(upper))))
2537

2638
// adds a variable zero-padding to the left to ensure otp is uniformly random
2739
expr := "%0" + strconv.Itoa(digits) + "v"

internal/crypto/crypto_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,80 @@ package crypto
33
import (
44
"testing"
55

6+
mrand "math/rand"
7+
68
"github.com/gofrs/uuid"
79
"github.com/stretchr/testify/assert"
810
)
911

12+
func TestGenerateOtp(t *testing.T) {
13+
14+
// Lock in current behavior.
15+
{
16+
mr := mrand.New(mrand.NewSource(0)) // #nosec G404
17+
tests := []struct {
18+
digits int
19+
exp string
20+
}{
21+
{1, "1"}, {1, "4"}, {1, "2"}, {1, "0"},
22+
{2, "65"}, {2, "83"}, {2, "18"}, {2, "04"},
23+
{3, "883"}, {3, "110"}, {3, "677"}, {3, "744"},
24+
{4, "6157"}, {4, "8369"}, {4, "3385"}, {4, "1617"},
25+
{5, "69588"}, {5, "96393"}, {5, "57989"}, {5, "57681"},
26+
{6, "284024"}, {6, "554454"}, {6, "975571"}, {6, "053470"},
27+
{7, "7076089"}, {7, "6287428"}, {7, "3903112"}, {7, "3915653"},
28+
{8, "44800453"}, {8, "38979394"}, {8, "70448040"}, {8, "29351463"},
29+
{9, "526897122"}, {9, "047135939"}, {9, "351530466"}, {9, "381602894"},
30+
{10, "6834743966"}, {10, "2026285792"}, {10, "7189110983"}, {10, "4023217386"},
31+
}
32+
for idx, test := range tests {
33+
t.Logf("test #%02d - exp %v using %v digits", idx, test.exp, test.digits)
34+
otp := generateOtp(mr, test.digits)
35+
assert.Equal(t, test.digits, len(otp))
36+
assert.Equal(t, test.exp, otp)
37+
}
38+
}
39+
40+
// and some heavily zero padded values
41+
{
42+
tests := []struct {
43+
digits int
44+
exp string
45+
seed int64
46+
}{
47+
{4, "0009", 5},
48+
{4, "0072", 133},
49+
{4, "0040", 203},
50+
{4, "0095", 551},
51+
{5, "00061", 248},
52+
{5, "00056", 977},
53+
{5, "00013", 981},
54+
{5, "00038", 2504},
55+
{6, "000056", 977},
56+
{6, "000094", 21852},
57+
{6, "000099", 30190},
58+
{6, "000012", 32646},
59+
{8, "00000374", 15749},
60+
{8, "00000995", 198113},
61+
{8, "00000271", 213316},
62+
{8, "00000612", 226219},
63+
{10, "0058477947", 1},
64+
{10, "0018825892", 79},
65+
{10, "0039133437", 148},
66+
{10, "0004026570", 248},
67+
{10, "0000007968", 1380744},
68+
}
69+
for idx, test := range tests {
70+
t.Logf("test #%02d - exp %v using %v digits (seed: %v)",
71+
idx, test.exp, test.digits, test.seed)
72+
mr := mrand.New(mrand.NewSource(test.seed)) // #nosec G404
73+
otp := generateOtp(mr, test.digits)
74+
assert.Equal(t, test.digits, len(otp))
75+
assert.Equal(t, test.exp, otp)
76+
}
77+
}
78+
}
79+
1080
func TestEncryptedStringPositive(t *testing.T) {
1181
id := uuid.Must(uuid.NewV4()).String()
1282

0 commit comments

Comments
 (0)