Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add matrix 1.3 api feature to refresh access token #294

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type Client struct {
UserID id.UserID // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
DeviceID id.DeviceID // The device ID of the client.
AccessToken string // The access_token for the client.
RefreshToken string // Token to be used for refreshing AccessToken.
RefreshTime time.Time // Time after which the AccessToken must be refreshed.
UserAgent string // The value for the User-Agent header
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
Syncer Syncer // The thing which can process /sync responses
Expand Down Expand Up @@ -865,7 +867,7 @@ func (cli *Client) GetLoginFlows(ctx context.Context) (resp *RespLoginFlows, err
return
}

// Login a user to the homeserver according to https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login
// Login a user to the homeserver according to https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3login
func (cli *Client) Login(ctx context.Context, req *ReqLogin) (resp *RespLogin, err error) {
_, err = cli.MakeFullRequest(ctx, FullRequest{
Method: http.MethodPost,
Expand All @@ -874,7 +876,10 @@ func (cli *Client) Login(ctx context.Context, req *ReqLogin) (resp *RespLogin, e
ResponseJSON: &resp,
SensitiveContent: len(req.Password) > 0 || len(req.Token) > 0,
})
if req.StoreCredentials && err == nil {
if err != nil {
return resp, err
}
if req.StoreCredentials {
cli.DeviceID = resp.DeviceID
cli.AccessToken = resp.AccessToken
cli.UserID = resp.UserID
Expand All @@ -884,7 +889,7 @@ func (cli *Client) Login(ctx context.Context, req *ReqLogin) (resp *RespLogin, e
Str("device_id", cli.DeviceID.String()).
Msg("Stored credentials after login")
}
if req.StoreHomeserverURL && err == nil && resp.WellKnown != nil && len(resp.WellKnown.Homeserver.BaseURL) > 0 {
if req.StoreHomeserverURL && resp.WellKnown != nil && len(resp.WellKnown.Homeserver.BaseURL) > 0 {
var urlErr error
cli.HomeserverURL, urlErr = url.Parse(resp.WellKnown.Homeserver.BaseURL)
if urlErr != nil {
Expand All @@ -898,7 +903,51 @@ func (cli *Client) Login(ctx context.Context, req *ReqLogin) (resp *RespLogin, e
Msg("Updated homeserver URL after login")
}
}
return
cli.RefreshToken = resp.RefreshToken
if req.RefreshToken && resp.RefreshToken != "" {
cli.RefreshTime = cli.getRefreshTime(resp.ExpiresInMS)
}
return resp, err
}

func (cli *Client) getRefreshTime(expiresInMS int64) time.Time {
expiryDuration := time.Duration(expiresInMS) * time.Millisecond
fudgeDuration := -time.Duration(expiryDuration / 5) // 20% fudge before we refresh the token
return time.Now().Add(expiryDuration).Add(fudgeDuration)
}

func (cli *Client) Refresh(ctx context.Context) (err error) {
// Check access token, Refresh if needed.
if time.Now().After(cli.RefreshTime) && cli.RefreshToken != "" {
return cli.refreshInner(ctx)
}
return nil
}

// Refresh the AccessToken with the homeserver according to https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3refresh
func (cli *Client) refreshInner(ctx context.Context) (err error) {
req := ReqRefresh{
RefreshToken: cli.RefreshToken,
}
resp := RespRefresh{}
_, err = cli.MakeFullRequest(ctx, FullRequest{
Method: http.MethodPost,
URL: cli.BuildClientURL("v3", "refresh"),
RequestJSON: req,
ResponseJSON: &resp,
SensitiveContent: true,
})
if err != nil {
return err
}
cli.Log.Debug().
Msg("Refreshed AccessToken successfully")
cli.AccessToken = resp.AccessToken
cli.RefreshToken = resp.RefreshToken
if resp.RefreshToken != "" && resp.ExpiresInMS > 0 {
cli.RefreshTime = cli.getRefreshTime(resp.ExpiresInMS)
}
return nil
}

// Logout the current user. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout
Expand Down
7 changes: 6 additions & 1 deletion requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type UserIdentifier struct {
Phone string `json:"phone,omitempty"`
}

// ReqLogin is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login
// ReqLogin is the JSON request for https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3login
type ReqLogin struct {
Type AuthType `json:"type"`
Identifier UserIdentifier `json:"identifier"`
Expand All @@ -91,6 +91,11 @@ type ReqLogin struct {
StoreHomeserverURL bool `json:"-"`
}

// ReqRefresh is the JSON request for https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3refresh
type ReqRefresh struct {
RefreshToken string `json:"refresh_token"`
}

type ReqUIAuthFallback struct {
Session string `json:"session"`
User string `json:"user"`
Expand Down
19 changes: 14 additions & 5 deletions responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,26 @@ func (rlf *RespLoginFlows) HasFlow(flowType ...AuthType) bool {
return rlf.FirstFlowOfType(flowType...) != nil
}

// RespLogin is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login
// RespLogin is the JSON response for https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3login
type RespLogin struct {
AccessToken string `json:"access_token"`
DeviceID id.DeviceID `json:"device_id"`
UserID id.UserID `json:"user_id"`
WellKnown *ClientWellKnown `json:"well_known,omitempty"`
AccessToken string `json:"access_token"`
DeviceID id.DeviceID `json:"device_id"`
ExpiresInMS int64 `json:"expires_in_ms,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
UserID id.UserID `json:"user_id"`
WellKnown *ClientWellKnown `json:"well_known,omitempty"`
}

// RespLogout is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout
type RespLogout struct{}

// RespRefresh is the JSON response for https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3refresh
type RespRefresh struct {
AccessToken string `json:"access_token"`
ExpiresInMS int64 `json:"expires_in_ms,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}

// RespCreateRoom is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom
type RespCreateRoom struct {
RoomID id.RoomID `json:"room_id"`
Expand Down