1package store
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "github.com/mjl-/bstore"
12
13 "github.com/mjl-/mox/mox-"
14)
15
16func TestLoginAttempt(t *testing.T) {
17 os.RemoveAll("../testdata/store/data")
18 mox.ConfigStaticPath = filepath.FromSlash("../testdata/store/mox.conf")
19 mox.MustLoadConfig(true, false)
20
21 xctx, xcancel := context.WithCancel(ctxbg)
22 defer xcancel() // Stop clearing of LoginAttempts.
23 err := Init(xctx)
24 tcheck(t, err, "store init")
25 stopc := make(chan struct{})
26 writeLoginAttemptStop <- stopc
27 <-stopc
28 defer func() {
29 // Ensure Close() below finishes
30 go func() {
31 c := <-writeLoginAttemptStop
32 c <- struct{}{}
33 }()
34
35 err := Close()
36 tcheck(t, err, "store close")
37 }()
38
39 a1 := LoginAttempt{
40 Last: time.Now(),
41 First: time.Now(),
42 AccountName: "mjl1",
43 UserAgent: "0", // "0" so we update instead of insert when testing automatic cleanup below.
44 Result: AuthError,
45 }
46 a2 := a1
47 a2.AccountName = "mjl2"
48 a3 := a1
49 a3.AccountName = "mjl3"
50 a3.Last = a3.Last.Add(-31 * 24 * time.Hour) // Will be cleaned up.
51 a3.First = a3.Last
52 LoginAttemptAdd(ctxbg, pkglog, a1)
53 LoginAttemptAdd(ctxbg, pkglog, a2)
54 LoginAttemptAdd(ctxbg, pkglog, a3)
55
56 // Ensure there are no LoginAttempts that still need to be written.
57 loginAttemptDrain := func() {
58 for {
59 select {
60 case la := <-writeLoginAttempt:
61 loginAttemptWrite(la)
62 default:
63 return
64 }
65 }
66 }
67
68 loginAttemptDrain()
69
70 l, err := LoginAttemptList(ctxbg, "", 0)
71 tcheck(t, err, "list login attempts")
72 tcompare(t, len(l), 3)
73
74 // Test limit.
75 l, err = LoginAttemptList(ctxbg, "", 2)
76 tcheck(t, err, "list login attempts")
77 tcompare(t, len(l), 2)
78
79 // Test account filter.
80 l, err = LoginAttemptList(ctxbg, "mjl1", 2)
81 tcheck(t, err, "list login attempts")
82 tcompare(t, len(l), 1)
83
84 // Cleanup will remove the entry for mjl3 and leave others.
85 err = LoginAttemptCleanup(ctxbg)
86 tcheck(t, err, "cleanup login attempt")
87 l, err = LoginAttemptList(ctxbg, "", 0)
88 tcheck(t, err, "list login attempts")
89 tcompare(t, len(l), 2)
90
91 // Removing account will keep last entry for mjl2.
92 err = AuthDB.Write(ctxbg, func(tx *bstore.Tx) error {
93 return loginAttemptRemoveAccount(tx, "mjl1")
94 })
95 tcheck(t, err, "remove login attempt account")
96 l, err = LoginAttemptList(ctxbg, "", 0)
97 tcheck(t, err, "list login attempts")
98 tcompare(t, len(l), 1)
99
100 l, err = LoginAttemptList(ctxbg, "mjl2", 0)
101 tcheck(t, err, "list login attempts")
102 tcompare(t, len(l), 1)
103
104 // Insert 3 failing entries. Then add another and see we still have 3.
105 loginAttemptsMaxPerAccount = 3
106 for i := range loginAttemptsMaxPerAccount {
107 a := a2
108 a.UserAgent = fmt.Sprintf("%d", i)
109 LoginAttemptAdd(ctxbg, pkglog, a)
110 }
111 loginAttemptDrain()
112 l, err = LoginAttemptList(ctxbg, "", 0)
113 tcheck(t, err, "list login attempts")
114 tcompare(t, len(l), loginAttemptsMaxPerAccount)
115
116 a := a2
117 a.UserAgent = fmt.Sprintf("%d", loginAttemptsMaxPerAccount)
118 LoginAttemptAdd(ctxbg, pkglog, a)
119 loginAttemptDrain()
120 l, err = LoginAttemptList(ctxbg, "", 0)
121 tcheck(t, err, "list login attempts")
122 tcompare(t, len(l), loginAttemptsMaxPerAccount)
123}
124