oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/base64"
7 "encoding/hex"
8 "encoding/json"
9 "net/http"
10 "net/http/httptest"
11 "net/url"
12 "reflect"
13 "strings"
14 "testing"
15 "time"
16
17 "amadan.net/rastrillo/rastrillo/assertion"
18 assertioncrypto "amadan.net/rastrillo/rastrillo/crypto"
19 "amadan.net/rastrillo/rastrillo/sessions"
20)
21
22type routerHomeFixture struct {
23 app *App
24 key *assertioncrypto.Keypair
25 keyID string
26 sessions *sessions.Sessions
27}
28type routerBrowser struct {
29 f *routerHomeFixture
30 cookies map[string]*http.Cookie
31}
32
33func newRouterHomeFixture(t *testing.T) *routerHomeFixture {
34 t.Helper()
35 key, err := assertioncrypto.Generate()
36 if err != nil {
37 t.Fatal(err)
38 }
39 digest := sha256.Sum256(key.SignPub())
40 kid := hex.EncodeToString(digest[:])
41 manifest, err := json.Marshal(map[string]string{kid: base64.StdEncoding.EncodeToString(key.SignPub())})
42 if err != nil {
43 t.Fatal(err)
44 }
45 cfg := HomeConfig{Issuer: bridgeIssuer, ConsumerID: "router-docs", KeysBase64: base64.StdEncoding.EncodeToString(manifest), DEKBase64: base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{83}, 32))}
46 app, err := NewForTestWith(t, Config{Origin: bridgeOrigin, Home: &cfg, AllowedAddresses: "alice@example.com,bob@example.com", SampleDocuments: "0", Mailer: navigationMail{}})
47 if err != nil {
48 t.Fatal(err)
49 }
50 sess, err := sessions.New(sessions.Config{DB: app.db.Writer(), Origin: bridgeOrigin})
51 if err != nil {
52 t.Fatal(err)
53 }
54 return &routerHomeFixture{app: app, key: key, keyID: kid, sessions: sess}
55}
56func (f *routerHomeFixture) browser() *routerBrowser {
57 return &routerBrowser{f: f, cookies: map[string]*http.Cookie{}}
58}
59func (b *routerBrowser) request(method, path, body string) *http.Request {
60 r := httptest.NewRequest(method, bridgeOrigin+path, strings.NewReader(body))
61 for _, c := range b.cookies {
62 r.AddCookie(c)
63 }
64 if method == "POST" {
65 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
66 r.Header.Set("Origin", bridgeOrigin)
67 r.Header.Set("Sec-Fetch-Site", "same-origin")
68 }
69 return r
70}
71func (b *routerBrowser) serve(r *http.Request) *httptest.ResponseRecorder {
72 rec := httptest.NewRecorder()
73 var w http.ResponseWriter = rec
74 if b.f.app.store.team != nil {
75 w = &teamDeadlineRecorder{ResponseRecorder: rec}
76 }
77 b.f.app.Handler().ServeHTTP(w, r)
78 for _, c := range rec.Result().Cookies() {
79 if c.MaxAge < 0 {
80 delete(b.cookies, c.Name)
81 } else {
82 b.cookies[c.Name] = c
83 }
84 }
85 return rec
86}
87func (b *routerBrowser) do(method, path, body string) *httptest.ResponseRecorder {
88 return b.serve(b.request(method, path, body))
89}
90func routerRedirect(t *testing.T, rec *httptest.ResponseRecorder, want string) string {
91 t.Helper()
92 location := rec.Header().Get("Location")
93 if rec.Code != 303 || (want != "" && location != want) {
94 t.Fatalf("router redirect status=%d target=%q want=%q", rec.Code, location, want)
95 }
96 return location
97}
98func (b *routerBrowser) localLink(t *testing.T, address string) string {
99 t.Helper()
100 b.f.app.signinMail.wait()
101 b.f.app.mailRecorder.mu.Lock()
102 before := b.f.app.mailRecorder.sends
103 b.f.app.mailRecorder.mu.Unlock()
104 rec := b.do("POST", "/signin", url.Values{"address": {address}, "force": {"1"}}.Encode())
105 if rec.Code != 303 || !strings.Contains(rec.Header().Get("Location"), "sent=1") {
106 t.Fatal("local mail begin failed", rec.Code)
107 }
108 b.f.app.signinMail.wait() // the link is sent after the response
109 b.f.app.mailRecorder.mu.Lock()
110 to, body, after := b.f.app.mailRecorder.lastTo, b.f.app.mailRecorder.lastBody, b.f.app.mailRecorder.sends
111 b.f.app.mailRecorder.mu.Unlock()
112 if after != before+1 || to != address {
113 t.Fatal("actual local mail not captured")
114 }
115 link := magicLinkPattern.FindString(body)
116 path, err := linkPath(link)
117 if err != nil || path == "" {
118 t.Fatal("captured verification link missing")
119 }
120 return path
121}
122
123// localSignin completes a local email sign-in. It lands on the list, or
124// on landing when the browser was sent to sign in from a page (spec
125// §9.4: the remembered return path, not "/").
126func (b *routerBrowser) localSignin(t *testing.T, address string, landing ...string) {
127 t.Helper()
128 want := "/"
129 if len(landing) == 1 {
130 want = landing[0]
131 }
132 path := b.localLink(t, address)
133 verifyPath, verifyForm := confirmForm(path)
134 rec := b.do("POST", verifyPath, verifyForm)
135 routerRedirect(t, rec, localLinkCompletePath)
136 routerRedirect(t, b.do("GET", localLinkCompletePath, ""), want)
137 if _, ok := b.cookies[b.f.sessions.CookieName()]; !ok {
138 t.Fatal("local verification did not set real session")
139 }
140}
141func (b *routerBrowser) start(t *testing.T, path, maxAge string) string {
142 t.Helper()
143 rec := b.do("GET", path, "")
144 target, err := url.Parse(routerRedirect(t, rec, ""))
145 if err != nil {
146 t.Fatal(err)
147 }
148 if target.Scheme+"://"+target.Host != bridgeIssuer || target.Path != "/authorize" || target.Query().Get("consumer") != "router-docs" || target.Query().Get("max_age") != maxAge || target.Query().Get("nonce") == "" {
149 t.Fatal("router selected wrong issuer/freshness")
150 }
151 return target.Query().Get("nonce")
152}
153func (f *routerHomeFixture) token(t *testing.T, nonce, subject string, auth time.Time, change func(*assertion.Claims)) string {
154 t.Helper()
155 id, err := assertion.RandomID()
156 if err != nil {
157 t.Fatal(err)
158 }
159 now := time.Now().Unix()
160 c := assertion.Claims{Version: 1, Issuer: bridgeIssuer, Audience: bridgeOrigin, Subject: subject, KeyID: f.keyID, ID: id, Nonce: nonce, IssuedAt: now, ExpiresAt: now + 120, AuthTime: auth.Unix()}
161 if change != nil {
162 change(&c)
163 }
164 token, err := assertion.Sign(f.key, c)
165 if err != nil {
166 t.Fatal(err)
167 }
168 return token
169}
170func (b *routerBrowser) callback(token, origin string) *httptest.ResponseRecorder {
171 // The issuer POST deliberately carries no browser cookies, as with Lax cookies.
172 r := httptest.NewRequest("POST", bridgeOrigin+"/auth/assertion", strings.NewReader(url.Values{"assertion": {token}}.Encode()))
173 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
174 r.Header.Set("Origin", origin)
175 r.Header.Set("Sec-Fetch-Site", "cross-site")
176 return b.serve(r)
177}
178func (b *routerBrowser) exchange(t *testing.T, nonce, subject string, auth time.Time) *httptest.ResponseRecorder {
179 t.Helper()
180 routerRedirect(t, b.callback(b.f.token(t, nonce, subject, auth, nil), bridgeIssuer), "/auth/finish")
181 return b.do("GET", "/auth/finish", "")
182}
183func (b *routerBrowser) linkProof(t *testing.T, subject, address string) {
184 t.Helper()
185 page := b.do("GET", "/account/home", "")
186 if page.Code != 200 || !strings.Contains(page.Body.String(), address) {
187 t.Fatal("explicit initiation page did not identify local account", page.Code)
188 }
189 next := routerRedirect(t, b.do("POST", "/account/home", ""), "")
190 nonce := b.start(t, next, "300")
191 routerRedirect(t, b.exchange(t, nonce, subject, time.Now().Add(-time.Minute)), "/account/home/confirm")
192 confirm := b.do("GET", "/account/home/confirm", "")
193 if confirm.Code != 200 || !strings.Contains(confirm.Body.String(), address) {
194 t.Fatal("confirmation did not identify local account", confirm.Code)
195 }
196}
197func routerRows(t *testing.T, f *routerHomeFixture, table string) int {
198 t.Helper()
199 var n int
200 if err := f.app.db.Writer().QueryRow("SELECT COUNT(*) FROM " + table).Scan(&n); err != nil {
201 t.Fatal(err)
202 }
203 return n
204}
205
206func TestHomeRouterExistingAccountRoundTripPreservesOwnedAndSharedContent(t *testing.T) {
207 f := newRouterHomeFixture(t)
208 s := f.app.store
209 ctx := t.Context()
210 alice, err := s.EnsurePerson(ctx, "alice@example.com")
211 if err != nil {
212 t.Fatal(err)
213 }
214 bob, err := s.EnsurePerson(ctx, "bob@example.com")
215 if err != nil {
216 t.Fatal(err)
217 }
218 own, err := s.Create(ctx, alice, "Alice original")
219 if err != nil {
220 t.Fatal(err)
221 }
222 shared, err := s.Create(ctx, bob, "Bob shared")
223 if err != nil {
224 t.Fatal(err)
225 }
226 hidden, err := s.Create(ctx, bob, "Bob private")
227 if err != nil {
228 t.Fatal(err)
229 }
230 if err = s.Grant(ctx, bob, shared.Sqid, "alice@example.com", RoleWriter); err != nil {
231 t.Fatal(err)
232 }
233 for _, doc := range []*Document{own, shared} {
234 owner := alice
235 address := "alice@example.com"
236 if doc.ID == shared.ID {
237 owner = bob
238 address = "bob@example.com"
239 }
240 if _, err = s.ApplyUpdate(ctx, doc, localActor(owner, address), ReadFixture(t, "hello")); err != nil {
241 t.Fatal(err)
242 }
243 }
244 var originalDocs []Document
245 var originalUpdates []DocUpdate
246 var originalGrants []Grant
247 if err = s.db.Order("id").Find(&originalDocs).Error; err != nil {
248 t.Fatal(err)
249 }
250 if err = s.db.Order("id").Find(&originalUpdates).Error; err != nil {
251 t.Fatal(err)
252 }
253 if err = s.db.Order("id").Find(&originalGrants).Error; err != nil {
254 t.Fatal(err)
255 }
256 b := f.browser()
257 resource := "/d/" + own.Sqid
258 signin := routerRedirect(t, b.do("GET", resource, ""), "")
259 parsed, err := url.Parse(signin)
260 if err != nil || parsed.Path != "/signin" || parsed.Query().Get("return_to") != resource {
261 t.Fatal("unsigned document lost original destination")
262 }
263 start := routerRedirect(t, b.do("GET", signin, ""), "")
264 nonce := b.start(t, start, "")
265 unknown := b.exchange(t, nonce, "opaque-alice", time.Now().Add(-time.Hour))
266 if unknown.Code != 403 || !strings.Contains(unknown.Body.String(), "/signin/local") {
267 t.Fatal("unknown Home subject did not show local guidance", unknown.Code)
268 }
269 if routerRows(t, f, "people") != 2 || routerRows(t, f, "documents") != 3 || routerRows(t, f, "docs_identity_links") != 0 || b.cookies[f.sessions.CookieName()] != nil {
270 t.Fatal("unknown Home subject created an account or session")
271 }
272 // The local link lands on the document this browser was sent to sign
273 // in from, not the list (spec §9.4).
274 b.localSignin(t, "alice@example.com", resource)
275 localCookie := b.cookies[f.sessions.CookieName()].Value
276 b.linkProof(t, "opaque-alice", "alice@example.com")
277 if b.cookies[f.sessions.CookieName()].Value != localCookie || routerRows(t, f, "docs_identity_links") != 0 {
278 t.Fatal("proof replaced local session or linked before confirmation")
279 }
280 routerRedirect(t, b.do("POST", "/account/home/confirm", ""), resource)
281 if b.cookies[f.sessions.CookieName()].Value != localCookie || routerRows(t, f, "docs_identity_links") != 1 {
282 t.Fatal("confirmation failed local session retention")
283 }
284 if rec := b.do("GET", resource, ""); rec.Code != 200 {
285 t.Fatal("linked local account cannot read original document", rec.Code)
286 }
287 signoutChained(t, b.do("POST", "/signout", ""))
288 signedOut := b.do("GET", "/signed-out", "")
289 if signedOut.Code != http.StatusOK || signedOut.Header().Get("Location") != "" || !strings.Contains(signedOut.Body.String(), `href="/signin"`) {
290 t.Fatal("signout automatically resumed Home authentication")
291 }
292 if b.cookies[f.sessions.CookieName()] != nil {
293 t.Fatal("actual signout retained session cookie")
294 }
295 oldRequest := httptest.NewRequest("GET", bridgeOrigin+resource, nil)
296 oldRequest.AddCookie(&http.Cookie{Name: f.sessions.CookieName(), Value: localCookie})
297 oldRec := httptest.NewRecorder()
298 f.app.Handler().ServeHTTP(oldRec, oldRequest)
299 if oldRec.Code != 303 {
300 t.Fatal("actual signout did not revoke old session")
301 }
302 signin = routerRedirect(t, b.do("GET", resource, ""), "")
303 start = routerRedirect(t, b.do("GET", signin, ""), "")
304 nonce = b.start(t, start, "")
305 originalAuth := time.Now().Add(-2 * time.Hour).Truncate(time.Second)
306 routerRedirect(t, b.exchange(t, nonce, "opaque-alice", originalAuth), resource)
307 session, ok := f.sessions.From(b.request("GET", resource, ""))
308 if !ok || session.Method != homeAssertionMethod || session.Subject != "opaque-alice" || !session.AuthTime.Equal(originalAuth) {
309 t.Fatal("ordinary Home session lost actual authentication time")
310 }
311 for _, doc := range []*Document{own, shared} {
312 rec := b.do("GET", "/d/"+doc.Sqid, "")
313 if rec.Code != 200 {
314 t.Fatal("Home session lost owned/shared access", rec.Code)
315 }
316 }
317 if rec := b.do("GET", "/d/"+hidden.Sqid, ""); rec.Code != 404 {
318 t.Fatal("Home link widened another owner's private access", rec.Code)
319 }
320 var afterDocs []Document
321 var afterUpdates []DocUpdate
322 var afterGrants []Grant
323 if err = s.db.Order("id").Find(&afterDocs).Error; err != nil {
324 t.Fatal(err)
325 }
326 if err = s.db.Order("id").Find(&afterUpdates).Error; err != nil {
327 t.Fatal(err)
328 }
329 if err = s.db.Order("id").Find(&afterGrants).Error; err != nil {
330 t.Fatal(err)
331 }
332 if !reflect.DeepEqual(originalDocs, afterDocs) || !reflect.DeepEqual(originalUpdates, afterUpdates) || !reflect.DeepEqual(originalGrants, afterGrants) || routerRows(t, f, "people") != 2 {
333 t.Fatal("account link changed owners/grants/content encoding or bytes")
334 }
335 // A different current local account cannot be silently replaced by Alice's SSO.
336 other := f.browser()
337 other.localSignin(t, "bob@example.com")
338 before := other.cookies[f.sessions.CookieName()].Value
339 nonce = other.start(t, "/auth/start?return_to=%2F", "")
340 if rec := other.exchange(t, nonce, "opaque-alice", time.Now()); rec.Code != 403 {
341 t.Fatal("different local account was replaced", rec.Code)
342 }
343 if other.cookies[f.sessions.CookieName()].Value != before {
344 t.Fatal("conflict rotated local account")
345 }
346}
347
348func TestHomeRouterCSRFGuardsBothLinkMutations(t *testing.T) {
349 f := newRouterHomeFixture(t)
350 b := f.browser()
351 b.localSignin(t, "alice@example.com")
352 if rec := b.do("GET", "/account/home", ""); rec.Code != 200 {
353 t.Fatal("missing local initiation page")
354 }
355 for _, fetch := range []string{"cross-site", ""} {
356 r := b.request("POST", "/account/home", "")
357 r.Header.Set("Origin", "https://evil.example")
358 r.Header.Set("Sec-Fetch-Site", fetch)
359 rec := b.serve(r)
360 if rec.Code != 403 || routerRows(t, f, "docs_link_flows") != 0 {
361 t.Fatal("actual router permitted foreign link initiation", rec.Code)
362 }
363 }
364 b.linkProof(t, "opaque-alice", "alice@example.com")
365 for _, fetch := range []string{"cross-site", ""} {
366 r := b.request("POST", "/account/home/confirm", "")
367 r.Header.Set("Origin", "https://evil.example")
368 r.Header.Set("Sec-Fetch-Site", fetch)
369 rec := b.serve(r)
370 if rec.Code != 403 || routerRows(t, f, "docs_identity_links") != 0 || routerRows(t, f, "docs_link_flows") != 1 {
371 t.Fatal("actual router permitted foreign confirmation or consumed valid proof", rec.Code)
372 }
373 }
374 routerRedirect(t, b.do("POST", "/account/home/confirm", ""), "/")
375 if routerRows(t, f, "docs_identity_links") != 1 {
376 t.Fatal("valid same-origin confirmation failed after refusal")
377 }
378}
379
380func TestHomeRouterRejectsDuplicateCookiesBeforeActualEmailVerification(t *testing.T) {
381 f := newRouterHomeFixture(t)
382 b := f.browser()
383 b.localSignin(t, "alice@example.com")
384 old := b.cookies[f.sessions.CookieName()]
385 path := b.localLink(t, "alice@example.com")
386 for _, name := range []string{f.sessions.CookieName(), "__Host-rastrillo_pending"} {
387 verifyPath, verifyForm := confirmForm(path)
388 r := b.request("POST", verifyPath, verifyForm)
389 if name == f.sessions.CookieName() {
390 r.AddCookie(old)
391 } else {
392 r.AddCookie(&http.Cookie{Name: name, Value: "one"})
393 r.AddCookie(&http.Cookie{Name: name, Value: "two"})
394 }
395 rec := b.serve(r)
396 if rec.Code != 400 {
397 t.Fatal("duplicate cookie reached actual Verify", name, rec.Code)
398 }
399 if b.cookies[f.sessions.CookieName()].Value != old.Value {
400 t.Fatal("refused duplicate rotated browser session")
401 }
402 if _, ok := f.sessions.From(b.request("GET", "/", "")); !ok {
403 t.Fatal("refused duplicate revoked actual session")
404 }
405 }
406 // Both refusals happened before CompleteMagicLink consumed the token.
407 verifyPath, verifyForm := confirmForm(path)
408 routerRedirect(t, b.do("POST", verifyPath, verifyForm), localLinkCompletePath)
409 if b.cookies[f.sessions.CookieName()].Value == old.Value {
410 t.Fatal("clean real Verify did not rotate cookie")
411 }
412 r := httptest.NewRequest("GET", bridgeOrigin+"/", nil)
413 r.AddCookie(old)
414 if _, ok := f.sessions.From(r); ok {
415 t.Fatal("successful real Verify did not revoke old session")
416 }
417 routerRedirect(t, b.do("GET", localLinkCompletePath, ""), "/")
418}
419
420func TestHomeRouterStaleLocalGestureRestartsWithoutResumingProof(t *testing.T) {
421 f := newRouterHomeFixture(t)
422 b := f.browser()
423 b.localSignin(t, "alice@example.com")
424 b.linkProof(t, "opaque-alice", "alice@example.com")
425 oldProof := b.cookies[homeProofCookie]
426 oldSession := b.cookies[f.sessions.CookieName()]
427 if oldProof == nil || routerRows(t, f, "docs_link_flows") != 1 {
428 t.Fatal("pending proof fixture missing")
429 }
430 // Move only the actual stored local verification time into the past. The
431 // old pending proof remains present so its post-rotation refusal is exercised.
432 if _, err := f.app.db.Writer().Exec(`UPDATE sessions SET created_at=? WHERE token_hash=?`, time.Now().Add(-6*time.Minute).UTC().Format(time.RFC3339), sessions.HashToken(oldSession.Value)); err != nil {
433 t.Fatal(err)
434 }
435 routerRedirect(t, b.do("GET", "/account/home", ""), "/signin/local")
436 if b.cookies[localLinkNavigationCookie] == nil || b.cookies[homeInitCookie] != nil || b.cookies[homeProofCookie] != nil {
437 t.Fatal("stale local restart retained link browser proof")
438 }
439 if rec := b.do("GET", "/signin/local", ""); rec.Code != 200 {
440 t.Fatal("signed-in stale visitor could not see local signin")
441 }
442 path := b.localLink(t, "alice@example.com")
443 verifyPath, verifyForm := confirmForm(path)
444 routerRedirect(t, b.do("POST", verifyPath, verifyForm), localLinkCompletePath)
445 routerRedirect(t, b.do("GET", localLinkCompletePath, ""), "/account/home")
446 if b.cookies[f.sessions.CookieName()].Value == oldSession.Value || b.cookies[homeProofCookie] != nil || b.cookies[homeInitCookie] != nil || routerRows(t, f, "docs_identity_links") != 0 {
447 t.Fatal("navigation minted or resumed link authority")
448 }
449 if rec := b.do("GET", "/account/home", ""); rec.Code != 200 {
450 t.Fatal("fresh local gesture did not reach initiation page")
451 }
452 // A copied old proof cannot attach to the freshly rotated local session.
453 b.cookies[homeProofCookie] = oldProof
454 if rec := b.do("POST", "/account/home/confirm", ""); rec.Code != 403 {
455 t.Fatal("old proof resumed after actual session rotation", rec.Code)
456 }
457 if routerRows(t, f, "docs_identity_links") != 0 || routerRows(t, f, "docs_link_flows") != 0 {
458 t.Fatal("old proof failure created link or retained reusable proof")
459 }
460 oldRequest := httptest.NewRequest("GET", bridgeOrigin+"/", nil)
461 oldRequest.AddCookie(oldSession)
462 if _, ok := f.sessions.From(oldRequest); ok {
463 t.Fatal("old local browser token survived reverification")
464 }
465 // Only this new explicit POST creates a new initiation, not the email or GET.
466 routerRedirect(t, b.do("POST", "/account/home", ""), "/auth/start?return_to=%2Faccount%2Fhome%2Fconfirm")
467 if routerRows(t, f, "docs_link_flows") != 1 || routerRows(t, f, "docs_identity_links") != 0 || b.cookies[homeProofCookie] != nil {
468 t.Fatal("new explicit initiation state wrong")
469 }
470}
471
472func TestHomeRouterCallbackChecksExactOriginIssuerAndAudience(t *testing.T) {
473 f := newRouterHomeFixture(t)
474 b := f.browser()
475 nonce := b.start(t, "/auth/start?return_to=%2F", "")
476 for _, tc := range []struct {
477 name, origin string
478 change func(*assertion.Claims)
479 }{
480 {"wrong origin", "https://evil.example", nil},
481 {"missing origin", "", nil},
482 {"wrong signed issuer", bridgeIssuer, func(c *assertion.Claims) { c.Issuer = "https://other-id.example" }},
483 {"wrong signed audience", bridgeIssuer, func(c *assertion.Claims) { c.Audience = "https://other.docs.oficina.works" }},
484 } {
485 t.Run(tc.name, func(t *testing.T) {
486 rec := b.callback(f.token(t, nonce, "opaque", time.Now(), tc.change), tc.origin)
487 if rec.Code != 400 || routerRows(t, f, "oficina_handoff_inbox") != 0 || b.cookies[f.sessions.CookieName()] != nil {
488 t.Fatal("invalid callback created inbox/session", rec.Code)
489 }
490 })
491 }
492 r := b.request("GET", "/auth/start?return_to=%2F", "")
493 r.Host = "other.docs.oficina.works"
494 if rec := b.serve(r); rec.Code != 404 {
495 t.Fatal("router accepted another team host", rec.Code)
496 }
497 // A valid issuer callback still crosses the outer CSRF boundary and reaches
498 // the shared consumer. Duplicate local cookies cannot poison that callback.
499 token := f.token(t, nonce, "opaque", time.Now(), nil)
500 r = httptest.NewRequest("POST", bridgeOrigin+"/auth/assertion", strings.NewReader(url.Values{"assertion": {token}}.Encode()))
501 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
502 r.Header.Set("Origin", bridgeIssuer)
503 r.Header.Set("Sec-Fetch-Site", "cross-site")
504 r.AddCookie(&http.Cookie{Name: f.sessions.CookieName(), Value: "bad"})
505 r.AddCookie(&http.Cookie{Name: f.sessions.CookieName(), Value: "bad"})
506 routerRedirect(t, b.serve(r), "/auth/finish")
507 if rec := b.do("GET", "/auth/finish", ""); rec.Code != 403 || !strings.Contains(rec.Body.String(), "/signin/local") {
508 t.Fatal("unknown verified identity not guided after valid callback")
509 }
510 if routerRows(t, f, "people") != 0 || routerRows(t, f, "docs_identity_links") != 0 {
511 t.Fatal("unknown callback created person/link")
512 }
513}
514