Files

85 lines
3.2 KiB
Go
Raw Permalink Normal View History

package onboarding
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"data-terminal-cloud/internal/identity"
"github.com/google/uuid"
)
const webBLECSRF = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
func webBLESession(t *testing.T, f *fixture, p identity.Principal) string {
t.Helper()
session := randomToken(32)
_, err := f.s.pool.Exec(f.ctx, `INSERT INTO onboarding_account_session(token_hash,csrf_hash,user_id,expires_at) SELECT $1,$2,id,now()+interval '1 hour' FROM user_account WHERE oidc_issuer=$3 AND oidc_subject=$4`, digest([]byte(session)), digest([]byte(webBLECSRF)), p.Issuer, p.Subject)
must(t, err)
return session
}
func webBLERequest(t *testing.T, s *Service, endpoint, session, csrf, origin string, in any) (int, map[string]any) {
t.Helper()
raw, _ := json.Marshal(in)
r := httptest.NewRequest("POST", "https://platform.example/api/v1/web/"+endpoint, bytes.NewReader(raw))
r.Header.Set("Origin", origin)
r.Header.Set("Content-Type", "application/json")
r.Header.Set("X-Request-ID", uuid.NewString())
r.Header.Set("X-CSRF-Token", csrf)
if session != "" {
r.AddCookie(&http.Cookie{Name: s.cookieName(true), Value: session})
}
w := httptest.NewRecorder()
mux := http.NewServeMux()
s.RegisterRoutes(mux)
mux.ServeHTTP(w, r)
var out map[string]any
must(t, json.Unmarshal(w.Body.Bytes(), &out))
data, _ := out["data"].(map[string]any)
return w.Code, data
}
func TestWebBLEStateRequiresOwnerOriginAndCSRF(t *testing.T) {
f := serverFixture(t)
f.web.Hash = ""
p := accountOnlyUser(t, f)
session := webBLESession(t, f, p)
in := BLEDeviceInput{DeviceID: f.d.FactoryID}
for _, endpoint := range []string{"ble-device-state", "device-subject", "ble-bind-tokens"} {
for _, auth := range [][3]string{{"", webBLECSRF, "https://platform.example"}, {session, "wrong", "https://platform.example"}, {session, webBLECSRF, "https://evil.example"}} {
status, _ := webBLERequest(t, f.s, endpoint, auth[0], auth[1], auth[2], in)
if status != 401 && status != 403 {
t.Fatalf("%s allowed invalid auth: %d", endpoint, status)
}
}
}
status, out := webBLERequest(t, f.s, "ble-bind-tokens", session, webBLECSRF, "https://platform.example", BLETokenInput{DeviceID: f.d.FactoryID})
if status != 200 {
t.Fatal("account-first token", status, out)
}
_, err := f.s.BindBLE(f.ctx, f.d, uuid.NewString(), BLEBindInput{Token: out["bind_token"].(string)})
must(t, err)
f.refresh()
generation := f.d.Generation
status, out = webBLERequest(t, f.s, "ble-device-state", session, webBLECSRF, "https://platform.example", in)
if status != 200 || out["bound"] != true || out["id"] != f.d.PublicID {
t.Fatal(status, out)
}
other := webBLESession(t, f, accountOnlyUser(t, f))
status, _ = webBLERequest(t, f.s, "ble-device-state", other, webBLECSRF, "https://platform.example", in)
if status != 403 {
t.Fatal("other account can reconfigure", status)
}
status, _ = webBLERequest(t, f.s, "ble-bind-tokens", session, webBLECSRF, "https://platform.example", BLETokenInput{DeviceID: f.d.FactoryID})
if status != 409 {
t.Fatal("bound device received another claim", status)
}
f.refresh()
if f.d.Generation != generation {
t.Fatal("Wi-Fi eligibility changed ownership")
}
}