diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go new file mode 100644 index 000000000..cb809dcd3 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go @@ -0,0 +1,54 @@ +package auth + +import ( + "fmt" + "net/http" + "strings" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/util" +) + +// VerifyAccessToken verifies that an access token was supplied in the given HTTP request +// and returns the user ID it corresponds to. Returns err if there was a fatal problem checking +// the token. Returns resErr (an error response which can be sent to the client) if the token is invalid. +func VerifyAccessToken(req *http.Request) (userID string, resErr *util.JSONResponse, err error) { + token, tokenErr := extractAccessToken(req) + if tokenErr != nil { + resErr = &util.JSONResponse{ + Code: 401, + JSON: jsonerror.MissingToken(tokenErr.Error()), + } + return + } + if token == "fail" { + err = fmt.Errorf("Fatal error") + } + // TODO: Check the token against the database + return +} + +// extractAccessToken from a request, or return an error detailing what went wrong. The +// error message MUST be human-readable and comprehensible to the client. +func extractAccessToken(req *http.Request) (string, error) { + // cf https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/api/auth.py#L631 + authBearer := req.Header.Get("Authorization") + queryToken := req.URL.Query().Get("access_token") + if authBearer != "" && queryToken != "" { + return "", fmt.Errorf("mixing Authorization headers and access_token query parameters") + } + + if queryToken != "" { + return queryToken, nil + } + + if authBearer != "" { + parts := strings.SplitN(authBearer, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return "", fmt.Errorf("invalid Authorization header") + } + return parts[1], nil + } + + return "", fmt.Errorf("missing access token") +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go b/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go index a0111197c..ea64896db 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go +++ b/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go @@ -1,6 +1,9 @@ package jsonerror -import "fmt" +import ( + "fmt" + "github.com/matrix-org/util" +) // MatrixError represents the "standard error response" in Matrix. // http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards @@ -13,6 +16,20 @@ func (e *MatrixError) Error() string { return fmt.Sprintf("%s: %s", e.ErrCode, e.Err) } +// InternalServerError returns a 500 Internal Server Error in a matrix-compliant +// format. +func InternalServerError() util.JSONResponse { + return util.JSONResponse{ + Code: 500, + JSON: Unknown("Internal Server Error"), + } +} + +// Unknown is an unexpected error +func Unknown(msg string) *MatrixError { + return &MatrixError{"M_UNKNOWN", msg} +} + // Forbidden is an error when the client tries to access a resource // they are not allowed to access. func Forbidden(msg string) *MatrixError { diff --git a/src/github.com/matrix-org/dendrite/clientapi/readers/sync.go b/src/github.com/matrix-org/dendrite/clientapi/readers/sync.go index 5f4516fb1..05def8148 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/readers/sync.go +++ b/src/github.com/matrix-org/dendrite/clientapi/readers/sync.go @@ -3,12 +3,23 @@ package readers import ( "net/http" + "github.com/matrix-org/dendrite/clientapi/auth" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/util" ) // Sync implements /sync func Sync(req *http.Request) util.JSONResponse { logger := util.GetLogger(req.Context()) - logger.Info("Doing stuff...") + userID, resErr, err := auth.VerifyAccessToken(req) + if err != nil { + logger.WithError(err).Error("Failed to verify access token") + return jsonerror.InternalServerError() + } + if resErr != nil { + return *resErr + } + + logger.WithField("userID", userID).Info("Doing stuff...") return util.MessageResponse(404, "Not implemented yet") } diff --git a/src/github.com/matrix-org/dendrite/clientapi/storage/access_tokens_table.go b/src/github.com/matrix-org/dendrite/clientapi/storage/access_tokens_table.go new file mode 100644 index 000000000..fe804443e --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/storage/access_tokens_table.go @@ -0,0 +1,30 @@ +package storage + +import ( + "database/sql" +) + +const accessTokensSchema = ` +CREATE TABLE IF NOT EXISTS access_tokens ( + id BIGINT PRIMARY KEY, + user_id TEXT NOT NULL, + device_id TEXT, + token TEXT NOT NULL, + -- Timestamp (ms) when this access token was last used. + last_used BIGINT, + UNIQUE(token) +); + +CREATE INDEX access_tokens_device_id ON access_tokens (user_id, device_id) ; +` + +type accessTokenStatements struct { +} + +func (s *accessTokenStatements) prepare(db *sql.DB) (err error) { + _, err = db.Exec(accessTokensSchema) + if err != nil { + return + } + return +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/storage/sql.go b/src/github.com/matrix-org/dendrite/clientapi/storage/sql.go new file mode 100644 index 000000000..91092e42c --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/storage/sql.go @@ -0,0 +1,37 @@ +package storage + +import ( + "database/sql" +) + +type statements struct { + userIPStatements + accessTokenStatements + usersStatements +} + +func (s *statements) prepare(db *sql.DB) error { + var err error + + if err = s.prepareUserStatements(db); err != nil { + return err + } + + return nil +} + +func (s *statements) prepareUserStatements(db *sql.DB) error { + var err error + + if err = s.accessTokenStatements.prepare(db); err != nil { + return err + } + if err = s.userIPStatements.prepare(db); err != nil { + return err + } + if err = s.usersStatements.prepare(db); err != nil { + return err + } + + return nil +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/storage/users_ips_table.go b/src/github.com/matrix-org/dendrite/clientapi/storage/users_ips_table.go new file mode 100644 index 000000000..26f2f5b80 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/storage/users_ips_table.go @@ -0,0 +1,30 @@ +package storage + +import ( + "database/sql" +) + +const userIPsSchema = ` +CREATE TABLE IF NOT EXISTS user_ips ( + user_id TEXT NOT NULL, + access_token TEXT NOT NULL, + device_id TEXT, + ip TEXT NOT NULL, + user_agent TEXT NOT NULL, + last_seen BIGINT NOT NULL +); + +CREATE INDEX user_ips_user_ip ON user_ips(user_id, access_token, ip); +CREATE INDEX user_ips_device_id ON user_ips (user_id, device_id, last_seen); +` + +type userIPStatements struct { +} + +func (s *userIPStatements) prepare(db *sql.DB) (err error) { + _, err = db.Exec(userIPsSchema) + if err != nil { + return + } + return +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/storage/users_table.go b/src/github.com/matrix-org/dendrite/clientapi/storage/users_table.go new file mode 100644 index 000000000..f7c9c6b05 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/storage/users_table.go @@ -0,0 +1,34 @@ +package storage + +import ( + "database/sql" +) + +const usersSchema = ` +CREATE TABLE IF NOT EXISTS users ( + user_id TEXT NOT NULL, + -- bcrypt hash of the users password. Can be null for passwordless users like + -- application service users. + password_hash TEXT, + -- Timestamp (ms) when this user was registered on the server. + created_at BIGINT, + -- The ID of the application service which created this user, if applicable. + appservice_id TEXT, + -- Flag which if set indicates this user is a server administrator. + is_admin SMALLINT DEFAULT 0 NOT NULL, + -- Flag which if set indicates this user is a guest. + is_guest SMALLINT DEFAULT 0 NOT NULL, + UNIQUE(user_id) +); +` + +type usersStatements struct { +} + +func (s *usersStatements) prepare(db *sql.DB) (err error) { + _, err = db.Exec(usersSchema) + if err != nil { + return + } + return +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/sendmessage.go b/src/github.com/matrix-org/dendrite/clientapi/writers/sendmessage.go index ae4103da4..4b8ad9052 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/writers/sendmessage.go +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/sendmessage.go @@ -3,12 +3,27 @@ package writers import ( "net/http" + log "github.com/Sirupsen/logrus" + "github.com/matrix-org/dendrite/clientapi/auth" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/util" ) // SendMessage implements /rooms/{roomID}/send/{eventType} func SendMessage(req *http.Request, roomID, eventType string) util.JSONResponse { logger := util.GetLogger(req.Context()) - logger.WithField("roomID", roomID).WithField("eventType", eventType).Info("Doing stuff...") + userID, resErr, err := auth.VerifyAccessToken(req) + if err != nil { + logger.WithError(err).Error("Failed to verify access token") + return jsonerror.InternalServerError() + } + if resErr != nil { + return *resErr + } + logger.WithFields(log.Fields{ + "roomID": roomID, + "eventType": eventType, + "userID": userID, + }).Info("Doing stuff...") return util.MessageResponse(404, "Not implemented yet") }