package server_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" ) func internalAuth(srv http.Handler, secret string, body map[string]string) *httptest.ResponseRecorder { b, _ := json.Marshal(body) req := httptest.NewRequest(http.MethodPost, "/internal/auth", bytes.NewReader(b)) if secret != "" { req.Header.Set("Authorization", "Bearer "+secret) } rr := httptest.NewRecorder() srv.ServeHTTP(rr, req) return rr } func TestInternalAuthService(t *testing.T) { const secret = "test-secret" srv, d := newSecretServer(t, secret) id := makeApprovedUser(t, d, "Xmppfan", "hunter2hunter2") if err := d.SetUserServiceEnabled(id, "prosody", true); err != nil { t.Fatal(err) } makeApprovedUser(t, d, "noxmpp", "hunter2hunter2") cases := []struct { name string secret string body map[string]string want int }{ {"enabled, folded name", secret, map[string]string{"username": "xmppfan", "password": "hunter2hunter2", "service": "prosody"}, http.StatusOK}, {"service not enabled", secret, map[string]string{"username": "noxmpp", "password": "hunter2hunter2", "service": "prosody"}, http.StatusForbidden}, {"other service", secret, map[string]string{"username": "Xmppfan", "password": "hunter2hunter2", "service": "ergo"}, http.StatusForbidden}, {"wrong password", secret, map[string]string{"username": "Xmppfan", "password": "nope", "service": "prosody"}, http.StatusUnauthorized}, {"no service keeps old behaviour", secret, map[string]string{"username": "noxmpp", "password": "hunter2hunter2"}, http.StatusOK}, {"no service is case-sensitive", secret, map[string]string{"username": "xmppfan", "password": "hunter2hunter2"}, http.StatusUnauthorized}, {"bad bearer", "wrong", map[string]string{"username": "Xmppfan", "password": "hunter2hunter2", "service": "prosody"}, http.StatusUnauthorized}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if rr := internalAuth(srv, tc.secret, tc.body); rr.Code != tc.want { t.Errorf("status: got %d, want %d", rr.Code, tc.want) } }) } rr := internalAuth(srv, secret, map[string]string{"username": "xmppfan", "password": "hunter2hunter2", "service": "prosody"}) var resp struct{ Username string } if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil || resp.Username != "Xmppfan" { t.Errorf("canonical username: got %q (%v)", resp.Username, err) } } func TestInternalAuthServiceLimiter(t *testing.T) { const secret = "test-secret" srv, d := newSecretServer(t, secret) id := makeApprovedUser(t, d, "target", "hunter2hunter2") d.SetUserServiceEnabled(id, "ergo", true) for i := 0; i < 10; i++ { internalAuth(srv, secret, map[string]string{"username": "TARGET", "password": "guess", "service": "ergo"}) } rr := internalAuth(srv, secret, map[string]string{"username": "target", "password": "hunter2hunter2", "service": "ergo"}) if rr.Code != http.StatusTooManyRequests { t.Errorf("after 10 failures: got %d, want 429", rr.Code) } rr = internalAuth(srv, secret, map[string]string{"username": "target", "password": "hunter2hunter2", "service": "prosody"}) if rr.Code == http.StatusTooManyRequests { t.Error("limiter should be per service") } } func TestInternalUsers(t *testing.T) { const secret = "test-secret" srv, d := newSecretServer(t, secret) id := makeApprovedUser(t, d, "alice", "hunter2hunter2") d.SetUserServiceEnabled(id, "prosody", true) makeApprovedUser(t, d, "bob", "hunter2hunter2") get := func(secret, q string) *httptest.ResponseRecorder { req := httptest.NewRequest(http.MethodGet, "/internal/users"+q, nil) if secret != "" { req.Header.Set("Authorization", "Bearer "+secret) } rr := httptest.NewRecorder() srv.ServeHTTP(rr, req) return rr } rr := get(secret, "?service=prosody") var resp struct{ Users []string } if rr.Code != http.StatusOK || json.NewDecoder(rr.Body).Decode(&resp) != nil { t.Fatalf("status %d", rr.Code) } if len(resp.Users) != 1 || resp.Users[0] != "alice" { t.Errorf("want [alice], got %v", resp.Users) } if rr := get("", "?service=prosody"); rr.Code != http.StatusUnauthorized { t.Errorf("no bearer: got %d", rr.Code) } if rr := get(secret, ""); rr.Code != http.StatusBadRequest { t.Errorf("no service: got %d", rr.Code) } }