14 |
15 |
16 |
17 |
18 |
19 |

20 |
21 |
22 |
23 | Single-sign on:
24 |
28 |
29 |
30 |
31 | Password Authentication:
32 |
40 |
41 |
42 |
43 | Log in currently unavailable.
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/setup/config/config_userapi.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "golang.org/x/crypto/bcrypt"
4 |
5 | type UserAPI struct {
6 | Matrix *Global `yaml:"-"`
7 |
8 | // The cost when hashing passwords.
9 | BCryptCost int `yaml:"bcrypt_cost"`
10 |
11 | // The length of time an OpenID token is condidered valid in milliseconds
12 | OpenIDTokenLifetimeMS int64 `yaml:"openid_token_lifetime_ms"`
13 |
14 | // Disable TLS validation on HTTPS calls to push gatways. NOT RECOMMENDED!
15 | PushGatewayDisableTLSValidation bool `yaml:"push_gateway_disable_tls_validation"`
16 |
17 | // The Account database stores the login details and account information
18 | // for local users. It is accessed by the UserAPI.
19 | AccountDatabase DatabaseOptions `yaml:"account_database,omitempty"`
20 |
21 | // Users who register on this homeserver will automatically
22 | // be joined to the rooms listed under this option.
23 | AutoJoinRooms []string `yaml:"auto_join_rooms"`
24 |
25 | // The number of workers to start for the DeviceListUpdater. Defaults to 8.
26 | // This only needs updating if the "InputDeviceListUpdate" stream keeps growing indefinitely.
27 | WorkerCount int `yaml:"worker_count"`
28 | }
29 |
30 | const DefaultOpenIDTokenLifetimeMS = 3600000 // 60 minutes
31 |
32 | func (c *UserAPI) Defaults(opts DefaultOpts) {
33 | c.BCryptCost = bcrypt.DefaultCost
34 | c.OpenIDTokenLifetimeMS = DefaultOpenIDTokenLifetimeMS
35 | c.WorkerCount = 8
36 | if opts.Generate {
37 | if !opts.SingleDatabase {
38 | c.AccountDatabase.ConnectionString = "file:userapi_accounts.db"
39 | }
40 | }
41 | }
42 |
43 | func (c *UserAPI) Verify(configErrs *ConfigErrors) {
44 | checkPositive(configErrs, "user_api.openid_token_lifetime_ms", c.OpenIDTokenLifetimeMS)
45 | if c.Matrix.DatabaseOptions.ConnectionString == "" {
46 | checkNotEmpty(configErrs, "user_api.account_database.connection_string", string(c.AccountDatabase.ConnectionString))
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/clientapi/routing/deactivate.go:
--------------------------------------------------------------------------------
1 | package routing
2 |
3 | import (
4 | "io"
5 | "net/http"
6 |
7 | "github.com/matrix-org/dendrite/clientapi/auth"
8 | "github.com/matrix-org/dendrite/userapi/api"
9 | "github.com/matrix-org/gomatrixserverlib"
10 | "github.com/matrix-org/gomatrixserverlib/spec"
11 | "github.com/matrix-org/util"
12 | )
13 |
14 | // Deactivate handles POST requests to /account/deactivate
15 | func Deactivate(
16 | req *http.Request,
17 | userInteractiveAuth *auth.UserInteractive,
18 | accountAPI api.ClientUserAPI,
19 | deviceAPI *api.Device,
20 | ) util.JSONResponse {
21 | ctx := req.Context()
22 | defer req.Body.Close() // nolint:errcheck
23 | bodyBytes, err := io.ReadAll(req.Body)
24 | if err != nil {
25 | return util.JSONResponse{
26 | Code: http.StatusBadRequest,
27 | JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
28 | }
29 | }
30 |
31 | login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, deviceAPI)
32 | if errRes != nil {
33 | return *errRes
34 | }
35 |
36 | localpart, serverName, err := gomatrixserverlib.SplitID('@', login.Username())
37 | if err != nil {
38 | util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
39 | return util.JSONResponse{
40 | Code: http.StatusInternalServerError,
41 | JSON: spec.InternalServerError{},
42 | }
43 | }
44 |
45 | var res api.PerformAccountDeactivationResponse
46 | err = accountAPI.PerformAccountDeactivation(ctx, &api.PerformAccountDeactivationRequest{
47 | Localpart: localpart,
48 | ServerName: serverName,
49 | }, &res)
50 | if err != nil {
51 | util.GetLogger(ctx).WithError(err).Error("userAPI.PerformAccountDeactivation failed")
52 | return util.JSONResponse{
53 | Code: http.StatusInternalServerError,
54 | JSON: spec.InternalServerError{},
55 | }
56 | }
57 |
58 | return util.JSONResponse{
59 | Code: http.StatusOK,
60 | JSON: struct{}{},
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/clientapi/auth/login_application_service.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 The Matrix.org Foundation C.I.C.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package auth
16 |
17 | import (
18 | "context"
19 |
20 | "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
21 | "github.com/matrix-org/dendrite/clientapi/httputil"
22 | "github.com/matrix-org/dendrite/internal"
23 | "github.com/matrix-org/dendrite/setup/config"
24 | "github.com/matrix-org/util"
25 | )
26 |
27 | // LoginTypeApplicationService describes how to authenticate as an
28 | // application service
29 | type LoginTypeApplicationService struct {
30 | Config *config.ClientAPI
31 | Token string
32 | }
33 |
34 | // Name implements Type
35 | func (t *LoginTypeApplicationService) Name() string {
36 | return authtypes.LoginTypeApplicationService
37 | }
38 |
39 | // LoginFromJSON implements Type
40 | func (t *LoginTypeApplicationService) LoginFromJSON(
41 | ctx context.Context, reqBytes []byte,
42 | ) (*Login, LoginCleanupFunc, *util.JSONResponse) {
43 | var r Login
44 | if err := httputil.UnmarshalJSON(reqBytes, &r); err != nil {
45 | return nil, nil, err
46 | }
47 |
48 | _, err := internal.ValidateApplicationServiceRequest(t.Config, r.Identifier.User, t.Token)
49 | if err != nil {
50 | return nil, nil, err
51 | }
52 |
53 | cleanup := func(ctx context.Context, j *util.JSONResponse) {}
54 | return &r, cleanup, nil
55 | }
56 |
--------------------------------------------------------------------------------
/clientapi/userutil/userutil.go:
--------------------------------------------------------------------------------
1 | // Licensed under the Apache License, Version 2.0 (the "License");
2 | // you may not use this file except in compliance with the License.
3 | // You may obtain a copy of the License at
4 | //
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to in writing, software
8 | // distributed under the License is distributed on an "AS IS" BASIS,
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | // See the License for the specific language governing permissions and
11 | // limitations under the License.
12 |
13 | package userutil
14 |
15 | import (
16 | "errors"
17 | "fmt"
18 | "strings"
19 |
20 | "github.com/matrix-org/dendrite/setup/config"
21 | "github.com/matrix-org/gomatrixserverlib"
22 | "github.com/matrix-org/gomatrixserverlib/spec"
23 | )
24 |
25 | // ParseUsernameParam extracts localpart from usernameParam.
26 | // usernameParam can either be a user ID or just the localpart/username.
27 | // If serverName is passed, it is verified against the domain obtained from usernameParam (if present)
28 | // Returns error in case of invalid usernameParam.
29 | func ParseUsernameParam(usernameParam string, cfg *config.Global) (string, spec.ServerName, error) {
30 | localpart := usernameParam
31 |
32 | if strings.HasPrefix(usernameParam, "@") {
33 | lp, domain, err := gomatrixserverlib.SplitID('@', usernameParam)
34 |
35 | if err != nil {
36 | return "", "", errors.New("invalid username")
37 | }
38 |
39 | if !cfg.IsLocalServerName(domain) {
40 | return "", "", errors.New("user ID does not belong to this server")
41 | }
42 |
43 | return lp, domain, nil
44 | }
45 | return localpart, cfg.ServerName, nil
46 | }
47 |
48 | // MakeUserID generates user ID from localpart & server name
49 | func MakeUserID(localpart string, server spec.ServerName) string {
50 | return fmt.Sprintf("@%s:%s", localpart, string(server))
51 | }
52 |
--------------------------------------------------------------------------------
/internal/pushrules/condition.go:
--------------------------------------------------------------------------------
1 | package pushrules
2 |
3 | // A Condition dictates extra conditions for a matching rules. See
4 | // ConditionKind.
5 | type Condition struct {
6 | // Kind is the primary discriminator for the condition
7 | // type. Required.
8 | Kind ConditionKind `json:"kind"`
9 |
10 | // Key indicates the dot-separated path of Event fields to
11 | // match. Required for EventMatchCondition and
12 | // SenderNotificationPermissionCondition.
13 | Key string `json:"key,omitempty"`
14 |
15 | // Pattern indicates the value pattern that must match. Required
16 | // for EventMatchCondition.
17 | Pattern *string `json:"pattern,omitempty"`
18 |
19 | // Is indicates the condition that must be fulfilled. Required for
20 | // RoomMemberCountCondition.
21 | Is string `json:"is,omitempty"`
22 | }
23 |
24 | // ConditionKind represents a kind of condition.
25 | //
26 | // SPEC: Unrecognised conditions MUST NOT match any events,
27 | // effectively making the push rule disabled.
28 | type ConditionKind string
29 |
30 | const (
31 | UnknownCondition ConditionKind = ""
32 |
33 | // EventMatchCondition indicates the condition looks for a key
34 | // path and matches a pattern. How paths that don't reference a
35 | // simple value match against rules is implementation-specific.
36 | EventMatchCondition ConditionKind = "event_match"
37 |
38 | // ContainsDisplayNameCondition indicates the current user's
39 | // display name must be found in the content body.
40 | ContainsDisplayNameCondition ConditionKind = "contains_display_name"
41 |
42 | // RoomMemberCountCondition matches a simple arithmetic comparison
43 | // against the total number of members in a room.
44 | RoomMemberCountCondition ConditionKind = "room_member_count"
45 |
46 | // SenderNotificationPermissionCondition compares power level for
47 | // the sender in the event's room.
48 | SenderNotificationPermissionCondition ConditionKind = "sender_notification_permission"
49 | )
50 |
--------------------------------------------------------------------------------
/userapi/storage/postgres/deltas/2022042612000000_xsigning_idx.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 The Matrix.org Foundation C.I.C.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package deltas
16 |
17 | import (
18 | "context"
19 | "database/sql"
20 | "fmt"
21 | )
22 |
23 | func UpFixCrossSigningSignatureIndexes(ctx context.Context, tx *sql.Tx) error {
24 | _, err := tx.ExecContext(ctx, `
25 | ALTER TABLE keyserver_cross_signing_sigs DROP CONSTRAINT keyserver_cross_signing_sigs_pkey;
26 | ALTER TABLE keyserver_cross_signing_sigs ADD PRIMARY KEY (origin_user_id, origin_key_id, target_user_id, target_key_id);
27 |
28 | CREATE INDEX IF NOT EXISTS keyserver_cross_signing_sigs_idx ON keyserver_cross_signing_sigs (origin_user_id, target_user_id, target_key_id);
29 | `)
30 | if err != nil {
31 | return fmt.Errorf("failed to execute upgrade: %w", err)
32 | }
33 | return nil
34 | }
35 |
36 | func DownFixCrossSigningSignatureIndexes(ctx context.Context, tx *sql.Tx) error {
37 | _, err := tx.ExecContext(ctx, `
38 | ALTER TABLE keyserver_cross_signing_sigs DROP CONSTRAINT keyserver_cross_signing_sigs_pkey;
39 | ALTER TABLE keyserver_cross_signing_sigs ADD PRIMARY KEY (origin_user_id, target_user_id, target_key_id);
40 |
41 | DROP INDEX IF EXISTS keyserver_cross_signing_sigs_idx;
42 | `)
43 | if err != nil {
44 | return fmt.Errorf("failed to execute downgrade: %w", err)
45 | }
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/setup/process/process.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | "github.com/getsentry/sentry-go"
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | type ProcessContext struct {
12 | mu sync.RWMutex
13 | wg sync.WaitGroup // used to wait for components to shutdown
14 | ctx context.Context // cancelled when Stop is called
15 | shutdown context.CancelFunc // shut down Dendrite
16 | degraded map[string]struct{} // reasons why the process is degraded
17 | }
18 |
19 | func NewProcessContext() *ProcessContext {
20 | ctx, shutdown := context.WithCancel(context.Background())
21 | return &ProcessContext{
22 | ctx: ctx,
23 | shutdown: shutdown,
24 | wg: sync.WaitGroup{},
25 | }
26 | }
27 |
28 | func (b *ProcessContext) Context() context.Context {
29 | return context.WithValue(b.ctx, "scope", "process") // nolint:staticcheck
30 | }
31 |
32 | func (b *ProcessContext) ComponentStarted() {
33 | b.wg.Add(1)
34 | }
35 |
36 | func (b *ProcessContext) ComponentFinished() {
37 | b.wg.Done()
38 | }
39 |
40 | func (b *ProcessContext) ShutdownDendrite() {
41 | b.shutdown()
42 | }
43 |
44 | func (b *ProcessContext) WaitForShutdown() <-chan struct{} {
45 | return b.ctx.Done()
46 | }
47 |
48 | func (b *ProcessContext) WaitForComponentsToFinish() {
49 | b.wg.Wait()
50 | }
51 |
52 | func (b *ProcessContext) Degraded(err error) {
53 | b.mu.Lock()
54 | defer b.mu.Unlock()
55 | if _, ok := b.degraded[err.Error()]; !ok {
56 | logrus.WithError(err).Warn("Dendrite has entered a degraded state")
57 | sentry.CaptureException(err)
58 | b.degraded[err.Error()] = struct{}{}
59 | }
60 | }
61 |
62 | func (b *ProcessContext) IsDegraded() (bool, []string) {
63 | b.mu.RLock()
64 | defer b.mu.RUnlock()
65 | if len(b.degraded) == 0 {
66 | return false, nil
67 | }
68 | reasons := make([]string, 0, len(b.degraded))
69 | for reason := range b.degraded {
70 | reasons = append(reasons, reason)
71 | }
72 | return true, reasons
73 | }
74 |
--------------------------------------------------------------------------------
/roomserver/storage/shared/prepare.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017-2018 New Vector Ltd
2 | // Copyright 2019-2020 The Matrix.org Foundation C.I.C.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package shared
17 |
18 | import (
19 | "context"
20 | "database/sql"
21 | )
22 |
23 | // StatementList is a list of SQL statements to prepare and a pointer to where to store the resulting prepared statement.
24 | type StatementList []struct {
25 | Statement **sql.Stmt
26 | SQL string
27 | }
28 |
29 | // Prepare the SQL for each statement in the list and assign the result to the prepared statement.
30 | func (s StatementList) Prepare(db *sql.DB) (err error) {
31 | for _, statement := range s {
32 | if *statement.Statement, err = db.Prepare(statement.SQL); err != nil {
33 | return
34 | }
35 | }
36 | return
37 | }
38 |
39 | type transaction struct {
40 | ctx context.Context
41 | txn *sql.Tx
42 | }
43 |
44 | // Commit implements types.Transaction
45 | func (t *transaction) Commit() error {
46 | if t.txn == nil {
47 | // The Updater structs can operate in useTxns=false mode. The code will still call this though.
48 | return nil
49 | }
50 | return t.txn.Commit()
51 | }
52 |
53 | // Rollback implements types.Transaction
54 | func (t *transaction) Rollback() error {
55 | if t.txn == nil {
56 | // The Updater structs can operate in useTxns=false mode. The code will still call this though.
57 | return nil
58 | }
59 | return t.txn.Rollback()
60 | }
61 |
--------------------------------------------------------------------------------
/test/wasm/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /*
4 | Copyright 2021 The Matrix.org Foundation C.I.C.
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | const fs = require('fs');
20 | const path = require('path');
21 | const childProcess = require('child_process');
22 |
23 | (async function() {
24 | // sql.js
25 | const initSqlJs = require('sql.js');
26 | await initSqlJs().then(SQL => {
27 | global._go_sqlite = SQL;
28 | console.log("Loaded sqlite")
29 | });
30 | // dendritejs expects to write to `/idb` so we create that here
31 | // Since this is testing only, we use the default in-memory FS
32 | global._go_sqlite.FS.mkdir("/idb");
33 |
34 | // WebSocket
35 | const WebSocket = require('isomorphic-ws');
36 | global.WebSocket = WebSocket;
37 |
38 | // Load the generic Go Wasm exec helper inline to trigger built-in run call
39 | // This approach avoids copying `wasm_exec.js` into the repo, which is nice
40 | // to aim for since it can differ between Go versions.
41 | const goRoot = await new Promise((resolve, reject) => {
42 | childProcess.execFile('go', ['env', 'GOROOT'], (err, out) => {
43 | if (err) {
44 | reject("Can't find go");
45 | }
46 | resolve(out.trim());
47 | });
48 | });
49 | const execPath = path.join(goRoot, 'misc/wasm/wasm_exec.js');
50 | const execCode = fs.readFileSync(execPath, 'utf8');
51 | eval(execCode);
52 | })();
53 |
--------------------------------------------------------------------------------
/clientapi/routing/server_notices_test.go:
--------------------------------------------------------------------------------
1 | package routing
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_sendServerNoticeRequest_validate(t *testing.T) {
8 | type fields struct {
9 | UserID string `json:"user_id,omitempty"`
10 | Content struct {
11 | MsgType string `json:"msgtype,omitempty"`
12 | Body string `json:"body,omitempty"`
13 | } `json:"content,omitempty"`
14 | Type string `json:"type,omitempty"`
15 | StateKey string `json:"state_key,omitempty"`
16 | }
17 |
18 | content := struct {
19 | MsgType string `json:"msgtype,omitempty"`
20 | Body string `json:"body,omitempty"`
21 | }{
22 | MsgType: "m.text",
23 | Body: "Hello world!",
24 | }
25 |
26 | tests := []struct {
27 | name string
28 | fields fields
29 | wantOk bool
30 | }{
31 | {
32 | name: "empty request",
33 | fields: fields{},
34 | },
35 | {
36 | name: "msgtype empty",
37 | fields: fields{
38 | UserID: "@alice:localhost",
39 | Content: struct {
40 | MsgType string `json:"msgtype,omitempty"`
41 | Body string `json:"body,omitempty"`
42 | }{
43 | Body: "Hello world!",
44 | },
45 | },
46 | },
47 | {
48 | name: "msg body empty",
49 | fields: fields{
50 | UserID: "@alice:localhost",
51 | },
52 | },
53 | {
54 | name: "statekey empty",
55 | fields: fields{
56 | UserID: "@alice:localhost",
57 | Content: content,
58 | },
59 | wantOk: true,
60 | },
61 | {
62 | name: "type empty",
63 | fields: fields{
64 | UserID: "@alice:localhost",
65 | Content: content,
66 | },
67 | wantOk: true,
68 | },
69 | }
70 | for _, tt := range tests {
71 | t.Run(tt.name, func(t *testing.T) {
72 | r := sendServerNoticeRequest{
73 | UserID: tt.fields.UserID,
74 | Content: tt.fields.Content,
75 | Type: tt.fields.Type,
76 | StateKey: tt.fields.StateKey,
77 | }
78 | if gotOk := r.valid(); gotOk != tt.wantOk {
79 | t.Errorf("valid() = %v, want %v", gotOk, tt.wantOk)
80 | }
81 | })
82 | }
83 | }
84 |
--------------------------------------------------------------------------------