1package idp
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "fmt"
8
9 dexstorage "github.com/dexidp/dex/storage"
10
11 "tug/internal/storage"
12)
13
14var ErrStaleEpoch = errors.New("idp: the grant's credentials epoch is no longer current")
15
16var epochGuardHook = func(ctx context.Context, write, userID string, grant, current int64) {}
17
18type epochGuard struct {
19 dexstorage.Storage
20 store *storage.Store
21}
22
23func guardEpochs(s dexstorage.Storage, store *storage.Store) dexstorage.Storage {
24 return epochGuard{Storage: s, store: store}
25}
26
27func (g epochGuard) current(ctx context.Context, write, connectorID string, data []byte) error {
28 if connectorID != ConnectorID {
29 return nil
30 }
31 userID, epoch, _, err := parseGrant(data)
32 if err != nil {
33 return nil
34 }
35 user, err := g.store.UserByID(ctx, userID)
36 if err != nil {
37 return fmt.Errorf("idp: %s: read credentials epoch: %w", write, err)
38 }
39 epochGuardHook(ctx, write, userID, epoch, user.CredentialsEpoch)
40 if user.CredentialsEpoch != epoch {
41 return fmt.Errorf("idp: %s: %w", write, ErrStaleEpoch)
42 }
43 return nil
44}
45
46func (g epochGuard) CreateAuthRequest(ctx context.Context, a dexstorage.AuthRequest) error {
47 if raw, _ := ctx.Value(hintContext{}).(string); raw != "" {
48 p, err := record(a.ConnectorData)
49 if err != nil {
50 return err
51 }
52 p.Hint = hintSubject(raw)
53 a.ConnectorData = encodePending(p)
54 }
55 return g.Storage.CreateAuthRequest(ctx, a)
56}
57
58func (g epochGuard) GetAuthCode(ctx context.Context, id string) (dexstorage.AuthCode, error) {
59 c, err := g.Storage.GetAuthCode(ctx, id)
60 if err != nil {
61 return dexstorage.AuthCode{}, err
62 }
63 if err := g.current(ctx, "GetAuthCode", c.ConnectorID, c.ConnectorData); err != nil {
64 return dexstorage.AuthCode{}, err
65 }
66 return c, nil
67}
68
69func (g epochGuard) CreateAuthCode(ctx context.Context, c dexstorage.AuthCode) error {
70 if err := g.current(ctx, "CreateAuthCode", c.ConnectorID, c.ConnectorData); err != nil {
71 return err
72 }
73 return g.Storage.CreateAuthCode(ctx, c)
74}
75
76func (g epochGuard) CreateRefresh(ctx context.Context, r dexstorage.RefreshToken) error {
77 if err := g.current(ctx, "CreateRefresh", r.ConnectorID, r.ConnectorData); err != nil {
78 return err
79 }
80 return g.Storage.CreateRefresh(ctx, r)
81}
82
83func (g epochGuard) CreateOfflineSessions(ctx context.Context, o dexstorage.OfflineSessions) error {
84 if err := g.current(ctx, "CreateOfflineSessions", o.ConnID, o.ConnectorData); err != nil {
85 return err
86 }
87 return g.Storage.CreateOfflineSessions(ctx, o)
88}
89
90func (g epochGuard) UpdateOfflineSessions(ctx context.Context, userID, connID string, updater func(dexstorage.OfflineSessions) (dexstorage.OfflineSessions, error)) error {
91 return g.Storage.UpdateOfflineSessions(ctx, userID, connID, func(old dexstorage.OfflineSessions) (dexstorage.OfflineSessions, error) {
92 before := old.ConnectorData
93 next, err := updater(old)
94 if err != nil || bytes.Equal(next.ConnectorData, before) {
95 return next, err
96 }
97 return next, g.current(ctx, "UpdateOfflineSessions", next.ConnID, next.ConnectorData)
98 })
99}