Skip to content

Commit

Permalink
Add functions to support refresh tokens (#2178)
Browse files Browse the repository at this point in the history
* Add functions for refreshing access tokens

* Add function to change the client's access token in flight

* Appease the linter

* Use sensible code style
  • Loading branch information
turt2live authored Feb 15, 2022
1 parent 2910e62 commit e86d886
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 10 deletions.
29 changes: 29 additions & 0 deletions src/@types/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// disable lint because these are wire responses
/* eslint-disable camelcase */

/**
* Represents a response to the CSAPI `/refresh` endpoint.
*/
export interface IRefreshTokenResponse {
access_token: string;
expires_in_ms: number;
refresh_token: string;
}

/* eslint-enable camelcase */
47 changes: 38 additions & 9 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ limitations under the License.
*/

import { EventEmitter } from "events";
import { EmoteEvent, MessageEvent, NoticeEvent, IPartialEvent } from "matrix-events-sdk";
import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent } from "matrix-events-sdk";

import { ISyncStateData, SyncApi, SyncState } from "./sync";
import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent } from "./models/event";
Expand Down Expand Up @@ -52,6 +52,7 @@ import {
PREFIX_MEDIA_R0,
PREFIX_R0,
PREFIX_UNSTABLE,
PREFIX_V1,
retryNetworkOperation,
UploadContentResponseType,
} from "./http-api";
Expand Down Expand Up @@ -87,14 +88,7 @@ import {
} from "./crypto/keybackup";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
import { MatrixScheduler } from "./scheduler";
import {
IAuthData,
ICryptoCallbacks,
IMinimalEvent,
IRoomEvent,
IStateEvent,
NotificationCountType,
} from "./matrix";
import { IAuthData, ICryptoCallbacks, IMinimalEvent, IRoomEvent, IStateEvent, NotificationCountType } from "./matrix";
import {
CrossSigningKey,
IAddSecretStorageKeyOpts,
Expand Down Expand Up @@ -160,6 +154,7 @@ import { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, Rule
import { IThreepid } from "./@types/threepids";
import { CryptoStore } from "./crypto/store/base";
import { MediaHandler } from "./webrtc/mediaHandler";
import { IRefreshTokenResponse } from "./@types/auth";

export type Store = IStore;
export type SessionStore = WebStorageSessionStore;
Expand Down Expand Up @@ -6619,6 +6614,14 @@ export class MatrixClient extends EventEmitter {
return this.http.opts.accessToken || null;
}

/**
* Set the access token associated with this account.
* @param {string} token The new access token.
*/
public setAccessToken(token: string) {
this.http.opts.accessToken = token;
}

/**
* @return {boolean} true if there is a valid access_token for this client.
*/
Expand Down Expand Up @@ -6695,6 +6698,7 @@ export class MatrixClient extends EventEmitter {

const params: any = {
auth: auth,
refresh_token: true, // always ask for a refresh token - does nothing if unsupported
};
if (username !== undefined && username !== null) {
params.username = username;
Expand Down Expand Up @@ -6772,6 +6776,31 @@ export class MatrixClient extends EventEmitter {
return this.http.request(callback, Method.Post, "/register", params, data);
}

/**
* Refreshes an access token using a provided refresh token. The refresh token
* must be valid for the current access token known to the client instance.
*
* Note that this function will not cause a logout if the token is deemed
* unknown by the server - the caller is responsible for managing logout
* actions on error.
* @param {string} refreshToken The refresh token.
* @return {Promise<IRefreshTokenResponse>} Resolves to the new token.
* @return {module:http-api.MatrixError} Rejects with an error response.
*/
public refreshToken(refreshToken: string): Promise<IRefreshTokenResponse> {
return this.http.authedRequest(
undefined,
Method.Post,
"/refresh",
undefined,
{ refresh_token: refreshToken },
{
prefix: PREFIX_V1,
inhibitLogoutEmit: true, // we don't want to cause logout loops
},
);
}

/**
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: TODO
Expand Down
8 changes: 7 additions & 1 deletion src/http-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ interface IRequestOpts<T> {
json?: boolean; // defaults to true
qsStringifyOptions?: CoreOptions["qsStringifyOptions"];
bodyParser?(body: string): T;

// Set to true to prevent the request function from emitting
// a Session.logged_out event. This is intended for use on
// endpoints where M_UNKNOWN_TOKEN is a valid/notable error
// response, such as with token refreshes.
inhibitLogoutEmit?: boolean;
}

export interface IUpload {
Expand Down Expand Up @@ -596,7 +602,7 @@ export class MatrixHttpApi {
const requestPromise = this.request<T, O>(callback, method, path, queryParams, data, requestOpts);

requestPromise.catch((err: MatrixError) => {
if (err.errcode == 'M_UNKNOWN_TOKEN') {
if (err.errcode == 'M_UNKNOWN_TOKEN' && !requestOpts?.inhibitLogoutEmit) {
this.eventEmitter.emit("Session.logged_out", err);
} else if (err.errcode == 'M_CONSENT_NOT_GIVEN') {
this.eventEmitter.emit(
Expand Down

0 comments on commit e86d886

Please sign in to comment.