From aee7c3f4738d74b70753cf2c3da545d853acd85d Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 2 Dec 2016 16:51:20 +0000 Subject: [PATCH 1/6] Add Register() with req/resp structs --- client.go | 48 ++++++++++++++++++++++++++++++++++++++++++++---- requests.go | 11 +++++++++++ responses.go | 18 ++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 requests.go diff --git a/client.go b/client.go index 2ada98e..bff490d 100644 --- a/client.go +++ b/client.go @@ -162,9 +162,9 @@ func (cli *Client) StopSync() { // MakeRequest makes a JSON HTTP request to the given URL. // If "resBody" is not nil, the response body will be json.Unmarshalled into it. // -// Returns the HTTP body as bytes on 2xx. Returns an error if the response is not 2xx. This error -// is an HTTPError which includes the returned HTTP status code and possibly a RespError as the -// WrappedError, if the HTTP body could be decoded as a RespError. +// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along +// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned +// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError. func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) { var req *http.Request var err error @@ -205,7 +205,7 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{ msg = msg + ": " + string(contents) } - return nil, HTTPError{ + return contents, HTTPError{ Code: res.StatusCode, Message: msg, WrappedError: wrap, @@ -253,6 +253,46 @@ func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bo return } +func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { + var bodyBytes []byte + bodyBytes, err = cli.MakeRequest("POST", u, req, nil) + if err != nil { + httpErr, ok := err.(HTTPError) + if !ok { // network error + return + } + if httpErr.Code == 401 { + // body should be RespUserInteractive, if it isn't, fail with the error + err = json.Unmarshal(bodyBytes, &uiaResp) + return + } + return + } + // body should be RespRegister + err = json.Unmarshal(bodyBytes, &resp) + return +} + +// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// +// Registers with kind=user. For kind=guest, see RegisterGuest. +func (cli *Client) Register(req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { + u := cli.BuildURL("register") + return cli.register(u, req) +} + +// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// with kind=guest. +// +// For kind=user, see Register. +func (cli *Client) RegisterGuest(req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { + query := map[string]string{ + "kind": "guest", + } + u := cli.BuildURLWithQuery([]string{"register"}, query) + return cli.register(u, req) +} + // JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias // // If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will diff --git a/requests.go b/requests.go new file mode 100644 index 0000000..8e236f3 --- /dev/null +++ b/requests.go @@ -0,0 +1,11 @@ +package gomatrix + +// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +type ReqRegister struct { + Username string `json:"username,omitempty"` + BindEmail bool `json:"bind_email,omitempty"` + Password string `json:"password,omitempty"` + DeviceID string `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name"` + Auth interface{} `json:"auth,omitempty"` +} diff --git a/responses.go b/responses.go index 1528f97..a4564ad 100644 --- a/responses.go +++ b/responses.go @@ -35,6 +35,24 @@ type RespMediaUpload struct { ContentURI string `json:"content_uri"` } +// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api +type RespUserInteractive struct { + Flows []struct { + Stages []string `json:"stages"` + } `json:"flows"` + Params map[string]interface{} `json:"params"` + Session string `json:"string"` +} + +// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +type RespRegister struct { + AccessToken string `json:"access_token"` + DeviceID string `json:"device_id"` + HomeServer string `json:"home_server"` + RefreshToken string `json:"refresh_token"` + UserID string `json:"user_id"` +} + // RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync type RespSync struct { NextBatch string `json:"next_batch"` From 4517ecd6ed3cef14fb81911999ae0925a1d3f2af Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 2 Dec 2016 16:52:32 +0000 Subject: [PATCH 2/6] Remove named return args --- client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index bff490d..11d797c 100644 --- a/client.go +++ b/client.go @@ -276,7 +276,7 @@ func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uia // Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register // // Registers with kind=user. For kind=guest, see RegisterGuest. -func (cli *Client) Register(req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { +func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { u := cli.BuildURL("register") return cli.register(u, req) } @@ -285,7 +285,7 @@ func (cli *Client) Register(req *ReqRegister) (resp *RespRegister, uiaResp *Resp // with kind=guest. // // For kind=user, see Register. -func (cli *Client) RegisterGuest(req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { +func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { query := map[string]string{ "kind": "guest", } From 831dfd27a687ff025edc0dc7d0a1ed9a01fba48d Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 2 Dec 2016 17:02:25 +0000 Subject: [PATCH 3/6] Add missing User-Interactive fields. Only add access_token if one was given. --- client.go | 4 +++- responses.go | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 11d797c..af0f605 100644 --- a/client.go +++ b/client.go @@ -75,7 +75,9 @@ func (cli *Client) BuildBaseURL(urlPath ...string) string { parts = append(parts, urlPath...) hsURL.Path = path.Join(parts...) query := hsURL.Query() - query.Set("access_token", cli.AccessToken) + if cli.AccessToken != "" { + query.Set("access_token", cli.AccessToken) + } hsURL.RawQuery = query.Encode() return hsURL.String() } diff --git a/responses.go b/responses.go index a4564ad..a387630 100644 --- a/responses.go +++ b/responses.go @@ -40,8 +40,11 @@ type RespUserInteractive struct { Flows []struct { Stages []string `json:"stages"` } `json:"flows"` - Params map[string]interface{} `json:"params"` - Session string `json:"string"` + Params map[string]interface{} `json:"params"` + Session string `json:"string"` + Completed []string `json:"completed"` + ErrCode string `json:"errcode"` + Error string `json:"error"` } // RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register From 09b6649b471486d58e6a0c80f4f7d12a6a1bb230 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 2 Dec 2016 17:13:10 +0000 Subject: [PATCH 4/6] Add helper function to find single stage flows --- responses.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/responses.go b/responses.go index a387630..e086824 100644 --- a/responses.go +++ b/responses.go @@ -47,6 +47,16 @@ type RespUserInteractive struct { Error string `json:"error"` } +// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName. +func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool { + for _, f := range r.Flows { + if len(f.Stages) == 1 && f.Stages[0] == stageName { + return true + } + } + return false +} + // RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register type RespRegister struct { AccessToken string `json:"access_token"` From c72cc1919a166fb3fdb8154b8a8a18dbfaaab4cd Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 2 Dec 2016 17:35:07 +0000 Subject: [PATCH 5/6] Add RegisterDummy helper function --- client.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client.go b/client.go index af0f605..fb58da1 100644 --- a/client.go +++ b/client.go @@ -295,6 +295,40 @@ func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInte return cli.register(u, req) } +// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth +// +// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration +// this way. If the homeserver does not, an error is returned. +// +// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{ +// Username: "alice", +// Password: "wonderland", +// }) +// if err != nil { +// panic(err) +// } +// token := res.AccessToken +func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { + res, uia, err := cli.Register(req) + if err != nil && uia == nil { + return nil, err + } + if uia != nil && uia.HasSingleStageFlow("m.login.dummy") { + req.Auth = struct { + Type string `json:"type"` + Session string `json:"session,omitempty"` + }{"m.login.dummy", uia.Session} + res, _, err = cli.Register(req) + if err != nil { + return nil, err + } + } + if res == nil { + return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?") + } + return res, nil +} + // JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias // // If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will From 86e5c414aa47fc8b647206b0504ed76065f332d8 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Mon, 5 Dec 2016 15:05:03 +0000 Subject: [PATCH 6/6] Add setOnClient --- client.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index fb58da1..6a6beb7 100644 --- a/client.go +++ b/client.go @@ -298,17 +298,18 @@ func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInte // RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth // // Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration -// this way. If the homeserver does not, an error is returned. +// this way. If the homeserver does not, an error is returned. If "setOnClient" is true, the access_token and user_id will be set on +// this client instance. // // res, err := cli.RegisterDummy(&gomatrix.ReqRegister{ // Username: "alice", // Password: "wonderland", -// }) +// }, false) // if err != nil { // panic(err) // } // token := res.AccessToken -func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { +func (cli *Client) RegisterDummy(req *ReqRegister, setOnClient bool) (*RespRegister, error) { res, uia, err := cli.Register(req) if err != nil && uia == nil { return nil, err @@ -326,6 +327,10 @@ func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { if res == nil { return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?") } + if setOnClient { + cli.UserID = res.UserID + cli.AccessToken = res.AccessToken + } return res, nil }