Raw
  1package idp
  2
  3import (
  4	"bytes"
  5	"context"
  6	"crypto/sha256"
  7	"crypto/subtle"
  8	"encoding/base64"
  9	"encoding/json"
 10	"errors"
 11	"fmt"
 12	"io"
 13	"time"
 14
 15	dexstorage "github.com/dexidp/dex/storage"
 16
 17	"tug/internal/transport/web"
 18)
 19
 20func record(data []byte) (pending, error) {
 21	var p pending
 22	if len(data) == 0 {
 23		return p, nil
 24	}
 25	data = bytes.TrimSpace(data)
 26	if len(data) == 0 || data[0] != '{' {
 27		return p, errDeadRequest
 28	}
 29	d := json.NewDecoder(bytes.NewReader(data))
 30	d.DisallowUnknownFields()
 31	if err := d.Decode(&p); err != nil {
 32		return p, errDeadRequest
 33	}
 34	if err := d.Decode(new(any)); err != io.EOF {
 35		return p, errDeadRequest
 36	}
 37	return p, nil
 38}
 39
 40func bindingDigest(proof string) string {
 41	sum := sha256.Sum256([]byte(proof))
 42	return base64.RawURLEncoding.EncodeToString(sum[:])
 43}
 44
 45func (v *Verifier) bound(ctx context.Context, state, proof string) (dexstorage.AuthRequest, pending, error) {
 46	a, p, err := v.waiting(ctx, state)
 47	if err != nil {
 48		return a, pending{}, err
 49	}
 50	return a, p, checkBinding(p, proof, false)
 51}
 52
 53func (v *Verifier) BoundClient(ctx context.Context, state, proof string) (string, error) {
 54	a, _, err := v.bound(ctx, state, proof)
 55	return a.ClientID, err
 56}
 57
 58func checkBinding(p pending, proof string, bare bool) error {
 59	if bare && p.Binding == "" && proof == "" {
 60		return nil
 61	}
 62	if proof == "" || p.Binding == "" || subtle.ConstantTimeCompare([]byte(p.Binding), []byte(bindingDigest(proof))) != 1 {
 63		return web.ErrLoginElsewhere
 64	}
 65	return nil
 66}
 67
 68func requestFailure(err error) error {
 69	switch {
 70	case errors.Is(err, errDeadRequest), errors.Is(err, dexstorage.ErrNotFound):
 71		return fmt.Errorf("%w: %w", web.ErrLoginExpired, err)
 72	case errors.Is(err, web.ErrLoginElsewhere):
 73		return err
 74	case err != nil:
 75		return fmt.Errorf("%w: auth request: %w", web.ErrLoginBusy, err)
 76	}
 77	return nil
 78}
 79
 80type LoginClaim struct {
 81	Proof   string
 82	Expiry  time.Time
 83	Silent  bool
 84	request dexstorage.AuthRequest
 85	record  pending
 86}
 87
 88func loginClaim(a dexstorage.AuthRequest, p pending, proof string) LoginClaim {
 89	return LoginClaim{Proof: proof, Expiry: a.Expiry, Silent: a.Prompt == promptNone, request: a, record: p}
 90}
 91
 92type LoginRequest struct {
 93	Silent bool
 94	Expiry time.Time
 95}
 96
 97func (v *Verifier) Unclaimed(ctx context.Context, state string) (LoginRequest, error) {
 98	a, p, err := v.waiting(ctx, state)
 99	if err != nil {
100		return LoginRequest{}, err
101	}
102	if p.Binding != "" {
103		return LoginRequest{}, web.ErrLoginElsewhere
104	}
105	return LoginRequest{Silent: a.Prompt == promptNone, Expiry: a.Expiry}, nil
106}
107
108func (v *Verifier) Claim(ctx context.Context, state, proof string) (LoginClaim, error) {
109	if proof == "" {
110		return LoginClaim{}, web.ErrLoginElsewhere
111	}
112	a, p, err := v.waiting(ctx, state)
113	if err != nil {
114		return LoginClaim{}, err
115	}
116	if p.Binding != "" {
117		if err := checkBinding(p, proof, false); err != nil {
118			return LoginClaim{}, err
119		}
120		return loginClaim(a, p, proof), nil
121	}
122	var claim LoginClaim
123	err = v.deps.dex.UpdateAuthRequest(ctx, state, func(a dexstorage.AuthRequest) (dexstorage.AuthRequest, error) {
124		if !v.live(a) {
125			return a, errDeadRequest
126		}
127		p, err := record(a.ConnectorData)
128		if err != nil {
129			return a, err
130		}
131		if p.Binding == "" {
132			p.Binding = bindingDigest(proof)
133			a.ConnectorData = encodePending(p)
134		}
135		if err := checkBinding(p, proof, false); err != nil {
136			return a, err
137		}
138		claim = loginClaim(a, p, proof)
139		return a, nil
140	})
141	if err != nil {
142		return LoginClaim{}, requestFailure(err)
143	}
144	return claim, nil
145}
146
147func (v *Verifier) updateBound(ctx context.Context, state, proof string, bare bool, update func(dexstorage.AuthRequest, pending) (pending, error)) error {
148	err := v.deps.dex.UpdateAuthRequest(ctx, state, func(a dexstorage.AuthRequest) (dexstorage.AuthRequest, error) {
149		if !v.live(a) {
150			return a, errDeadRequest
151		}
152		p, err := record(a.ConnectorData)
153		if err != nil {
154			return a, err
155		}
156		if err := checkBinding(p, proof, bare); err != nil {
157			return a, err
158		}
159		p, err = update(a, p)
160		if err != nil {
161			return a, err
162		}
163		a.ConnectorData = encodePending(p)
164		return a, nil
165	})
166	return requestFailure(err)
167}