alias JSON endpoint and testing

This commit is contained in:
Henry 2021-03-15 12:25:07 +01:00
parent 50e4ebbaca
commit b9bcbb42ec
9 changed files with 248 additions and 10 deletions

150
web/handlers/aliases.go Normal file
View File

@ -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)
}

View File

@ -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")
}

View File

@ -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()

View File

@ -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))

View File

@ -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

View File

@ -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)
}

View File

@ -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,

View File

@ -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)

View File

@ -0,0 +1,6 @@
{{ define "title" }}{{.Alias.Name}}{{ end }}
{{ define "content" }}
<div>
<h1>{{.Alias.Name}}</h1>
</div>
{{end}}