mas: first successful attempt of login with via mas

This commit is contained in:
Roman Isaev 2024-12-29 23:53:37 +00:00
parent 150be588f5
commit 63a199cec3
No known key found for this signature in database
GPG key ID: 7BE2B6A6C89AEC7F
31 changed files with 1224 additions and 287 deletions

View file

@ -27,6 +27,7 @@ import (
"github.com/tidwall/gjson"
capi "github.com/element-hq/dendrite/clientapi/api"
"github.com/element-hq/dendrite/clientapi/auth"
"github.com/element-hq/dendrite/test"
"github.com/element-hq/dendrite/test/testrig"
"github.com/element-hq/dendrite/userapi"
@ -48,7 +49,8 @@ func TestAdminCreateToken(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
aliceAdmin: {},
bob: {},
@ -199,7 +201,8 @@ func TestAdminListRegistrationTokens(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
aliceAdmin: {},
bob: {},
@ -317,7 +320,8 @@ func TestAdminGetRegistrationToken(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
aliceAdmin: {},
bob: {},
@ -418,7 +422,8 @@ func TestAdminDeleteRegistrationToken(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
aliceAdmin: {},
bob: {},
@ -512,7 +517,8 @@ func TestAdminUpdateRegistrationToken(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
aliceAdmin: {},
bob: {},
@ -697,7 +703,8 @@ func TestAdminResetPassword(t *testing.T) {
// Needed for changing the password/login
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the userAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
// Create the users in the userapi and login
accessTokens := map[*test.User]userDevice{
@ -801,8 +808,9 @@ func TestPurgeRoom(t *testing.T) {
t.Fatalf("failed to send events: %v", err)
}
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
// Create the users in the userapi and login
accessTokens := map[*test.User]userDevice{
@ -872,8 +880,10 @@ func TestAdminEvacuateRoom(t *testing.T) {
t.Fatalf("failed to send events: %v", err)
}
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
// Create the users in the userapi and login
accessTokens := map[*test.User]userDevice{
@ -976,8 +986,10 @@ func TestAdminEvacuateUser(t *testing.T) {
t.Fatalf("failed to send events: %v", err)
}
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
// Create the users in the userapi and login
accessTokens := map[*test.User]userDevice{
@ -1059,8 +1071,10 @@ func TestAdminMarkAsStale(t *testing.T) {
rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
// Create the users in the userapi and login
accessTokens := map[*test.User]userDevice{
@ -1147,8 +1161,10 @@ func TestAdminQueryEventReports(t *testing.T) {
t.Fatalf("failed to send events: %v", err)
}
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
alice: {},
@ -1376,8 +1392,10 @@ func TestEventReportsGetDelete(t *testing.T) {
t.Fatalf("failed to send events: %v", err)
}
userVerifier := auth.DefaultUserVerifier{UserAPI: userAPI}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, &userVerifier, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{
alice: {},

View file

@ -16,8 +16,6 @@ import (
"strings"
"github.com/element-hq/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
)
// OWASP recommends at least 128 bits of entropy for tokens: https://www.owasp.org/index.php/Insufficient_Session-ID_Length
@ -37,51 +35,6 @@ type AccountDatabase interface {
GetAccountByPassword(ctx context.Context, localpart, password string) (*api.Account, error)
}
// VerifyUserFromRequest authenticates the HTTP request,
// on success returns Device of the requester.
// Finds local user or an application service user.
// Note: For an AS user, AS dummy device is returned.
// On failure returns an JSON error response which can be sent to the client.
func VerifyUserFromRequest(
req *http.Request, userAPI api.QueryAcccessTokenAPI,
) (*api.Device, *util.JSONResponse) {
// Try to find the Application Service user
token, err := ExtractAccessToken(req)
if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.MissingToken(err.Error()),
}
}
var res api.QueryAccessTokenResponse
err = userAPI.QueryAccessToken(req.Context(), &api.QueryAccessTokenRequest{
AccessToken: token,
AppServiceUserID: req.URL.Query().Get("user_id"),
}, &res)
if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryAccessToken failed")
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
}
if res.Err != "" {
if strings.HasPrefix(strings.ToLower(res.Err), "forbidden:") { // TODO: use actual error and no string comparison
return nil, &util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden(res.Err),
}
}
}
if res.Device == nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.UnknownToken("Unknown token"),
}
}
return res.Device, nil
}
// GenerateAccessToken creates a new access token. Returns an error if failed to generate
// random bytes.
func GenerateAccessToken() (string, error) {

View file

@ -11,4 +11,5 @@ const (
LoginTypeRecaptcha = "m.login.recaptcha"
LoginTypeApplicationService = "m.login.application_service"
LoginTypeToken = "m.login.token"
LoginTypeCrossSigningReset = "org.matrix.cross_signing_reset"
)

View file

@ -0,0 +1,59 @@
package auth
import (
"net/http"
"strings"
"github.com/element-hq/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
)
// DefaultUserVerifier implements UserVerifier interface
type DefaultUserVerifier struct {
UserAPI api.QueryAcccessTokenAPI
}
// VerifyUserFromRequest authenticates the HTTP request,
// on success returns Device of the requester.
// Finds local user or an application service user.
// Note: For an AS user, AS dummy device is returned.
// On failure returns an JSON error response which can be sent to the client.
func (d *DefaultUserVerifier) VerifyUserFromRequest(req *http.Request) (*api.Device, *util.JSONResponse) {
util.GetLogger(req.Context()).Debug("Default VerifyUserFromRequest")
// Try to find the Application Service user
token, err := ExtractAccessToken(req)
if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.MissingToken(err.Error()),
}
}
var res api.QueryAccessTokenResponse
err = d.UserAPI.QueryAccessToken(req.Context(), &api.QueryAccessTokenRequest{
AccessToken: token,
AppServiceUserID: req.URL.Query().Get("user_id"),
}, &res)
if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryAccessToken failed")
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
}
if res.Err != "" {
if strings.HasPrefix(strings.ToLower(res.Err), "forbidden:") { // TODO: use actual error and no string comparison
return nil, &util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden(res.Err),
}
}
}
if res.Device == nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.UnknownToken("Unknown token"),
}
}
return res.Device, nil
}

View file

@ -36,7 +36,9 @@ func AddPublicRoutes(
fsAPI federationAPI.ClientFederationAPI,
userAPI userapi.ClientUserAPI,
userDirectoryProvider userapi.QuerySearchProfilesAPI,
extRoomsProvider api.ExtraPublicRoomsProvider, enableMetrics bool,
extRoomsProvider api.ExtraPublicRoomsProvider,
userVerifier httputil.UserVerifier,
enableMetrics bool,
) {
js, natsClient := natsInstance.Prepare(processContext, &cfg.Global.JetStream)
@ -55,6 +57,7 @@ func AddPublicRoutes(
cfg, rsAPI, asAPI,
userAPI, userDirectoryProvider, federation,
syncProducer, transactionsCache, fsAPI,
extRoomsProvider, natsClient, enableMetrics,
extRoomsProvider, natsClient,
userVerifier, enableMetrics,
)
}

View file

@ -21,6 +21,7 @@ import (
"golang.org/x/exp/constraints"
clientapi "github.com/element-hq/dendrite/clientapi/api"
clienthttputil "github.com/element-hq/dendrite/clientapi/httputil"
"github.com/element-hq/dendrite/internal/httputil"
roomserverAPI "github.com/element-hq/dendrite/roomserver/api"
"github.com/element-hq/dendrite/setup/config"
@ -517,6 +518,166 @@ func AdminCheckUsernameAvailable(
}
}
func AdminHandleUserDeviceByUserID(
req *http.Request,
userAPI userapi.ClientUserAPI,
) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
userID, ok := vars["userID"]
if !ok {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.MissingParam("Expecting user ID."),
}
}
logger := util.GetLogger(req.Context())
switch req.Method {
case http.MethodPost:
local, domain, err := gomatrixserverlib.SplitID('@', userID)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam(userID),
}
}
var payload struct {
DeviceID string `json:"device_id"`
}
if resErr := clienthttputil.UnmarshalJSONRequest(req, &payload); resErr != nil {
return *resErr
}
var rs userapi.PerformDeviceCreationResponse
if err := userAPI.PerformDeviceCreation(req.Context(), &userapi.PerformDeviceCreationRequest{
Localpart: local,
ServerName: domain,
DeviceID: &payload.DeviceID,
IPAddr: "",
UserAgent: req.UserAgent(),
NoDeviceListUpdate: false,
FromRegistration: false,
}, &rs); err != nil {
logger.WithError(err).Debug("PerformDeviceCreation failed")
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
logger.WithError(err).Debug("PerformDeviceCreation succeeded")
return util.JSONResponse{
Code: http.StatusCreated,
JSON: struct{}{},
}
case http.MethodGet:
var res userapi.QueryDevicesResponse
if err := userAPI.QueryDevices(req.Context(), &userapi.QueryDevicesRequest{UserID: userID}, &res); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
jsonDevices := make([]deviceJSON, 0, len(res.Devices))
for i := range res.Devices {
d := &res.Devices[i]
jsonDevices = append(jsonDevices, deviceJSON{
DeviceID: d.ID,
DisplayName: d.DisplayName,
LastSeenIP: d.LastSeenIP,
LastSeenTS: d.LastSeenTS,
})
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct {
Devices []deviceJSON `json:"devices"`
Total int `json:"total"`
}{
Devices: jsonDevices,
Total: len(res.Devices),
},
}
default:
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
JSON: struct{}{},
}
}
}
type adminExternalID struct {
AuthProvider string `json:"auth_provider"`
ExternalID string `json:"external_id"`
}
type adminCreateOrModifyAccountRequest struct {
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url"`
// TODO: the following fields are not used here, but they are used in Synapse. Probably we should reproduce the logic of the
// endpoint fully compatible.
// Password string `json:"password"`
// LogoutDevices bool `json:"logout_devices"`
// Threepids json.RawMessage `json:"threepids"`
// ExternalIDs []adminExternalID `json:"external_ids"`
// Admin bool `json:"admin"`
// Deactivated bool `json:"deactivated"`
// Locked bool `json:"locked"`
}
func AdminCreateOrModifyAccount(req *http.Request, userAPI userapi.ClientUserAPI) util.JSONResponse {
logger := util.GetLogger(req.Context())
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
userID, ok := vars["userID"]
if !ok {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.MissingParam("Expecting user ID."),
}
}
local, domain, err := gomatrixserverlib.SplitID('@', userID)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam(userID),
}
}
var r adminCreateOrModifyAccountRequest
if resErr := clienthttputil.UnmarshalJSONRequest(req, &r); resErr != nil {
logger.Debugf("UnmarshalJSONRequest failed: %+v", *resErr)
return *resErr
}
logger.Debugf("adminCreateOrModifyAccountRequest is: %+v", r)
statusCode := http.StatusOK
{
var res userapi.PerformAccountCreationResponse
err = userAPI.PerformAccountCreation(req.Context(), &userapi.PerformAccountCreationRequest{
AccountType: userapi.AccountTypeUser,
Localpart: local,
ServerName: domain,
OnConflict: api.ConflictUpdate,
AvatarURL: r.AvatarURL,
DisplayName: r.DisplayName,
}, &res)
if err != nil {
util.GetLogger(req.Context()).WithError(err).Debugln("Failed creating account")
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
if res.AccountCreated {
statusCode = http.StatusCreated
}
}
return util.JSONResponse{
Code: statusCode,
JSON: nil,
}
}
// GetEventReports returns reported events for a given user/room.
func GetEventReports(
req *http.Request,

View file

@ -8,6 +8,8 @@ package routing
import (
"net/http"
"strings"
"time"
"github.com/element-hq/dendrite/clientapi/auth"
"github.com/element-hq/dendrite/clientapi/auth/authtypes"
@ -39,28 +41,83 @@ func UploadCrossSigningDeviceKeys(
if sessionID == "" {
sessionID = util.RandomString(sessionIDLength)
}
if uploadReq.Auth.Type != authtypes.LoginTypePassword {
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(
sessionID,
[]authtypes.Flow{
{
Stages: []authtypes.LoginType{authtypes.LoginTypePassword},
},
},
nil,
),
isCrossSigningSetup := false
masterKeyUpdatableWithoutUIA := false
{
var keysResp api.QueryMasterKeysResponse
keyserverAPI.QueryMasterKeys(req.Context(), &api.QueryMasterKeysRequest{UserID: device.UserID}, &keysResp)
if err := keysResp.Error; err != nil {
return convertKeyError(err)
}
if k := keysResp.Key; k != nil {
isCrossSigningSetup = true
if k.UpdatableWithoutUIABeforeMs != nil {
masterKeyUpdatableWithoutUIA = time.Now().UnixMilli() < *k.UpdatableWithoutUIABeforeMs
}
}
}
typePassword := auth.LoginTypePassword{
GetAccountByPassword: accountAPI.QueryAccountByPassword,
Config: cfg,
if isCrossSigningSetup {
// With MSC3861, UIA is not possible. Instead, the auth service has to explicitly mark the master key as replaceable.
if cfg.MSCs.MSC3861Enabled() {
if !masterKeyUpdatableWithoutUIA {
url := ""
if m := cfg.MSCs.MSC3861; m.AccountManagementURL != "" {
url = strings.Join([]string{m.AccountManagementURL, "?action=", authtypes.LoginTypeCrossSigningReset}, "")
} else {
url = m.Issuer
}
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(
"dummy",
[]authtypes.Flow{
{
Stages: []authtypes.LoginType{authtypes.LoginTypeCrossSigningReset},
},
},
map[string]interface{}{
authtypes.LoginTypeCrossSigningReset: map[string]string{
"url": url,
},
},
strings.Join([]string{
"To reset your end-to-end encryption cross-signing, identity, you first need to approve it at",
url,
"and then try again.",
}, " "),
),
}
}
// XXX: is it necessary?
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeCrossSigningReset)
} else {
if uploadReq.Auth.Type != authtypes.LoginTypePassword {
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(
sessionID,
[]authtypes.Flow{
{
Stages: []authtypes.LoginType{authtypes.LoginTypePassword},
},
},
nil,
"",
),
}
}
typePassword := auth.LoginTypePassword{
GetAccountByPassword: accountAPI.QueryAccountByPassword,
Config: cfg,
}
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
return *authErr
}
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
}
}
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
return *authErr
}
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
uploadReq.UserID = device.UserID
keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes)
@ -108,7 +165,17 @@ func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.Clie
keyserverAPI.PerformUploadDeviceSignatures(req.Context(), uploadReq, uploadRes)
if err := uploadRes.Error; err != nil {
switch {
return convertKeyError(err)
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}
func convertKeyError(err *api.KeyError) util.JSONResponse {
switch {
case err.IsInvalidSignature:
return util.JSONResponse{
Code: http.StatusBadRequest,
@ -130,10 +197,4 @@ func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.Clie
JSON: spec.Unknown(err.Error()),
}
}
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}

View file

@ -67,6 +67,7 @@ func Password(
},
},
nil,
"",
),
}
}

View file

@ -234,6 +234,7 @@ type userInteractiveResponse struct {
Completed []authtypes.LoginType `json:"completed"`
Params map[string]interface{} `json:"params"`
Session string `json:"session"`
Msg string `json:"msg,omitempty"`
}
// newUserInteractiveResponse will return a struct to be sent back to the client
@ -242,9 +243,10 @@ func newUserInteractiveResponse(
sessionID string,
fs []authtypes.Flow,
params map[string]interface{},
msg string,
) userInteractiveResponse {
return userInteractiveResponse{
fs, sessions.getCompletedStages(sessionID), params, sessionID,
fs, sessions.getCompletedStages(sessionID), params, sessionID, msg,
}
}
@ -817,7 +819,7 @@ func checkAndCompleteFlow(
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(sessionID,
cfg.Derived.Registration.Flows, cfg.Derived.Registration.Params),
cfg.Derived.Registration.Flows, cfg.Derived.Registration.Params, ""),
}
}

View file

@ -67,7 +67,9 @@ func Setup(
transactionsCache *transactions.Cache,
federationSender federationAPI.ClientFederationAPI,
extRoomsProvider api.ExtraPublicRoomsProvider,
natsClient *nats.Conn, enableMetrics bool,
natsClient *nats.Conn,
userVerifier httputil.UserVerifier,
enableMetrics bool,
) {
cfg := &dendriteCfg.ClientAPI
mscCfg := &dendriteCfg.MSCs
@ -171,19 +173,19 @@ func Setup(
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
}
dendriteAdminRouter.Handle("/admin/registrationTokens/new",
httputil.MakeAdminAPI("admin_registration_tokens_new", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_registration_tokens_new", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminCreateNewRegistrationToken(req, cfg, userAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/registrationTokens",
httputil.MakeAdminAPI("admin_list_registration_tokens", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_list_registration_tokens", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminListRegistrationTokens(req, cfg, userAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/registrationTokens/{token}",
httputil.MakeAdminAPI("admin_get_registration_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_get_registration_token", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
switch req.Method {
case http.MethodGet:
return AdminGetRegistrationToken(req, cfg, userAPI)
@ -202,43 +204,43 @@ func Setup(
).Methods(http.MethodGet, http.MethodPut, http.MethodDelete, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/evacuateRoom/{roomID}",
httputil.MakeAdminAPI("admin_evacuate_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_evacuate_room", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminEvacuateRoom(req, rsAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}",
httputil.MakeAdminAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_evacuate_user", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminEvacuateUser(req, rsAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/purgeRoom/{roomID}",
httputil.MakeAdminAPI("admin_purge_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_purge_room", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminPurgeRoom(req, rsAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/resetPassword/{userID}",
httputil.MakeAdminAPI("admin_reset_password", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_reset_password", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminResetPassword(req, cfg, device, userAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}",
httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_download_state", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminDownloadState(req, device, rsAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/fulltext/reindex",
httputil.MakeAdminAPI("admin_fultext_reindex", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_fultext_reindex", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminReindex(req, cfg, device, natsClient)
}),
).Methods(http.MethodGet, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/refreshDevices/{userID}",
httputil.MakeAdminAPI("admin_refresh_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_refresh_devices", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminMarkAsStale(req, cfg, userAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
@ -252,7 +254,7 @@ func Setup(
}
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_server_notice", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
// not specced, but ensure we're rate limiting requests to this endpoint
if r := rateLimits.Limit(req, device); r != nil {
return *r
@ -273,7 +275,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
synapseAdminRouter.Handle("/admin/v1/send_server_notice",
httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_server_notice", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
// not specced, but ensure we're rate limiting requests to this endpoint
if r := rateLimits.Limit(req, device); r != nil {
return *r
@ -301,12 +303,12 @@ func Setup(
unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter()
v3mux.Handle("/createRoom",
httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("createRoom", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return CreateRoom(req, device, cfg, userAPI, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/join/{roomIDOrAlias}",
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI(spec.Join, userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -340,11 +342,21 @@ func Setup(
httputil.MakeServiceAdminAPI("admin_username_available", m.AdminToken, func(r *http.Request) util.JSONResponse {
return AdminCheckUsernameAvailable(r, userAPI, cfg)
})).Methods(http.MethodGet)
synapseAdminRouter.Handle("/admin/v2/users/{userID}",
httputil.MakeServiceAdminAPI("admin_provision_user", m.AdminToken, func(r *http.Request) util.JSONResponse {
return AdminCreateOrModifyAccount(r, userAPI)
})).Methods(http.MethodPut)
synapseAdminRouter.Handle("/admin/v2/users/{userID}/devices",
httputil.MakeServiceAdminAPI("admin_user_devices", m.AdminToken, func(r *http.Request) util.JSONResponse {
return AdminHandleUserDeviceByUserID(r, userAPI)
})).Methods(http.MethodPost, http.MethodGet)
}
if mscCfg.Enabled("msc2753") {
v3mux.Handle("/peek/{roomIDOrAlias}",
httputil.MakeAuthAPI(spec.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI(spec.Peek, userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -359,12 +371,12 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
}
v3mux.Handle("/joined_rooms",
httputil.MakeAuthAPI("joined_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("joined_rooms", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetJoinedRooms(req, device, rsAPI)
}, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/join",
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI(spec.Join, userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -386,7 +398,7 @@ func Setup(
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/leave",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("membership", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -400,7 +412,7 @@ func Setup(
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/unpeek",
httputil.MakeAuthAPI("unpeek", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("unpeek", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -411,7 +423,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/ban",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("membership", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -420,7 +432,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/invite",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("membership", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -432,7 +444,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/kick",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("membership", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -441,7 +453,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/unban",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("membership", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -450,7 +462,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/send/{eventType}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_message", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -459,7 +471,7 @@ func Setup(
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_message", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -470,7 +482,7 @@ func Setup(
}, httputil.WithAllowGuests()),
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -478,7 +490,7 @@ func Setup(
return OnIncomingStateRequest(req.Context(), device, rsAPI, vars["roomID"])
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -486,7 +498,7 @@ func Setup(
return GetAliases(req, rsAPI, device, vars["roomID"])
})).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -497,7 +509,7 @@ func Setup(
return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], eventType, "", eventFormat)
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -507,7 +519,7 @@ func Setup(
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_message", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -519,7 +531,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_message", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -533,7 +545,7 @@ func Setup(
// TODO: clear based on some criteria
roomHierarchyPaginationCache := NewRoomHierarchyPaginationCache()
v1mux.Handle("/rooms/{roomID}/hierarchy",
httputil.MakeAuthAPI("spaces", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("spaces", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -567,7 +579,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/directory/room/{roomAlias}",
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("directory_room", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -577,7 +589,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/directory/room/{roomAlias}",
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("directory_room", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -596,7 +608,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/directory/list/room/{roomID}",
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("directory_list", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -605,7 +617,7 @@ func Setup(
}),
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("directory_list", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -616,7 +628,7 @@ func Setup(
// Undocumented endpoint
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("directory_list", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -632,19 +644,19 @@ func Setup(
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
v3mux.Handle("/logout",
httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("logout", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return Logout(req, userAPI, device)
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/logout/all",
httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("logout", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return LogoutAll(req, userAPI, device)
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/typing/{userID}",
httputil.MakeAuthAPI("rooms_typing", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_typing", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -656,7 +668,7 @@ func Setup(
}),
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_redact", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -665,7 +677,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}",
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_redact", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -676,7 +688,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/sendToDevice/{eventType}/{txnID}",
httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_to_device", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -690,7 +702,7 @@ func Setup(
// rather than r0. It's an exact duplicate of the above handler.
// TODO: Remove this if/when sytest is fixed!
unstableMux.Handle("/sendToDevice/{eventType}/{txnID}",
httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("send_to_device", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -701,7 +713,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/account/whoami",
httputil.MakeAuthAPI("whoami", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("whoami", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -710,7 +722,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/account/password",
httputil.MakeAuthAPI("password", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("password", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -719,7 +731,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/account/deactivate",
httputil.MakeAuthAPI("deactivate", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("deactivate", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -739,7 +751,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
v3mux.Handle("/auth/{authType}/fallback/web",
httputil.MakeHTTPAPI("auth_fallback", userAPI, enableMetrics, func(w http.ResponseWriter, req *http.Request) {
httputil.MakeHTTPAPI("auth_fallback", userVerifier, enableMetrics, func(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
AuthFallback(w, req, vars["authType"], cfg)
}),
@ -748,7 +760,7 @@ func Setup(
// Push rules
v3mux.Handle("/pushrules",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("missing trailing slash"),
@ -757,13 +769,13 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetAllPushRules(req.Context(), device, userAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("scope, kind and rule ID must be specified"),
@ -772,7 +784,7 @@ func Setup(
).Methods(http.MethodPut)
v3mux.Handle("/pushrules/{scope}/",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -782,7 +794,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/{scope}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("missing trailing slash after scope"),
@ -791,7 +803,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/{scope:[^/]+/?}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("kind and rule ID must be specified"),
@ -800,7 +812,7 @@ func Setup(
).Methods(http.MethodPut)
v3mux.Handle("/pushrules/{scope}/{kind}/",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -810,7 +822,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/{scope}/{kind}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("missing trailing slash after kind"),
@ -819,7 +831,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/{scope}/{kind:[^/]+/?}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("rule ID must be specified"),
@ -828,7 +840,7 @@ func Setup(
).Methods(http.MethodPut)
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -838,7 +850,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -852,7 +864,7 @@ func Setup(
).Methods(http.MethodPut)
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -862,7 +874,7 @@ func Setup(
).Methods(http.MethodDelete)
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -872,7 +884,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("push_rules", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -904,7 +916,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/profile/{userID}/avatar_url",
httputil.MakeAuthAPI("profile_avatar_url", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("profile_avatar_url", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -929,7 +941,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/profile/{userID}/displayname",
httputil.MakeAuthAPI("profile_displayname", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("profile_displayname", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -946,19 +958,19 @@ func Setup(
threePIDClient := base.CreateClient(dendriteCfg, nil) // TODO: Move this somewhere else, e.g. pass in as parameter
v3mux.Handle("/account/3pid",
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("account_3pid", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetAssociated3PIDs(req, userAPI, device)
}),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/account/3pid",
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("account_3pid", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return CheckAndSave3PIDAssociation(req, userAPI, device, cfg, threePIDClient)
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/account/3pid/delete",
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("account_3pid", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return Forget3PID(req, userAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
@ -970,7 +982,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/voip/turnServer",
httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("turn_server", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -979,13 +991,13 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/thirdparty/protocols",
httputil.MakeAuthAPI("thirdparty_protocols", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("thirdparty_protocols", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return Protocols(req, asAPI, device, "")
}, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/thirdparty/protocol/{protocolID}",
httputil.MakeAuthAPI("thirdparty_protocols", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("thirdparty_protocols", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -995,7 +1007,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/thirdparty/user/{protocolID}",
httputil.MakeAuthAPI("thirdparty_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("thirdparty_user", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1005,13 +1017,13 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/thirdparty/user",
httputil.MakeAuthAPI("thirdparty_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("thirdparty_user", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return User(req, asAPI, device, "", req.URL.Query())
}, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/thirdparty/location/{protocolID}",
httputil.MakeAuthAPI("thirdparty_location", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("thirdparty_location", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1021,7 +1033,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/thirdparty/location",
httputil.MakeAuthAPI("thirdparty_location", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("thirdparty_location", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return Location(req, asAPI, device, "", req.URL.Query())
}, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions)
@ -1037,7 +1049,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/user/{userID}/account_data/{type}",
httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("user_account_data", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1047,7 +1059,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}",
httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("user_account_data", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1057,7 +1069,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/user/{userID}/account_data/{type}",
httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("user_account_data", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1067,7 +1079,7 @@ func Setup(
).Methods(http.MethodGet)
v3mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}",
httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("user_account_data", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1077,7 +1089,7 @@ func Setup(
).Methods(http.MethodGet)
v3mux.Handle("/admin/whois/{userID}",
httputil.MakeAuthAPI("admin_whois", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("admin_whois", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1087,7 +1099,7 @@ func Setup(
).Methods(http.MethodGet)
v3mux.Handle("/user/{userID}/openid/request_token",
httputil.MakeAuthAPI("openid_request_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("openid_request_token", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1100,7 +1112,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/user_directory/search",
httputil.MakeAuthAPI("userdirectory_search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("userdirectory_search", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1126,7 +1138,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/read_markers",
httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_read_markers", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1139,7 +1151,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/forget",
httputil.MakeAuthAPI("rooms_forget", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_forget", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1152,7 +1164,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/upgrade",
httputil.MakeAuthAPI("rooms_upgrade", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_upgrade", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1162,13 +1174,13 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/devices",
httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_devices", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetDevicesByLocalpart(req, userAPI, device)
}, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/devices/{deviceID}",
httputil.MakeAuthAPI("get_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_device", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1178,7 +1190,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/devices/{deviceID}",
httputil.MakeAuthAPI("device_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("device_data", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1188,7 +1200,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/devices/{deviceID}",
httputil.MakeAuthAPI("delete_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("delete_device", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1198,25 +1210,25 @@ func Setup(
).Methods(http.MethodDelete, http.MethodOptions)
v3mux.Handle("/delete_devices",
httputil.MakeAuthAPI("delete_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("delete_devices", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return DeleteDevices(req, userInteractiveAuth, userAPI, device)
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/notifications",
httputil.MakeAuthAPI("get_notifications", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_notifications", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetNotifications(req, device, userAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushers",
httputil.MakeAuthAPI("get_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_pushers", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetPushers(req, device, userAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/pushers/set",
httputil.MakeAuthAPI("set_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("set_pushers", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1226,7 +1238,7 @@ func Setup(
// Stub implementations for sytest
v3mux.Handle("/events",
httputil.MakeAuthAPI("events", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("events", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{Code: http.StatusOK, JSON: map[string]interface{}{
"chunk": []interface{}{},
"start": "",
@ -1236,7 +1248,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/initialSync",
httputil.MakeAuthAPI("initial_sync", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("initial_sync", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{Code: http.StatusOK, JSON: map[string]interface{}{
"end": "",
}}
@ -1244,7 +1256,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/user/{userId}/rooms/{roomId}/tags",
httputil.MakeAuthAPI("get_tags", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_tags", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1254,7 +1266,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}",
httputil.MakeAuthAPI("put_tag", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("put_tag", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1264,7 +1276,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}",
httputil.MakeAuthAPI("delete_tag", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("delete_tag", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1274,7 +1286,7 @@ func Setup(
).Methods(http.MethodDelete, http.MethodOptions)
v3mux.Handle("/capabilities",
httputil.MakeAuthAPI("capabilities", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("capabilities", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1284,7 +1296,7 @@ func Setup(
// Key Backup Versions (Metadata)
getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1292,11 +1304,11 @@ func Setup(
return KeyBackupVersion(req, userAPI, device, vars["version"])
})
getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return KeyBackupVersion(req, userAPI, device, "")
})
putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1304,7 +1316,7 @@ func Setup(
return ModifyKeyBackupVersionAuthData(req, userAPI, device, vars["version"])
})
deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1312,7 +1324,7 @@ func Setup(
return DeleteKeyBackupVersion(req, userAPI, device, vars["version"])
})
postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return CreateKeyBackupVersion(req, userAPI, device)
})
@ -1331,7 +1343,7 @@ func Setup(
// Inserting E2E Backup Keys
// Bulk room and session
putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
version := req.URL.Query().Get("version")
if version == "" {
return util.JSONResponse{
@ -1348,7 +1360,7 @@ func Setup(
})
// Single room bulk session
putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1380,7 +1392,7 @@ func Setup(
})
// Single room, single session
putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1422,11 +1434,11 @@ func Setup(
// Querying E2E Backup Keys
getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), "", "")
})
getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1434,7 +1446,7 @@ func Setup(
return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), vars["roomID"], "")
})
getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1454,11 +1466,11 @@ func Setup(
// Cross-signing device keys
postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, userAPI, device, userAPI, cfg)
})
postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return UploadCrossSigningDeviceSignatures(req, userAPI, device)
}, httputil.WithAllowGuests())
@ -1470,27 +1482,27 @@ func Setup(
// Supplying a device ID is deprecated.
v3mux.Handle("/keys/upload/{deviceID}",
httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("keys_upload", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return UploadKeys(req, userAPI, device)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/keys/upload",
httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("keys_upload", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return UploadKeys(req, userAPI, device)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/keys/query",
httputil.MakeAuthAPI("keys_query", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("keys_query", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return QueryKeys(req, userAPI, device)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/keys/claim",
httputil.MakeAuthAPI("keys_claim", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("keys_claim", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return ClaimKeys(req, userAPI)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}",
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI(spec.Join, userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -1503,7 +1515,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/presence/{userId}/status",
httputil.MakeAuthAPI("set_presence", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("set_presence", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1512,7 +1524,7 @@ func Setup(
}),
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/presence/{userId}/status",
httputil.MakeAuthAPI("get_presence", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_presence", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1522,7 +1534,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/joined_members",
httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_members", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1532,7 +1544,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/report/{eventID}",
httputil.MakeAuthAPI("report_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("report_event", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1542,7 +1554,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
synapseAdminRouter.Handle("/admin/v1/event_reports",
httputil.MakeAdminAPI("admin_report_events", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_report_events", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
from := parseUint64OrDefault(req.URL.Query().Get("from"), 0)
limit := parseUint64OrDefault(req.URL.Query().Get("limit"), 100)
dir := req.URL.Query().Get("dir")
@ -1556,7 +1568,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
synapseAdminRouter.Handle("/admin/v1/event_reports/{reportID}",
httputil.MakeAdminAPI("admin_report_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_report_event", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -1566,7 +1578,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
synapseAdminRouter.Handle("/admin/v1/event_reports/{reportID}",
httputil.MakeAdminAPI("admin_report_event_delete", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAdminAPI("admin_report_event_delete", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)

View file

@ -58,17 +58,23 @@ func WithAuth() AuthAPIOption {
}
}
type UserVerifier interface {
// VerifyUserFromRequest authenticates the HTTP request,
// on success returns Device of the requester.
VerifyUserFromRequest(req *http.Request) (*userapi.Device, *util.JSONResponse)
}
// MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request.
func MakeAuthAPI(
metricsName string, userAPI userapi.QueryAcccessTokenAPI,
metricsName string, userVerifier UserVerifier,
f func(*http.Request, *userapi.Device) util.JSONResponse,
checks ...AuthAPIOption,
) http.Handler {
h := func(req *http.Request) util.JSONResponse {
logger := util.GetLogger(req.Context())
device, err := auth.VerifyUserFromRequest(req, userAPI)
device, err := userVerifier.VerifyUserFromRequest(req)
if err != nil {
logger.Debugf("VerifyUserFromRequest %s -> HTTP %d", req.RemoteAddr, err.Code)
logger.Debugf("VerifyUserFromRequest %s -> HTTP %d: JSON %+v", req.RemoteAddr, err.Code, err.JSON)
return *err
}
// add the user ID to the logger
@ -122,11 +128,11 @@ func MakeAuthAPI(
// MakeAdminAPI is a wrapper around MakeAuthAPI which enforces that the request can only be
// completed by a user that is a server administrator.
func MakeAdminAPI(
metricsName string, userAPI userapi.QueryAcccessTokenAPI,
metricsName string, userVerifier UserVerifier,
f func(*http.Request, *userapi.Device) util.JSONResponse,
) http.Handler {
return MakeAuthAPI(metricsName, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if device.AccountType != userapi.AccountTypeAdmin {
return MakeAuthAPI(metricsName, userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if device == nil || device.AccountType != userapi.AccountTypeAdmin {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden("This API can only be used by admin users."),
@ -136,8 +142,8 @@ func MakeAdminAPI(
})
}
// MakeServiceAdminAPI is a wrapper around MakeAuthAPI which enforces that the request can only be
// completed by a trusted service e.g. Matrix Auth Service.
// MakeServiceAdminAPI is a wrapper around MakeExternalAPI which enforces that the request can only be
// completed by a trusted service e.g. Matrix Auth Service (MAS).
func MakeServiceAdminAPI(
metricsName, serviceToken string,
f func(*http.Request) util.JSONResponse,
@ -232,7 +238,7 @@ func MakeExternalAPI(metricsName string, f func(*http.Request) util.JSONResponse
// MakeHTTPAPI adds Span metrics to the HTML Handler function
// This is used to serve HTML alongside JSON error messages
func MakeHTTPAPI(metricsName string, userAPI userapi.QueryAcccessTokenAPI, enableMetrics bool, f func(http.ResponseWriter, *http.Request), checks ...AuthAPIOption) http.Handler {
func MakeHTTPAPI(metricsName string, userVerifier UserVerifier, enableMetrics bool, f func(http.ResponseWriter, *http.Request), checks ...AuthAPIOption) http.Handler {
withSpan := func(w http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodOptions {
util.SetCORSHeaders(w)
@ -252,7 +258,7 @@ func MakeHTTPAPI(metricsName string, userAPI userapi.QueryAcccessTokenAPI, enabl
if opts.WithAuth {
logger := util.GetLogger(req.Context())
_, jsonErr := auth.VerifyUserFromRequest(req, userAPI)
_, jsonErr := userVerifier.VerifyUserFromRequest(req)
if jsonErr != nil {
w.WriteHeader(jsonErr.Code)
if err := json.NewEncoder(w).Encode(jsonErr.JSON); err != nil {

View file

@ -12,7 +12,6 @@ import (
"github.com/element-hq/dendrite/mediaapi/routing"
"github.com/element-hq/dendrite/mediaapi/storage"
"github.com/element-hq/dendrite/setup/config"
userapi "github.com/element-hq/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/sirupsen/logrus"
@ -23,10 +22,10 @@ func AddPublicRoutes(
routers httputil.Routers,
cm *sqlutil.Connections,
cfg *config.Dendrite,
userAPI userapi.MediaUserAPI,
client *fclient.Client,
fedClient fclient.FederationClient,
keyRing gomatrixserverlib.JSONVerifier,
userVerifier httputil.UserVerifier,
) {
mediaDB, err := storage.NewMediaAPIDatasource(cm, &cfg.MediaAPI.Database)
if err != nil {
@ -34,6 +33,6 @@ func AddPublicRoutes(
}
routing.Setup(
routers, cfg, mediaDB, userAPI, client, fedClient, keyRing,
routers, cfg, mediaDB, client, fedClient, keyRing, userVerifier,
)
}

View file

@ -42,10 +42,10 @@ func Setup(
routers httputil.Routers,
cfg *config.Dendrite,
db storage.Database,
userAPI userapi.MediaUserAPI,
client *fclient.Client,
federationClient fclient.FederationClient,
keyRing gomatrixserverlib.JSONVerifier,
userVerifier httputil.UserVerifier,
) {
rateLimits := httputil.NewRateLimits(&cfg.ClientAPI.RateLimiting)
@ -58,7 +58,7 @@ func Setup(
}
uploadHandler := httputil.MakeAuthAPI(
"upload", userAPI,
"upload", userVerifier,
func(req *http.Request, dev *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, dev); r != nil {
return *r
@ -67,7 +67,7 @@ func Setup(
},
)
configHandler := httputil.MakeAuthAPI("config", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
configHandler := httputil.MakeAuthAPI("config", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
@ -97,13 +97,13 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
// v1 client endpoints requiring auth
downloadHandlerAuthed := httputil.MakeHTTPAPI("download", userAPI, cfg.Global.Metrics.Enabled, makeDownloadAPI("download_authed_client", &cfg.MediaAPI, rateLimits, db, client, federationClient, activeRemoteRequests, activeThumbnailGeneration, false), httputil.WithAuth())
downloadHandlerAuthed := httputil.MakeHTTPAPI("download", userVerifier, cfg.Global.Metrics.Enabled, makeDownloadAPI("download_authed_client", &cfg.MediaAPI, rateLimits, db, client, federationClient, activeRemoteRequests, activeThumbnailGeneration, false), httputil.WithAuth())
v1mux.Handle("/config", configHandler).Methods(http.MethodGet, http.MethodOptions)
v1mux.Handle("/download/{serverName}/{mediaId}", downloadHandlerAuthed).Methods(http.MethodGet, http.MethodOptions)
v1mux.Handle("/download/{serverName}/{mediaId}/{downloadName}", downloadHandlerAuthed).Methods(http.MethodGet, http.MethodOptions)
v1mux.Handle("/thumbnail/{serverName}/{mediaId}",
httputil.MakeHTTPAPI("thumbnail", userAPI, cfg.Global.Metrics.Enabled, makeDownloadAPI("thumbnail_authed_client", &cfg.MediaAPI, rateLimits, db, client, federationClient, activeRemoteRequests, activeThumbnailGeneration, false), httputil.WithAuth()),
httputil.MakeHTTPAPI("thumbnail", userVerifier, cfg.Global.Metrics.Enabled, makeDownloadAPI("thumbnail_authed_client", &cfg.MediaAPI, rateLimits, db, client, federationClient, activeRemoteRequests, activeThumbnailGeneration, false), httputil.WithAuth()),
).Methods(http.MethodGet, http.MethodOptions)
// same, but for federation

View file

@ -1,15 +1,18 @@
package config
import "slices"
type MSCs struct {
Matrix *Global `yaml:"-"`
// The MSCs to enable. Supported MSCs include:
// 'msc3861': Delegate auth to an OIDC provider. This line MUST always go first if the msc is used https://github.com/matrix-org/matrix-spec-proposals/pull/3861
// 'msc2444': Peeking over federation - https://github.com/matrix-org/matrix-doc/pull/2444
// 'msc2753': Peeking via /sync - https://github.com/matrix-org/matrix-doc/pull/2753
// 'msc2836': Threading - https://github.com/matrix-org/matrix-doc/pull/2836
// 'msc3861': Delegate auth to an OIDC provider https://github.com/matrix-org/matrix-spec-proposals/pull/3861
MSCs []string `yaml:"mscs"`
// MSC3861 contains config related to the experimental feature MSC3861. It takes effect only if 'msc3861' is included in 'MSCs' array
MSC3861 *MSC3861 `yaml:"msc3861,omitempty"`
Database DatabaseOptions `yaml:"database,omitempty"`
@ -42,6 +45,10 @@ func (c *MSCs) Verify(configErrs *ConfigErrors) {
}
}
func (c *MSCs) MSC3861Enabled() bool {
return slices.Contains(c.MSCs, "msc3861") && c.MSC3861 != nil && c.MSC3861.Enabled
}
type MSC3861 struct {
Enabled bool `yaml:"enabled"`
Issuer string `yaml:"issuer"`

View file

@ -7,9 +7,12 @@
package setup
import (
"net/http"
appserviceAPI "github.com/element-hq/dendrite/appservice/api"
"github.com/element-hq/dendrite/clientapi"
"github.com/element-hq/dendrite/clientapi/api"
"github.com/element-hq/dendrite/clientapi/auth"
"github.com/element-hq/dendrite/federationapi"
federationAPI "github.com/element-hq/dendrite/federationapi/api"
"github.com/element-hq/dendrite/internal/caching"
@ -27,6 +30,7 @@ import (
userapi "github.com/element-hq/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/util"
)
// Monolith represents an instantiation of all dependencies required to build
@ -46,6 +50,8 @@ type Monolith struct {
// Optional
ExtPublicRoomsProvider api.ExtraPublicRoomsProvider
ExtUserDirectoryProvider userapi.QuerySearchProfilesAPI
UserVerifierProvider *UserVerifierProvider
}
// AddAllPublicRoutes attaches all public paths to the given router
@ -58,6 +64,10 @@ func (m *Monolith) AddAllPublicRoutes(
caches *caching.Caches,
enableMetrics bool,
) {
if m.UserVerifierProvider == nil {
m.UserVerifierProvider = NewUserVerifierProvider(&auth.DefaultUserVerifier{UserAPI: m.UserAPI})
}
userDirectoryProvider := m.ExtUserDirectoryProvider
if userDirectoryProvider == nil {
userDirectoryProvider = m.UserAPI
@ -65,15 +75,29 @@ func (m *Monolith) AddAllPublicRoutes(
clientapi.AddPublicRoutes(
processCtx, routers, cfg, natsInstance, m.FedClient, m.RoomserverAPI, m.AppserviceAPI, transactions.New(),
m.FederationAPI, m.UserAPI, userDirectoryProvider,
m.ExtPublicRoomsProvider, enableMetrics,
m.ExtPublicRoomsProvider, m.UserVerifierProvider, enableMetrics,
)
federationapi.AddPublicRoutes(
processCtx, routers, cfg, natsInstance, m.UserAPI, m.FedClient, m.KeyRing, m.RoomserverAPI, m.FederationAPI, enableMetrics,
)
mediaapi.AddPublicRoutes(routers, cm, cfg, m.UserAPI, m.Client, m.FedClient, m.KeyRing)
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, natsInstance, m.UserAPI, m.RoomserverAPI, caches, enableMetrics)
mediaapi.AddPublicRoutes(routers, cm, cfg, m.Client, m.FedClient, m.KeyRing, m.UserVerifierProvider)
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, natsInstance, m.UserAPI, m.RoomserverAPI, caches, m.UserVerifierProvider, enableMetrics)
if m.RelayAPI != nil {
relayapi.AddPublicRoutes(routers, cfg, m.KeyRing, m.RelayAPI)
}
}
type UserVerifierProvider struct {
UserVerifier httputil.UserVerifier
}
func (u *UserVerifierProvider) VerifyUserFromRequest(req *http.Request) (*userapi.Device, *util.JSONResponse) {
return u.UserVerifier.VerifyUserFromRequest(req)
}
func NewUserVerifierProvider(userVerifier httputil.UserVerifier) *UserVerifierProvider {
return &UserVerifierProvider{
UserVerifier: userVerifier,
}
}

View file

@ -98,7 +98,7 @@ func toClientResponse(ctx context.Context, res *MSC2836EventRelationshipsRespons
// Enable this MSC
func Enable(
cfg *config.Dendrite, cm *sqlutil.Connections, routers httputil.Routers, rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationInternalAPI,
userAPI userapi.UserInternalAPI, keyRing gomatrixserverlib.JSONVerifier,
userVerifier httputil.UserVerifier, keyRing gomatrixserverlib.JSONVerifier,
) error {
db, err := NewDatabase(cm, &cfg.MSCs.Database)
if err != nil {
@ -124,7 +124,7 @@ func Enable(
})
routers.Client.Handle("/unstable/event_relationships",
httputil.MakeAuthAPI("eventRelationships", userAPI, eventRelationshipHandler(db, rsAPI, fsAPI)),
httputil.MakeAuthAPI("eventRelationships", userVerifier, eventRelationshipHandler(db, rsAPI, fsAPI)),
).Methods(http.MethodPost, http.MethodOptions)
routers.Federation.Handle("/unstable/event_relationships", httputil.MakeExternalAPI(

View file

@ -0,0 +1,17 @@
package msc3861
import (
"github.com/element-hq/dendrite/setup"
)
func Enable(m *setup.Monolith) error {
userVerifier, err := newMSC3861UserVerifier(
m.UserAPI, m.Config.Global.ServerName,
m.Config.MSCs.MSC3861, !m.Config.ClientAPI.GuestsDisabled,
)
if err != nil {
return err
}
m.UserVerifierProvider.UserVerifier = userVerifier
return nil
}

View file

@ -0,0 +1,444 @@
package msc3861
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"slices"
"strings"
"github.com/element-hq/dendrite/clientapi/auth"
"github.com/element-hq/dendrite/setup/config"
"github.com/element-hq/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
)
const externalAuthProvider string = "oauth-delegated"
// Scopes as defined by MSC2967
// https://github.com/matrix-org/matrix-spec-proposals/pull/2967
const (
scopeMatrixAPI string = "urn:matrix:org.matrix.msc2967.client:api:*"
scopeMatrixGuest string = "urn:matrix:org.matrix.msc2967.client:api:guest"
scopeMatrixDevicePrefix string = "urn:matrix:org.matrix.msc2967.client:device:"
)
type errCode string
const (
codeIntrospectionNot2xx errCode = "introspectionIsNot2xx"
codeInvalidClientToken errCode = "invalidClientToken"
codeAuthError errCode = "authError"
codeMxidError errCode = "mxidError"
codeOpenidConfigEndpointNon2xx errCode = "openidConfigEndpointNon2xx"
codeOpenidConfigDecodingFailed errCode = "openidConfigDecodingFailed"
)
// MSC3861UserVerifier implements UserVerifier interface
type MSC3861UserVerifier struct {
userAPI api.UserInternalAPI
serverName spec.ServerName
cfg *config.MSC3861
httpClient *http.Client
openIdConfig *OpenIDConfiguration
allowGuest bool
}
func newMSC3861UserVerifier(
userAPI api.UserInternalAPI,
serverName spec.ServerName,
cfg *config.MSC3861,
allowGuest bool,
) (*MSC3861UserVerifier, error) {
openIdConfig, err := fetchOpenIDConfiguration(&http.Client{}, cfg.Issuer)
if err != nil {
return nil, err
}
return &MSC3861UserVerifier{
userAPI: userAPI,
serverName: serverName,
cfg: cfg,
openIdConfig: openIdConfig,
allowGuest: allowGuest,
httpClient: http.DefaultClient,
}, nil
}
type mscError struct {
Code errCode
Msg string
}
func (r *mscError) Error() string {
return fmt.Sprintf("%s: %s", r.Code, r.Msg)
}
// VerifyUserFromRequest authenticates the HTTP request, on success returns Device of the requester.
func (m *MSC3861UserVerifier) VerifyUserFromRequest(req *http.Request) (*api.Device, *util.JSONResponse) {
util.GetLogger(req.Context()).Debug("MSC3861.VerifyUserFromRequest")
// Try to find the Application Service user
token, err := auth.ExtractAccessToken(req)
if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.MissingToken(err.Error()),
}
}
// TODO: try to get appservice user first. See https://github.com/element-hq/synapse/blob/develop/synapse/api/auth/msc3861_delegated.py#L273
userData, err := m.getUserByAccessToken(req.Context(), token)
if err != nil {
switch e := err.(type) {
case (*mscError):
switch e.Code {
case codeIntrospectionNot2xx, codeOpenidConfigDecodingFailed, codeOpenidConfigEndpointNon2xx:
return nil, &util.JSONResponse{
Code: http.StatusServiceUnavailable,
JSON: spec.Unknown(e.Error()),
}
case codeInvalidClientToken:
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.Forbidden(e.Error()),
}
case codeAuthError:
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.Unknown(e.Error()),
}
case codeMxidError:
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.Unknown(e.Error()),
}
default:
r := util.ErrorResponse(err)
return nil, &r
}
default:
r := util.ErrorResponse(err)
return nil, &r
}
}
// Do not record requests from MAS using the virtual `__oidc_admin` user.
if token != m.cfg.AdminToken {
// TODO: not sure which exact data we should record here. See the link for reference
// https://github.com/element-hq/synapse/blob/develop/synapse/api/auth/base.py#L365
}
if !m.allowGuest && userData.IsGuest {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.Forbidden(strings.Join([]string{"Insufficient scope: ", scopeMatrixAPI}, "")),
}
}
return userData.Device, nil
}
type requester struct {
Device *api.Device
UserID *spec.UserID
Scope []string
IsGuest bool
}
func (m *MSC3861UserVerifier) getUserByAccessToken(ctx context.Context, token string) (*requester, error) {
var userID *spec.UserID
logger := util.GetLogger(ctx)
if adminToken := m.cfg.AdminToken; adminToken != "" && token == adminToken {
// XXX: This is a temporary solution so that the admin API can be called by
// the OIDC provider. This will be removed once we have OIDC client
// credentials grant support in matrix-authentication-service.
logger.Info("Admin token used")
// XXX: that user doesn't exist and won't be provisioned.
adminUser, err := createUserID("__oidc_admin", m.serverName)
if err != nil {
return nil, err
}
return &requester{
UserID: adminUser,
Scope: []string{"urn:synapse:admin:*"},
Device: &api.Device{UserID: adminUser.Local(), AccountType: api.AccountTypeAdmin},
}, nil
}
introspectionResult, err := m.introspectToken(ctx, token)
if err != nil {
logger.WithError(err).Error("MSC3861UserVerifier:introspectToken")
return nil, err
}
logger.Debugf("Introspection result: %+v", *introspectionResult)
if !introspectionResult.Active {
return nil, &mscError{Code: codeInvalidClientToken, Msg: "Token is not active"}
}
scopes := introspectionResult.Scopes()
hasUserScope, hasGuestScope := slices.Contains(scopes, scopeMatrixAPI), slices.Contains(scopes, scopeMatrixGuest)
if !hasUserScope && !hasGuestScope {
return nil, &mscError{Code: codeInvalidClientToken, Msg: "No scope in token granting user rights"}
}
sub := introspectionResult.Sub
if sub == "" {
return nil, &mscError{Code: codeInvalidClientToken, Msg: "Invalid sub claim in the introspection result"}
}
localpart := ""
{
var rs api.QueryLocalpartExternalIDResponse
if err = m.userAPI.QueryExternalUserIDByLocalpartAndProvider(ctx, &api.QueryLocalpartExternalIDRequest{
ExternalID: sub,
AuthProvider: externalAuthProvider,
}, &rs); err != nil && err != sql.ErrNoRows {
return nil, err
}
if l := rs.LocalpartExternalID; l != nil {
localpart = l.Localpart
}
}
if localpart == "" {
// If we could not find a user via the external_id, it either does not exist,
// or the external_id was never recorded
username := introspectionResult.Username
if username == "" {
return nil, &mscError{Code: codeAuthError, Msg: "Invalid username claim in the introspection result"}
}
userID, err = createUserID(username, m.serverName)
if err != nil {
logger.WithError(err).Error("getUserByAccessToken:createUserID")
return nil, err
}
// First try to find a user from the username claim
var account *api.Account
{
var rs api.QueryAccountByLocalpartResponse
err := m.userAPI.QueryAccountByLocalpart(ctx, &api.QueryAccountByLocalpartRequest{Localpart: userID.Local(), ServerName: userID.Domain()}, &rs)
if err != nil && err != sql.ErrNoRows {
logger.WithError(err).Error("QueryAccountByLocalpart")
return nil, err
}
account = rs.Account
}
if account == nil {
// If the user does not exist, we should create it on the fly
var rs api.PerformAccountCreationResponse
if err = m.userAPI.PerformAccountCreation(ctx, &api.PerformAccountCreationRequest{
AccountType: api.AccountTypeUser,
Localpart: userID.Local(),
ServerName: userID.Domain(),
}, &rs); err != nil {
logger.WithError(err).Error("PerformAccountCreation")
return nil, err
}
}
if err := m.userAPI.PerformLocalpartExternalUserIDCreation(ctx, &api.PerformLocalpartExternalUserIDCreationRequest{
Localpart: userID.Local(),
ExternalID: sub,
AuthProvider: externalAuthProvider,
}); err != nil {
logger.WithError(err).Error("PerformLocalpartExternalUserIDCreation")
return nil, err
}
localpart = userID.Local()
}
if userID == nil {
userID, err = createUserID(localpart, m.serverName)
if err != nil {
logger.WithError(err).Error("getUserByAccessToken:createUserID")
return nil, err
}
}
deviceIDs := make([]string, 0, 1)
for i := range scopes {
if s := scopes[i]; strings.HasPrefix(s, scopeMatrixDevicePrefix) {
deviceIDs = append(deviceIDs, s[len(scopeMatrixDevicePrefix):])
}
}
if len(deviceIDs) != 1 {
logger.Errorf("Invalid device IDs in scope: %+v", deviceIDs)
return nil, &mscError{Code: codeAuthError, Msg: "Invalid device IDs in scope"}
}
var device *api.Device
deviceID := deviceIDs[0]
if len(deviceID) > 255 || len(deviceID) < 1 {
return nil, &mscError{
Code: codeAuthError,
Msg: strings.Join([]string{"Invalid device ID in scope: ", deviceID}, ""),
}
}
logger.Debugf("deviceID is: %s", deviceID)
logger.Debugf("scope is: %+v", scopes)
userDeviceExists := false
{
var rs api.QueryDevicesResponse
err := m.userAPI.QueryDevices(ctx, &api.QueryDevicesRequest{UserID: userID.String()}, &rs)
if err != nil && err != sql.ErrNoRows {
return nil, err
}
for i := range rs.Devices {
if d := &rs.Devices[i]; d.ID == deviceID {
userDeviceExists = true
device = d
break
}
}
}
logger.Debugf("userDeviceExists is: %t", userDeviceExists)
if !userDeviceExists {
var rs api.PerformDeviceCreationResponse
deviceDisplayName := "OIDC-native client"
if err := m.userAPI.PerformDeviceCreation(ctx, &api.PerformDeviceCreationRequest{
Localpart: localpart,
ServerName: m.serverName,
AccessToken: token,
DeviceID: &deviceID,
DeviceDisplayName: &deviceDisplayName,
// TODO: Cannot add IPAddr and Useragent values here. Should we care about it here?
}, &rs); err != nil {
logger.WithError(err).Error("PerformDeviceCreation")
return nil, err
}
device = rs.Device
logger.Debugf("PerformDeviceCreationResponse is: %+v", rs)
}
return &requester{
Device: device,
UserID: userID,
Scope: scopes,
IsGuest: hasGuestScope && !hasUserScope,
}, nil
}
func createUserID(local string, serverName spec.ServerName) (*spec.UserID, error) {
userID, err := spec.NewUserID(strings.Join([]string{"@", local, ":", string(serverName)}, ""), false)
if err != nil {
return nil, &mscError{Code: codeMxidError, Msg: err.Error()}
}
return userID, nil
}
func (m *MSC3861UserVerifier) introspectToken(ctx context.Context, token string) (*introspectionResponse, error) {
formBody := url.Values{"token": []string{token}}
encoded := formBody.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, m.openIdConfig.IntrospectionEndpoint, strings.NewReader(encoded))
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(m.cfg.ClientID, m.cfg.ClientSecret)
resp, err := m.httpClient.Do(req)
if err != nil {
return nil, err
}
body := resp.Body
defer resp.Body.Close()
if c := resp.StatusCode; c < 200 || c >= 300 {
return nil, errors.New(strings.Join([]string{"The introspection endpoint returned a '", resp.Status ,"' response"}, ""))
}
var ir introspectionResponse
if err := json.NewDecoder(body).Decode(&ir); err != nil {
return nil, err
}
return &ir, nil
}
type OpenIDConfiguration struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
JWKsURI string `json:"jwks_uri"`
RegistrationEndpoint string `json:"registration_endpoint"`
ScopesSupported []string `json:"scopes_supported"`
ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
TokenEndpointAuthSigningAlgCaluesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported"`
RevocationEnpoint string `json:"revocation_endpoint"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported"`
RevocationEndpointAuthSigningAlgValues []string `json:"revocation_endpoint_auth_signing_alg_values_supported"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported"`
IntrospectionEndpointAuthSigningAlgValues []string `json:"introspection_endpoint_auth_signing_alg_values_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
UserinfoSigningAlgValuesSupported []string `json:"userinfo_signing_alg_values_supported"`
DisplayValuesSupported []string `json:"display_values_supported"`
ClaimTypesSupported []string `json:"claim_types_supported"`
ClaimsSupported []string `json:"claims_supported"`
ClaimsParameterSupported bool `json:"claims_parameter_supported"`
RequestParameterSupported bool `json:"request_parameter_supported"`
RequestURIParameterSupported bool `json:"request_uri_parameter_supported"`
PromptValuesSupported []string `json:"prompt_values_supported"`
DeviceAuthorizaEndpoint string `json:"device_authorization_endpoint"`
AccountManagementURI string `json:"account_management_uri"`
AccountManagementActionsSupported []string `json:"account_management_actions_supported"`
}
func fetchOpenIDConfiguration(httpClient *http.Client, authHostURL string) (*
OpenIDConfiguration, error) {
u, err := url.Parse(authHostURL)
if err != nil {
return nil, err
}
u = u.JoinPath(".well-known/openid-configuration")
resp, err := httpClient.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, &mscError{Code: codeOpenidConfigEndpointNon2xx, Msg: ".well-known/openid-configuration endpoint returned non-200 response"}
}
var oic OpenIDConfiguration
if err := json.NewDecoder(resp.Body).Decode(&oic); err != nil {
return nil, &mscError{Code: codeOpenidConfigDecodingFailed, Msg: err.Error()}
}
return &oic, nil
}
// introspectionResponse as described in the RFC https://datatracker.ietf.org/doc/html/rfc7662#section-2.2
type introspectionResponse struct {
Active bool `json:"active"` // required
Scope string `json:"scope"` // optional
Username string `json:"username"` // optional
TokenType string `json:"token_type"` // optional
Exp *int64 `json:"exp"` // optional
Iat *int64 `json:"iat"` // optional
Nfb *int64 `json:"nfb"` // optional
Sub string `json:"sub"` // optional
Jti string `json:"jti"` // optional
Aud string `json:"aud"` // optional
Iss string `json:"iss"` // optional
}
func (i *introspectionResponse) Scopes() []string {
return strings.Split(i.Scope, " ")
}

View file

@ -16,6 +16,7 @@ import (
"github.com/element-hq/dendrite/setup"
"github.com/element-hq/dendrite/setup/config"
"github.com/element-hq/dendrite/setup/mscs/msc2836"
"github.com/element-hq/dendrite/setup/mscs/msc3861"
"github.com/matrix-org/util"
"github.com/sirupsen/logrus"
)
@ -34,10 +35,11 @@ func Enable(cfg *config.Dendrite, cm *sqlutil.Connections, routers httputil.Rout
func EnableMSC(cfg *config.Dendrite, cm *sqlutil.Connections, routers httputil.Routers, monolith *setup.Monolith, msc string, caches *caching.Caches) error {
switch msc {
case "msc2836":
return msc2836.Enable(cfg, cm, routers, monolith.RoomserverAPI, monolith.FederationAPI, monolith.UserAPI, monolith.KeyRing)
return msc2836.Enable(cfg, cm, routers, monolith.RoomserverAPI, monolith.FederationAPI, monolith.UserVerifierProvider, monolith.KeyRing)
case "msc2444": // enabled inside federationapi
case "msc2753": // enabled inside clientapi
case "msc3861": // enabled inside clientapi
case "msc3861":
return msc3861.Enable(monolith)
default:
logrus.Warnf("EnableMSC: unknown MSC '%s', this MSC is either not supported or is natively supported by Dendrite", msc)
}

View file

@ -36,16 +36,17 @@ func Setup(
lazyLoadCache caching.LazyLoadCache,
fts fulltext.Indexer,
rateLimits *httputil.RateLimits,
userVerifier httputil.UserVerifier,
) {
v1unstablemux := csMux.PathPrefix("/{apiversion:(?:v1|unstable)}/").Subrouter()
v3mux := csMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter()
// TODO: Add AS support for all handlers below.
v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return srp.OnIncomingSyncRequest(req, device)
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
// not specced, but ensure we're rate limiting requests to this endpoint
if r := rateLimits.Limit(req, device); r != nil {
return *r
@ -58,7 +59,7 @@ func Setup(
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/event/{eventID}",
httputil.MakeAuthAPI("rooms_get_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_get_event", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -68,7 +69,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/user/{userId}/filter",
httputil.MakeAuthAPI("put_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("put_filter", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -78,7 +79,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/user/{userId}/filter/{filterId}",
httputil.MakeAuthAPI("get_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("get_filter", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -87,12 +88,12 @@ func Setup(
}),
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return srp.OnIncomingKeyChangeRequest(req, device)
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomId}/context/{eventId}",
httputil.MakeAuthAPI("context", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("context", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -108,7 +109,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v1unstablemux.Handle("/rooms/{roomId}/relations/{eventId}",
httputil.MakeAuthAPI("relations", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("relations", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -122,7 +123,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v1unstablemux.Handle("/rooms/{roomId}/relations/{eventId}/{relType}",
httputil.MakeAuthAPI("relation_type", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("relation_type", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -136,7 +137,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v1unstablemux.Handle("/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}",
httputil.MakeAuthAPI("relation_type_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("relation_type_event", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
@ -150,7 +151,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/search",
httputil.MakeAuthAPI("search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("search", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if !cfg.Fulltext.Enabled {
return util.JSONResponse{
Code: http.StatusNotImplemented,
@ -173,7 +174,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/members",
httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
httputil.MakeAuthAPI("rooms_members", userVerifier, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)

View file

@ -42,6 +42,7 @@ func AddPublicRoutes(
userAPI userapi.SyncUserAPI,
rsAPI api.SyncRoomserverAPI,
caches caching.LazyLoadCache,
userVerifier httputil.UserVerifier,
enableMetrics bool,
) {
js, natsClient := natsInstance.Prepare(processContext, &dendriteCfg.Global.JetStream)
@ -149,5 +150,6 @@ func AddPublicRoutes(
routers.Client, requestPool, syncDB, userAPI,
rsAPI, &dendriteCfg.SyncAPI, caches, fts,
rateLimits,
userVerifier,
)
}

View file

@ -32,6 +32,8 @@ type UserInternalAPI interface {
QuerySearchProfilesAPI // used by p2p demos
QueryAccountByLocalpart(ctx context.Context, req *QueryAccountByLocalpartRequest, res *QueryAccountByLocalpartResponse) (err error)
QueryExternalUserIDByLocalpartAndProvider(ctx context.Context, req *QueryLocalpartExternalIDRequest, res *QueryLocalpartExternalIDResponse) (err error)
PerformLocalpartExternalUserIDCreation(ctx context.Context, req *PerformLocalpartExternalUserIDCreationRequest) (err error)
}
// api functions required by the appservice api
@ -129,6 +131,7 @@ type QuerySearchProfilesAPI interface {
QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error
}
// FIXME: typo in Acccess
// common function for creating authenticated endpoints (used in client/media/sync api)
type QueryAcccessTokenAPI interface {
QueryAccessToken(ctx context.Context, req *QueryAccessTokenRequest, res *QueryAccessTokenResponse) error
@ -316,6 +319,9 @@ type PerformAccountCreationRequest struct {
Localpart string // Required: The localpart for this account. Ignored if account type is guest.
ServerName spec.ServerName // optional: if not specified, default server name used instead
DisplayName string // optional: this is populated only by MAS. In the legacy flow it's not used
AvatarURL string // optional: this is populated only by MAS. In the legacy flow it's not used
AppServiceID string // optional: the application service ID (not user ID) creating this account, if any.
Password string // optional: if missing then this account will be a passwordless account
OnConflict Conflict
@ -653,10 +659,26 @@ type QueryAccountByLocalpartResponse struct {
Account *Account
}
type QueryLocalpartExternalIDRequest struct {
ExternalID string
AuthProvider string
}
type QueryLocalpartExternalIDResponse struct {
LocalpartExternalID *LocalpartExternalID
}
type PerformLocalpartExternalUserIDCreationRequest struct {
Localpart string
ExternalID string
AuthProvider string
}
// API functions required by the clientapi
type ClientKeyAPI interface {
UploadDeviceKeysAPI
QueryKeys(ctx context.Context, req *QueryKeysRequest, res *QueryKeysResponse)
QueryMasterKeys(ctx context.Context, req *QueryMasterKeysRequest, res *QueryMasterKeysResponse)
PerformUploadKeys(ctx context.Context, req *PerformUploadKeysRequest, res *PerformUploadKeysResponse) error
PerformUploadDeviceSignatures(ctx context.Context, req *PerformUploadDeviceSignaturesRequest, res *PerformUploadDeviceSignaturesResponse)
@ -918,6 +940,16 @@ type QueryKeysResponse struct {
Error *KeyError
}
type QueryMasterKeysRequest struct {
UserID string
}
type QueryMasterKeysResponse struct {
Key *types.CrossSigningKey
// Set if there was a fatal error processing this query
Error *KeyError
}
type QueryKeyChangesRequest struct {
// The offset of the last received key event, or sarama.OffsetOldest if this is from the beginning
Offset int64

View file

@ -114,7 +114,9 @@ func (a *UserInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.
byPurpose[fclient.CrossSigningKeyPurposeMaster] = req.MasterKey
for _, key := range req.MasterKey.Keys { // iterates once, see sanityCheckKey
toStore[fclient.CrossSigningKeyPurposeMaster] = key
toStore[fclient.CrossSigningKeyPurposeMaster] = types.CrossSigningKey{
KeyData: key,
}
}
hasMasterKey = true
}
@ -130,7 +132,9 @@ func (a *UserInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.
byPurpose[fclient.CrossSigningKeyPurposeSelfSigning] = req.SelfSigningKey
for _, key := range req.SelfSigningKey.Keys { // iterates once, see sanityCheckKey
toStore[fclient.CrossSigningKeyPurposeSelfSigning] = key
toStore[fclient.CrossSigningKeyPurposeSelfSigning] = types.CrossSigningKey{
KeyData: key,
}
}
}
@ -145,7 +149,9 @@ func (a *UserInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.
byPurpose[fclient.CrossSigningKeyPurposeUserSigning] = req.UserSigningKey
for _, key := range req.UserSigningKey.Keys { // iterates once, see sanityCheckKey
toStore[fclient.CrossSigningKeyPurposeUserSigning] = key
toStore[fclient.CrossSigningKeyPurposeUserSigning] = types.CrossSigningKey{
KeyData: key,
}
}
}
@ -198,7 +204,7 @@ func (a *UserInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.
changed = true
break
}
if !bytes.Equal(old, new) {
if !bytes.Equal(old.KeyData, new.KeyData) {
// One of the existing keys for a purpose we already knew about has
// changed.
changed = true
@ -210,7 +216,7 @@ func (a *UserInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.
}
// Store the keys.
if err := a.KeyDatabase.StoreCrossSigningKeysForUser(ctx, req.UserID, toStore); err != nil {
if err := a.KeyDatabase.StoreCrossSigningKeysForUser(ctx, req.UserID, toStore, nil); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.DB.StoreCrossSigningKeysForUser: %s", err),
}

View file

@ -234,6 +234,19 @@ func (a *UserInternalAPI) PerformMarkAsStaleIfNeeded(ctx context.Context, req *a
return a.Updater.ManualUpdate(ctx, req.Domain, req.UserID)
}
func (a *UserInternalAPI) QueryMasterKeys(ctx context.Context, req *api.QueryMasterKeysRequest, res *api.QueryMasterKeysResponse) {
crossSigningKeyMap, err := a.KeyDatabase.CrossSigningKeysDataForUserAndKeyType(ctx, req.UserID, fclient.CrossSigningKeyPurposeMaster)
if err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("failed to query user cross signing master keys: %s", err),
}
return
}
if key, ok := crossSigningKeyMap[fclient.CrossSigningKeyPurposeMaster]; ok {
res.Key = &key
}
}
// nolint:gocyclo
func (a *UserInternalAPI) QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse) {
var respMu sync.Mutex

View file

@ -7,6 +7,7 @@
package internal
import (
"cmp"
"context"
"database/sql"
"encoding/json"
@ -247,10 +248,17 @@ func (a *UserInternalAPI) PerformAccountCreation(ctx context.Context, req *api.P
return nil
}
if _, _, err = a.DB.SetDisplayName(ctx, req.Localpart, serverName, req.Localpart); err != nil {
displayName := cmp.Or(req.DisplayName, req.Localpart)
if _, _, err = a.DB.SetDisplayName(ctx, req.Localpart, serverName, displayName); err != nil {
return fmt.Errorf("a.DB.SetDisplayName: %w", err)
}
if req.AvatarURL != "" {
if _, _, err := a.DB.SetAvatarURL(ctx, req.Localpart, serverName, req.AvatarURL); err != nil {
return fmt.Errorf("a.DB.SetAvatarURL: %w", err)
}
}
postRegisterJoinRooms(a.Config, acc, a.RSAPI)
res.AccountCreated = true
@ -594,6 +602,15 @@ func (a *UserInternalAPI) QueryAccountByLocalpart(ctx context.Context, req *api.
return
}
func (a *UserInternalAPI) PerformLocalpartExternalUserIDCreation(ctx context.Context, req *api.PerformLocalpartExternalUserIDCreationRequest) (err error) {
return a.DB.CreateLocalpartExternalID(ctx, req.Localpart, req.ExternalID, req.AuthProvider)
}
func (a *UserInternalAPI) QueryExternalUserIDByLocalpartAndProvider(ctx context.Context, req *api.QueryLocalpartExternalIDRequest, res *api.QueryLocalpartExternalIDResponse) (err error) {
res.LocalpartExternalID, err = a.DB.GetLocalpartForExternalID(ctx, req.ExternalID, req.AuthProvider)
return
}
// Return the appservice 'device' or nil if the token is not an appservice. Returns an error if there was a problem
// creating a 'device'.
func (a *UserInternalAPI) queryAppServiceToken(ctx context.Context, token, appServiceUserID string) (*api.Device, error) {

View file

@ -226,9 +226,10 @@ type KeyDatabase interface {
CrossSigningKeysForUser(ctx context.Context, userID string) (map[fclient.CrossSigningKeyPurpose]fclient.CrossSigningKey, error)
CrossSigningKeysDataForUser(ctx context.Context, userID string) (types.CrossSigningKeyMap, error)
CrossSigningKeysDataForUserAndKeyType(ctx context.Context, userID string, keyType fclient.CrossSigningKeyPurpose) (types.CrossSigningKeyMap, error)
CrossSigningSigsForTarget(ctx context.Context, originUserID, targetUserID string, targetKeyID gomatrixserverlib.KeyID) (types.CrossSigningSigMap, error)
StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap) error
StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap, updatableWithoutUIABeforeMs *int64) error
StoreCrossSigningSigsForTarget(ctx context.Context, originUserID string, originKeyID gomatrixserverlib.KeyID, targetUserID string, targetKeyID gomatrixserverlib.KeyID, signature spec.Base64Bytes) error
DeleteStaleDeviceLists(

View file

@ -24,23 +24,29 @@ CREATE TABLE IF NOT EXISTS keyserver_cross_signing_keys (
user_id TEXT NOT NULL,
key_type SMALLINT NOT NULL,
key_data TEXT NOT NULL,
updatable_without_uia_before_ms BIGINT DEFAULT NULL,
PRIMARY KEY (user_id, key_type)
);
`
const selectCrossSigningKeysForUserSQL = "" +
"SELECT key_type, key_data FROM keyserver_cross_signing_keys" +
"SELECT key_type, key_data, updatable_without_uia_before_ms FROM keyserver_cross_signing_keys" +
" WHERE user_id = $1"
const selectCrossSigningKeysForUserAndKeyTypeSQL = "" +
"SELECT key_type, key_data, updatable_without_uia_before_ms FROM keyserver_cross_signing_keys" +
" WHERE user_id = $1 AND key_type = $2"
const upsertCrossSigningKeysForUserSQL = "" +
"INSERT INTO keyserver_cross_signing_keys (user_id, key_type, key_data)" +
" VALUES($1, $2, $3)" +
" ON CONFLICT (user_id, key_type) DO UPDATE SET key_data = $3"
type crossSigningKeysStatements struct {
db *sql.DB
selectCrossSigningKeysForUserStmt *sql.Stmt
upsertCrossSigningKeysForUserStmt *sql.Stmt
db *sql.DB
selectCrossSigningKeysForUserStmt *sql.Stmt
selectCrossSigningKeysForUserAndKeyTypeStmt *sql.Stmt
upsertCrossSigningKeysForUserStmt *sql.Stmt
}
func NewPostgresCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, error) {
@ -53,6 +59,7 @@ func NewPostgresCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, erro
}
return s, sqlutil.StatementList{
{&s.selectCrossSigningKeysForUserStmt, selectCrossSigningKeysForUserSQL},
{&s.selectCrossSigningKeysForUserAndKeyTypeStmt, selectCrossSigningKeysForUserAndKeyTypeSQL},
{&s.upsertCrossSigningKeysForUserStmt, upsertCrossSigningKeysForUserSQL},
}.Prepare(db)
}
@ -69,27 +76,64 @@ func (s *crossSigningKeysStatements) SelectCrossSigningKeysForUser(
for rows.Next() {
var keyTypeInt int16
var keyData spec.Base64Bytes
if err = rows.Scan(&keyTypeInt, &keyData); err != nil {
var updatableWithoutUIABeforeMs *int64
if err = rows.Scan(&keyTypeInt, &keyData, &updatableWithoutUIABeforeMs); err != nil {
return nil, err
}
keyType, ok := types.KeyTypeIntToPurpose[keyTypeInt]
if !ok {
return nil, fmt.Errorf("unknown key purpose int %d", keyTypeInt)
}
r[keyType] = keyData
r[keyType] = types.CrossSigningKey{
UpdatableWithoutUIABeforeMs: updatableWithoutUIABeforeMs,
KeyData: keyData,
}
}
err = rows.Err()
return
}
func (s *crossSigningKeysStatements) SelectCrossSigningKeysForUserAndKeyType(
ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose,
) (r types.CrossSigningKeyMap, err error) {
keyTypeInt, ok := types.KeyTypePurposeToInt[keyType]
if !ok {
return nil, fmt.Errorf("unknown key purpose %q", keyType)
}
rows, err := sqlutil.TxStmt(txn, s.selectCrossSigningKeysForUserAndKeyTypeStmt).QueryContext(ctx, userID, keyTypeInt)
if err != nil {
return nil, err
}
defer internal.CloseAndLogIfError(ctx, rows, "SelectCrossSigningKeysForUserAndKeyType: rows.close() failed")
r = types.CrossSigningKeyMap{}
for rows.Next() {
var keyTypeInt int16
var keyData spec.Base64Bytes
var updatableWithoutUIABeforeMs *int64
if err = rows.Scan(&keyTypeInt, &keyData, &updatableWithoutUIABeforeMs); err != nil {
return nil, err
}
keyType, ok := types.KeyTypeIntToPurpose[keyTypeInt]
if !ok {
return nil, fmt.Errorf("unknown key purpose int %d", keyTypeInt)
}
r[keyType] = types.CrossSigningKey{
UpdatableWithoutUIABeforeMs: updatableWithoutUIABeforeMs,
KeyData: keyData,
}
}
err = rows.Err()
return
}
func (s *crossSigningKeysStatements) UpsertCrossSigningKeysForUser(
ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes,
ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes, updatableWithoutUIABeforeMs *int64,
) error {
keyTypeInt, ok := types.KeyTypePurposeToInt[keyType]
if !ok {
return fmt.Errorf("unknown key purpose %q", keyType)
}
if _, err := sqlutil.TxStmt(txn, s.upsertCrossSigningKeysForUserStmt).ExecContext(ctx, userID, keyTypeInt, keyData); err != nil {
if _, err := sqlutil.TxStmt(txn, s.upsertCrossSigningKeysForUserStmt).ExecContext(ctx, userID, keyTypeInt, keyData, updatableWithoutUIABeforeMs); err != nil {
return fmt.Errorf("s.upsertCrossSigningKeysForUserStmt: %w", err)
}
return nil

View file

@ -1101,12 +1101,12 @@ func (d *KeyDatabase) CrossSigningKeysForUser(ctx context.Context, userID string
}
results := map[fclient.CrossSigningKeyPurpose]fclient.CrossSigningKey{}
for purpose, key := range keyMap {
keyID := gomatrixserverlib.KeyID("ed25519:" + key.Encode())
keyID := gomatrixserverlib.KeyID("ed25519:" + key.KeyData.Encode())
result := fclient.CrossSigningKey{
UserID: userID,
Usage: []fclient.CrossSigningKeyPurpose{purpose},
Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{
keyID: key,
keyID: key.KeyData,
},
}
sigMap, err := d.CrossSigningSigsTable.SelectCrossSigningSigsForTarget(ctx, nil, userID, userID, keyID)
@ -1137,16 +1137,21 @@ func (d *KeyDatabase) CrossSigningKeysDataForUser(ctx context.Context, userID st
return d.CrossSigningKeysTable.SelectCrossSigningKeysForUser(ctx, nil, userID)
}
// CrossSigningKeysForUserAndKeyType returns the latest known cross-signing keys for a user and key type, if any.
func (d *KeyDatabase) CrossSigningKeysDataForUserAndKeyType(ctx context.Context, userID string, keyType fclient.CrossSigningKeyPurpose) (types.CrossSigningKeyMap, error) {
return d.CrossSigningKeysTable.SelectCrossSigningKeysForUserAndKeyType(ctx, nil, userID, keyType)
}
// CrossSigningSigsForTarget returns the signatures for a given user's key ID, if any.
func (d *KeyDatabase) CrossSigningSigsForTarget(ctx context.Context, originUserID, targetUserID string, targetKeyID gomatrixserverlib.KeyID) (types.CrossSigningSigMap, error) {
return d.CrossSigningSigsTable.SelectCrossSigningSigsForTarget(ctx, nil, originUserID, targetUserID, targetKeyID)
}
// StoreCrossSigningKeysForUser stores the latest known cross-signing keys for a user.
func (d *KeyDatabase) StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap) error {
func (d *KeyDatabase) StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap, updatableWithoutUIABeforeMs *int64) error {
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
for keyType, keyData := range keyMap {
if err := d.CrossSigningKeysTable.UpsertCrossSigningKeysForUser(ctx, txn, userID, keyType, keyData); err != nil {
for keyType, key := range keyMap {
if err := d.CrossSigningKeysTable.UpsertCrossSigningKeysForUser(ctx, txn, userID, keyType, key.KeyData, key.UpdatableWithoutUIABeforeMs); err != nil {
return fmt.Errorf("d.CrossSigningKeysTable.InsertCrossSigningKeysForUser: %w", err)
}
}

View file

@ -24,22 +24,28 @@ CREATE TABLE IF NOT EXISTS keyserver_cross_signing_keys (
user_id TEXT NOT NULL,
key_type INTEGER NOT NULL,
key_data TEXT NOT NULL,
updatable_without_uia_before_ms BIGINT DEFAULT NULL,
PRIMARY KEY (user_id, key_type)
);
`
const selectCrossSigningKeysForUserSQL = "" +
"SELECT key_type, key_data FROM keyserver_cross_signing_keys" +
"SELECT key_type, key_data, updatable_without_uia_before_ms FROM keyserver_cross_signing_keys" +
" WHERE user_id = $1"
const selectCrossSigningKeysForUserAndKeyTypeSQL = "" +
"SELECT key_type, key_data, updatable_without_uia_before_ms FROM keyserver_cross_signing_keys" +
" WHERE user_id = $1 AND key_type = $2"
const upsertCrossSigningKeysForUserSQL = "" +
"INSERT OR REPLACE INTO keyserver_cross_signing_keys (user_id, key_type, key_data)" +
" VALUES($1, $2, $3)"
"INSERT OR REPLACE INTO keyserver_cross_signing_keys (user_id, key_type, key_data, updatable_without_uia_before_ms)" +
" VALUES($1, $2, $3, $4)"
type crossSigningKeysStatements struct {
db *sql.DB
selectCrossSigningKeysForUserStmt *sql.Stmt
upsertCrossSigningKeysForUserStmt *sql.Stmt
db *sql.DB
selectCrossSigningKeysForUserStmt *sql.Stmt
selectCrossSigningKeysForUserAndKeyTypeStmt *sql.Stmt
upsertCrossSigningKeysForUserStmt *sql.Stmt
}
func NewSqliteCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, error) {
@ -52,6 +58,7 @@ func NewSqliteCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, error)
}
return s, sqlutil.StatementList{
{&s.selectCrossSigningKeysForUserStmt, selectCrossSigningKeysForUserSQL},
{&s.selectCrossSigningKeysForUserAndKeyTypeStmt, selectCrossSigningKeysForUserAndKeyTypeSQL},
{&s.upsertCrossSigningKeysForUserStmt, upsertCrossSigningKeysForUserSQL},
}.Prepare(db)
}
@ -68,27 +75,64 @@ func (s *crossSigningKeysStatements) SelectCrossSigningKeysForUser(
for rows.Next() {
var keyTypeInt int16
var keyData spec.Base64Bytes
if err = rows.Scan(&keyTypeInt, &keyData); err != nil {
var updatableWithoutUiaBeforeMs *int64
if err = rows.Scan(&keyTypeInt, &keyData, &updatableWithoutUiaBeforeMs); err != nil {
return nil, err
}
keyType, ok := types.KeyTypeIntToPurpose[keyTypeInt]
if !ok {
return nil, fmt.Errorf("unknown key purpose int %d", keyTypeInt)
}
r[keyType] = keyData
r[keyType] = types.CrossSigningKey{
UpdatableWithoutUIABeforeMs: updatableWithoutUiaBeforeMs,
KeyData: keyData,
}
}
err = rows.Err()
return
}
func (s *crossSigningKeysStatements) SelectCrossSigningKeysForUserAndKeyType(
ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose,
) (r types.CrossSigningKeyMap, err error) {
keyTypeInt, ok := types.KeyTypePurposeToInt[keyType]
if !ok {
return nil, fmt.Errorf("unknown key purpose %q", keyType)
}
rows, err := sqlutil.TxStmt(txn, s.selectCrossSigningKeysForUserAndKeyTypeStmt).QueryContext(ctx, userID, keyTypeInt)
if err != nil {
return nil, err
}
defer internal.CloseAndLogIfError(ctx, rows, "SelectCrossSigningKeysForUserAndKeyType: rows.close() failed")
r = types.CrossSigningKeyMap{}
for rows.Next() {
var keyTypeInt int16
var keyData spec.Base64Bytes
var updatableWithoutUIABeforeMs *int64
if err = rows.Scan(&keyTypeInt, &keyData, &updatableWithoutUIABeforeMs); err != nil {
return nil, err
}
keyType, ok := types.KeyTypeIntToPurpose[keyTypeInt]
if !ok {
return nil, fmt.Errorf("unknown key purpose int %d", keyTypeInt)
}
r[keyType] = types.CrossSigningKey{
UpdatableWithoutUIABeforeMs: updatableWithoutUIABeforeMs,
KeyData: keyData,
}
}
err = rows.Err()
return
}
func (s *crossSigningKeysStatements) UpsertCrossSigningKeysForUser(
ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes,
ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes, updatableWithoutUIABeforeMs *int64,
) error {
keyTypeInt, ok := types.KeyTypePurposeToInt[keyType]
if !ok {
return fmt.Errorf("unknown key purpose %q", keyType)
}
if _, err := sqlutil.TxStmt(txn, s.upsertCrossSigningKeysForUserStmt).ExecContext(ctx, userID, keyTypeInt, keyData); err != nil {
if _, err := sqlutil.TxStmt(txn, s.upsertCrossSigningKeysForUserStmt).ExecContext(ctx, userID, keyTypeInt, keyData, updatableWithoutUIABeforeMs); err != nil {
return fmt.Errorf("s.upsertCrossSigningKeysForUserStmt: %w", err)
}
return nil

View file

@ -133,10 +133,6 @@ type LocalpartExternalIDsTable interface {
Delete(ctx context.Context, txn *sql.Tx, externalID, authProvider string) error
}
type UIAuthSessionsTable interface {
SelectByID(ctx context.Context, txn *sql.Tx, sessionID int) (*api.UIAuthSession, error)
}
type NotificationFilter uint32
const (
@ -202,7 +198,8 @@ type StaleDeviceLists interface {
type CrossSigningKeys interface {
SelectCrossSigningKeysForUser(ctx context.Context, txn *sql.Tx, userID string) (r types.CrossSigningKeyMap, err error)
UpsertCrossSigningKeysForUser(ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes) error
SelectCrossSigningKeysForUserAndKeyType(ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose) (r types.CrossSigningKeyMap, err error)
UpsertCrossSigningKeysForUser(ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes, updatableWithoutUIABeforeMs *int64) error
}
type CrossSigningSigs interface {

View file

@ -37,8 +37,13 @@ var KeyTypeIntToPurpose = map[int16]fclient.CrossSigningKeyPurpose{
3: fclient.CrossSigningKeyPurposeUserSigning,
}
type CrossSigningKey struct {
UpdatableWithoutUIABeforeMs *int64
KeyData spec.Base64Bytes
}
// Map of purpose -> public key
type CrossSigningKeyMap map[fclient.CrossSigningKeyPurpose]spec.Base64Bytes
type CrossSigningKeyMap map[fclient.CrossSigningKeyPurpose]CrossSigningKey
// Map of user ID -> key ID -> signature
type CrossSigningSigMap map[string]map[gomatrixserverlib.KeyID]spec.Base64Bytes