Raw
 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}