implement bridge between muxrcp and http
This commit is contained in:
parent
afa6bee285
commit
396961e5e8
|
@ -19,6 +19,8 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
@ -203,11 +205,14 @@ func runroomsrv() error {
|
||||||
return fmt.Errorf("failed to initiate database: %w", err)
|
return fmt.Errorf("failed to initiate database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bridge := signinwithssb.NewSignalBridge()
|
||||||
|
|
||||||
// create the shs+muxrpc server
|
// create the shs+muxrpc server
|
||||||
roomsrv, err := mksrv.New(
|
roomsrv, err := mksrv.New(
|
||||||
db.Members,
|
db.Members,
|
||||||
db.Aliases,
|
db.Aliases,
|
||||||
db.AuthWithSSB,
|
db.AuthWithSSB,
|
||||||
|
bridge,
|
||||||
httpsDomain,
|
httpsDomain,
|
||||||
opts...)
|
opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -250,6 +255,7 @@ func runroomsrv() error {
|
||||||
},
|
},
|
||||||
roomsrv.StateManager,
|
roomsrv.StateManager,
|
||||||
roomsrv.Network,
|
roomsrv.Network,
|
||||||
|
bridge,
|
||||||
handlers.Databases{
|
handlers.Databases{
|
||||||
Aliases: db.Aliases,
|
Aliases: db.Aliases,
|
||||||
AuthFallback: db.AuthFallback,
|
AuthFallback: db.AuthFallback,
|
||||||
|
@ -286,7 +292,8 @@ func runroomsrv() error {
|
||||||
STSIncludeSubdomains: false,
|
STSIncludeSubdomains: false,
|
||||||
|
|
||||||
// See for more https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
// See for more https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
||||||
ContentSecurityPolicy: "default-src 'self'", // enforce no external content
|
// helpful: https://report-uri.com/home/generate
|
||||||
|
ContentSecurityPolicy: "default-src 'self'; img-src 'self' data:", // enforce no external content
|
||||||
|
|
||||||
BrowserXssFilter: true,
|
BrowserXssFilter: true,
|
||||||
FrameDeny: true,
|
FrameDeny: true,
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -20,6 +20,7 @@ require (
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
|
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/unrolled/secure v1.0.8
|
github.com/unrolled/secure v1.0.8
|
||||||
github.com/vcraescu/go-paginator/v2 v2.0.0
|
github.com/vcraescu/go-paginator/v2 v2.0.0
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -372,6 +372,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package signinwithssb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignalBridge implements a way for muxrpc and http handlers to communicate about SIWSSB events
|
||||||
|
type SignalBridge struct {
|
||||||
|
mu *sync.Mutex
|
||||||
|
|
||||||
|
sessions sessionMap
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessionMap map[string]chan Event
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Worked bool
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSignalBridge returns a new SignalBridge
|
||||||
|
func NewSignalBridge() *SignalBridge {
|
||||||
|
return &SignalBridge{
|
||||||
|
mu: new(sync.Mutex),
|
||||||
|
sessions: make(sessionMap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterSession registers a new session on the bridge.
|
||||||
|
// It returns a channel from which future events can be read
|
||||||
|
// and the server challenge, which acts as the session key.
|
||||||
|
func (sb *SignalBridge) RegisterSession() string {
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
|
||||||
|
c := GenerateChallenge()
|
||||||
|
_, used := sb.sessions[c]
|
||||||
|
if used {
|
||||||
|
for used { // generate new challanges until we have an un-used one
|
||||||
|
c = GenerateChallenge()
|
||||||
|
_, used = sb.sessions[c]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evtCh := make(chan Event)
|
||||||
|
sb.sessions[c] = evtCh
|
||||||
|
|
||||||
|
go func() { // make sure the session doesn't go stale and collect dust (ie unused memory)
|
||||||
|
time.Sleep(10 * time.Minute)
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
delete(sb.sessions, c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *SignalBridge) GetEventChannel(sc string) (<-chan Event, bool) {
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
ch, has := sb.sessions[sc]
|
||||||
|
return ch, has
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompleteSession uses the passed challange to send on and close the open channel.
|
||||||
|
// It will return an error if the session doesn't exist.
|
||||||
|
func (sb *SignalBridge) CompleteSession(sc string, success bool, token string) error {
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
|
||||||
|
ch, ok := sb.sessions[sc]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("no such session")
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- Event{
|
||||||
|
Worked: success,
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
|
||||||
|
// remove session
|
||||||
|
delete(sb.sessions, sc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package signinwithssb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBridge(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
sb := NewSignalBridge()
|
||||||
|
|
||||||
|
// try to use a non-existant session
|
||||||
|
err := sb.CompleteSession("nope", false)
|
||||||
|
a.Error(err)
|
||||||
|
|
||||||
|
// make a new session
|
||||||
|
updates, sc := sb.RegisterSession()
|
||||||
|
|
||||||
|
b, err := DecodeChallengeString(sc)
|
||||||
|
a.NoError(err)
|
||||||
|
a.Len(b, challengeLength)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := sb.CompleteSession(sc, true)
|
||||||
|
a.NoError(err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(time.Second / 4)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case evt := <-updates:
|
||||||
|
a.True(evt.Worked)
|
||||||
|
default:
|
||||||
|
t.Error("no updates")
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"go.cryptoscope.co/muxrpc/v2"
|
"go.cryptoscope.co/muxrpc/v2"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
validate "github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
validate "github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
@ -23,6 +24,8 @@ type Handler struct {
|
||||||
sessions roomdb.AuthWithSSBService
|
sessions roomdb.AuthWithSSBService
|
||||||
members roomdb.MembersService
|
members roomdb.MembersService
|
||||||
|
|
||||||
|
bridge *signinwithssb.SignalBridge
|
||||||
|
|
||||||
roomDomain string // the http(s) domain of the room to signal redirect addresses
|
roomDomain string // the http(s) domain of the room to signal redirect addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,9 +33,11 @@ type Handler struct {
|
||||||
func New(
|
func New(
|
||||||
log kitlog.Logger,
|
log kitlog.Logger,
|
||||||
self refs.FeedRef,
|
self refs.FeedRef,
|
||||||
sessiondb roomdb.AuthWithSSBService,
|
roomDomain string,
|
||||||
membersdb roomdb.MembersService,
|
membersdb roomdb.MembersService,
|
||||||
roomDomain string) Handler {
|
sessiondb roomdb.AuthWithSSBService,
|
||||||
|
bridge *signinwithssb.SignalBridge,
|
||||||
|
) Handler {
|
||||||
|
|
||||||
var h Handler
|
var h Handler
|
||||||
h.self = self
|
h.self = self
|
||||||
|
@ -40,6 +45,7 @@ func New(
|
||||||
h.logger = log
|
h.logger = log
|
||||||
h.sessions = sessiondb
|
h.sessions = sessiondb
|
||||||
h.members = membersdb
|
h.members = membersdb
|
||||||
|
h.bridge = bridge
|
||||||
|
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
@ -50,6 +56,11 @@ func (h Handler) SendSolution(ctx context.Context, req *muxrpc.Request) (interfa
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
member, err := h.members.GetByFeed(ctx, *clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client is not a room member")
|
||||||
|
}
|
||||||
|
|
||||||
var params []string
|
var params []string
|
||||||
if err := json.Unmarshal(req.RawArgs, ¶ms); err != nil {
|
if err := json.Unmarshal(req.RawArgs, ¶ms); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -67,18 +78,22 @@ func (h Handler) SendSolution(ctx context.Context, req *muxrpc.Request) (interfa
|
||||||
|
|
||||||
sig, err := base64.StdEncoding.DecodeString(params[2])
|
sig, err := base64.StdEncoding.DecodeString(params[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("sc is not valid base64 data: %w", err)
|
h.bridge.CompleteSession(sol.ServerChallenge, false, "")
|
||||||
|
return nil, fmt.Errorf("signature is not valid base64 data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sol.Validate(sig) {
|
if !sol.Validate(sig) {
|
||||||
|
h.bridge.CompleteSession(sol.ServerChallenge, false, "")
|
||||||
return nil, fmt.Errorf("not a valid solution")
|
return nil, fmt.Errorf("not a valid solution")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
tok, err := h.sessions.CreateToken(ctx, member.ID)
|
||||||
// h.challenges.Solved(sc)
|
if err != nil {
|
||||||
// return true, nil
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("TODO: update SSE")
|
h.bridge.CompleteSession(sol.ServerChallenge, true, tok)
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Handler) InvalidateAllSolutions(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
|
func (h Handler) InvalidateAllSolutions(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ package roomsrv
|
||||||
import (
|
import (
|
||||||
kitlog "github.com/go-kit/kit/log"
|
kitlog "github.com/go-kit/kit/log"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/signinwithssb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/muxrpc/handlers/signinwithssb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
|
||||||
muxrpc "go.cryptoscope.co/muxrpc/v2"
|
muxrpc "go.cryptoscope.co/muxrpc/v2"
|
||||||
"go.cryptoscope.co/muxrpc/v2/typemux"
|
"go.cryptoscope.co/muxrpc/v2/typemux"
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// instantiate and register the muxrpc handlers
|
// instantiate and register the muxrpc handlers
|
||||||
func (s *Server) initHandlers(aliasDB roomdb.AliasesService) {
|
func (s *Server) initHandlers() {
|
||||||
// inistaniate handler packages
|
// inistaniate handler packages
|
||||||
whoami := whoami.New(s.Whoami())
|
whoami := whoami.New(s.Whoami())
|
||||||
|
|
||||||
|
@ -28,16 +27,17 @@ func (s *Server) initHandlers(aliasDB roomdb.AliasesService) {
|
||||||
aliasHandler := alias.New(
|
aliasHandler := alias.New(
|
||||||
kitlog.With(s.logger, "unit", "aliases"),
|
kitlog.With(s.logger, "unit", "aliases"),
|
||||||
s.Whoami(),
|
s.Whoami(),
|
||||||
aliasDB,
|
s.Aliases,
|
||||||
s.domain,
|
s.domain,
|
||||||
)
|
)
|
||||||
|
|
||||||
siwssbHandler := signinwithssb.New(
|
siwssbHandler := signinwithssb.New(
|
||||||
kitlog.With(s.logger, "unit", "auth-with-ssb"),
|
kitlog.With(s.logger, "unit", "auth-with-ssb"),
|
||||||
s.Whoami(),
|
s.Whoami(),
|
||||||
s.authWithSSB,
|
|
||||||
s.Members,
|
|
||||||
s.domain,
|
s.domain,
|
||||||
|
s.Members,
|
||||||
|
s.authWithSSB,
|
||||||
|
s.authWithSSBBridge,
|
||||||
)
|
)
|
||||||
|
|
||||||
// register muxrpc commands
|
// register muxrpc commands
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/multicloser"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/multicloser"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
@ -67,7 +68,8 @@ type Server struct {
|
||||||
Members roomdb.MembersService
|
Members roomdb.MembersService
|
||||||
Aliases roomdb.AliasesService
|
Aliases roomdb.AliasesService
|
||||||
|
|
||||||
authWithSSB roomdb.AuthWithSSBService
|
authWithSSB roomdb.AuthWithSSBService
|
||||||
|
authWithSSBBridge *signinwithssb.SignalBridge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) Whoami() refs.FeedRef {
|
func (s Server) Whoami() refs.FeedRef {
|
||||||
|
@ -78,6 +80,7 @@ func New(
|
||||||
membersdb roomdb.MembersService,
|
membersdb roomdb.MembersService,
|
||||||
aliasdb roomdb.AliasesService,
|
aliasdb roomdb.AliasesService,
|
||||||
awsdb roomdb.AuthWithSSBService,
|
awsdb roomdb.AuthWithSSBService,
|
||||||
|
bridge *signinwithssb.SignalBridge,
|
||||||
domainName string,
|
domainName string,
|
||||||
opts ...Option,
|
opts ...Option,
|
||||||
) (*Server, error) {
|
) (*Server, error) {
|
||||||
|
@ -88,6 +91,7 @@ func New(
|
||||||
s.Aliases = aliasdb
|
s.Aliases = aliasdb
|
||||||
|
|
||||||
s.authWithSSB = awsdb
|
s.authWithSSB = awsdb
|
||||||
|
s.authWithSSBBridge = bridge
|
||||||
|
|
||||||
s.domain = domainName
|
s.domain = domainName
|
||||||
|
|
||||||
|
@ -148,7 +152,7 @@ func New(
|
||||||
|
|
||||||
s.StateManager = roomstate.NewManager(s.rootCtx, s.logger)
|
s.StateManager = roomstate.NewManager(s.rootCtx, s.logger)
|
||||||
|
|
||||||
s.initHandlers(aliasdb)
|
s.initHandlers()
|
||||||
|
|
||||||
if err := s.initNetwork(); err != nil {
|
if err := s.initNetwork(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
let streamName = document.querySelector("#stream-name").attributes.stream.value
|
// get the challange from out of the HTML
|
||||||
|
let sc = document.querySelector("#challange").attributes.ch.value
|
||||||
|
var evtSource = new EventSource(`/sse/events?sc=${sc}`);
|
||||||
|
|
||||||
var evtSource = new EventSource(`/sse/events?stream=${streamName}`);
|
var ping = document.querySelector('#ping');
|
||||||
|
var failed = document.querySelector('#failed');
|
||||||
|
|
||||||
var eventList = document.querySelector('#event-list');
|
evtSource.onerror = (e) => {
|
||||||
|
failed.textContent = "Warning: The connection to the server was interupted."
|
||||||
|
}
|
||||||
|
|
||||||
evtSource.addEventListener("testing", (e) => {
|
evtSource.addEventListener("ping", (e) => {
|
||||||
// console.log(e)
|
ping.textContent = e.data;
|
||||||
|
})
|
||||||
|
|
||||||
var newElement = document.createElement("li");
|
evtSource.addEventListener("failed", (e) => {
|
||||||
newElement.textContent = `(${e.lastEventId}) message: ${e.data}`;
|
failed.textContent = e.data;
|
||||||
eventList.prepend(newElement);
|
})
|
||||||
})
|
|
||||||
|
evtSource.addEventListener("success", (e) => {
|
||||||
|
console.log('trigger redirect!')
|
||||||
|
alert(e.data)
|
||||||
|
})
|
||||||
|
|
|
@ -5,23 +5,25 @@ package auth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"image/color"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
kitlog "github.com/go-kit/kit/log"
|
kitlog "github.com/go-kit/kit/log"
|
||||||
"github.com/go-kit/kit/log/level"
|
"github.com/go-kit/kit/log/level"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
"go.cryptoscope.co/muxrpc/v2"
|
"go.cryptoscope.co/muxrpc/v2"
|
||||||
"go.mindeco.de/http/render"
|
"go.mindeco.de/http/render"
|
||||||
"go.mindeco.de/logging"
|
"go.mindeco.de/logging"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/randutil"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||||
|
@ -41,8 +43,12 @@ type WithSSBHandler struct {
|
||||||
cookieStore sessions.Store
|
cookieStore sessions.Store
|
||||||
|
|
||||||
endpoints network.Endpoints
|
endpoints network.Endpoints
|
||||||
|
|
||||||
|
bridge *signinwithssb.SignalBridge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type registerToEventSourceMap map[string]<-chan signinwithssb.Event
|
||||||
|
|
||||||
func NewWithSSBHandler(
|
func NewWithSSBHandler(
|
||||||
m *mux.Router,
|
m *mux.Router,
|
||||||
r *render.Renderer,
|
r *render.Renderer,
|
||||||
|
@ -52,6 +58,7 @@ func NewWithSSBHandler(
|
||||||
membersDB roomdb.MembersService,
|
membersDB roomdb.MembersService,
|
||||||
sessiondb roomdb.AuthWithSSBService,
|
sessiondb roomdb.AuthWithSSBService,
|
||||||
cookies sessions.Store,
|
cookies sessions.Store,
|
||||||
|
bridge *signinwithssb.SignalBridge,
|
||||||
) *WithSSBHandler {
|
) *WithSSBHandler {
|
||||||
|
|
||||||
var ssb WithSSBHandler
|
var ssb WithSSBHandler
|
||||||
|
@ -61,11 +68,11 @@ func NewWithSSBHandler(
|
||||||
ssb.endpoints = endpoints
|
ssb.endpoints = endpoints
|
||||||
ssb.sessiondb = sessiondb
|
ssb.sessiondb = sessiondb
|
||||||
ssb.cookieStore = cookies
|
ssb.cookieStore = cookies
|
||||||
|
ssb.bridge = bridge
|
||||||
|
|
||||||
m.Get(router.AuthWithSSBSignIn).HandlerFunc(r.HTML("auth/withssb_sign_in.tmpl", ssb.login))
|
m.Get(router.AuthWithSSBSignIn).HandlerFunc(r.HTML("auth/withssb_sign_in.tmpl", ssb.login))
|
||||||
|
|
||||||
m.HandleFunc("/sse/login/{sc}", r.HTML("auth/withssb_server_start.tmpl", ssb.startWithServer))
|
m.HandleFunc("/sse/login/{sc}", r.HTML("auth/withssb_server_start.tmpl", ssb.startWithServer))
|
||||||
|
|
||||||
m.HandleFunc("/sse/events", ssb.eventSource)
|
m.HandleFunc("/sse/events", ssb.eventSource)
|
||||||
|
|
||||||
return &ssb
|
return &ssb
|
||||||
|
@ -267,48 +274,41 @@ func (h WithSSBHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||||
// server-sent-events stuff
|
// server-sent-events stuff
|
||||||
|
|
||||||
func (h WithSSBHandler) startWithServer(w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (h WithSSBHandler) startWithServer(w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
logger := logging.FromContext(req.Context())
|
sc := h.bridge.RegisterSession()
|
||||||
|
|
||||||
streamName := randutil.String(20)
|
var queryParams = make(url.Values)
|
||||||
// h.events.CreateStream(streamName)
|
queryParams.Set("action", "start-http-auth")
|
||||||
|
|
||||||
logger = level.Debug(logger)
|
var startAuthURI url.URL
|
||||||
logger = kitlog.With(logger, "event", streamName)
|
startAuthURI.Scheme = "ssb"
|
||||||
|
startAuthURI.Opaque = "experimental"
|
||||||
|
startAuthURI.RawQuery = queryParams.Encode()
|
||||||
|
|
||||||
logger.Log("event", "started stream")
|
qrCode, err := qrcode.New(startAuthURI.String(), qrcode.High)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qrCode.BackgroundColor = color.RGBA{R: 0xf9, G: 0xfa, B: 0xfb}
|
||||||
|
qrCode.ForegroundColor = color.Black
|
||||||
|
|
||||||
// tick := time.NewTicker(5 * time.Second)
|
qrCodeData, err := qrCode.PNG(-8)
|
||||||
// go func() {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// var (
|
qrURI := "data:image/png;base64," + base64.StdEncoding.EncodeToString(qrCodeData)
|
||||||
// evtBuf = make([]byte, 4)
|
|
||||||
// evtID = uint32(0)
|
|
||||||
// )
|
|
||||||
// for range tick.C {
|
|
||||||
// binary.BigEndian.PutUint32(evtBuf, evtID)
|
|
||||||
// evtID++
|
|
||||||
// h.events.Publish(streamName, &sse.Event{
|
|
||||||
// ID: evtBuf,
|
|
||||||
// Data: []byte(fmt.Sprintf("boring: %d", evtID)),
|
|
||||||
// Event: []byte("testing"),
|
|
||||||
// })
|
|
||||||
// logger.Log("event", "sent", "id", evtID)
|
|
||||||
// }
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// go func() {
|
|
||||||
// time.Sleep(1 * time.Minute)
|
|
||||||
// tick.Stop()
|
|
||||||
// logger.Log("event", "stopped")
|
|
||||||
// }()
|
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
StreamName string
|
SSBURI template.URL
|
||||||
}{streamName}, nil
|
QRCodeURI template.URL
|
||||||
|
ServerChallenge string
|
||||||
|
}{template.URL(startAuthURI.String()), template.URL(qrURI), sc}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type event struct {
|
type event struct {
|
||||||
ID, Data, Event []byte
|
ID uint32
|
||||||
|
Data string
|
||||||
|
Event string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h WithSSBHandler) eventSource(w http.ResponseWriter, r *http.Request) {
|
func (h WithSSBHandler) eventSource(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -317,37 +317,40 @@ func (h WithSSBHandler) eventSource(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
notifier, ok := w.(http.CloseNotifier)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "cant notify about closed requests", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup server-sent events
|
||||||
|
// https://html.spec.whatwg.org/multipage/server-sent-events.html
|
||||||
w.Header().Set("Content-Type", "text/event-stream")
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
w.Header().Set("Transfer-Encoding", "chunked")
|
w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
|
|
||||||
// Get the StreamID from the URL
|
// Get the StreamID from the URL
|
||||||
streamID := r.URL.Query().Get("stream")
|
sc := r.URL.Query().Get("sc")
|
||||||
if streamID == "" {
|
if sc == "" {
|
||||||
http.Error(w, "Please specify a stream!", http.StatusInternalServerError)
|
http.Error(w, "Please specify a stream!", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := logging.FromContext(r.Context())
|
logger := logging.FromContext(r.Context())
|
||||||
logger = level.Debug(logger)
|
logger = level.Debug(logger)
|
||||||
logger = kitlog.With(logger, "stream", streamID)
|
logger = kitlog.With(logger, "stream", sc)
|
||||||
|
|
||||||
logger.Log("event", "stream opened")
|
logger.Log("event", "stream opened")
|
||||||
|
|
||||||
// TODO: load map with channel
|
evtCh, has := h.bridge.GetEventChannel(sc)
|
||||||
|
if !has {
|
||||||
|
http.Error(w, "No such session!", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// tick := time.NewTicker(time.Second / 4)
|
tick := time.NewTicker(3 * time.Second)
|
||||||
tick := time.NewTicker(time.Second)
|
|
||||||
notify := w.(http.CloseNotifier).CloseNotify()
|
|
||||||
go func() {
|
go func() {
|
||||||
<-notify
|
time.Sleep(3 * time.Minute)
|
||||||
tick.Stop()
|
|
||||||
logger.Log("event", "request closed")
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(5 * time.Minute)
|
|
||||||
tick.Stop()
|
tick.Stop()
|
||||||
logger.Log("event", "stopped")
|
logger.Log("event", "stopped")
|
||||||
}()
|
}()
|
||||||
|
@ -357,32 +360,52 @@ func (h WithSSBHandler) eventSource(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Push events to client
|
// Push events to client
|
||||||
var (
|
var (
|
||||||
evtBuf = make([]byte, 4)
|
evtID = uint32(0)
|
||||||
evtID = uint32(0)
|
|
||||||
evt event
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for range tick.C {
|
notify := notifier.CloseNotify()
|
||||||
binary.BigEndian.PutUint32(evtBuf, evtID)
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-notify:
|
||||||
|
logger.Log("event", "request closed")
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-tick.C:
|
||||||
|
msg := fmt.Sprintf("Waiting for solution (session age: %s)", time.Since(start))
|
||||||
|
sendServerEvent(w, event{
|
||||||
|
ID: evtID,
|
||||||
|
Data: msg,
|
||||||
|
Event: "ping",
|
||||||
|
})
|
||||||
|
logger.Log("event", "sent", "ping", evtID)
|
||||||
|
|
||||||
|
case update := <-evtCh:
|
||||||
|
evt := event{
|
||||||
|
ID: evtID,
|
||||||
|
Data: "challange validation failed",
|
||||||
|
Event: "failed",
|
||||||
|
}
|
||||||
|
|
||||||
|
if update.Worked {
|
||||||
|
evt.Event = "success"
|
||||||
|
evt.Data = update.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
sendServerEvent(w, evt)
|
||||||
|
|
||||||
|
logger.Log("event", "sent", "worked", update.Worked)
|
||||||
|
}
|
||||||
evtID++
|
evtID++
|
||||||
|
|
||||||
evt = event{
|
|
||||||
ID: []byte(fmt.Sprintf("%d", evtID)),
|
|
||||||
Data: []byte(fmt.Sprintf("age: %s", time.Since(start))),
|
|
||||||
Event: []byte("testing"),
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "id: %s\n", evt.ID)
|
|
||||||
fmt.Fprintf(w, "data: %s\n", evt.Data)
|
|
||||||
if len(evt.Event) > 0 {
|
|
||||||
fmt.Fprintf(w, "event: %s\n", evt.Event)
|
|
||||||
}
|
|
||||||
// if len(evt.Retry) > 0 {
|
|
||||||
// fmt.Fprintf(w, "retry: %s\n", evt.Retry)
|
|
||||||
// }
|
|
||||||
fmt.Fprint(w, "\n")
|
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
|
|
||||||
logger.Log("event", "sent", "id", evtID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendServerEvent(w io.Writer, evt event) {
|
||||||
|
fmt.Fprintf(w, "id: %d\n", evt.ID)
|
||||||
|
fmt.Fprintf(w, "data: %s\n", evt.Data)
|
||||||
|
if len(evt.Event) > 0 {
|
||||||
|
fmt.Fprintf(w, "event: %s\n", evt.Event)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, "\n")
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
"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"
|
||||||
|
@ -71,6 +72,7 @@ func New(
|
||||||
netInfo NetworkInfo,
|
netInfo NetworkInfo,
|
||||||
roomState *roomstate.Manager,
|
roomState *roomstate.Manager,
|
||||||
roomEndpoints network.Endpoints,
|
roomEndpoints network.Endpoints,
|
||||||
|
bridge *signinwithssb.SignalBridge,
|
||||||
dbs Databases,
|
dbs Databases,
|
||||||
) (http.Handler, error) {
|
) (http.Handler, error) {
|
||||||
m := router.CompleteApp()
|
m := router.CompleteApp()
|
||||||
|
@ -237,6 +239,7 @@ func New(
|
||||||
dbs.Members,
|
dbs.Members,
|
||||||
dbs.AuthWithSSB,
|
dbs.AuthWithSSB,
|
||||||
cookieStore,
|
cookieStore,
|
||||||
|
bridge,
|
||||||
)
|
)
|
||||||
|
|
||||||
// just hooks up the router to the handler
|
// just hooks up the router to the handler
|
||||||
|
|
|
@ -25,7 +25,7 @@ func AuthenticateFromContext(r *render.Renderer) Middleware {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
if FromContext(req.Context()) == nil {
|
if FromContext(req.Context()) == nil {
|
||||||
r.Error(w, req, http.StatusUnauthorized, weberrors.ErrBadRequest{})
|
r.Error(w, req, http.StatusUnauthorized, weberrors.ErrNotAuthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
next.ServeHTTP(w, req)
|
next.ServeHTTP(w, req)
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
<h1 id="welcome" class="text-lg">{{i18n "AuthWithSSBWelcome"}}</h1>
|
<h1 id="welcome" class="text-lg">{{i18n "AuthWithSSBWelcome"}}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>Template Data</h3>
|
<h3>TODO: qr code of the code</h3>
|
||||||
<pre>{{.}}</pre>
|
<img src="{{.QRCodeURI}}" alt="QR-Code to pass challange to an App" />
|
||||||
|
<pre>{{.SSBURI}}</pre>
|
||||||
|
|
||||||
<h3>Server events</h3>
|
<h3>Server events</h3>
|
||||||
<ul id="event-list"></ul>
|
<p id="ping"></p>
|
||||||
|
<p id="failed" class="text-red-500"></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="stream-name" stream="{{.StreamName}}"></div>
|
<div id="challange" ch="{{.ServerChallenge}}"></div>
|
||||||
<script src="/assets/events-demo.js"></script>
|
<script src="/assets/events-demo.js"></script>
|
||||||
{{end}}
|
{{end}}
|
Loading…
Reference in New Issue