Invite endpoints
* Add revoke and created templates * Render invite accept with domain * Flesh out accept page
This commit is contained in:
parent
9f1fef1916
commit
98468e93a5
@ -213,6 +213,7 @@ func runroomsrv() error {
|
||||
dashboardH, err := handlers.New(
|
||||
kitlog.With(log, "package", "web"),
|
||||
repo.New(repoDir),
|
||||
httpsDomain,
|
||||
roomsrv.StateManager,
|
||||
db.AuthWithSSB,
|
||||
db.AuthFallback,
|
||||
|
@ -98,6 +98,7 @@ func (h allowListHandler) removeConfirm(rw http.ResponseWriter, req *http.Reques
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"Entry": entry,
|
||||
csrf.TemplateTag: csrf.TemplateField(req),
|
||||
|
@ -149,7 +149,7 @@ func TestAllowList(t *testing.T) {
|
||||
// check for link to remove confirm link
|
||||
link, yes := elems.ContentsFiltered("a").Attr("href")
|
||||
a.True(yes, "a-tag has href attribute")
|
||||
a.Equal("/members/remove/confirm?id=666", link)
|
||||
a.Equal("/admin/members/remove/confirm?id=666", link)
|
||||
}
|
||||
|
||||
func TestAllowListRemoveConfirmation(t *testing.T) {
|
||||
|
@ -4,6 +4,7 @@ package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@ -18,6 +19,7 @@ import (
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/user"
|
||||
)
|
||||
|
||||
type testSession struct {
|
||||
@ -30,6 +32,10 @@ type testSession struct {
|
||||
NoticeDB *mockdb.FakeNoticesService
|
||||
InvitesDB *mockdb.FakeInviteService
|
||||
|
||||
User *admindb.User
|
||||
|
||||
Domain string
|
||||
|
||||
RoomState *roomstate.Manager
|
||||
}
|
||||
|
||||
@ -46,7 +52,15 @@ func newSession(t *testing.T) *testSession {
|
||||
ctx := context.TODO()
|
||||
ts.RoomState = roomstate.NewManager(ctx, log)
|
||||
|
||||
ts.Router = router.Admin(nil)
|
||||
ts.Router = router.CompleteApp()
|
||||
|
||||
ts.Domain = randomString(10)
|
||||
|
||||
// fake user
|
||||
ts.User = &admindb.User{
|
||||
ID: 1234,
|
||||
Name: "room mate",
|
||||
}
|
||||
|
||||
// setup rendering
|
||||
|
||||
@ -77,14 +91,34 @@ func newSession(t *testing.T) *testSession {
|
||||
}
|
||||
|
||||
ts.Mux = http.NewServeMux()
|
||||
ts.Mux.Handle("/", Handler(r,
|
||||
|
||||
handler := Handler(
|
||||
ts.Domain,
|
||||
r,
|
||||
ts.RoomState,
|
||||
ts.AllowListDB,
|
||||
ts.InvitesDB,
|
||||
ts.NoticeDB,
|
||||
ts.PinnedDB,
|
||||
))
|
||||
)
|
||||
|
||||
handler = user.MiddlewareForTests(ts.User)(handler)
|
||||
|
||||
ts.Mux.Handle("/", handler)
|
||||
|
||||
ts.Client = tester.New(ts.Mux, t)
|
||||
|
||||
return &ts
|
||||
}
|
||||
|
||||
// utils
|
||||
|
||||
func randomString(n int) string {
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
||||
s := make([]rune, n)
|
||||
for i := range s {
|
||||
s[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
@ -9,13 +9,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
|
||||
"github.com/vcraescu/go-paginator/v2"
|
||||
"github.com/vcraescu/go-paginator/v2/adapter"
|
||||
"github.com/vcraescu/go-paginator/v2/view"
|
||||
|
||||
"go.mindeco.de/http/render"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
)
|
||||
|
||||
@ -26,7 +25,9 @@ var HTMLTemplates = []string{
|
||||
"admin/allow-list.tmpl",
|
||||
"admin/allow-list-remove-confirm.tmpl",
|
||||
|
||||
"admin/invites.tmpl",
|
||||
"admin/invite-list.tmpl",
|
||||
"admin/invite-revoke-confirm.tmpl",
|
||||
"admin/invite-created.tmpl",
|
||||
|
||||
"admin/notice-edit.tmpl",
|
||||
}
|
||||
@ -34,6 +35,7 @@ var HTMLTemplates = []string{
|
||||
// Handler supplies the elevated access pages to known users.
|
||||
// It is not registering on the mux router like other pages to clean up the authorize flow.
|
||||
func Handler(
|
||||
domainName string,
|
||||
r *render.Renderer,
|
||||
roomState *roomstate.Manager,
|
||||
al admindb.AllowListService,
|
||||
@ -67,9 +69,13 @@ func Handler(
|
||||
var ih = invitesHandler{
|
||||
r: r,
|
||||
db: is,
|
||||
|
||||
domainName: domainName,
|
||||
}
|
||||
mux.HandleFunc("/invites", r.HTML("admin/invites.tmpl", ih.overview))
|
||||
mux.HandleFunc("/invites/create", ih.create)
|
||||
mux.HandleFunc("/invites", r.HTML("admin/invite-list.tmpl", ih.overview))
|
||||
mux.HandleFunc("/invites/create", r.HTML("admin/invite-created.tmpl", ih.create))
|
||||
mux.HandleFunc("/invites/revoke/confirm", r.HTML("admin/invite-revoke-confirm.tmpl", ih.revokeConfirm))
|
||||
mux.HandleFunc("/invites/revoke", ih.revoke)
|
||||
|
||||
var nh = noticeHandler{
|
||||
r: r,
|
||||
|
@ -1,13 +1,18 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.mindeco.de/http/render"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
"go.mindeco.de/http/render"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/user"
|
||||
)
|
||||
|
||||
@ -15,6 +20,8 @@ type invitesHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
db admindb.InviteService
|
||||
|
||||
domainName string
|
||||
}
|
||||
|
||||
func (h invitesHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
@ -34,38 +41,106 @@ func (h invitesHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
||||
}
|
||||
|
||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||
|
||||
return pageData, nil
|
||||
}
|
||||
|
||||
func (h invitesHandler) create(w http.ResponseWriter, req *http.Request) {
|
||||
func (h invitesHandler) create(w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if req.Method != "POST" {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request"))
|
||||
return
|
||||
return nil, fmt.Errorf("bad request")
|
||||
}
|
||||
if err := req.ParseForm(); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request: %w", err))
|
||||
return
|
||||
return nil, fmt.Errorf("bad request: %w", err)
|
||||
}
|
||||
|
||||
user := user.FromContext(req.Context())
|
||||
if user == nil {
|
||||
err := fmt.Errorf("warning: no user session for elevated access request")
|
||||
h.r.Error(w, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
return nil, fmt.Errorf("warning: no user session for elevated access request")
|
||||
}
|
||||
|
||||
aliasSuggestion := req.Form.Get("alias_suggestion")
|
||||
|
||||
token, err := h.db.Create(req.Context(), user.ID, aliasSuggestion)
|
||||
if err != nil {
|
||||
h.r.Error(w, req, http.StatusInternalServerError, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urlTo := web.NewURLTo(router.CompleteApp())
|
||||
acceptURL := urlTo(router.CompleteInviteAccept, "token", token)
|
||||
acceptURL.Host = h.domainName
|
||||
acceptURL.Scheme = "https"
|
||||
|
||||
return map[string]interface{}{
|
||||
"Token": token,
|
||||
"AccepURL": acceptURL.String(),
|
||||
|
||||
"AliasSuggestion": aliasSuggestion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h invitesHandler) revokeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: add GetByID to invite service
|
||||
var invite admindb.Invite
|
||||
list, err := h.db.List(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, elem := range list {
|
||||
if elem.ID == id {
|
||||
invite = elem
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, weberrors.ErrNotFound{What: "invite"}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"Invite": invite,
|
||||
csrf.TemplateTag: csrf.TemplateField(req),
|
||||
}, nil
|
||||
}
|
||||
|
||||
const redirectToInvites = "/admin/invites"
|
||||
|
||||
func (h invitesHandler) revoke(rw http.ResponseWriter, req *http.Request) {
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToInvites, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("use me:", token)
|
||||
id, err := strconv.ParseInt(req.FormValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToInvites, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, req, "/admin/invites", http.StatusFound)
|
||||
status := http.StatusFound
|
||||
err = h.db.Revoke(req.Context(), id)
|
||||
if err != nil {
|
||||
if !errors.Is(err, admindb.ErrNotFound) {
|
||||
// TODO "flash" errors
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
|
||||
http.Redirect(rw, req, redirectToInvites, status)
|
||||
}
|
||||
|
@ -2,10 +2,15 @@ package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
)
|
||||
|
||||
func TestInvitesCreateForm(t *testing.T) {
|
||||
@ -45,3 +50,39 @@ func TestInvitesCreateForm(t *testing.T) {
|
||||
a.True(ok, "input has a name")
|
||||
a.Equal("alias_suggestion", name, "wrong name on input field")
|
||||
}
|
||||
|
||||
func TestInvitesCreate(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRemove := urlTo(router.AdminInvitesCreate)
|
||||
|
||||
testInvite := "your-fake-test-invite"
|
||||
ts.InvitesDB.CreateReturns(testInvite, nil)
|
||||
|
||||
rec := ts.Client.PostForm(urlRemove.String(), url.Values{
|
||||
"alias_suggestion": []string{"jerry"},
|
||||
})
|
||||
a.Equal(http.StatusOK, rec.Code)
|
||||
|
||||
a.Equal(1, ts.InvitesDB.CreateCallCount())
|
||||
_, userID, aliasSuggestion := ts.InvitesDB.CreateArgsForCall(0)
|
||||
a.EqualValues(ts.User.ID, userID)
|
||||
a.EqualValues("jerry", aliasSuggestion)
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(rec.Body)
|
||||
require.NoError(t, err, "failed to parse response")
|
||||
|
||||
assertLocalized(t, doc, []localizedElement{
|
||||
{"title", "AdminInviteCreatedTitle"},
|
||||
{"#welcome", "AdminInviteCreatedWelcome"},
|
||||
})
|
||||
|
||||
wantURL := urlTo(router.CompleteInviteAccept, "token", testInvite)
|
||||
wantURL.Host = ts.Domain
|
||||
wantURL.Scheme = "https"
|
||||
|
||||
shownLink := doc.Find("#invite-accept-link").Text()
|
||||
a.Equal(wantURL.String(), shownLink)
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ import (
|
||||
var HTMLTemplates = []string{
|
||||
"landing/index.tmpl",
|
||||
"landing/about.tmpl",
|
||||
"invite/accept.tmpl",
|
||||
"invite/consumed.tmpl",
|
||||
"notice/list.tmpl",
|
||||
"notice/show.tmpl",
|
||||
"error.tmpl",
|
||||
@ -41,6 +43,7 @@ var HTMLTemplates = []string{
|
||||
func New(
|
||||
logger logging.Interface,
|
||||
repo repo.Interface,
|
||||
domainName string,
|
||||
roomState *roomstate.Manager,
|
||||
as admindb.AuthWithSSBService,
|
||||
fs admindb.AuthFallbackService,
|
||||
@ -201,7 +204,9 @@ func New(
|
||||
// hookup handlers to the router
|
||||
roomsAuth.Handler(m, r, a)
|
||||
|
||||
adminHandler := a.Authenticate(admin.Handler(r,
|
||||
adminHandler := a.Authenticate(admin.Handler(
|
||||
domainName,
|
||||
r,
|
||||
roomState,
|
||||
al,
|
||||
is,
|
||||
@ -224,11 +229,18 @@ func New(
|
||||
}))
|
||||
m.Get(router.CompleteAbout).Handler(r.StaticHTML("landing/about.tmpl"))
|
||||
|
||||
var nr noticeHandler
|
||||
nr.notices = ns
|
||||
nr.pinned = ps
|
||||
m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nr.list))
|
||||
m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nr.show))
|
||||
var nh = noticeHandler{
|
||||
notices: ns,
|
||||
pinned: ps,
|
||||
}
|
||||
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 ih = inviteHandler{
|
||||
invites: is,
|
||||
}
|
||||
m.Get(router.CompleteInviteAccept).Handler(r.HTML("invite/accept.tmpl", ih.acceptForm))
|
||||
m.Get(router.CompleteInviteConsume).Handler(r.HTML("invite/consumed.tmpl", ih.consume))
|
||||
|
||||
m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets)))
|
||||
|
||||
|
@ -68,7 +68,6 @@ func TestRestricted(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
testURLs := []string{
|
||||
// "/admin/",
|
||||
"/admin/admin",
|
||||
"/admin/admin/",
|
||||
}
|
||||
@ -113,13 +112,13 @@ func TestFallbackAuth(t *testing.T) {
|
||||
doc, resp := ts.Client.GetHTML(signInFormURL.String())
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
assertCSRFTokenPresent(t, doc.Find("form"))
|
||||
|
||||
csrfCookie := resp.Result().Cookies()
|
||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
||||
t.Log(csrfCookie)
|
||||
|
||||
jar.SetCookies(signInFormURL, csrfCookie)
|
||||
|
||||
assertCSRFTokenPresent(t, doc.Find("form"))
|
||||
|
||||
csrfTokenElem := doc.Find("input[type=hidden]")
|
||||
a.Equal(1, csrfTokenElem.Length())
|
||||
|
||||
@ -137,8 +136,6 @@ func TestFallbackAuth(t *testing.T) {
|
||||
}
|
||||
ts.AuthFallbackDB.CheckReturns(int64(23), nil)
|
||||
|
||||
t.Log(loginVals)
|
||||
|
||||
signInURL, err := ts.Router.Get(router.AuthFallbackSignIn).URL()
|
||||
r.Nil(err)
|
||||
|
||||
@ -214,7 +211,6 @@ func TestFallbackAuth(t *testing.T) {
|
||||
html, resp = ts.Client.GetHTML(durl)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
t.Log(html.Find("body").Text())
|
||||
assertLocalized(t, html, []localizedElement{
|
||||
{"#welcome", "AdminDashboardWelcome"},
|
||||
{"title", "AdminDashboardTitle"},
|
||||
@ -224,7 +220,6 @@ func TestFallbackAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
// utils
|
||||
|
||||
type localizedElement struct {
|
||||
Selector, Label string
|
||||
}
|
||||
@ -238,8 +233,7 @@ func assertLocalized(t *testing.T, html *goquery.Document, elems []localizedElem
|
||||
|
||||
func assertCSRFTokenPresent(t *testing.T, sel *goquery.Selection) {
|
||||
a := assert.New(t)
|
||||
|
||||
csrfField := sel.Find("input[name=gorilla.csrf.Token]")
|
||||
csrfField := sel.Find("input[name='gorilla.csrf.Token']")
|
||||
a.EqualValues(1, csrfField.Length(), "no csrf-token input tag")
|
||||
tipe, ok := csrfField.Attr("type")
|
||||
a.True(ok, "csrf input has a type")
|
||||
|
60
web/handlers/invites.go
Normal file
60
web/handlers/invites.go
Normal file
@ -0,0 +1,60 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.mindeco.de/http/render"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
type inviteHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
invites admindb.InviteService
|
||||
alaises admindb.AliasService
|
||||
}
|
||||
|
||||
func (h inviteHandler) acceptForm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
inv, err := h.invites.GetByToken(req.Context(), req.URL.Query().Get("token"))
|
||||
if err != nil {
|
||||
if errors.Is(err, admindb.ErrNotFound) {
|
||||
return nil, weberrors.ErrNotFound{What: "invite"}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"Invite": inv,
|
||||
csrf.TemplateTag: csrf.TemplateField(req),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h inviteHandler) consume(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, weberrors.ErrBadRequest{Where: "form data", Details: err}
|
||||
}
|
||||
|
||||
token := req.FormValue("token")
|
||||
|
||||
newMember, err := refs.ParseFeedRef(req.FormValue("new_member"))
|
||||
if err != nil {
|
||||
return nil, weberrors.ErrBadRequest{Where: "form data", Details: err}
|
||||
}
|
||||
|
||||
inv, err := h.invites.Consume(req.Context(), token, *newMember)
|
||||
if err != nil {
|
||||
if errors.Is(err, admindb.ErrNotFound) {
|
||||
return nil, weberrors.ErrNotFound{What: "invite"}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"TunnelAddress": "pew pew",
|
||||
}, nil
|
||||
}
|
1
web/handlers/invites_test.go
Normal file
1
web/handlers/invites_test.go
Normal file
@ -0,0 +1 @@
|
||||
package handlers
|
@ -34,6 +34,7 @@ type testSession struct {
|
||||
AuthDB *mockdb.FakeAuthWithSSBService
|
||||
AuthFallbackDB *mockdb.FakeAuthFallbackService
|
||||
AllowListDB *mockdb.FakeAllowListService
|
||||
InvitesDB *mockdb.FakeInviteService
|
||||
PinnedDB *mockdb.FakePinnedNoticesService
|
||||
NoticeDB *mockdb.FakeNoticesService
|
||||
|
||||
@ -59,6 +60,7 @@ func setup(t *testing.T) *testSession {
|
||||
ts.AuthDB = new(mockdb.FakeAuthWithSSBService)
|
||||
ts.AuthFallbackDB = new(mockdb.FakeAuthFallbackService)
|
||||
ts.AllowListDB = new(mockdb.FakeAllowListService)
|
||||
ts.InvitesDB = new(mockdb.FakeInviteService)
|
||||
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
|
||||
defaultNotice := &admindb.Notice{
|
||||
Title: "Default Notice Title",
|
||||
@ -76,10 +78,12 @@ func setup(t *testing.T) *testSession {
|
||||
h, err := New(
|
||||
log,
|
||||
testRepo,
|
||||
"localhost",
|
||||
ts.RoomState,
|
||||
ts.AuthDB,
|
||||
ts.AuthFallbackDB,
|
||||
ts.AllowListDB,
|
||||
ts.InvitesDB,
|
||||
ts.NoticeDB,
|
||||
ts.PinnedDB,
|
||||
)
|
||||
|
@ -24,15 +24,31 @@ AdminAllowListRemoveConfirmWelcome = "Are you sure you want to remove this membe
|
||||
AdminAllowListRemoveConfirmTitle = "Confirm member removal"
|
||||
|
||||
AdminInvitesTitle = "Invites"
|
||||
AdminInvitesWelcome = "Here ytou can create invite tokens for people who are not yet members of this room."
|
||||
AdminInvitesWelcome = "Create invite tokens for people who are not yet members of this room."
|
||||
AdminInvitesAliasSuggestion = "Suggested alias (optional)"
|
||||
AdminInvitesRevoke = "Revoke"
|
||||
AdminInviteRevoke = "Revoke"
|
||||
AdminInviteRevokeConfirmTitle = "Confirm invite revocation"
|
||||
AdminInviteRevokeConfirmWelcome = "Are you sure you want to remove this invite? If you already sent it out, they will not be able to use it."
|
||||
|
||||
# TODO: add placeholder support to the template helpers (https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60)
|
||||
AdminInviteCreatedBy = "Created by:"
|
||||
AdminInviteSuggestedAliasIs = "The suggested alias is:"
|
||||
AdminInviteSuggestedAliasIsShort = "Alias:"
|
||||
|
||||
AdminInviteCreatedTitle = "Invite created successfully"
|
||||
AdminInviteCreatedWelcome = "Here is the invite you created. Please copy it before closing the page or it will be lost."
|
||||
|
||||
|
||||
NavAdminLanding = "Home"
|
||||
NavAdminDashboard = "Dashboard"
|
||||
NavAdminInvites = "Invites"
|
||||
NavAdminNotices = "Notices"
|
||||
|
||||
InviteAccept = "Accept invite"
|
||||
InviteAcceptTitle = "Accept Invite
|
||||
InviteAcceptWelcome = "elaborate welcome message for a new member with good words and stuff."
|
||||
InviteAcceptAliasSuggestion = "The persone who created thought you might like this alias:"
|
||||
|
||||
NoticeEditTitle = "Edit Notice"
|
||||
NoticeList = "Notices"
|
||||
NoticeListWelcome = "Here you can manage the contents of the landing page and other important documents such as code of conduct and privacy policy."
|
||||
|
@ -14,8 +14,10 @@ const (
|
||||
AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirm"
|
||||
AdminAllowListRemove = "admin:allow-list:remove"
|
||||
|
||||
AdminInvitesOverview = "admin:invites:overview"
|
||||
AdminInvitesCreate = "admin:invites:create"
|
||||
AdminInvitesOverview = "admin:invites:overview"
|
||||
AdminInvitesRevokeConfirm = "admin:invites:revoke:confirm"
|
||||
AdminInvitesRevoke = "admin:invites:revoke"
|
||||
AdminInvitesCreate = "admin:invites:create"
|
||||
|
||||
AdminNoticeEdit = "admin:notice:edit"
|
||||
AdminNoticeSave = "admin:notice:save"
|
||||
@ -43,6 +45,8 @@ func Admin(m *mux.Router) *mux.Router {
|
||||
m.Path("/notice/save").Methods("POST").Name(AdminNoticeSave)
|
||||
|
||||
m.Path("/invites").Methods("GET").Name(AdminInvitesOverview)
|
||||
m.Path("/invites/revoke/confirm").Methods("GET").Name(AdminInvitesRevokeConfirm)
|
||||
m.Path("/invites/revoke").Methods("POST").Name(AdminInvitesRevoke)
|
||||
m.Path("/invites/create").Methods("POST").Name(AdminInvitesCreate)
|
||||
|
||||
return m
|
||||
|
@ -13,6 +13,9 @@ const (
|
||||
|
||||
CompleteNoticeShow = "complete:notice:show"
|
||||
CompleteNoticeList = "complete:notice:list"
|
||||
|
||||
CompleteInviteAccept = "complete:invite:accept"
|
||||
CompleteInviteConsume = "complete:invite:consume"
|
||||
)
|
||||
|
||||
// CompleteApp constructs a mux.Router containing the routes for batch Complete html frontend
|
||||
@ -25,6 +28,9 @@ func CompleteApp() *mux.Router {
|
||||
m.Path("/").Methods("GET").Name(CompleteIndex)
|
||||
m.Path("/about").Methods("GET").Name(CompleteAbout)
|
||||
|
||||
m.Path("/invite/accept").Methods("GET").Name(CompleteInviteAccept)
|
||||
m.Path("/invite/consume").Methods("POST").Name(CompleteInviteConsume)
|
||||
|
||||
m.Path("/notice/show").Methods("GET").Name(CompleteNoticeShow)
|
||||
m.Path("/notice/list").Methods("GET").Name(CompleteNoticeList)
|
||||
|
||||
|
23
web/templates/admin/invite-created.tmpl
Normal file
23
web/templates/admin/invite-created.tmpl
Normal file
@ -0,0 +1,23 @@
|
||||
{{ define "title" }}{{i18n "AdminInviteCreatedTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="flex flex-col justify-center items-center h-64">
|
||||
|
||||
<span
|
||||
id="welcome"
|
||||
class="text-center"
|
||||
>{{i18n "AdminInviteCreatedWelcome"}}</span>
|
||||
|
||||
<a
|
||||
href="{{urlTo "complete:invite:accept" "token" .Token}}"
|
||||
id="accept-link"
|
||||
class="px-4 h-8 shadow rounded flex flex-row justify-center items-center bg-white align-middle text-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-opacity-50"
|
||||
>{{i18n "InviteAccept"}}</a>
|
||||
|
||||
<pre id="invite-accept-link">{{.AccepURL}}</pre>
|
||||
|
||||
{{if ne .AliasSuggestion ""}}
|
||||
<!-- https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60 -->
|
||||
<p>{{i18n "AdminInviteSuggestedAliasIs"}} {{.AliasSuggestion}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
@ -36,12 +36,18 @@
|
||||
<li class="flex flex-row items-center h-12">
|
||||
<span
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
|
||||
>{{.CreatedBy.Name}}</span>
|
||||
>
|
||||
<!-- https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60 -->
|
||||
{{i18n "AdminInviteCreatedBy"}} {{.CreatedBy.Name}}
|
||||
{{if ne .AliasSuggestion ""}}
|
||||
({{i18n "AdminInviteSuggestedAliasIsShort"}} {{.AliasSuggestion}})
|
||||
{{end}}
|
||||
</span>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:invites:revoke:confirm" "id" .ID}}"
|
||||
class="pl-4 w-20 py-2 text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
|
||||
>{{i18n "AdminInvitesRevoke"}}</a>
|
||||
>{{i18n "AdminInviteRevoke"}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
35
web/templates/admin/invite-revoke-confirm.tmpl
Normal file
35
web/templates/admin/invite-revoke-confirm.tmpl
Normal file
@ -0,0 +1,35 @@
|
||||
{{ define "title" }}{{i18n "AdminInviteRevokeConfirmTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="flex flex-col justify-center items-center h-64">
|
||||
|
||||
<span
|
||||
id="welcome"
|
||||
class="text-center"
|
||||
>{{i18n "AdminInviteRevokeConfirmWelcome"}}</span>
|
||||
|
||||
<pre
|
||||
class="my-4 font-mono truncate max-w-full text-lg text-gray-700"
|
||||
>{{.Invite.CreatedBy.Name}}</pre>
|
||||
|
||||
{{if ne .Invite.AliasSuggestion ""}}
|
||||
<!-- https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60 -->
|
||||
<p>{{i18n "AdminInviteSuggestedAliasIs"}} {{.Invite.AliasSuggestion}}</p>
|
||||
{{end}}
|
||||
|
||||
<form id="confirm" action="{{urlTo "admin:invites:revoke"}}" method="POST">
|
||||
{{ .csrfField }}
|
||||
<input type="hidden" name="id" value={{.Invite.ID}}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<a
|
||||
href="javascript:history.back()"
|
||||
class="px-4 h-8 shadow rounded flex flex-row justify-center items-center bg-white align-middle text-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-opacity-50"
|
||||
>{{i18n "GenericGoBack"}}</a>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="shadow rounded px-4 h-8 text-gray-100 bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-pink-600 focus:ring-opacity-50"
|
||||
>{{i18n "GenericConfirm"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
16
web/templates/invite/accept.tmpl
Normal file
16
web/templates/invite/accept.tmpl
Normal file
@ -0,0 +1,16 @@
|
||||
{{ define "title" }}{{i18n "InviteAcceptTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="flex flex-col justify-center items-center h-64">
|
||||
|
||||
<span
|
||||
id="welcome"
|
||||
class="text-center"
|
||||
>{{i18n "InviteAcceptWelcome"}}</span>
|
||||
|
||||
|
||||
{{if ne .AliasSuggestion ""}}
|
||||
<!-- https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60 -->
|
||||
<p>{{i18n "InviteAcceptAliasSuggestion"}} {{.AliasSuggestion}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
12
web/templates/invite/consumed.tmpl
Normal file
12
web/templates/invite/consumed.tmpl
Normal file
@ -0,0 +1,12 @@
|
||||
{{ define "title" }}{{i18n "InviteConsumedTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="flex flex-col justify-center items-center h-64">
|
||||
|
||||
<span
|
||||
id="welcome"
|
||||
class="text-center"
|
||||
>{{i18n "InviteConsumedWelcome"}}</span>
|
||||
|
||||
<p class="color-red-600">TODO: present tunnel address and ssb uri redirect</p>
|
||||
</div>
|
||||
{{end}}
|
21
web/user/testing.go
Normal file
21
web/user/testing.go
Normal file
@ -0,0 +1,21 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
|
||||
)
|
||||
|
||||
// MiddlewareForTests gives us a way to inject _test users_. It should not be used in production.
|
||||
// This is exists here because we need to use roomUserContextKey which shouldn't be exported either.
|
||||
// TODO: could be protected with an extra build tag.
|
||||
// (Sadly +build test does not exist https://github.com/golang/go/issues/21360 )
|
||||
func MiddlewareForTests(user *admindb.User) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := context.WithValue(req.Context(), roomUserContextKey, user)
|
||||
next.ServeHTTP(w, req.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
|
||||
params = append(params, v.Ref())
|
||||
default:
|
||||
level.Error(l).Log("msg", "invalid param type", "param", fmt.Sprintf("%T", p), "route", routeName)
|
||||
return &url.URL{}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user