1package store
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "os"
8 "path/filepath"
9 "runtime/debug"
10 "time"
11
12 "github.com/mjl-/bstore"
13
14 "github.com/mjl-/mox/metrics"
15 "github.com/mjl-/mox/mlog"
16 "github.com/mjl-/mox/mox-"
17 "github.com/mjl-/mox/moxvar"
18)
19
20// AuthDB and AuthDBTypes are exported for ../backup.go.
21var AuthDB *bstore.DB
22var AuthDBTypes = []any{TLSPublicKey{}, LoginAttempt{}, LoginAttemptState{}}
23
24// Init opens auth.db and starts the login writer.
25func Init(ctx context.Context) error {
26 if AuthDB != nil {
27 return fmt.Errorf("already initialized")
28 }
29 pkglog := mlog.New("store", nil)
30 p := mox.DataDirPath("auth.db")
31 os.MkdirAll(filepath.Dir(p), 0770)
32 opts := bstore.Options{Timeout: 5 * time.Second, Perm: 0660, RegisterLogger: moxvar.RegisterLogger(p, pkglog.Logger)}
33 var err error
34 AuthDB, err = bstore.Open(ctx, p, &opts, AuthDBTypes...)
35 if err != nil {
36 return err
37 }
38
39 startLoginAttemptWriter()
40
41 go func() {
42 defer func() {
43 x := recover()
44 if x == nil {
45 return
46 }
47
48 mlog.New("store", nil).Error("unhandled panic in LoginAttemptCleanup", slog.Any("err", x))
49 debug.PrintStack()
50 metrics.PanicInc(metrics.Store)
51
52 }()
53
54 t := time.NewTicker(24 * time.Hour)
55 for {
56 err := LoginAttemptCleanup(ctx)
57 pkglog.Check(err, "cleaning up old historic login attempts")
58
59 select {
60 case <-t.C:
61 case <-ctx.Done():
62 return
63 }
64 }
65 }()
66
67 return nil
68}
69
70// Close closes auth.db and stops the login writer.
71func Close() error {
72 if AuthDB == nil {
73 return fmt.Errorf("not open")
74 }
75
76 stopc := make(chan struct{})
77 writeLoginAttemptStop <- stopc
78 <-stopc
79
80 err := AuthDB.Close()
81 AuthDB = nil
82
83 return err
84}
85