package idp import ( "bytes" "context" "errors" "fmt" dexstorage "github.com/dexidp/dex/storage" "tug/internal/storage" ) var ErrStaleEpoch = errors.New("idp: the grant's credentials epoch is no longer current") var epochGuardHook = func(ctx context.Context, write, userID string, grant, current int64) {} type epochGuard struct { dexstorage.Storage store *storage.Store } func guardEpochs(s dexstorage.Storage, store *storage.Store) dexstorage.Storage { return epochGuard{Storage: s, store: store} } func (g epochGuard) current(ctx context.Context, write, connectorID string, data []byte) error { if connectorID != ConnectorID { return nil } userID, epoch, _, err := parseGrant(data) if err != nil { return nil } user, err := g.store.UserByID(ctx, userID) if err != nil { return fmt.Errorf("idp: %s: read credentials epoch: %w", write, err) } epochGuardHook(ctx, write, userID, epoch, user.CredentialsEpoch) if user.CredentialsEpoch != epoch { return fmt.Errorf("idp: %s: %w", write, ErrStaleEpoch) } return nil } func (g epochGuard) CreateAuthRequest(ctx context.Context, a dexstorage.AuthRequest) error { if raw, _ := ctx.Value(hintContext{}).(string); raw != "" { p, err := record(a.ConnectorData) if err != nil { return err } p.Hint = hintSubject(raw) a.ConnectorData = encodePending(p) } return g.Storage.CreateAuthRequest(ctx, a) } func (g epochGuard) GetAuthCode(ctx context.Context, id string) (dexstorage.AuthCode, error) { c, err := g.Storage.GetAuthCode(ctx, id) if err != nil { return dexstorage.AuthCode{}, err } if err := g.current(ctx, "GetAuthCode", c.ConnectorID, c.ConnectorData); err != nil { return dexstorage.AuthCode{}, err } return c, nil } func (g epochGuard) CreateAuthCode(ctx context.Context, c dexstorage.AuthCode) error { if err := g.current(ctx, "CreateAuthCode", c.ConnectorID, c.ConnectorData); err != nil { return err } return g.Storage.CreateAuthCode(ctx, c) } func (g epochGuard) CreateRefresh(ctx context.Context, r dexstorage.RefreshToken) error { if err := g.current(ctx, "CreateRefresh", r.ConnectorID, r.ConnectorData); err != nil { return err } return g.Storage.CreateRefresh(ctx, r) } func (g epochGuard) CreateOfflineSessions(ctx context.Context, o dexstorage.OfflineSessions) error { if err := g.current(ctx, "CreateOfflineSessions", o.ConnID, o.ConnectorData); err != nil { return err } return g.Storage.CreateOfflineSessions(ctx, o) } func (g epochGuard) UpdateOfflineSessions(ctx context.Context, userID, connID string, updater func(dexstorage.OfflineSessions) (dexstorage.OfflineSessions, error)) error { return g.Storage.UpdateOfflineSessions(ctx, userID, connID, func(old dexstorage.OfflineSessions) (dexstorage.OfflineSessions, error) { before := old.ConnectorData next, err := updater(old) if err != nil || bytes.Equal(next.ConnectorData, before) { return next, err } return next, g.current(ctx, "UpdateOfflineSessions", next.ConnID, next.ConnectorData) }) }