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}