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 functions to support refresh tokens #2178

Merged
merged 5 commits into from
Feb 15, 2022
Merged
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
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