mirror of
https://github.com/matrix-org/dendrite.git
synced 2024-11-22 14:21:55 -06:00
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/latest-event-updater
This commit is contained in:
commit
9e2afb5586
2
.github/codecov.yaml
vendored
2
.github/codecov.yaml
vendored
|
@ -7,7 +7,7 @@ coverage:
|
||||||
project:
|
project:
|
||||||
default:
|
default:
|
||||||
target: auto
|
target: auto
|
||||||
threshold: 0%
|
threshold: 0.1%
|
||||||
base: auto
|
base: auto
|
||||||
flags:
|
flags:
|
||||||
- unittests
|
- unittests
|
||||||
|
|
|
@ -55,7 +55,7 @@ func DirectoryRoom(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
JSON: spec.InvalidParam("Room alias must be in the form '#localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ func SetLocalAlias(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
JSON: spec.InvalidParam("Room alias must be in the form '#localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -630,6 +630,7 @@ func handleGuestRegistration(
|
||||||
AccessToken: token,
|
AccessToken: token,
|
||||||
IPAddr: req.RemoteAddr,
|
IPAddr: req.RemoteAddr,
|
||||||
UserAgent: req.UserAgent(),
|
UserAgent: req.UserAgent(),
|
||||||
|
FromRegistration: true,
|
||||||
}, &devRes)
|
}, &devRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -919,6 +920,7 @@ func completeRegistration(
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
IPAddr: ipAddr,
|
IPAddr: ipAddr,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
|
FromRegistration: true,
|
||||||
}, &devRes)
|
}, &devRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
@ -5,11 +5,14 @@ import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/routing"
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
@ -17,7 +20,10 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi"
|
"github.com/matrix-org/dendrite/federationapi"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
|
@ -362,3 +368,126 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotaryServer(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
httpBody string
|
||||||
|
pubKeyRequest *gomatrixserverlib.PublicKeyNotaryLookupRequest
|
||||||
|
validateFunc func(t *testing.T, response util.JSONResponse)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty httpBody",
|
||||||
|
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
||||||
|
nk, ok := resp.JSON.(spec.MatrixError)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, spec.ErrorBadJSON, nk.ErrCode)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid but empty httpBody",
|
||||||
|
httpBody: "{}",
|
||||||
|
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
||||||
|
want := util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: routing.NotaryKeysResponse{ServerKeys: []json.RawMessage{}},
|
||||||
|
}
|
||||||
|
assert.Equal(t, want, resp)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request all keys using an empty criteria",
|
||||||
|
httpBody: `{"server_keys":{"servera":{}}}`,
|
||||||
|
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "servera", gjson.GetBytes(nk.ServerKeys[0], "server_name").Str)
|
||||||
|
assert.True(t, gjson.GetBytes(nk.ServerKeys[0], "verify_keys.ed25519:someID").Exists())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request all keys using null as the criteria",
|
||||||
|
httpBody: `{"server_keys":{"servera":null}}`,
|
||||||
|
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "servera", gjson.GetBytes(nk.ServerKeys[0], "server_name").Str)
|
||||||
|
assert.True(t, gjson.GetBytes(nk.ServerKeys[0], "verify_keys.ed25519:someID").Exists())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request specific key",
|
||||||
|
httpBody: `{"server_keys":{"servera":{"ed25519:someID":{}}}}`,
|
||||||
|
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "servera", gjson.GetBytes(nk.ServerKeys[0], "server_name").Str)
|
||||||
|
assert.True(t, gjson.GetBytes(nk.ServerKeys[0], "verify_keys.ed25519:someID").Exists())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request multiple servers",
|
||||||
|
httpBody: `{"server_keys":{"servera":{"ed25519:someID":{}},"serverb":{"ed25519:someID":{}}}}`,
|
||||||
|
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
||||||
|
assert.True(t, ok)
|
||||||
|
wantServers := map[string]struct{}{
|
||||||
|
"servera": {},
|
||||||
|
"serverb": {},
|
||||||
|
}
|
||||||
|
for _, js := range nk.ServerKeys {
|
||||||
|
serverName := gjson.GetBytes(js, "server_name").Str
|
||||||
|
_, ok = wantServers[serverName]
|
||||||
|
assert.True(t, ok, "unexpected servername: %s", serverName)
|
||||||
|
delete(wantServers, serverName)
|
||||||
|
assert.True(t, gjson.GetBytes(js, "verify_keys.ed25519:someID").Exists())
|
||||||
|
}
|
||||||
|
if len(wantServers) > 0 {
|
||||||
|
t.Fatalf("expected response to also contain: %#v", wantServers)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
|
defer close()
|
||||||
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
|
natsInstance := jetstream.NATSInstance{}
|
||||||
|
fc := &fedClient{
|
||||||
|
keys: map[spec.ServerName]struct {
|
||||||
|
key ed25519.PrivateKey
|
||||||
|
keyID gomatrixserverlib.KeyID
|
||||||
|
}{
|
||||||
|
"servera": {
|
||||||
|
key: test.PrivateKeyA,
|
||||||
|
keyID: "ed25519:someID",
|
||||||
|
},
|
||||||
|
"serverb": {
|
||||||
|
key: test.PrivateKeyB,
|
||||||
|
keyID: "ed25519:someID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fedAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, fc, nil, caches, nil, true)
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.httpBody))
|
||||||
|
req.Host = string(cfg.Global.ServerName)
|
||||||
|
|
||||||
|
resp := routing.NotaryKeys(req, &cfg.FederationAPI, fedAPI, tc.pubKeyRequest)
|
||||||
|
// assert that we received the expected response
|
||||||
|
tc.validateFunc(t, resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,15 @@ func (a *FederationInternalAPI) fetchServerKeysFromCache(
|
||||||
ctx context.Context, req *api.QueryServerKeysRequest,
|
ctx context.Context, req *api.QueryServerKeysRequest,
|
||||||
) ([]gomatrixserverlib.ServerKeys, error) {
|
) ([]gomatrixserverlib.ServerKeys, error) {
|
||||||
var results []gomatrixserverlib.ServerKeys
|
var results []gomatrixserverlib.ServerKeys
|
||||||
|
|
||||||
|
// We got a request for _all_ server keys, return them.
|
||||||
|
if len(req.KeyIDToCriteria) == 0 {
|
||||||
|
serverKeysResponses, _ := a.db.GetNotaryKeys(ctx, req.ServerName, []gomatrixserverlib.KeyID{})
|
||||||
|
if len(serverKeysResponses) == 0 {
|
||||||
|
return nil, fmt.Errorf("failed to find server key response for server %s", req.ServerName)
|
||||||
|
}
|
||||||
|
return serverKeysResponses, nil
|
||||||
|
}
|
||||||
for keyID, criteria := range req.KeyIDToCriteria {
|
for keyID, criteria := range req.KeyIDToCriteria {
|
||||||
serverKeysResponses, _ := a.db.GetNotaryKeys(ctx, req.ServerName, []gomatrixserverlib.KeyID{keyID})
|
serverKeysResponses, _ := a.db.GetNotaryKeys(ctx, req.ServerName, []gomatrixserverlib.KeyID{keyID})
|
||||||
if len(serverKeysResponses) == 0 {
|
if len(serverKeysResponses) == 0 {
|
||||||
|
|
|
@ -95,6 +95,12 @@ func Backfill(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce a limit of 100 events, as not to hit the DB to hard.
|
||||||
|
// Synapse has a hard limit of 100 events as well.
|
||||||
|
if req.Limit > 100 {
|
||||||
|
req.Limit = 100
|
||||||
|
}
|
||||||
|
|
||||||
// Query the Roomserver.
|
// Query the Roomserver.
|
||||||
if err = rsAPI.PerformBackfill(httpReq.Context(), &req, &res); err != nil {
|
if err = rsAPI.PerformBackfill(httpReq.Context(), &req, &res); err != nil {
|
||||||
util.GetLogger(httpReq.Context()).WithError(err).Error("query.PerformBackfill failed")
|
util.GetLogger(httpReq.Context()).WithError(err).Error("query.PerformBackfill failed")
|
||||||
|
|
|
@ -197,6 +197,10 @@ func localKeys(cfg *config.FederationAPI, serverName spec.ServerName) (*gomatrix
|
||||||
return &keys, err
|
return &keys, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotaryKeysResponse struct {
|
||||||
|
ServerKeys []json.RawMessage `json:"server_keys"`
|
||||||
|
}
|
||||||
|
|
||||||
func NotaryKeys(
|
func NotaryKeys(
|
||||||
httpReq *http.Request, cfg *config.FederationAPI,
|
httpReq *http.Request, cfg *config.FederationAPI,
|
||||||
fsAPI federationAPI.FederationInternalAPI,
|
fsAPI federationAPI.FederationInternalAPI,
|
||||||
|
@ -217,10 +221,9 @@ func NotaryKeys(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var response struct {
|
response := NotaryKeysResponse{
|
||||||
ServerKeys []json.RawMessage `json:"server_keys"`
|
ServerKeys: []json.RawMessage{},
|
||||||
}
|
}
|
||||||
response.ServerKeys = []json.RawMessage{}
|
|
||||||
|
|
||||||
for serverName, kidToCriteria := range req.ServerKeys {
|
for serverName, kidToCriteria := range req.ServerKeys {
|
||||||
var keyList []gomatrixserverlib.ServerKeys
|
var keyList []gomatrixserverlib.ServerKeys
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
|
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
|
||||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
|
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
|
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20231212115925-41497b7563eb
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20240109180417-3495e573f2b7
|
||||||
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7
|
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7
|
||||||
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
|
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
|
@ -115,7 +115,7 @@ require (
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.10.1 // indirect
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
|
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
|
||||||
github.com/quic-go/quic-go v0.37.4 // indirect
|
github.com/quic-go/quic-go v0.37.7 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
github.com/rs/zerolog v1.29.1 // indirect
|
github.com/rs/zerolog v1.29.1 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -208,8 +208,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91 h1:s7fexw
|
||||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo=
|
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
|
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20231212115925-41497b7563eb h1:Nn+Fr96oi7bIfdOwX5A2L6A2MZCM+lqwLe4/+3+nYj8=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20240109180417-3495e573f2b7 h1:EaUvK2ay6cxMxeshC1p6QswS9+rQFbUc2YerkRFyVXQ=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20231212115925-41497b7563eb/go.mod h1:M8m7seOroO5ePlgxA7AFZymnG90Cnh94rYQyngSrZkk=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20240109180417-3495e573f2b7/go.mod h1:HZGsVJ3bUE+DkZtufkH9H0mlsvbhEGK5CpX0Zlavylg=
|
||||||
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 h1:6t8kJr8i1/1I5nNttw6nn1ryQJgzVlBmSGgPiiaTdw4=
|
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 h1:6t8kJr8i1/1I5nNttw6nn1ryQJgzVlBmSGgPiiaTdw4=
|
||||||
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7/go.mod h1:ReWMS/LoVnOiRAdq9sNUC2NZnd1mZkMNB52QhpTRWjg=
|
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7/go.mod h1:ReWMS/LoVnOiRAdq9sNUC2NZnd1mZkMNB52QhpTRWjg=
|
||||||
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y=
|
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y=
|
||||||
|
@ -286,8 +286,8 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi
|
||||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
|
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
|
github.com/quic-go/quic-go v0.37.7 h1:AgKsQLZ1+YCwZd2GYhBUsJDYZwEkA5gENtAjb+MxONU=
|
||||||
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
|
github.com/quic-go/quic-go v0.37.7/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
|
|
@ -123,6 +123,7 @@ func makeDownloadAPI(
|
||||||
|
|
||||||
// Set internal headers returned regardless of the outcome of the request
|
// Set internal headers returned regardless of the outcome of the request
|
||||||
util.SetCORSHeaders(w)
|
util.SetCORSHeaders(w)
|
||||||
|
w.Header().Set("Cross-Origin-Resource-Policy", "cross-origin")
|
||||||
// Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors
|
// Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PerformCreateRoomRequest struct {
|
type PerformCreateRoomRequest struct {
|
||||||
|
@ -91,14 +90,44 @@ type PerformBackfillRequest struct {
|
||||||
VirtualHost spec.ServerName `json:"virtual_host"`
|
VirtualHost spec.ServerName `json:"virtual_host"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrevEventIDs returns the prev_event IDs of all backwards extremities, de-duplicated in a lexicographically sorted order.
|
// limitPrevEventIDs is the maximum of eventIDs we
|
||||||
|
// return when calling PrevEventIDs.
|
||||||
|
const limitPrevEventIDs = 100
|
||||||
|
|
||||||
|
// PrevEventIDs returns the prev_event IDs of either 100 backwards extremities or
|
||||||
|
// len(r.BackwardsExtremities). Limited to 100, due to Synapse/Dendrite stopping after reaching
|
||||||
|
// this limit. (which sounds sane)
|
||||||
func (r *PerformBackfillRequest) PrevEventIDs() []string {
|
func (r *PerformBackfillRequest) PrevEventIDs() []string {
|
||||||
var prevEventIDs []string
|
var uniqueIDs map[string]struct{}
|
||||||
for _, pes := range r.BackwardsExtremities {
|
|
||||||
prevEventIDs = append(prevEventIDs, pes...)
|
// Create a unique eventID map of either 100 or len(r.BackwardsExtremities).
|
||||||
|
// 100 since Synapse/Dendrite stops after reaching 100 events.
|
||||||
|
if len(r.BackwardsExtremities) > limitPrevEventIDs {
|
||||||
|
uniqueIDs = make(map[string]struct{}, limitPrevEventIDs)
|
||||||
|
} else {
|
||||||
|
uniqueIDs = make(map[string]struct{}, len(r.BackwardsExtremities))
|
||||||
}
|
}
|
||||||
prevEventIDs = util.UniqueStrings(prevEventIDs)
|
|
||||||
return prevEventIDs
|
outerLoop:
|
||||||
|
for _, pes := range r.BackwardsExtremities {
|
||||||
|
for _, evID := range pes {
|
||||||
|
uniqueIDs[evID] = struct{}{}
|
||||||
|
// We found enough unique eventIDs.
|
||||||
|
if len(uniqueIDs) >= limitPrevEventIDs {
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map -> []string
|
||||||
|
result := make([]string, len(uniqueIDs))
|
||||||
|
i := 0
|
||||||
|
for evID := range uniqueIDs {
|
||||||
|
result[i] = evID
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformBackfillResponse is a response to PerformBackfill.
|
// PerformBackfillResponse is a response to PerformBackfill.
|
||||||
|
|
81
roomserver/api/perform_test.go
Normal file
81
roomserver/api/perform_test.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkPrevEventIDs(b *testing.B) {
|
||||||
|
for _, x := range []int64{1, 10, 100, 500, 1000, 2000} {
|
||||||
|
benchPrevEventIDs(b, int(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchPrevEventIDs(b *testing.B, count int) {
|
||||||
|
bwExtrems := generateBackwardsExtremities(b, count)
|
||||||
|
backfiller := PerformBackfillRequest{
|
||||||
|
BackwardsExtremities: bwExtrems,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run(fmt.Sprintf("Original%d", count), func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
prevIDs := backfiller.PrevEventIDs()
|
||||||
|
_ = prevIDs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testLike interface {
|
||||||
|
Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomIDCharsCount = 10
|
||||||
|
|
||||||
|
func generateBackwardsExtremities(t testLike, count int) map[string][]string {
|
||||||
|
t.Helper()
|
||||||
|
result := make(map[string][]string, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
eventID := randomEventId(int64(i))
|
||||||
|
result[eventID] = []string{
|
||||||
|
randomEventId(int64(i + 1)),
|
||||||
|
randomEventId(int64(i + 2)),
|
||||||
|
randomEventId(int64(i + 3)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const alphanumerics = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
|
// randomEventId generates a pseudo-random string of length n.
|
||||||
|
func randomEventId(src int64) string {
|
||||||
|
randSrc := rand.NewSource(src)
|
||||||
|
b := make([]byte, randomIDCharsCount)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = alphanumerics[randSrc.Int63()%int64(len(alphanumerics))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrevEventIDs(t *testing.T) {
|
||||||
|
// generate 10 backwards extremities
|
||||||
|
bwExtrems := generateBackwardsExtremities(t, 10)
|
||||||
|
backfiller := PerformBackfillRequest{
|
||||||
|
BackwardsExtremities: bwExtrems,
|
||||||
|
}
|
||||||
|
|
||||||
|
prevIDs := backfiller.PrevEventIDs()
|
||||||
|
// Given how "generateBackwardsExtremities" works, this
|
||||||
|
// generates 12 unique event IDs
|
||||||
|
assert.Equal(t, 12, len(prevIDs))
|
||||||
|
|
||||||
|
// generate 200 backwards extremities
|
||||||
|
backfiller.BackwardsExtremities = generateBackwardsExtremities(t, 200)
|
||||||
|
prevIDs = backfiller.PrevEventIDs()
|
||||||
|
// PrevEventIDs returns at max 100 event IDs
|
||||||
|
assert.Equal(t, 100, len(prevIDs))
|
||||||
|
}
|
|
@ -889,10 +889,10 @@ func (d *Database) assignRoomNID(
|
||||||
}
|
}
|
||||||
// Check if we already have a numeric ID in the database.
|
// Check if we already have a numeric ID in the database.
|
||||||
roomNID, err := d.RoomsTable.SelectRoomNID(ctx, txn, roomID)
|
roomNID, err := d.RoomsTable.SelectRoomNID(ctx, txn, roomID)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// We don't have a numeric ID so insert one into the database.
|
// We don't have a numeric ID so insert one into the database.
|
||||||
roomNID, err = d.RoomsTable.InsertRoomNID(ctx, txn, roomID, roomVersion)
|
roomNID, err = d.RoomsTable.InsertRoomNID(ctx, txn, roomID, roomVersion)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// We raced with another insert so run the select again.
|
// We raced with another insert so run the select again.
|
||||||
roomNID, err = d.RoomsTable.SelectRoomNID(ctx, txn, roomID)
|
roomNID, err = d.RoomsTable.SelectRoomNID(ctx, txn, roomID)
|
||||||
}
|
}
|
||||||
|
@ -914,10 +914,10 @@ func (d *Database) assignEventTypeNID(
|
||||||
}
|
}
|
||||||
// Check if we already have a numeric ID in the database.
|
// Check if we already have a numeric ID in the database.
|
||||||
eventTypeNID, err := d.EventTypesTable.SelectEventTypeNID(ctx, txn, eventType)
|
eventTypeNID, err := d.EventTypesTable.SelectEventTypeNID(ctx, txn, eventType)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// We don't have a numeric ID so insert one into the database.
|
// We don't have a numeric ID so insert one into the database.
|
||||||
eventTypeNID, err = d.EventTypesTable.InsertEventTypeNID(ctx, txn, eventType)
|
eventTypeNID, err = d.EventTypesTable.InsertEventTypeNID(ctx, txn, eventType)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// We raced with another insert so run the select again.
|
// We raced with another insert so run the select again.
|
||||||
eventTypeNID, err = d.EventTypesTable.SelectEventTypeNID(ctx, txn, eventType)
|
eventTypeNID, err = d.EventTypesTable.SelectEventTypeNID(ctx, txn, eventType)
|
||||||
}
|
}
|
||||||
|
@ -938,16 +938,19 @@ func (d *EventDatabase) assignStateKeyNID(
|
||||||
}
|
}
|
||||||
// Check if we already have a numeric ID in the database.
|
// Check if we already have a numeric ID in the database.
|
||||||
eventStateKeyNID, err := d.EventStateKeysTable.SelectEventStateKeyNID(ctx, txn, eventStateKey)
|
eventStateKeyNID, err := d.EventStateKeysTable.SelectEventStateKeyNID(ctx, txn, eventStateKey)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// We don't have a numeric ID so insert one into the database.
|
// We don't have a numeric ID so insert one into the database.
|
||||||
eventStateKeyNID, err = d.EventStateKeysTable.InsertEventStateKeyNID(ctx, txn, eventStateKey)
|
eventStateKeyNID, err = d.EventStateKeysTable.InsertEventStateKeyNID(ctx, txn, eventStateKey)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// We raced with another insert so run the select again.
|
// We raced with another insert so run the select again.
|
||||||
eventStateKeyNID, err = d.EventStateKeysTable.SelectEventStateKeyNID(ctx, txn, eventStateKey)
|
eventStateKeyNID, err = d.EventStateKeysTable.SelectEventStateKeyNID(ctx, txn, eventStateKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
d.Cache.StoreEventStateKey(eventStateKeyNID, eventStateKey)
|
d.Cache.StoreEventStateKey(eventStateKeyNID, eventStateKey)
|
||||||
return eventStateKeyNID, err
|
return eventStateKeyNID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractRoomVersionFromCreateEvent(event gomatrixserverlib.PDU) (
|
func extractRoomVersionFromCreateEvent(event gomatrixserverlib.PDU) (
|
||||||
|
|
|
@ -50,6 +50,9 @@ import (
|
||||||
//go:embed static/*.gotmpl
|
//go:embed static/*.gotmpl
|
||||||
var staticContent embed.FS
|
var staticContent embed.FS
|
||||||
|
|
||||||
|
//go:embed static/client/login
|
||||||
|
var loginFallback embed.FS
|
||||||
|
|
||||||
const HTTPServerTimeout = time.Minute * 5
|
const HTTPServerTimeout = time.Minute * 5
|
||||||
|
|
||||||
// CreateClient creates a new client (normally used for media fetch requests).
|
// CreateClient creates a new client (normally used for media fetch requests).
|
||||||
|
@ -158,6 +161,14 @@ func SetupAndServeHTTP(
|
||||||
_, _ = w.Write(landingPage.Bytes())
|
_, _ = w.Write(landingPage.Bytes())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// We only need the files beneath the static/client/login folder.
|
||||||
|
sub, err := fs.Sub(loginFallback, "static/client/login")
|
||||||
|
if err != nil {
|
||||||
|
logrus.Panicf("unable to read embedded files, this should never happen: %s", err)
|
||||||
|
}
|
||||||
|
// Serve a static page for login fallback
|
||||||
|
routers.Static.PathPrefix("/client/login/").Handler(http.StripPrefix("/_matrix/static/client/login/", http.FileServer(http.FS(sub))))
|
||||||
|
|
||||||
var clientHandler http.Handler
|
var clientHandler http.Handler
|
||||||
clientHandler = routers.Client
|
clientHandler = routers.Client
|
||||||
if cfg.Global.Sentry.Enabled {
|
if cfg.Global.Sentry.Enabled {
|
||||||
|
|
47
setup/base/static/client/login/index.html
Normal file
47
setup/base/static/client/login/index.html
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title> Login </title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="js/jquery-3.4.1.min.js"></script>
|
||||||
|
<script src="js/login.js"></script>
|
||||||
|
</head>
|
||||||
|
<body onload="matrixLogin.onLoad()">
|
||||||
|
<div id="container">
|
||||||
|
<h1 id="title"></h1>
|
||||||
|
|
||||||
|
<span id="feedback"></span>
|
||||||
|
|
||||||
|
<div id="loading">
|
||||||
|
<img src="spinner.gif" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sso_flow" class="login_flow" style="display: none;">
|
||||||
|
Single-sign on:
|
||||||
|
<form id="sso_form" action="/_matrix/client/v3/login/sso/redirect" method="get">
|
||||||
|
<input id="sso_redirect_url" type="hidden" name="redirectUrl" value=""/>
|
||||||
|
<input type="submit" value="Log in"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="password_flow" class="login_flow" style="display: none;">
|
||||||
|
Password Authentication:
|
||||||
|
<form onsubmit="matrixLogin.passwordLogin(); return false;">
|
||||||
|
<input id="user_id" size="32" type="text" placeholder="Matrix ID (e.g. bob)" autocapitalize="off" autocorrect="off" />
|
||||||
|
<br/>
|
||||||
|
<input id="password" size="32" type="password" placeholder="Password"/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input type="submit" value="Log in"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="no_login_types" type="button" class="login_flow" style="display: none;">
|
||||||
|
Log in currently unavailable.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
setup/base/static/client/login/js/jquery-3.4.1.min.js
vendored
Normal file
2
setup/base/static/client/login/js/jquery-3.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
291
setup/base/static/client/login/js/login.js
Normal file
291
setup/base/static/client/login/js/login.js
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
window.matrixLogin = {
|
||||||
|
endpoint: location.origin + "/_matrix/client/v3/login",
|
||||||
|
serverAcceptsPassword: false,
|
||||||
|
serverAcceptsSso: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Titles get updated through the process to give users feedback.
|
||||||
|
const TITLE_PRE_AUTH = "Log in with one of the following methods";
|
||||||
|
const TITLE_POST_AUTH = "Logging in...";
|
||||||
|
|
||||||
|
// The cookie used to store the original query parameters when using SSO.
|
||||||
|
const COOKIE_KEY = "dendrite_login_fallback_qs";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Submit a login request.
|
||||||
|
*
|
||||||
|
* type: The login type as a string (e.g. "m.login.foo").
|
||||||
|
* data: An object of data specific to the login type.
|
||||||
|
* extra: (Optional) An object to search for extra information to send with the
|
||||||
|
* login request, e.g. device_id.
|
||||||
|
* callback: (Optional) Function to call on successful login.
|
||||||
|
*/
|
||||||
|
function submitLogin(type, data, extra, callback) {
|
||||||
|
console.log("Logging in with " + type);
|
||||||
|
setTitle(TITLE_POST_AUTH);
|
||||||
|
|
||||||
|
// Add the login type.
|
||||||
|
data.type = type;
|
||||||
|
|
||||||
|
// Add the device information, if it was provided.
|
||||||
|
if (extra.device_id) {
|
||||||
|
data.device_id = extra.device_id;
|
||||||
|
}
|
||||||
|
if (extra.initial_device_display_name) {
|
||||||
|
data.initial_device_display_name = extra.initial_device_display_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post(matrixLogin.endpoint, JSON.stringify(data), function(response) {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
matrixLogin.onLogin(response);
|
||||||
|
}).fail(errorFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display an error to the user and show the login form again.
|
||||||
|
*/
|
||||||
|
function errorFunc(err) {
|
||||||
|
// We want to show the error to the user rather than redirecting immediately to the
|
||||||
|
// SSO portal (if SSO is the only login option), so we inhibit the redirect.
|
||||||
|
showLogin(true);
|
||||||
|
|
||||||
|
if (err.responseJSON && err.responseJSON.error) {
|
||||||
|
setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setFeedbackString("Request failed: " + err.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display an error to the user.
|
||||||
|
*/
|
||||||
|
function setFeedbackString(text) {
|
||||||
|
$("#feedback").text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (Maybe) Show the login forms.
|
||||||
|
*
|
||||||
|
* This actually does a few unrelated functions:
|
||||||
|
*
|
||||||
|
* * Configures the SSO redirect URL to come back to this page.
|
||||||
|
* * Configures and shows the SSO form, if the server supports SSO.
|
||||||
|
* * Otherwise, shows the password form.
|
||||||
|
*/
|
||||||
|
function showLogin(inhibitRedirect) {
|
||||||
|
setTitle(TITLE_PRE_AUTH);
|
||||||
|
|
||||||
|
// If inhibitRedirect is false, and SSO is the only supported login method,
|
||||||
|
// we can redirect straight to the SSO page.
|
||||||
|
if (matrixLogin.serverAcceptsSso) {
|
||||||
|
// Set the redirect to come back to this page, a login token will get
|
||||||
|
// added as a query parameter and handled after the redirect.
|
||||||
|
$("#sso_redirect_url").val(window.location.origin + window.location.pathname);
|
||||||
|
|
||||||
|
// Before submitting SSO, set the current query parameters into a cookie
|
||||||
|
// for retrieval later.
|
||||||
|
var qs = parseQsFromUrl();
|
||||||
|
setCookie(COOKIE_KEY, JSON.stringify(qs));
|
||||||
|
|
||||||
|
// If password is not supported and redirects are allowed, then submit
|
||||||
|
// the form (redirecting to the SSO provider).
|
||||||
|
if (!inhibitRedirect && !matrixLogin.serverAcceptsPassword) {
|
||||||
|
$("#sso_form").submit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, show the SSO form
|
||||||
|
$("#sso_flow").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrixLogin.serverAcceptsPassword) {
|
||||||
|
$("#password_flow").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither password or SSO are supported, show an error to the user.
|
||||||
|
if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) {
|
||||||
|
$("#no_login_types").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#loading").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hides the forms and shows a loading throbber.
|
||||||
|
*/
|
||||||
|
function showSpinner() {
|
||||||
|
$("#password_flow").hide();
|
||||||
|
$("#sso_flow").hide();
|
||||||
|
$("#no_login_types").hide();
|
||||||
|
$("#loading").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper to show the page's main title.
|
||||||
|
*/
|
||||||
|
function setTitle(title) {
|
||||||
|
$("#title").text(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query the login endpoint for the homeserver's supported flows.
|
||||||
|
*
|
||||||
|
* This populates matrixLogin.serverAccepts* variables.
|
||||||
|
*/
|
||||||
|
function fetchLoginFlows(cb) {
|
||||||
|
$.get(matrixLogin.endpoint, function(response) {
|
||||||
|
for (var i = 0; i < response.flows.length; i++) {
|
||||||
|
var flow = response.flows[i];
|
||||||
|
if ("m.login.sso" === flow.type) {
|
||||||
|
matrixLogin.serverAcceptsSso = true;
|
||||||
|
console.log("Server accepts SSO");
|
||||||
|
}
|
||||||
|
if ("m.login.password" === flow.type) {
|
||||||
|
matrixLogin.serverAcceptsPassword = true;
|
||||||
|
console.log("Server accepts password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb();
|
||||||
|
}).fail(errorFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called on load to fetch login flows and attempt SSO login (if a token is available).
|
||||||
|
*/
|
||||||
|
matrixLogin.onLoad = function() {
|
||||||
|
fetchLoginFlows(function() {
|
||||||
|
// (Maybe) attempt logging in via SSO if a token is available.
|
||||||
|
if (!tryTokenLogin()) {
|
||||||
|
showLogin(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Submit simple user & password login.
|
||||||
|
*/
|
||||||
|
matrixLogin.passwordLogin = function() {
|
||||||
|
var user = $("#user_id").val();
|
||||||
|
var pwd = $("#password").val();
|
||||||
|
|
||||||
|
setFeedbackString("");
|
||||||
|
|
||||||
|
showSpinner();
|
||||||
|
submitLogin(
|
||||||
|
"m.login.password",
|
||||||
|
{user: user, password: pwd},
|
||||||
|
parseQsFromUrl());
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The onLogin function gets called after a successful login.
|
||||||
|
*
|
||||||
|
* It is expected that implementations override this to be notified when the
|
||||||
|
* login is complete. The response to the login call is provided as the single
|
||||||
|
* parameter.
|
||||||
|
*/
|
||||||
|
matrixLogin.onLogin = function(response) {
|
||||||
|
// clobber this function
|
||||||
|
console.warn("onLogin - This function should be replaced to proceed.");
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process the query parameters from the current URL into an object.
|
||||||
|
*/
|
||||||
|
function parseQsFromUrl() {
|
||||||
|
var pos = window.location.href.indexOf("?");
|
||||||
|
if (pos == -1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
var query = window.location.href.substr(pos + 1);
|
||||||
|
|
||||||
|
var result = {};
|
||||||
|
query.split("&").forEach(function(part) {
|
||||||
|
var item = part.split("=");
|
||||||
|
var key = item[0];
|
||||||
|
var val = item[1];
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
val = decodeURIComponent(val);
|
||||||
|
}
|
||||||
|
result[key] = val;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process the cookies and return an object.
|
||||||
|
*/
|
||||||
|
function parseCookies() {
|
||||||
|
var allCookies = document.cookie;
|
||||||
|
var result = {};
|
||||||
|
allCookies.split(";").forEach(function(part) {
|
||||||
|
var item = part.split("=");
|
||||||
|
// Cookies might have arbitrary whitespace between them.
|
||||||
|
var key = item[0].trim();
|
||||||
|
// You can end up with a broken cookie that doesn't have an equals sign
|
||||||
|
// in it. Set to an empty value.
|
||||||
|
var val = (item[1] || "").trim();
|
||||||
|
// Values might be URI encoded.
|
||||||
|
if (val) {
|
||||||
|
val = decodeURIComponent(val);
|
||||||
|
}
|
||||||
|
result[key] = val;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set a cookie that is valid for 1 hour.
|
||||||
|
*/
|
||||||
|
function setCookie(key, value) {
|
||||||
|
// The maximum age is set in seconds.
|
||||||
|
var maxAge = 60 * 60;
|
||||||
|
// Set the cookie, this defaults to the current domain and path.
|
||||||
|
document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes a cookie by key.
|
||||||
|
*/
|
||||||
|
function deleteCookie(key) {
|
||||||
|
// Delete a cookie by setting the expiration to 0. (Note that the value
|
||||||
|
// doesn't matter.)
|
||||||
|
document.cookie = key + "=deleted;expires=0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Submits the login token if one is found in the query parameters. Returns a
|
||||||
|
* boolean of whether the login token was found or not.
|
||||||
|
*/
|
||||||
|
function tryTokenLogin() {
|
||||||
|
// Check if the login token is in the query parameters.
|
||||||
|
var qs = parseQsFromUrl();
|
||||||
|
|
||||||
|
var loginToken = qs.loginToken;
|
||||||
|
if (!loginToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the original query parameters (from before the SSO redirect).
|
||||||
|
// They are stored as JSON in a cookie.
|
||||||
|
var cookies = parseCookies();
|
||||||
|
var originalQueryParams = JSON.parse(cookies[COOKIE_KEY] || "{}")
|
||||||
|
|
||||||
|
// If the login is successful, delete the cookie.
|
||||||
|
function callback() {
|
||||||
|
deleteCookie(COOKIE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLogin(
|
||||||
|
"m.login.token",
|
||||||
|
{token: loginToken},
|
||||||
|
originalQueryParams,
|
||||||
|
callback);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
BIN
setup/base/static/client/login/spinner.gif
Normal file
BIN
setup/base/static/client/login/spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
79
setup/base/static/client/login/style.css
Normal file
79
setup/base/static/client/login/style.css
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 12pt;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link { color: #666; }
|
||||||
|
a:visited { color: #666; }
|
||||||
|
a:hover { color: #000; }
|
||||||
|
a:active { color: #000; }
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textbox, input[type="text"], input[type="password"] {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.radiobuttons {
|
||||||
|
text-align: left;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add some padding to the viewport.
|
||||||
|
*/
|
||||||
|
#container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Center all direct children of the main form.
|
||||||
|
*/
|
||||||
|
#container > * {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A wrapper around each login flow.
|
||||||
|
*/
|
||||||
|
.login_flow {
|
||||||
|
width: 300px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px #ccc solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used to show error content.
|
||||||
|
*/
|
||||||
|
#feedback {
|
||||||
|
/* Red text. */
|
||||||
|
color: #ff0000;
|
||||||
|
/* A little space to not overlap the box-shadow. */
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
|
@ -135,13 +135,6 @@ func OnIncomingMessagesRequest(
|
||||||
var fromStream *types.StreamingToken
|
var fromStream *types.StreamingToken
|
||||||
fromQuery := req.URL.Query().Get("from")
|
fromQuery := req.URL.Query().Get("from")
|
||||||
toQuery := req.URL.Query().Get("to")
|
toQuery := req.URL.Query().Get("to")
|
||||||
emptyFromSupplied := fromQuery == ""
|
|
||||||
if emptyFromSupplied {
|
|
||||||
// NOTSPEC: We will pretend they used the latest sync token if no ?from= was provided.
|
|
||||||
// We do this to allow clients to get messages without having to call `/sync` e.g Cerulean
|
|
||||||
currPos := srp.Notifier.CurrentPosition()
|
|
||||||
fromQuery = currPos.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Direction to return events from.
|
// Direction to return events from.
|
||||||
dir := req.URL.Query().Get("dir")
|
dir := req.URL.Query().Get("dir")
|
||||||
|
@ -155,6 +148,23 @@ func OnIncomingMessagesRequest(
|
||||||
// to have one of the two accepted values (so dir == "f" <=> !backwardOrdering).
|
// to have one of the two accepted values (so dir == "f" <=> !backwardOrdering).
|
||||||
backwardOrdering := (dir == "b")
|
backwardOrdering := (dir == "b")
|
||||||
|
|
||||||
|
emptyFromSupplied := fromQuery == ""
|
||||||
|
if emptyFromSupplied {
|
||||||
|
// If "from" isn't provided, it defaults to either the earliest stream
|
||||||
|
// position (if we're going forward) or to the latest one (if we're
|
||||||
|
// going backward).
|
||||||
|
|
||||||
|
var from types.TopologyToken
|
||||||
|
if backwardOrdering {
|
||||||
|
from = types.TopologyToken{Depth: math.MaxInt64, PDUPosition: math.MaxInt64}
|
||||||
|
} else {
|
||||||
|
// go 1 earlier than the first event so we correctly fetch the earliest event
|
||||||
|
// this is because Database.GetEventsInTopologicalRange is exclusive of the lower-bound.
|
||||||
|
from = types.TopologyToken{}
|
||||||
|
}
|
||||||
|
fromQuery = from.String()
|
||||||
|
}
|
||||||
|
|
||||||
from, err := types.NewTopologyTokenFromString(fromQuery)
|
from, err := types.NewTopologyTokenFromString(fromQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var streamToken types.StreamingToken
|
var streamToken types.StreamingToken
|
||||||
|
|
|
@ -379,6 +379,10 @@ type PerformDeviceCreationRequest struct {
|
||||||
// update for this account. Generally the only reason to do this is if the account
|
// update for this account. Generally the only reason to do this is if the account
|
||||||
// is an appservice account.
|
// is an appservice account.
|
||||||
NoDeviceListUpdate bool
|
NoDeviceListUpdate bool
|
||||||
|
|
||||||
|
// FromRegistration determines if this request comes from registering a new account
|
||||||
|
// and is in most cases false.
|
||||||
|
FromRegistration bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformDeviceCreationResponse is the response for PerformDeviceCreation
|
// PerformDeviceCreationResponse is the response for PerformDeviceCreation
|
||||||
|
@ -803,6 +807,10 @@ type PerformUploadKeysRequest struct {
|
||||||
// itself doesn't change but it's easier to pretend upload new keys and reuse the same code paths.
|
// itself doesn't change but it's easier to pretend upload new keys and reuse the same code paths.
|
||||||
// Without this flag, requests to modify device display names would delete device keys.
|
// Without this flag, requests to modify device display names would delete device keys.
|
||||||
OnlyDisplayNameUpdates bool
|
OnlyDisplayNameUpdates bool
|
||||||
|
|
||||||
|
// FromRegistration is set if this key upload comes right after creating an account
|
||||||
|
// and determines if we need to inform downstream components.
|
||||||
|
FromRegistration bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformUploadKeysResponse is the response to PerformUploadKeys
|
// PerformUploadKeysResponse is the response to PerformUploadKeys
|
||||||
|
|
|
@ -711,10 +711,16 @@ func (a *UserInternalAPI) uploadLocalDeviceKeys(ctx context.Context, req *api.Pe
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the request does _not_ come right after registering an account
|
||||||
|
// inform downstream components. However, we're fine with just creating the
|
||||||
|
// database entries above in other cases.
|
||||||
|
if !req.FromRegistration {
|
||||||
err = emitDeviceKeyChanges(a.KeyChangeProducer, existingKeys, keysToStore, req.OnlyDisplayNameUpdates)
|
err = emitDeviceKeyChanges(a.KeyChangeProducer, existingKeys, keysToStore, req.OnlyDisplayNameUpdates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).Errorf("Failed to emitDeviceKeyChanges: %s", err)
|
util.GetLogger(ctx).Errorf("Failed to emitDeviceKeyChanges: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *UserInternalAPI) uploadOneTimeKeys(ctx context.Context, req *api.PerformUploadKeysRequest, res *api.PerformUploadKeysResponse) {
|
func (a *UserInternalAPI) uploadOneTimeKeys(ctx context.Context, req *api.PerformUploadKeysRequest, res *api.PerformUploadKeysResponse) {
|
||||||
|
|
|
@ -316,7 +316,7 @@ func (a *UserInternalAPI) PerformDeviceCreation(ctx context.Context, req *api.Pe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// create empty device keys and upload them to trigger device list changes
|
// create empty device keys and upload them to trigger device list changes
|
||||||
return a.deviceListUpdate(dev.UserID, []string{dev.ID})
|
return a.deviceListUpdate(dev.UserID, []string{dev.ID}, req.FromRegistration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *UserInternalAPI) PerformDeviceDeletion(ctx context.Context, req *api.PerformDeviceDeletionRequest, res *api.PerformDeviceDeletionResponse) error {
|
func (a *UserInternalAPI) PerformDeviceDeletion(ctx context.Context, req *api.PerformDeviceDeletionRequest, res *api.PerformDeviceDeletionResponse) error {
|
||||||
|
@ -356,10 +356,10 @@ func (a *UserInternalAPI) PerformDeviceDeletion(ctx context.Context, req *api.Pe
|
||||||
return fmt.Errorf("a.KeyAPI.PerformDeleteKeys: %w", err)
|
return fmt.Errorf("a.KeyAPI.PerformDeleteKeys: %w", err)
|
||||||
}
|
}
|
||||||
// create empty device keys and upload them to delete what was once there and trigger device list changes
|
// create empty device keys and upload them to delete what was once there and trigger device list changes
|
||||||
return a.deviceListUpdate(req.UserID, deletedDeviceIDs)
|
return a.deviceListUpdate(req.UserID, deletedDeviceIDs, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *UserInternalAPI) deviceListUpdate(userID string, deviceIDs []string) error {
|
func (a *UserInternalAPI) deviceListUpdate(userID string, deviceIDs []string, fromRegistration bool) error {
|
||||||
deviceKeys := make([]api.DeviceKeys, len(deviceIDs))
|
deviceKeys := make([]api.DeviceKeys, len(deviceIDs))
|
||||||
for i, did := range deviceIDs {
|
for i, did := range deviceIDs {
|
||||||
deviceKeys[i] = api.DeviceKeys{
|
deviceKeys[i] = api.DeviceKeys{
|
||||||
|
@ -373,6 +373,7 @@ func (a *UserInternalAPI) deviceListUpdate(userID string, deviceIDs []string) er
|
||||||
if err := a.PerformUploadKeys(context.Background(), &api.PerformUploadKeysRequest{
|
if err := a.PerformUploadKeys(context.Background(), &api.PerformUploadKeysRequest{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
DeviceKeys: deviceKeys,
|
DeviceKeys: deviceKeys,
|
||||||
|
FromRegistration: fromRegistration,
|
||||||
}, &uploadRes); err != nil {
|
}, &uploadRes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue