609 lines
17 KiB
Go
609 lines
17 KiB
Go
// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ssbc/go-muxrpc/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.mindeco.de/http/auth"
|
|
|
|
refs "github.com/ssbc/go-ssb-refs"
|
|
"github.com/ssbc/go-ssb-room/v2/internal/maybemod/keys"
|
|
"github.com/ssbc/go-ssb-room/v2/internal/signinwithssb"
|
|
"github.com/ssbc/go-ssb-room/v2/roomdb"
|
|
weberrors "github.com/ssbc/go-ssb-room/v2/web/errors"
|
|
"github.com/ssbc/go-ssb-room/v2/web/router"
|
|
"github.com/ssbc/go-ssb-room/v2/web/webassert"
|
|
)
|
|
|
|
func TestRestricted(t *testing.T) {
|
|
ts := setup(t)
|
|
a := assert.New(t)
|
|
|
|
testURLs := []string{
|
|
"/admin/",
|
|
"/admin/anything/",
|
|
}
|
|
|
|
for _, tstr := range testURLs {
|
|
turl, err := url.Parse(tstr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
html, resp := ts.Client.GetHTML(turl)
|
|
a.Equal(http.StatusForbidden, resp.Code, "wrong HTTP status code for %q", turl)
|
|
found := html.Find("h1").Text()
|
|
a.Equal("Error #403 - Forbidden", found, "wrong error message code for %q", turl)
|
|
}
|
|
}
|
|
|
|
func TestLoginForm(t *testing.T) {
|
|
ts := setup(t)
|
|
a := assert.New(t)
|
|
|
|
url := ts.URLTo(router.AuthFallbackLogin)
|
|
|
|
html, resp := ts.Client.GetHTML(url)
|
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
|
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AuthTitle"},
|
|
{"#welcome", "AuthFallbackWelcome"},
|
|
})
|
|
}
|
|
|
|
func TestFallbackAuthWrongPassword(t *testing.T) {
|
|
ts := setup(t)
|
|
a := assert.New(t)
|
|
|
|
signInFormURL := ts.URLTo(router.AuthFallbackLogin)
|
|
|
|
doc, resp := ts.Client.GetHTML(signInFormURL)
|
|
a.Equal(http.StatusOK, resp.Code)
|
|
|
|
csrfCookie := resp.Result().Cookies()
|
|
a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation")
|
|
|
|
passwordForm := doc.Find("#password-fallback")
|
|
webassert.CSRFTokenPresent(t, passwordForm)
|
|
|
|
csrfTokenElem := passwordForm.Find("input[type=hidden]")
|
|
a.Equal(1, csrfTokenElem.Length())
|
|
|
|
csrfName, has := csrfTokenElem.Attr("name")
|
|
a.True(has, "should have a name attribute")
|
|
|
|
csrfValue, has := csrfTokenElem.Attr("value")
|
|
a.True(has, "should have value attribute")
|
|
|
|
loginVals := url.Values{
|
|
"user": []string{"test"},
|
|
"pass": []string{"wong"},
|
|
|
|
csrfName: []string{csrfValue},
|
|
}
|
|
ts.AuthFallbackDB.CheckReturns(nil, weberrors.ErrRedirect{
|
|
Path: "/fallback/login",
|
|
Reason: auth.ErrBadLogin,
|
|
})
|
|
|
|
signInURL := ts.URLTo(router.AuthFallbackFinalize)
|
|
|
|
// important for CSRF
|
|
var refererHeader = make(http.Header)
|
|
refererHeader.Set("Referer", "https://localhost")
|
|
ts.Client.SetHeaders(refererHeader)
|
|
|
|
resp = ts.Client.PostForm(signInURL, loginVals)
|
|
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
|
a.Equal(1, ts.AuthFallbackDB.CheckCallCount())
|
|
|
|
// check flash error for bad login
|
|
res := resp.Result()
|
|
a.Equal(signInFormURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
|
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
|
|
|
html, resp := ts.Client.GetHTML(signInFormURL)
|
|
a.Equal(http.StatusOK, resp.Code)
|
|
|
|
flashes := html.Find("#flashes-list").Children()
|
|
a.Equal(1, flashes.Length())
|
|
a.Equal("ErrorAuthBadLogin", flashes.Text())
|
|
}
|
|
|
|
func TestFallbackAuthWorks(t *testing.T) {
|
|
ts := setup(t)
|
|
a := assert.New(t)
|
|
|
|
signInFormURL := ts.URLTo(router.AuthFallbackLogin)
|
|
|
|
doc, resp := ts.Client.GetHTML(signInFormURL)
|
|
a.Equal(http.StatusOK, resp.Code)
|
|
|
|
csrfCookie := resp.Result().Cookies()
|
|
a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation")
|
|
|
|
passwordForm := doc.Find("#password-fallback")
|
|
webassert.CSRFTokenPresent(t, passwordForm)
|
|
|
|
csrfTokenElem := passwordForm.Find("input[type=hidden]")
|
|
a.Equal(1, csrfTokenElem.Length())
|
|
|
|
csrfName, has := csrfTokenElem.Attr("name")
|
|
a.True(has, "should have a name attribute")
|
|
|
|
csrfValue, has := csrfTokenElem.Attr("value")
|
|
a.True(has, "should have value attribute")
|
|
|
|
loginVals := url.Values{
|
|
"user": []string{"test"},
|
|
"pass": []string{"test"},
|
|
|
|
csrfName: []string{csrfValue},
|
|
}
|
|
ts.AuthFallbackDB.CheckReturns(int64(23), nil)
|
|
|
|
signInURL := ts.URLTo(router.AuthFallbackFinalize)
|
|
|
|
// important for CSRF
|
|
var refererHeader = make(http.Header)
|
|
refererHeader.Set("Referer", "https://localhost")
|
|
ts.Client.SetHeaders(refererHeader)
|
|
|
|
resp = ts.Client.PostForm(signInURL, loginVals)
|
|
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
|
|
|
a.Equal(1, ts.AuthFallbackDB.CheckCallCount())
|
|
|
|
// now request the protected dashboard page
|
|
dashboardURL := ts.URLTo(router.AdminDashboard)
|
|
|
|
html, resp := ts.Client.GetHTML(dashboardURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AdminDashboardTitle"},
|
|
})
|
|
|
|
testRef, err := refs.NewFeedRefFromBytes(bytes.Repeat([]byte{0}, 32), refs.RefAlgoFeedSSB1)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
ts.RoomState.AddEndpoint(testRef, nil)
|
|
|
|
html, resp = ts.Client.GetHTML(dashboardURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AdminDashboardTitle"},
|
|
})
|
|
|
|
testRef2, err := refs.NewFeedRefFromBytes(bytes.Repeat([]byte{1}, 32), refs.RefAlgoFeedSSB1)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
ts.RoomState.AddEndpoint(testRef2, nil)
|
|
|
|
html, resp = ts.Client.GetHTML(dashboardURL)
|
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
|
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AdminDashboardTitle"},
|
|
})
|
|
}
|
|
|
|
func TestAuthWithSSBClientInitNotConnected(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the client is a member but not connected right now
|
|
ts.MembersDB.GetByFeedReturns(roomdb.Member{ID: 1234}, nil)
|
|
ts.MockedEndpoints.GetEndpointForReturns(nil, false)
|
|
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
|
|
cc := signinwithssb.GenerateChallenge()
|
|
|
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
|
"cid", client.Feed.String(),
|
|
"cc", cc,
|
|
)
|
|
r.NotNil(signInStartURL)
|
|
doc, resp := ts.Client.GetHTML(signInStartURL)
|
|
a.Equal(http.StatusForbidden, resp.Code)
|
|
|
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
|
// {"#welcome", "AuthWithSSBWelcome"},
|
|
// {"title", "AuthWithSSBTitle"},
|
|
})
|
|
}
|
|
|
|
func TestAuthWithSSBClientInitNotAllowed(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the client isnt a member
|
|
ts.MembersDB.GetByFeedReturns(roomdb.Member{}, roomdb.ErrNotFound)
|
|
ts.MockedEndpoints.GetEndpointForReturns(nil, false)
|
|
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
|
|
cc := signinwithssb.GenerateChallenge()
|
|
|
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
|
"cid", client.Feed.String(),
|
|
"cc", cc,
|
|
)
|
|
r.NotNil(signInStartURL)
|
|
|
|
doc, resp := ts.Client.GetHTML(signInStartURL)
|
|
a.Equal(http.StatusForbidden, resp.Code)
|
|
t.Log(resp.Body.String())
|
|
|
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
|
// {"#welcome", "AuthWithSSBWelcome"},
|
|
// {"title", "AuthWithSSBTitle"},
|
|
})
|
|
}
|
|
|
|
func TestAuthWithSSBClientAlternativeRoute(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the client isnt a member
|
|
ts.MembersDB.GetByFeedReturns(roomdb.Member{}, roomdb.ErrNotFound)
|
|
ts.MockedEndpoints.GetEndpointForReturns(nil, false)
|
|
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
|
|
cc := signinwithssb.GenerateChallenge()
|
|
|
|
loginURL := ts.URLTo(router.AuthLogin,
|
|
"ssb-http-auth", 1,
|
|
"cid", client.Feed.String(),
|
|
"cc", cc,
|
|
)
|
|
r.NotNil(loginURL)
|
|
|
|
t.Log(loginURL.String())
|
|
doc, resp := ts.Client.GetHTML(loginURL)
|
|
t.Log()
|
|
a.Equal(http.StatusForbidden, resp.Code)
|
|
|
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
|
// {"#welcome", "AuthWithSSBWelcome"},
|
|
// {"title", "AuthWithSSBTitle"},
|
|
})
|
|
}
|
|
|
|
func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the request to be signed later
|
|
var payload signinwithssb.ClientPayload
|
|
payload.ServerID = ts.NetworkInfo.RoomID
|
|
|
|
// the keypair for our client
|
|
testMember := roomdb.Member{ID: 1234}
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
testMember.PubKey = client.Feed
|
|
|
|
// setup the mocked database
|
|
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
|
ts.AuthWithSSB.CreateTokenReturns("abcdefgh", nil)
|
|
ts.AuthWithSSB.CheckTokenReturns(testMember.ID, nil)
|
|
ts.MembersDB.GetByIDReturns(testMember, nil)
|
|
|
|
// fill the basic infos of the request
|
|
payload.ClientID = client.Feed
|
|
|
|
// this is our fake "connected" client
|
|
var edp muxrpc.FakeEndpoint
|
|
|
|
// setup a mocked muxrpc call that asserts the arguments and returns the needed signature
|
|
edp.AsyncCalls(func(_ context.Context, ret interface{}, encoding muxrpc.RequestEncoding, method muxrpc.Method, args ...interface{}) error {
|
|
a.Equal(muxrpc.TypeString, encoding)
|
|
a.Equal("httpAuth.requestSolution", method.String())
|
|
|
|
r.Len(args, 2, "expected two args")
|
|
|
|
serverChallenge, ok := args[0].(string)
|
|
r.True(ok, "argument[0] is not a string: %T", args[0])
|
|
a.NotEqual("", serverChallenge)
|
|
// update the challenge
|
|
payload.ServerChallenge = serverChallenge
|
|
|
|
clientChallenge, ok := args[1].(string)
|
|
r.True(ok, "argument[1] is not a string: %T", args[1])
|
|
a.Equal(payload.ClientChallenge, clientChallenge)
|
|
|
|
strptr, ok := ret.(*string)
|
|
r.True(ok, "return is not a string pointer: %T", ret)
|
|
|
|
// sign the request now that we have the sc
|
|
clientSig := payload.Sign(client.Pair.Secret)
|
|
|
|
*strptr = base64.StdEncoding.EncodeToString(clientSig)
|
|
return nil
|
|
})
|
|
|
|
// setup the fake client endpoint
|
|
ts.MockedEndpoints.GetEndpointForReturns(&edp, true)
|
|
|
|
cc := signinwithssb.GenerateChallenge()
|
|
// update the challenge
|
|
payload.ClientChallenge = cc
|
|
|
|
// prepare the url
|
|
|
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
|
"cid", client.Feed.String(),
|
|
"cc", cc,
|
|
)
|
|
signInStartURL.Host = "localhost"
|
|
signInStartURL.Scheme = "https"
|
|
|
|
r.NotNil(signInStartURL)
|
|
|
|
t.Log(signInStartURL.String())
|
|
doc, resp := ts.Client.GetHTML(signInStartURL)
|
|
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
|
|
|
dashboardURL := ts.URLTo(router.AdminDashboard)
|
|
a.Equal(dashboardURL.Path, resp.Header().Get("Location"))
|
|
|
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
|
// {"#welcome", "AuthWithSSBWelcome"},
|
|
// {"title", "AuthWithSSBTitle"},
|
|
})
|
|
|
|
// analyse the endpoints call
|
|
a.Equal(1, ts.MockedEndpoints.GetEndpointForCallCount())
|
|
edpRef := ts.MockedEndpoints.GetEndpointForArgsForCall(0)
|
|
a.Equal(client.Feed.String(), edpRef.String())
|
|
|
|
// check the mock was called
|
|
a.Equal(1, edp.AsyncCallCount())
|
|
|
|
// check that we have a new cookie
|
|
sessionCookie := resp.Result().Cookies()
|
|
r.True(len(sessionCookie) > 0, "expecting one cookie!")
|
|
|
|
html, resp := ts.Client.GetHTML(dashboardURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AdminDashboardTitle"},
|
|
})
|
|
}
|
|
|
|
func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the keypair for our client
|
|
testMember := roomdb.Member{ID: 1234}
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
testMember.PubKey = client.Feed
|
|
|
|
// setup the mocked database
|
|
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
|
|
|
// prepare the url
|
|
|
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
|
"cid", client.Feed.String(),
|
|
)
|
|
r.NotNil(signInStartURL)
|
|
|
|
html, resp := ts.Client.GetHTML(signInStartURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AuthWithSSBTitle"},
|
|
{"#welcome", "AuthWithSSBWelcome"},
|
|
})
|
|
|
|
jsFile, has := html.Find("script").Attr("src")
|
|
a.True(has, "should have client code")
|
|
a.Equal("/assets/auth-withssb-uri.js", jsFile)
|
|
|
|
serverChallenge, has := html.Find("#challenge").Attr("data-sc")
|
|
a.True(has, "should have server challenge")
|
|
a.NotEqual("", serverChallenge)
|
|
|
|
ssbURI, has := html.Find("#start-auth-uri").Attr("href")
|
|
a.True(has, "should have an ssb:experimental uri")
|
|
a.True(strings.HasPrefix(ssbURI, "ssb:experimental?"), "not an ssb-uri? %s", ssbURI)
|
|
|
|
parsedURI, err := url.Parse(ssbURI)
|
|
r.NoError(err)
|
|
a.Equal("ssb", parsedURI.Scheme)
|
|
a.Equal("experimental", parsedURI.Opaque)
|
|
|
|
qry := parsedURI.Query()
|
|
a.Equal("start-http-auth", qry.Get("action"))
|
|
a.Equal(serverChallenge, qry.Get("sc"))
|
|
a.Equal(ts.NetworkInfo.RoomID.String(), qry.Get("sid"))
|
|
a.Equal(ts.NetworkInfo.MultiserverAddress(), qry.Get("multiserverAddress"))
|
|
|
|
qrCode, has := html.Find("#start-auth-qrcode").Attr("src")
|
|
a.True(has, "should have the inline image data")
|
|
a.True(strings.HasPrefix(qrCode, "data:image/png;base64,"))
|
|
|
|
// TODO: decode image data and check qr code(?)
|
|
|
|
// simulate muxrpc client
|
|
testToken := "our-test-token"
|
|
ts.AuthWithSSB.CheckTokenReturns(23, nil)
|
|
go func() {
|
|
time.Sleep(4 * time.Second)
|
|
err = ts.SignalBridge.SessionWorked(serverChallenge, testToken)
|
|
r.NoError(err)
|
|
}()
|
|
|
|
// start reading sse
|
|
sseURL := ts.URLTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
|
resp = ts.Client.GetBody(sseURL)
|
|
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
|
|
|
// check contents of sse channel
|
|
sseBody := resp.Body.String()
|
|
|
|
a.True(strings.Contains(sseBody, "data: Waiting for solution"), "ping data")
|
|
a.True(strings.Contains(sseBody, "event: ping\n"), "ping event")
|
|
|
|
wantDataToken := fmt.Sprintf("data: %s\n", testToken)
|
|
a.True(strings.Contains(sseBody, wantDataToken), "token data")
|
|
a.True(strings.Contains(sseBody, "event: success\n"), "success event")
|
|
|
|
// use the token and go to /withssb/finalize and get a cookie
|
|
// (this happens in the browser engine via login-events.js)
|
|
finalizeURL := ts.URLTo(router.AuthWithSSBFinalize, "token", testToken)
|
|
|
|
resp = ts.Client.GetBody(finalizeURL)
|
|
|
|
// now request the protected dashboard page
|
|
dashboardURL := ts.URLTo(router.AdminDashboard)
|
|
|
|
html, resp = ts.Client.GetHTML(dashboardURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
|
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
|
{"title", "AdminDashboardTitle"},
|
|
})
|
|
}
|
|
|
|
func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the keypair for our client
|
|
testMember := roomdb.Member{ID: 1234}
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
testMember.PubKey = client.Feed
|
|
|
|
// setup the mocked database
|
|
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
|
|
|
// prepare the url
|
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
|
"cid", client.Feed.String(),
|
|
)
|
|
r.NotNil(signInStartURL)
|
|
|
|
html, resp := ts.Client.GetHTML(signInStartURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
|
|
serverChallenge, has := html.Find("#challenge").Attr("data-sc")
|
|
a.True(has, "should have server challenge")
|
|
a.NotEqual("", serverChallenge)
|
|
|
|
// simulate muxrpc client
|
|
ts.AuthWithSSB.CheckTokenReturns(-1, roomdb.ErrNotFound)
|
|
go func() {
|
|
time.Sleep(4 * time.Second)
|
|
err = ts.SignalBridge.SessionFailed(serverChallenge, fmt.Errorf("wrong solution"))
|
|
r.NoError(err)
|
|
}()
|
|
|
|
// start reading sse
|
|
sseURL := ts.URLTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
|
resp = ts.Client.GetBody(sseURL)
|
|
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
|
|
|
// check contents of sse channel
|
|
sseBody := resp.Body.String()
|
|
|
|
a.True(strings.Contains(sseBody, "data: Waiting for solution"), "ping data")
|
|
a.True(strings.Contains(sseBody, "event: ping\n"), "ping event")
|
|
|
|
a.True(strings.Contains(sseBody, "data: wrong solution\n"), "reason data")
|
|
a.True(strings.Contains(sseBody, "event: failed\n"), "success event")
|
|
|
|
// use an invalid token
|
|
finalizeURL := ts.URLTo(router.AuthWithSSBFinalize, "token", "wrong")
|
|
resp = ts.Client.GetBody(finalizeURL)
|
|
a.Equal(http.StatusForbidden, resp.Result().StatusCode)
|
|
}
|
|
|
|
func TestAuthWithSSBServerOnAndroidChrome(t *testing.T) {
|
|
ts := setup(t)
|
|
a, r := assert.New(t), require.New(t)
|
|
|
|
// the keypair for our client
|
|
testMember := roomdb.Member{ID: 1234}
|
|
client, err := keys.NewKeyPair(nil)
|
|
r.NoError(err)
|
|
testMember.PubKey = client.Feed
|
|
|
|
// Mimic Android Chrome
|
|
var uaHeader = make(http.Header)
|
|
uaHeader.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MOB30H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36")
|
|
ts.Client.SetHeaders(uaHeader)
|
|
|
|
// setup the mocked database
|
|
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
|
|
|
// prepare the url
|
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
|
"cid", client.Feed.String(),
|
|
)
|
|
r.NotNil(signInStartURL)
|
|
|
|
html, resp := ts.Client.GetHTML(signInStartURL)
|
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
|
t.Log(html.Find("body").Text())
|
|
}
|
|
|
|
serverChallenge, has := html.Find("#challenge").Attr("data-sc")
|
|
a.True(has, "should have server challenge")
|
|
a.NotEqual("", serverChallenge)
|
|
|
|
ssbURI, has := html.Find("#start-auth-uri").Attr("href")
|
|
a.True(has, "should have an Android Intent URI")
|
|
a.True(strings.HasPrefix(ssbURI, "intent://experimental"), "not an Android Intent URI? %s", ssbURI)
|
|
|
|
parsedURI, err := url.Parse(ssbURI)
|
|
r.NoError(err)
|
|
a.Equal("intent", parsedURI.Scheme)
|
|
a.Equal("experimental", parsedURI.Host)
|
|
|
|
qry := parsedURI.Query()
|
|
a.Equal("start-http-auth", qry.Get("action"))
|
|
|
|
frag := parsedURI.Fragment
|
|
a.Equal("Intent;scheme=ssb;end;", frag)
|
|
}
|