Add a JSON endpoint for generating invites
When running in open mode invites can be freely generated by accessing /create-invite. This displays an HTML page which creates and displays an invite to the user. This commit adds an additional way of creating invites in open mode. A POST request can be sent to the same /create-invite endpoint with the Accept header set to application/json. This returns a JSON response which contains an invite url. The purpose of this change is to make automatic invite generation easier in SSB clients.
This commit is contained in:
parent
0a5b6d13e4
commit
ab664aafc3
|
@ -364,7 +364,7 @@ func New(
|
||||||
m.Get(router.CompleteInviteFacadeFallback).Handler(r.HTML("invite/facade-fallback.tmpl", ih.presentFacadeFallback))
|
m.Get(router.CompleteInviteFacadeFallback).Handler(r.HTML("invite/facade-fallback.tmpl", ih.presentFacadeFallback))
|
||||||
m.Get(router.CompleteInviteInsertID).Handler(r.HTML("invite/insert-id.tmpl", ih.presentInsert))
|
m.Get(router.CompleteInviteInsertID).Handler(r.HTML("invite/insert-id.tmpl", ih.presentInsert))
|
||||||
m.Get(router.CompleteInviteConsume).HandlerFunc(ih.consume)
|
m.Get(router.CompleteInviteConsume).HandlerFunc(ih.consume)
|
||||||
m.Get(router.OpenModeCreateInvite).HandlerFunc(r.HTML("admin/invite-created.tmpl", ih.createOpenMode))
|
m.Get(router.OpenModeCreateInvite).HandlerFunc(ih.createOpenMode)
|
||||||
|
|
||||||
// static assets
|
// static assets
|
||||||
m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets)))
|
m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets)))
|
||||||
|
@ -379,6 +379,7 @@ func New(
|
||||||
mainMux.Handle("/", m)
|
mainMux.Handle("/", m)
|
||||||
|
|
||||||
consumeURL := urlTo(router.CompleteInviteConsume)
|
consumeURL := urlTo(router.CompleteInviteConsume)
|
||||||
|
openModeCreateInviteURL := urlTo(router.OpenModeCreateInvite)
|
||||||
|
|
||||||
// apply HTTP middleware
|
// apply HTTP middleware
|
||||||
middlewares := []func(http.Handler) http.Handler{
|
middlewares := []func(http.Handler) http.Handler{
|
||||||
|
@ -394,6 +395,10 @@ func New(
|
||||||
next.ServeHTTP(w, csrf.UnsafeSkipCheck(req))
|
next.ServeHTTP(w, csrf.UnsafeSkipCheck(req))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.URL.Path == openModeCreateInviteURL.Path && req.Header.Get("Accept") == "application/json" {
|
||||||
|
next.ServeHTTP(w, csrf.UnsafeSkipCheck(req))
|
||||||
|
return
|
||||||
|
}
|
||||||
next.ServeHTTP(w, req)
|
next.ServeHTTP(w, req)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -350,7 +350,22 @@ func (html inviteConsumeHTMLResponder) SendError(err error) {
|
||||||
html.renderer.Error(html.rw, html.req, http.StatusInternalServerError, err)
|
html.renderer.Error(html.rw, html.req, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h inviteHandler) createOpenMode(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (h inviteHandler) createOpenMode(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Header.Get("Accept") {
|
||||||
|
case "application/json":
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
h.render.Error(rw, r, http.StatusBadRequest, errors.New("invalid method"))
|
||||||
|
}
|
||||||
|
h.createOpenModeJSON(rw, r)
|
||||||
|
default:
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
h.render.Error(rw, r, http.StatusBadRequest, errors.New("invalid method"))
|
||||||
|
}
|
||||||
|
h.render.HTML("admin/invite-created.tmpl", h.createOpenModeHTML)(rw, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h inviteHandler) createOpenModeHTML(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
|
||||||
token, err := h.invites.Create(ctx, -1)
|
token, err := h.invites.Create(ctx, -1)
|
||||||
|
@ -364,3 +379,30 @@ func (h inviteHandler) createOpenMode(rw http.ResponseWriter, req *http.Request)
|
||||||
"FacadeURL": facadeURL.String(),
|
"FacadeURL": facadeURL.String(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h inviteHandler) createOpenModeJSON(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
logger := logging.FromContext(req.Context())
|
||||||
|
ctx := req.Context()
|
||||||
|
enc := json.NewEncoder(rw)
|
||||||
|
|
||||||
|
token, err := h.invites.Create(ctx, -1)
|
||||||
|
if err != nil {
|
||||||
|
data := struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}{"failed", err.Error()}
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
if err := enc.Encode(data); err != nil {
|
||||||
|
level.Warn(logger).Log("event", "sending json error failed", "err", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]string{
|
||||||
|
"url": h.urlTo(router.CompleteInviteFacade, "token", token).String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := enc.Encode(response); err != nil {
|
||||||
|
level.Warn(logger).Log("event", "sending json response failed", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -342,3 +343,42 @@ func TestInviteConsumptionDenied(t *testing.T) {
|
||||||
// invite should not be consumed
|
// invite should not be consumed
|
||||||
r.EqualValues(0, ts.InvitesDB.ConsumeCallCount())
|
r.EqualValues(0, ts.InvitesDB.ConsumeCallCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenModeCreateInviteHTML(t *testing.T) {
|
||||||
|
ts := setup(t)
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
someToken := "fake-token"
|
||||||
|
ts.InvitesDB.CreateReturns(someToken, nil)
|
||||||
|
|
||||||
|
doc, resp := ts.Client.GetHTML(ts.URLTo(router.OpenModeCreateInvite))
|
||||||
|
r.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
facadeLink := doc.Find("#invite-facade-link")
|
||||||
|
r.NotNil(facadeLink)
|
||||||
|
r.Contains(facadeLink.AttrOr("href", ""), someToken)
|
||||||
|
r.Contains(facadeLink.Text(), someToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenModeCreateInviteJSON(t *testing.T) {
|
||||||
|
ts := setup(t)
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
someToken := "fake-token"
|
||||||
|
ts.InvitesDB.CreateReturns(someToken, nil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", ts.URLTo(router.OpenModeCreateInvite).String(), nil)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
ts.Mux.ServeHTTP(recorder, req)
|
||||||
|
r.Equal(http.StatusOK, recorder.Code)
|
||||||
|
|
||||||
|
response := map[string]string{}
|
||||||
|
err = json.Unmarshal(recorder.Body.Bytes(), &response)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
require.Contains(t, response["url"], someToken)
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ func CompleteApp() *mux.Router {
|
||||||
m.Path("/members/change-password").Methods("GET").Name(MembersChangePasswordForm)
|
m.Path("/members/change-password").Methods("GET").Name(MembersChangePasswordForm)
|
||||||
m.Path("/members/change-password").Methods("POST").Name(MembersChangePassword)
|
m.Path("/members/change-password").Methods("POST").Name(MembersChangePassword)
|
||||||
|
|
||||||
m.Path("/create-invite").Methods("GET").Name(OpenModeCreateInvite)
|
m.Path("/create-invite").Methods("GET", "POST").Name(OpenModeCreateInvite)
|
||||||
m.Path("/join").Methods("GET").Name(CompleteInviteFacade)
|
m.Path("/join").Methods("GET").Name(CompleteInviteFacade)
|
||||||
m.Path("/join-fallback").Methods("GET").Name(CompleteInviteFacadeFallback)
|
m.Path("/join-fallback").Methods("GET").Name(CompleteInviteFacadeFallback)
|
||||||
m.Path("/join-manually").Methods("GET").Name(CompleteInviteInsertID)
|
m.Path("/join-manually").Methods("GET").Name(CompleteInviteInsertID)
|
||||||
|
|
Loading…
Reference in New Issue