Skip to content

Commit de80b7f

Browse files
authored
fix: admin recovery CSRF & duplicate form elements (#2846)
1 parent 411cd79 commit de80b7f

File tree

3 files changed

+63
-7
lines changed

3 files changed

+63
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[
2+
{
3+
"type": "input",
4+
"group": "code",
5+
"attributes": {
6+
"name": "code",
7+
"type": "number",
8+
"required": true,
9+
"disabled": false,
10+
"node_type": "input"
11+
},
12+
"messages": [],
13+
"meta": {
14+
"label": {
15+
"id": 1070006,
16+
"text": "Verify code",
17+
"type": "info"
18+
}
19+
}
20+
},
21+
{
22+
"type": "input",
23+
"group": "code",
24+
"attributes": {
25+
"name": "method",
26+
"type": "submit",
27+
"value": "code",
28+
"disabled": false,
29+
"node_type": "input"
30+
},
31+
"messages": [],
32+
"meta": {
33+
"label": {
34+
"id": 1070005,
35+
"text": "Submit",
36+
"type": "info"
37+
}
38+
}
39+
}
40+
]

selfservice/strategy/code/strategy_recovery.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ func (s *Strategy) createRecoveryCode(w http.ResponseWriter, r *http.Request, _
168168
}
169169
flow.DangerousSkipCSRFCheck = true
170170
flow.State = recovery.StateEmailSent
171+
flow.UI.Nodes = node.Nodes{}
171172
flow.UI.Nodes.Append(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeNumber, node.WithRequiredInputAttribute).
172173
WithMetaLabel(text.NewInfoNodeLabelVerifyOTP()),
173174
)
@@ -270,9 +271,13 @@ func (s *Strategy) Recover(w http.ResponseWriter, r *http.Request, f *recovery.F
270271
}
271272
ctx := r.Context()
272273

273-
// If a CSRF violation occurs the flow is most likely FUBAR, as the user either lost the CSRF token, or an attack occured.
274-
// In this case, we just issue a new flow and "abandon" the old flow.
275-
if err := flow.EnsureCSRF(s.deps, r, f.Type, s.deps.Config().DisableAPIFlowEnforcement(ctx), s.deps.GenerateCSRFToken, body.CSRFToken); err != nil {
274+
if f.DangerousSkipCSRFCheck {
275+
s.deps.Logger().
276+
WithRequest(r).
277+
Debugf("A recovery flow with `DangerousSkipCSRFCheck` set has been submitted, skipping anti-CSRF measures.")
278+
} else if err := flow.EnsureCSRF(s.deps, r, f.Type, s.deps.Config().DisableAPIFlowEnforcement(ctx), s.deps.GenerateCSRFToken, body.CSRFToken); err != nil {
279+
// If a CSRF violation occurs the flow is most likely FUBAR, as the user either lost the CSRF token, or an attack occured.
280+
// In this case, we just issue a new flow and "abandon" the old flow.
276281
return s.retryRecoveryFlowWithError(w, r, flow.TypeBrowser, err)
277282
}
278283

selfservice/strategy/code/strategy_recovery_test.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,9 @@ func TestAdminStrategy(t *testing.T) {
134134

135135
action := gjson.GetBytes(body, "ui.action").String()
136136
require.NotEmpty(t, action)
137-
csrfToken := gjson.GetBytes(body, "ui.nodes.#(attributes.name==csrf_token).attributes.value").String()
138-
require.NotEmpty(t, csrfToken)
139137

140138
res, err = publicTS.Client().PostForm(action, url.Values{
141-
"csrf_token": {csrfToken},
142-
"code": {code},
139+
"code": {code},
143140
})
144141
assert.Equal(t, http.StatusOK, res.StatusCode)
145142

@@ -229,6 +226,20 @@ func TestAdminStrategy(t *testing.T) {
229226

230227
assertMessage(t, body, "The recovery code is invalid or has already been used. Please try again.")
231228
})
229+
230+
t.Run("case=form should not contain email field when creating recovery code", func(t *testing.T) {
231+
email := strings.ToLower(testhelpers.RandomEmail())
232+
i := createIdentityToRecover(t, reg, email)
233+
234+
c1, _, err := createCode(i.ID.String(), pointerx.String("1h"))
235+
require.NoError(t, err)
236+
237+
res, err := http.Get(c1.RecoveryLink)
238+
require.NoError(t, err)
239+
body := ioutilx.MustReadAll(res.Body)
240+
241+
snapshotx.SnapshotT(t, json.RawMessage(gjson.GetBytes(body, "ui.nodes").String()))
242+
})
232243
}
233244

234245
const (

0 commit comments

Comments
 (0)