package nativeauth import ( "encoding/json" "github.com/carlosframework/rastrillo/db" "github.com/carlosframework/rastrillo/migrate" "github.com/carlosframework/rastrillo/sessions" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" "time" ) func TestCallbackIsFixedAndContainsNoSession(t *testing.T) { d, err := db.Open(filepath.Join(t.TempDir(), "sessions.db"), nil) if err != nil { t.Fatal(err) } defer d.Close() if _, err = migrate.Apply(t.Context(), d, sessions.Schema); err != nil { t.Fatal(err) } s, err := sessions.New(sessions.Config{DB: d.Writer(), Origin: "https://app.test"}) if err != nil { t.Fatal(err) } a := New(s, "https://app.test", "Docs", s.Require) for _, callback := range []string{"https://evil.test", "works.oficina.sheets://signin", "works.oficina.docs://signin?state=evil", "works.oficina.docs://signin/", "javascript:alert(1)"} { data, _ := json.Marshal(map[string]string{"challenge": token(), "callback": callback}) req := httptest.NewRequest("POST", "https://app.test/native/request", strings.NewReader(string(data))) req.Header.Set("Content-Type", "application/json") req.Header.Set("Origin", "https://app.test") w := httptest.NewRecorder() a.ServeHTTP(w, req) if w.Code != 400 { t.Fatalf("callback %q status%d", callback, w.Code) } } for _, callback := range []string{"", "works.oficina.docs://signin"} { id := token() a.pending[id] = &request{challenge: token(), callback: callback, expires: time.Now().Add(time.Minute)} cookie, err := s.Mint(sessions.Session{Subject: "alice"}) if err != nil { t.Fatal(err) } req := httptest.NewRequest("POST", "https://app.test/native/authorize?id="+id, nil) req.Header.Set("Origin", "https://app.test") req.AddCookie(&http.Cookie{Name: s.CookieName(), Value: cookie}) w := httptest.NewRecorder() s.Middleware(a).ServeHTTP(w, req) if w.Code != 200 { t.Fatal(w.Code) } body := w.Body.String() if strings.Contains(body, cookie) || strings.Contains(body, "#ZgotmplZ") { t.Fatal("unsafe or broken callback") } if callback != "" && !strings.Contains(body, `href="works.oficina.docs://signin?state=`+id+`"`) { t.Fatal("missing bound return link") } if callback == "" && strings.Contains(body, `http-equiv="refresh"`) { t.Fatal("legacy flow redirected") } } }