From b9bcbb42ec38b6962ac2f889d86852113886d4f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Mar 2021 12:25:07 +0100 Subject: [PATCH] alias JSON endpoint and testing --- web/handlers/aliases.go | 150 ++++++++++++++++++++++++++++ web/handlers/aliases_test.go | 63 ++++++++++++ web/handlers/basic_test.go | 1 - web/handlers/http.go | 18 +++- web/handlers/invites.go | 3 - web/handlers/invites_test.go | 2 +- web/handlers/setup_test.go | 11 +- web/router/complete.go | 4 + web/templates/aliases-resolved.html | 6 ++ 9 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 web/handlers/aliases.go create mode 100644 web/handlers/aliases_test.go create mode 100644 web/templates/aliases-resolved.html diff --git a/web/handlers/aliases.go b/web/handlers/aliases.go new file mode 100644 index 0000000..3135897 --- /dev/null +++ b/web/handlers/aliases.go @@ -0,0 +1,150 @@ +package handlers + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "go.mindeco.de/http/render" + refs "go.mindeco.de/ssb-refs" + + "github.com/ssb-ngi-pointer/go-ssb-room/aliases" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" +) + +type aliasHandler struct { + r *render.Renderer + + db roomdb.AliasService + + muxrpcHostAndPort string + roomID refs.FeedRef +} + +func (a aliasHandler) resolve(rw http.ResponseWriter, req *http.Request) { + respEncoding := req.URL.Query().Get("encoding") + + var ar aliasResponder + switch respEncoding { + case "json": + ar = newAliasJSONResponder(rw) + default: + ar = newAliasHTMLResponder(a.r, rw, req) + } + + ar.UpdateRoomInfo(a.muxrpcHostAndPort, a.roomID) + + name := mux.Vars(req)["alias"] + if name == "" && !aliases.IsValid(name) { + ar.SendError(fmt.Errorf("invalid alias")) + return + } + + alias, err := a.db.Resolve(req.Context(), name) + if err != nil { + ar.SendError(err) + return + } + + ar.SendConfirmation(alias) +} + +// aliasResponder is supposed handle different encoding types transparently. +// It either sends the signed alias confirmation or an error. +type aliasResponder interface { + SendConfirmation(roomdb.Alias) + SendError(error) + + UpdateRoomInfo(hostAndPort string, roomID refs.FeedRef) +} + +// aliasJSONResponse dictates the field names and format of the JSON response for the alias web endpoint +type aliasJSONResponse struct { + Status string `json:"status"` + Address string `json:"address"` + RoomID string `json:"roomId"` + UserID string `json:"userId"` + Alias string `json:"alias"` + Signature string `json:"signature"` +} + +// handles JSON responses +type aliasJSONResponder struct { + enc *json.Encoder + + roomID refs.FeedRef + multiservAddr string +} + +func newAliasJSONResponder(rw http.ResponseWriter) aliasResponder { + return &aliasJSONResponder{ + enc: json.NewEncoder(rw), + } +} + +func (json *aliasJSONResponder) UpdateRoomInfo(hostAndPort string, roomID refs.FeedRef) { + json.roomID = roomID + + roomPubKey := base64.StdEncoding.EncodeToString(roomID.PubKey()) + json.multiservAddr = fmt.Sprintf("net:%s~shs:%s", hostAndPort, roomPubKey) +} + +func (json aliasJSONResponder) SendConfirmation(alias roomdb.Alias) { + var resp = aliasJSONResponse{ + Status: "successfull", + RoomID: json.roomID.Ref(), + Alias: alias.Name, + UserID: alias.Feed.Ref(), + Signature: base64.StdEncoding.EncodeToString(alias.Signature), + } + json.enc.Encode(resp) +} + +func (json aliasJSONResponder) SendError(err error) { + json.enc.Encode(struct { + Status string `json:"status"` + Error string `json:"error"` + }{"error", err.Error()}) +} + +// handles HTML responses +type aliasHTMLResponder struct { + renderer *render.Renderer + rw http.ResponseWriter + req *http.Request + + roomID refs.FeedRef + multiservAddr string +} + +func newAliasHTMLResponder(r *render.Renderer, rw http.ResponseWriter, req *http.Request) aliasResponder { + return &aliasHTMLResponder{ + renderer: r, + rw: rw, + req: req, + } +} + +func (html *aliasHTMLResponder) UpdateRoomInfo(hostAndPort string, roomID refs.FeedRef) { + html.roomID = roomID + + roomPubKey := base64.StdEncoding.EncodeToString(roomID.PubKey()) + html.multiservAddr = fmt.Sprintf("net:%s~shs:%s", hostAndPort, roomPubKey) +} + +func (html aliasHTMLResponder) SendConfirmation(alias roomdb.Alias) { + err := html.renderer.Render(html.rw, html.req, "aliases-resolved.html", http.StatusOK, struct { + Alias roomdb.Alias + }{alias}) + if err != nil { + log.Println("alias-resolve render errr:", err) + } + +} + +func (html aliasHTMLResponder) SendError(err error) { + html.renderer.Error(html.rw, html.req, http.StatusInternalServerError, err) +} diff --git a/web/handlers/aliases_test.go b/web/handlers/aliases_test.go new file mode 100644 index 0000000..478cbb2 --- /dev/null +++ b/web/handlers/aliases_test.go @@ -0,0 +1,63 @@ +package handlers + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" + "github.com/ssb-ngi-pointer/go-ssb-room/web/router" + refs "go.mindeco.de/ssb-refs" +) + +func TestAliasResolve(t *testing.T) { + ts := setup(t) + + a := assert.New(t) + r := require.New(t) + + var testAlias = roomdb.Alias{ + ID: 54321, + Name: "test-name", + Feed: refs.FeedRef{ + ID: bytes.Repeat([]byte{'F'}, 32), + Algo: "test", + }, + Signature: bytes.Repeat([]byte{'S'}, 32), + } + ts.AliasesDB.ResolveReturns(testAlias, nil) + + // default is HTML + htmlURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name) + r.Nil(err) + t.Log("resolving", htmlURL.String()) + html, resp := ts.Client.GetHTML(htmlURL.String()) + a.Equal(http.StatusOK, resp.Code) + + a.Equal(testAlias.Name, html.Find("title").Text()) + + // default is HTML + jsonURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name) + r.Nil(err) + q := jsonURL.Query() + q.Set("encoding", "json") + jsonURL.RawQuery = q.Encode() + t.Log("resolving", jsonURL.String()) + resp = ts.Client.GetBody(jsonURL.String()) + a.Equal(http.StatusOK, resp.Code) + + var ar aliasJSONResponse + err = json.NewDecoder(resp.Body).Decode(&ar) + r.NoError(err) + a.Equal(testAlias.Name, ar.Alias) + sigData, err := base64.StdEncoding.DecodeString(ar.Signature) + r.NoError(err) + a.Equal(testAlias.Signature, sigData) + a.Equal(testAlias.Feed.Ref(), ar.UserID, "wrong user feed on response") + a.Equal(ts.NetworkInfo.RoomID.Ref(), ar.RoomID, "wrong room feed on response") +} diff --git a/web/handlers/basic_test.go b/web/handlers/basic_test.go index 777a183..6da1e7f 100644 --- a/web/handlers/basic_test.go +++ b/web/handlers/basic_test.go @@ -26,7 +26,6 @@ func TestIndex(t *testing.T) { webassert.Localized(t, html, []webassert.LocalizedElement{ {"h1", "Default Notice Title"}, {"title", "Default Notice Title"}, - // {"#nav", "FooBar"}, }) content := html.Find("p").Text() diff --git a/web/handlers/http.go b/web/handlers/http.go index 5f62b5b..3418e8f 100644 --- a/web/handlers/http.go +++ b/web/handlers/http.go @@ -11,13 +11,14 @@ import ( "strconv" "time" + refs "go.mindeco.de/ssb-refs" + "github.com/gorilla/csrf" "github.com/gorilla/sessions" "github.com/russross/blackfriday/v2" "go.mindeco.de/http/auth" "go.mindeco.de/http/render" "go.mindeco.de/logging" - "golang.org/x/crypto/ed25519" "github.com/ssb-ngi-pointer/go-ssb-room/internal/repo" "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" @@ -33,6 +34,7 @@ import ( var HTMLTemplates = []string{ "landing/index.tmpl", "landing/about.tmpl", + "aliases-resolved.html", "invite/accept.tmpl", "invite/consumed.tmpl", "notice/list.tmpl", @@ -56,7 +58,7 @@ type NetworkInfo struct { PortMUXRPC uint PortHTTPS uint // 0 assumes default (443) - PubKey ed25519.PublicKey + RoomID refs.FeedRef Domain string } @@ -262,10 +264,20 @@ func New( m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list)) m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show)) + var ah = aliasHandler{ + r: r, + + db: dbs.Aliases, + + roomID: netInfo.RoomID, + muxrpcHostAndPort: fmt.Sprintf("%s:%d", netInfo.Domain, netInfo.PortMUXRPC), + } + m.Get(router.CompleteAliasResolve).HandlerFunc(ah.resolve) + var ih = inviteHandler{ invites: dbs.Invites, - roomPubKey: netInfo.PubKey, + roomPubKey: netInfo.RoomID.PubKey(), muxrpcHostAndPort: fmt.Sprintf("%s:%d", netInfo.Domain, netInfo.PortMUXRPC), } m.Get(router.CompleteInviteAccept).Handler(r.HTML("invite/accept.tmpl", ih.acceptForm)) diff --git a/web/handlers/invites.go b/web/handlers/invites.go index 925044a..a7e76e5 100644 --- a/web/handlers/invites.go +++ b/web/handlers/invites.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" - "go.mindeco.de/http/render" "go.mindeco.de/logging" "golang.org/x/crypto/ed25519" @@ -18,8 +17,6 @@ import ( ) type inviteHandler struct { - r *render.Renderer - invites roomdb.InviteService aliases roomdb.AliasService diff --git a/web/handlers/invites_test.go b/web/handlers/invites_test.go index 1b78b56..b65d164 100644 --- a/web/handlers/invites_test.go +++ b/web/handlers/invites_test.go @@ -226,7 +226,7 @@ func TestInviteConsumeInvite(t *testing.T) { // TODO: this is just a cheap stub for actual ssb-uri parsing a.True(strings.HasPrefix(gotRA, "net:localhost:8008~shs:"), "not for the test host: %s", gotRA) - a.True(strings.Contains(gotRA, base64.StdEncoding.EncodeToString(ts.NetworkInfo.PubKey)), "public key missing? %s", gotRA) + a.True(strings.Contains(gotRA, base64.StdEncoding.EncodeToString(ts.NetworkInfo.RoomID.PubKey())), "public key missing? %s", gotRA) a.True(strings.HasSuffix(gotRA, ":SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24="), "magic suffix missing: %s", gotRA) } diff --git a/web/handlers/setup_test.go b/web/handlers/setup_test.go index 77003db..c141dea 100644 --- a/web/handlers/setup_test.go +++ b/web/handlers/setup_test.go @@ -16,10 +16,11 @@ import ( "github.com/gorilla/mux" "go.mindeco.de/http/tester" "go.mindeco.de/logging/logtest" + refs "go.mindeco.de/ssb-refs" + "github.com/ssb-ngi-pointer/go-ssb-room/internal/repo" "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" "github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb" - "github.com/ssb-ngi-pointer/go-ssb-room/internal/repo" "github.com/ssb-ngi-pointer/go-ssb-room/roomstate" "github.com/ssb-ngi-pointer/go-ssb-room/web/i18n" "github.com/ssb-ngi-pointer/go-ssb-room/web/router" @@ -33,6 +34,7 @@ type testSession struct { // mocked dbs AuthDB *mockdb.FakeAuthWithSSBService AuthFallbackDB *mockdb.FakeAuthFallbackService + AliasesDB *mockdb.FakeAliasService AllowListDB *mockdb.FakeAllowListService InvitesDB *mockdb.FakeInviteService PinnedDB *mockdb.FakePinnedNoticesService @@ -61,6 +63,7 @@ func setup(t *testing.T) *testSession { ts.AuthDB = new(mockdb.FakeAuthWithSSBService) ts.AuthFallbackDB = new(mockdb.FakeAuthFallbackService) + ts.AliasesDB = new(mockdb.FakeAliasService) ts.AllowListDB = new(mockdb.FakeAllowListService) ts.InvitesDB = new(mockdb.FakeInviteService) ts.PinnedDB = new(mockdb.FakePinnedNoticesService) @@ -76,7 +79,10 @@ func setup(t *testing.T) *testSession { PortMUXRPC: 8008, PortHTTPS: 443, - PubKey: bytes.Repeat([]byte("test"), 8), + RoomID: refs.FeedRef{ + ID: bytes.Repeat([]byte("test"), 8), + Algo: refs.RefAlgoFeedSSB1, + }, } log, _ := logtest.KitLogger("complete", t) @@ -91,6 +97,7 @@ func setup(t *testing.T) *testSession { ts.NetworkInfo, ts.RoomState, Databases{ + Aliases: ts.AliasesDB, AuthWithSSB: ts.AuthDB, AuthFallback: ts.AuthFallbackDB, AllowList: ts.AllowListDB, diff --git a/web/router/complete.go b/web/router/complete.go index 134a2e2..847473c 100644 --- a/web/router/complete.go +++ b/web/router/complete.go @@ -14,6 +14,8 @@ const ( CompleteNoticeShow = "complete:notice:show" CompleteNoticeList = "complete:notice:list" + CompleteAliasResolve = "complete:alias:resolve" + CompleteInviteAccept = "complete:invite:accept" CompleteInviteConsume = "complete:invite:consume" ) @@ -28,6 +30,8 @@ func CompleteApp() *mux.Router { m.Path("/").Methods("GET").Name(CompleteIndex) m.Path("/about").Methods("GET").Name(CompleteAbout) + m.Path("/{alias}").Methods("GET").Name(CompleteAliasResolve) + m.Path("/invite/accept").Methods("GET").Name(CompleteInviteAccept) m.Path("/invite/consume").Methods("POST").Name(CompleteInviteConsume) diff --git a/web/templates/aliases-resolved.html b/web/templates/aliases-resolved.html new file mode 100644 index 0000000..517ab30 --- /dev/null +++ b/web/templates/aliases-resolved.html @@ -0,0 +1,6 @@ +{{ define "title" }}{{.Alias.Name}}{{ end }} +{{ define "content" }} +
+

{{.Alias.Name}}

+
+{{end}} \ No newline at end of file