basic auth middleware setup

This commit is contained in:
Henry 2021-02-08 12:57:14 +01:00
parent 9639586d47
commit 01ed66d6df
25 changed files with 679 additions and 45 deletions

29
admindb/interface.go Normal file
View File

@ -0,0 +1,29 @@
package admindb
import (
"go.mindeco.de/http/auth"
)
// FallbackAuth might be helpful for scenarios where one lost access to his ssb device or key
type FallbackAuth interface {
auth.Auther
}
// AuthService defines functions needed for the challange/response system of sign-in with ssb
type AuthService interface{}
// RoomService deals with changing the privacy modes and managing the allow/deny lists of the room
type RoomService interface{}
// AliasService manages alias handle registration and lookup
type AliasService interface{}
// for tests we use generated mocks from these interfaces created with https://github.com/maxbrunsfeld/counterfeiter
//go:generate counterfeiter -o mockdb/auth.go . AuthService
//go:generate counterfeiter -o mockdb/auth_fallback.go . FallbackAuth
//go:generate counterfeiter -o mockdb/room.go . RoomService
//go:generate counterfeiter -o mockdb/alias.go . AliasService

37
admindb/mockdb/alias.go Normal file
View File

@ -0,0 +1,37 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mockdb
import (
"sync"
"github.com/ssb-ngi-pointer/gossb-rooms/admindb"
)
type FakeAliasService struct {
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeAliasService) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeAliasService) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ admindb.AliasService = new(FakeAliasService)

37
admindb/mockdb/auth.go Normal file
View File

@ -0,0 +1,37 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mockdb
import (
"sync"
"github.com/ssb-ngi-pointer/gossb-rooms/admindb"
)
type FakeAuthService struct {
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeAuthService) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeAuthService) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ admindb.AuthService = new(FakeAuthService)

View File

@ -0,0 +1,118 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mockdb
import (
"sync"
"github.com/ssb-ngi-pointer/gossb-rooms/admindb"
)
type FakeFallbackAuth struct {
CheckStub func(string, string) (interface{}, error)
checkMutex sync.RWMutex
checkArgsForCall []struct {
arg1 string
arg2 string
}
checkReturns struct {
result1 interface{}
result2 error
}
checkReturnsOnCall map[int]struct {
result1 interface{}
result2 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeFallbackAuth) Check(arg1 string, arg2 string) (interface{}, error) {
fake.checkMutex.Lock()
ret, specificReturn := fake.checkReturnsOnCall[len(fake.checkArgsForCall)]
fake.checkArgsForCall = append(fake.checkArgsForCall, struct {
arg1 string
arg2 string
}{arg1, arg2})
stub := fake.CheckStub
fakeReturns := fake.checkReturns
fake.recordInvocation("Check", []interface{}{arg1, arg2})
fake.checkMutex.Unlock()
if stub != nil {
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeFallbackAuth) CheckCallCount() int {
fake.checkMutex.RLock()
defer fake.checkMutex.RUnlock()
return len(fake.checkArgsForCall)
}
func (fake *FakeFallbackAuth) CheckCalls(stub func(string, string) (interface{}, error)) {
fake.checkMutex.Lock()
defer fake.checkMutex.Unlock()
fake.CheckStub = stub
}
func (fake *FakeFallbackAuth) CheckArgsForCall(i int) (string, string) {
fake.checkMutex.RLock()
defer fake.checkMutex.RUnlock()
argsForCall := fake.checkArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeFallbackAuth) CheckReturns(result1 interface{}, result2 error) {
fake.checkMutex.Lock()
defer fake.checkMutex.Unlock()
fake.CheckStub = nil
fake.checkReturns = struct {
result1 interface{}
result2 error
}{result1, result2}
}
func (fake *FakeFallbackAuth) CheckReturnsOnCall(i int, result1 interface{}, result2 error) {
fake.checkMutex.Lock()
defer fake.checkMutex.Unlock()
fake.CheckStub = nil
if fake.checkReturnsOnCall == nil {
fake.checkReturnsOnCall = make(map[int]struct {
result1 interface{}
result2 error
})
}
fake.checkReturnsOnCall[i] = struct {
result1 interface{}
result2 error
}{result1, result2}
}
func (fake *FakeFallbackAuth) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.checkMutex.RLock()
defer fake.checkMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeFallbackAuth) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ admindb.FallbackAuth = new(FakeFallbackAuth)

37
admindb/mockdb/room.go Normal file
View File

@ -0,0 +1,37 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mockdb
import (
"sync"
"github.com/ssb-ngi-pointer/gossb-rooms/admindb"
)
type FakeRoomService struct {
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeRoomService) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeRoomService) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ admindb.RoomService = new(FakeRoomService)

View File

@ -166,6 +166,16 @@ func runroomsrv() error {
return fmt.Errorf("failed to instantiate ssb server: %w", err)
}
// open the HTTP listener
httpLis, err := net.Listen("tcp", listenAddrHTTP)
if err != nil {
return fmt.Errorf("failed to open listener for HTTPdashboard: %w", err)
}
r := repo.New(repoDir)
db, err := sqlite.OpenOrCreate(r.GetPath("roomdb"))
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
@ -173,6 +183,8 @@ func runroomsrv() error {
level.Warn(log).Log("event", "killed", "msg", "received signal, shutting down", "signal", sig.String())
cancel()
roomsrv.Shutdown()
httpLis.Close()
time.Sleep(2 * time.Second)
err := roomsrv.Close()
@ -183,17 +195,14 @@ func runroomsrv() error {
}()
// setup web dashboard handlers
dashboardH, err := handlers.New(nil, repo.New(repoDir))
dashboardH, err := handlers.New(
nil,
repo.New(repoDir),
)
if err != nil {
return fmt.Errorf("failed to create HTTPdashboard handler: %w", err)
}
// open the HTTP listener
httpLis, err := net.Listen("tcp", listenAddrHTTP)
if err != nil {
return fmt.Errorf("failed to open listener for HTTPdashboard: %w", err)
}
// TODO: setup other http goodies (such as CSRF and CSP)
level.Info(log).Log(
@ -207,7 +216,18 @@ func runroomsrv() error {
// start serving http connections
go func() {
err = http.Serve(httpLis, dashboardH)
srv := http.Server{
Addr: httpLis.Addr().String(),
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: dashboardH,
}
err = srv.Serve(httpLis)
if err != nil {
level.Error(log).Log("event", "http serve failed", "err", err)
}

5
go.mod
View File

@ -5,7 +5,9 @@ go 1.15
require (
github.com/BurntSushi/toml v0.3.1
github.com/go-kit/kit v0.10.0
github.com/gorilla/mux v1.7.3
github.com/gorilla/mux v1.8.0
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.4.2
github.com/keks/nocomment v0.0.0-20181007001506-30c6dcb4a472
github.com/nicksnyder/go-i18n/v2 v2.1.2
@ -22,6 +24,7 @@ require (
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
golang.org/x/text v0.3.3
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
exclude go.cryptoscope.co/ssb v0.0.0-20201207161753-31d0f24b7a79

16
go.sum
View File

@ -116,8 +116,13 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -206,7 +211,6 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc=
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@ -267,7 +271,6 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr
github.com/shurcooL/go v0.0.0-20190121191506-3fef8c783dec/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go v0.0.0-20190330031554-6713ea532688/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA=
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
@ -286,7 +289,6 @@ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -325,10 +327,6 @@ go.cryptoscope.co/secretstream v1.2.2 h1:kPxsgWrTDFyS9ZklcD0si1KGljPLz6mmPKnFQjG
go.cryptoscope.co/secretstream v1.2.2/go.mod h1:7nRGZ7fTqSgQAnv2Y4m8xQsS3MFxvB7I0C19reUNlXg=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mindeco.de v0.0.0-20191122233605-f02621a5bca7 h1:si0O0lnNT3SHzEBwuFfqWBaZlME/FnXWlxyZGdri3D8=
go.mindeco.de v0.0.0-20191122233605-f02621a5bca7/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
go.mindeco.de v1.6.0 h1:mrO5+eaJou/5a7ob+lMfH+DudT1TJ7oSjYqMTGaoYUA=
go.mindeco.de v1.6.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870 h1:TCI3AefMAaOYECvppn30+CfEB0Fn8IES1SKvvacc3/c=
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870/go.mod h1:OnBnV02ux4lLsZ39LID6yYLqSDp+dqTHb/3miYPkQFs=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@ -456,9 +454,11 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -15,6 +15,8 @@ import (
"go.mindeco.de/goutils"
)
const Production = false
// absolute path of where this package is located
var pkgDir = goutils.MustLocatePackage("github.com/ssb-ngi-pointer/gossb-rooms/web")

View File

@ -0,0 +1,30 @@
package admin
import (
"net/http"
"github.com/gorilla/mux"
"go.mindeco.de/http/render"
"github.com/ssb-ngi-pointer/gossb-rooms/web/router"
)
var HTMLTemplates = []string{
"/admin/dashboard.tmpl",
}
func Handler(m *mux.Router, r *render.Renderer) http.Handler {
if m == nil {
m = router.Admin(nil)
}
m.Get(router.AdminDashboard).HandlerFunc(r.HTML("/admin/dashboard.tmpl", dashboard))
return m
}
func dashboard(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
return struct {
Name string
}{"test"}, nil
}

View File

@ -0,0 +1,23 @@
package auth
import (
"net/http"
"github.com/gorilla/mux"
"go.mindeco.de/http/auth"
"go.mindeco.de/http/render"
"github.com/ssb-ngi-pointer/gossb-rooms/web/router"
)
func Handler(m *mux.Router, r *render.Renderer, a *auth.Handler) http.Handler {
if m == nil {
m = router.Auth(nil)
}
m.Get(router.AuthFallbackSignInForm).Handler(r.StaticHTML("/auth/fallback_sign_in.tmpl"))
m.Get(router.AuthFallbackSignIn).HandlerFunc(a.Authorize)
m.Get(router.AuthFallbackSignOut).HandlerFunc(a.Logout)
return m
}

View File

@ -1 +0,0 @@
package auth

View File

@ -1,21 +1,33 @@
package handlers
import (
"bytes"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"go.mindeco.de/http/auth"
"go.mindeco.de/http/render"
"github.com/ssb-ngi-pointer/gossb-rooms/admindb"
"github.com/ssb-ngi-pointer/gossb-rooms/internal/repo"
"github.com/ssb-ngi-pointer/gossb-rooms/web"
"github.com/ssb-ngi-pointer/gossb-rooms/web/handlers/admin"
roomsAuth "github.com/ssb-ngi-pointer/gossb-rooms/web/handlers/auth"
"github.com/ssb-ngi-pointer/gossb-rooms/web/handlers/news"
"github.com/ssb-ngi-pointer/gossb-rooms/web/i18n"
"github.com/ssb-ngi-pointer/gossb-rooms/web/router"
)
// New initializes the whole web stack for rooms, with all the sub-modules and routing.
func New(m *mux.Router, repo repo.Interface) (http.Handler, error) {
func New(
m *mux.Router,
repo repo.Interface,
as admindb.AuthService,
fs admindb.FallbackAuth,
) (http.Handler, error) {
if m == nil {
m = router.CompleteApp()
}
@ -27,10 +39,15 @@ func New(m *mux.Router, repo repo.Interface) (http.Handler, error) {
r, err := render.New(web.Templates,
render.BaseTemplates("/base.tmpl"),
render.AddTemplates(append(news.HTMLTemplates,
"/landing/index.tmpl",
"/landing/about.tmpl",
"/error.tmpl")...),
render.AddTemplates(concatTemplates(
[]string{
"/landing/index.tmpl",
"/landing/about.tmpl",
"/error.tmpl",
},
news.HTMLTemplates,
admin.HTMLTemplates,
)...),
render.FuncMap(web.TemplateFuncs(m)),
// TODO: add plural and template data variants
// TODO: move these to the i18n helper pkg
@ -45,7 +62,48 @@ func New(m *mux.Router, repo repo.Interface) (http.Handler, error) {
return nil, fmt.Errorf("web Handler: failed to create renderer: %w", err)
}
// TODO: generate & persist me
// repo.GetPath("web-cookie")
store := &sessions.CookieStore{
Codecs: securecookie.CodecsFromPairs(
bytes.Repeat([]byte("acab"), 8),
bytes.Repeat([]byte("beef"), 8),
// securecookie.GenerateRandomKey(32), // new key every time we startup
// securecookie.GenerateRandomKey(32),
),
Options: &sessions.Options{
Path: "/",
MaxAge: 30,
},
}
notAuthorizedH := r.HTML("/error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
statusCode := http.StatusUnauthorized
rw.WriteHeader(statusCode)
return errorTemplateData{
statusCode,
"Unauthorized",
"you are not authorized to access the requested site",
}, nil
})
a, err := auth.NewHandler(fs,
auth.SetStore(store),
auth.SetNotAuthorizedHandler(notAuthorizedH),
)
if err != nil {
return nil, fmt.Errorf("web Handler: failed to init fallback auth system: %w", err)
}
// hookup handlers to the router
roomsAuth.Handler(m, r, a)
adminRouter := m.PathPrefix("/admin").Subrouter()
adminRouter.Use(a.Authenticate)
// we dont strip path here because it somehow fucks with the middleware setup
adminRouter.PathPrefix("/").Handler(admin.Handler(adminRouter, r))
m.PathPrefix("/news").Handler(http.StripPrefix("/news", news.Handler(m, r)))
m.Get(router.CompleteIndex).Handler(r.StaticHTML("/landing/index.tmpl"))
@ -53,10 +111,34 @@ func New(m *mux.Router, repo repo.Interface) (http.Handler, error) {
m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets)))
m.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintf(rw, "404: url not found")
m.NotFoundHandler = r.HTML("/error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
rw.WriteHeader(http.StatusNotFound)
return errorTemplateData{http.StatusNotFound, "Not Found", "the requested page wasnt found.."}, nil
})
// TODO: disable in non-dev
if web.Production {
return m, nil
}
return r.GetReloader()(m), nil
}
// utils
type errorTemplateData struct {
StatusCode int
Status string
Err string
}
func concatTemplates(lst ...[]string) []string {
var catted []string
for _, tpls := range lst {
for _, t := range tpls {
catted = append(catted, t)
}
}
return catted
}

80
web/handlers/http_test.go Normal file
View File

@ -0,0 +1,80 @@
package handlers
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ssb-ngi-pointer/gossb-rooms/web/router"
)
func TestIndex(t *testing.T) {
setup(t)
t.Cleanup(teardown)
a := assert.New(t)
r := require.New(t)
url, err := testRouter.Get(router.CompleteIndex).URL()
r.Nil(err)
html, resp := testClient.GetHTML(url.String(), nil)
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
a.Equal("Index landing page", html.Find("#welcome").Text())
val, has := html.Find("#logo").Attr("src")
a.True(has, "logo src attribute not found")
a.Equal("/assets/img/test-hermie.png", val)
}
func TestAbout(t *testing.T) {
setup(t)
t.Cleanup(teardown)
a := assert.New(t)
r := require.New(t)
url, err := testRouter.Get(router.CompleteAbout).URL()
r.Nil(err)
html, resp := testClient.GetHTML(url.String(), nil)
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
found := html.Find("h1").Text()
a.Equal("The about page", found)
}
func TestNotFound(t *testing.T) {
setup(t)
t.Cleanup(teardown)
a := assert.New(t)
html, resp := testClient.GetHTML("/some/random/ASDKLANZXC", nil)
a.Equal(http.StatusNotFound, resp.Code, "wrong HTTP status code")
found := html.Find("h1").Text()
a.Equal("Error #404 - Not Found", found)
}
func TestNewsRegisterd(t *testing.T) {
setup(t)
t.Cleanup(teardown)
a := assert.New(t)
html, resp := testClient.GetHTML("/news/", nil)
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
found := html.Find("h1").Text()
t.Log(found)
// a.Equal("fooo", found)
}
func TestRestricted(t *testing.T) {
setup(t)
t.Cleanup(teardown)
a := assert.New(t)
html, resp := testClient.GetHTML("/admin/", nil)
a.Equal(http.StatusUnauthorized, resp.Code, "wrong HTTP status code")
found := html.Find("h1").Text()
a.Equal("Error #401 - Unauthorized", found)
}

View File

@ -17,8 +17,6 @@ var (
testMux *http.ServeMux
testClient *tester.Tester
testRouter = router.News(nil)
testAssets = http.Dir("../../templates")
)
func setup(t *testing.T) {
@ -27,7 +25,7 @@ func setup(t *testing.T) {
testFuncs["i18n"] = func(msgID string) string { return msgID }
log, _ := logtest.KitLogger("feed", t)
r, err := render.New(testAssets, //TODO: embedd web.Assets,
r, err := render.New(web.Templates,
render.SetLogger(log),
render.BaseTemplates("/testing/base.tmpl"),
render.AddTemplates(append(HTMLTemplates, "/error.tmpl")...),

View File

@ -0,0 +1,57 @@
package handlers
import (
"net/http"
"os"
"path/filepath"
"testing"
"github.com/pkg/errors"
"go.mindeco.de/http/tester"
"github.com/ssb-ngi-pointer/gossb-rooms/admindb/mockdb"
"github.com/ssb-ngi-pointer/gossb-rooms/internal/repo"
"github.com/ssb-ngi-pointer/gossb-rooms/web/router"
)
var (
testMux *http.ServeMux
testClient *tester.Tester
testRouter = router.CompleteApp()
// mocked dbs
testAuthDB *mockdb.FakeAuthService
testAuthFallbackDB *mockdb.FakeFallbackAuth
)
func setup(t *testing.T) {
testRepoPath := filepath.Join("testrun", t.Name())
os.RemoveAll(testRepoPath)
testRepo := repo.New(testRepoPath)
testAuthDB = new(mockdb.FakeAuthService)
testAuthFallbackDB = new(mockdb.FakeFallbackAuth)
h, err := New(
testRouter,
testRepo,
testAuthDB,
testAuthFallbackDB,
)
if err != nil {
t.Fatal(errors.Wrap(err, "setup: handler init failed"))
}
// log, _ := logtest.KitLogger("complete", t)
testMux = http.NewServeMux()
testMux.Handle("/", h)
testClient = tester.New(testMux, t)
}
func teardown() {
testMux = nil
testClient = nil
testAuthFallbackDB = nil
testAuthFallbackDB = nil
}

View File

@ -22,7 +22,7 @@ func New(r repo.Interface) (*Helper, error) {
bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
// TODO: could additionally embedd the defaults together with the html assets and templates
// TODO: could additionally embedd the defaults like we do with the html assets and templates
err := filepath.Walk(r.GetPath("i18n"), func(path string, info os.FileInfo, err error) error {
if err != nil {
@ -44,8 +44,8 @@ func New(r repo.Interface) (*Helper, error) {
return nil
})
if err != nil {
return nil, err
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("i18n: failed to iterate localizations: %w", err)
}
return &Helper{bundle: bundle}, nil

6
web/production.go Normal file
View File

@ -0,0 +1,6 @@
// +build !dev
package web
// Production can be used to determain different aspects at compile time (like hot template reloading)
const Production = true

21
web/router/admin.go Normal file
View File

@ -0,0 +1,21 @@
package router
import "github.com/gorilla/mux"
// constant names for the named routes
const (
AdminDashboard = "admin:dashboard"
)
// Admin constructs a mux.Router containing the routes for the admin dashboard and settings pages
func Admin(m *mux.Router) *mux.Router {
if m == nil {
m = mux.NewRouter()
}
// we dont strip path here because it somehow fucks with the middleware setup
m.Path("/admin").Methods("GET").Name(AdminDashboard)
// m.Path("/admin/settings").Methods("GET").Name(AdminSettings)
return m
}

View File

@ -4,8 +4,12 @@ import "github.com/gorilla/mux"
// constant names for the named routes
const (
AuthSignIn = "Auth:SignIn"
AuthSignOut = "Auth:SignOut"
AuthFallbackSignInForm = "Auth:Fallback:Form:SignIn"
AuthFallbackSignIn = "Auth:Fallback:SignIn"
AuthFallbackSignOut = "Auth:Fallback:SignOut"
AuthWithSSBSignIn = "Auth:WithSSB:SignIn"
AuthWithSSBSignOut = "Auth:WithSSB:SignOut"
)
// NewSignin constructs a mux.Router containing the routes for sign-in and -out
@ -14,8 +18,13 @@ func Auth(m *mux.Router) *mux.Router {
m = mux.NewRouter()
}
m.Path("/signIn").Methods("GET").Name(AuthSignIn)
m.Path("/signOut").Methods("GET").Name(AuthSignOut)
// register fallback
m.Path("/fallback/signin").Methods("GET").Name(AuthFallbackSignInForm)
m.Path("/fallback/signin").Methods("POST").Name(AuthFallbackSignIn)
m.Path("/fallback/signOut").Methods("GET").Name(AuthFallbackSignOut)
m.Path("/withssb/signIn").Methods("GET").Name(AuthWithSSBSignIn)
m.Path("/withssb/signOut").Methods("GET").Name(AuthWithSSBSignOut)
return m
}

View File

@ -1,6 +1,8 @@
package router
import "github.com/gorilla/mux"
import (
"github.com/gorilla/mux"
)
// constant names for the named routes
const (
@ -13,7 +15,7 @@ func CompleteApp() *mux.Router {
m := mux.NewRouter()
Auth(m.PathPrefix("/auth").Subrouter())
// Admin(m.PathPrefix("/profile").Subrouter())
Admin(m.PathPrefix("/admin").Subrouter())
News(m.PathPrefix("/news").Subrouter())
m.Path("/").Methods("GET").Name(CompleteIndex)

View File

@ -0,0 +1,11 @@
{{ define "title" }}{{i18n "AdminOverview"}}{{ end }}
{{ define "content" }}
<div class="page-header">
<h1 id="welcome">{{i18n "AdminWelcome"}}</h1>
</div>
<div class="row">
<div class="col-md-12">
<p>super cool dashboard!</p>
</div>
</div> <!-- /row -->
{{end}}

View File

@ -0,0 +1,13 @@
{{ define "title" }}fallback sign-in{{ end }}
{{ define "content" }}
<div class="page-header">
<h1 id="welcome">Fallback sign-in</h1>
</div>
<div class="row">
<div class="col-md-12">
<form action="POST">
<input type="submit">
</form>
</div>
</div> <!-- /row -->
{{end}}

View File

@ -1,9 +1,9 @@
{{ define "title" }}Landing - Index{{ end }}
{{ define "content" }}
<div class="page-header">
<h1>Index landing page</h1>
<h1 id="welcome">Index landing page</h1>
<img src="/assets/img/test-hermie.png">
<img id="logo" src="/assets/img/test-hermie.png">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed faucibus turpis in eu mi bibendum. A arcu cursus vitae congue mauris rhoncus aenean vel elit. Aliquet bibendum enim facilisis gravida neque convallis. Tempor orci eu lobortis elementum nibh tellus molestie. Sit amet est placerat in egestas erat imperdiet. Amet consectetur adipiscing elit duis tristique sollicitudin nibh sit amet. Ultrices in iaculis nunc sed. Odio facilisis mauris sit amet massa. Sed vulputate mi sit amet mauris commodo quis imperdiet. Hendrerit dolor magna eget est lorem ipsum dolor sit amet. Neque vitae tempus quam pellentesque nec nam. Quis risus sed vulputate odio ut. Tincidunt lobortis feugiat vivamus at augue.</p>
<p>Sem nulla pharetra diam sit amet nisl. Sed odio morbi quis commodo odio aenean sed adipiscing. In ornare quam viverra orci sagittis. Elit eget gravida cum sociis natoque. Auctor neque vitae tempus quam. Porttitor massa id neque aliquam vestibulum morbi blandit cursus risus. Ornare lectus sit amet est placerat in egestas erat. Ut enim blandit volutpat maecenas. Fermentum odio eu feugiat pretium nibh. At risus viverra adipiscing at. Et egestas quis ipsum suspendisse. Mollis nunc sed id semper risus in hendrerit. Semper feugiat nibh sed pulvinar.</p>

File diff suppressed because one or more lines are too long