diff --git a/sdk/src/platform/Auth.ts b/sdk/src/platform/Auth.ts index 309c6d37..7cf52f29 100644 --- a/sdk/src/platform/Auth.ts +++ b/sdk/src/platform/Auth.ts @@ -82,4 +82,7 @@ export interface AuthData { refresh_token_expires_in?: string; refresh_token_expire_time?: number; scope?: string; + code_verifier?: string; + owner_id?: string; + endpoint_id?: string; } diff --git a/sdk/src/platform/Platform-spec.ts b/sdk/src/platform/Platform-spec.ts index 09c63626..ab4ee3d7 100644 --- a/sdk/src/platform/Platform-spec.ts +++ b/sdk/src/platform/Platform-spec.ts @@ -83,7 +83,9 @@ describe('RingCentral.platform.Platform', () => { access_token_ttl: 100, refresh_token_ttl: 100, }); - expect((await platform.auth().data()).access_token).to.equal('ACCESS_TOKEN'); + const authData = await platform.auth().data(); + expect(authData.access_token).to.equal('ACCESS_TOKEN'); + expect(authData.code_verifier).not.to.be.empty; }), ); @@ -363,6 +365,27 @@ describe('RingCentral.platform.Platform', () => { expect(res[2].increment).to.equal(3); }), ); + + it( + 'skip auth header when auth data with code_verifier', + asyncTest(async sdk => { + tokenRefresh(); + + const platform = sdk.platform(); + const client = sdk.client(); + await platform.auth().cancelAccessToken(); + await platform.auth().setData({ + code_verifier: '1212121', + }); + let request; + client.on(client.events.requestSuccess, (_, r) => { + request = r; + }); + await platform.refresh(); + expect(request.headers.get('authorization')).to.equal(null); + expect((await platform.auth().data()).access_token).to.equal('ACCESS_TOKEN_FROM_REFRESH'); + }), + ); }); describe('get, post, put, patch, delete', () => { @@ -687,4 +710,25 @@ describe('RingCentral.platform.Platform', () => { }), ); }); + + describe('logout', () => { + it( + 'skip auth header when auth data with code_verifier', + asyncTest(async sdk => { + logout(); + const platform = sdk.platform(); + const client = sdk.client(); + await platform.auth().setData({ + code_verifier: '1212121', + }); + let request; + client.on(client.events.requestSuccess, (_, r) => { + request = r; + }); + await platform.logout(); + expect(request.headers.get('authorization')).to.equal(null); + expect(await platform.auth().accessTokenValid()).to.equal(false); + }), + ); + }); }); diff --git a/sdk/src/platform/Platform.ts b/sdk/src/platform/Platform.ts index 51e6da79..efbf082e 100644 --- a/sdk/src/platform/Platform.ts +++ b/sdk/src/platform/Platform.ts @@ -2,7 +2,7 @@ import {EventEmitter} from 'events'; import {createHash, randomBytes} from 'crypto'; import * as qs from 'querystring'; -import Auth, {AuthOptions} from './Auth'; +import Auth, {AuthOptions, AuthData} from './Auth'; import * as Constants from '../core/Constants'; import Cache from '../core/Cache'; import Client, {ApiError} from '../http/Client'; @@ -215,6 +215,7 @@ export default class Platform extends EventEmitter { let codeVerifier: any = randomBytes(32); codeVerifier = codeVerifier .toString('base64') + .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); return codeVerifier; @@ -359,7 +360,6 @@ export default class Platform extends EventEmitter { body.code = code; body.redirect_uri = this._redirectUri; if (this._codeVerifier && this._codeVerifier.length > 0) { - body.client_id = this._clientId; body.code_verifier = this._codeVerifier; skipAuthHeader = true; } @@ -374,7 +374,10 @@ export default class Platform extends EventEmitter { json = await response.clone().json(); } - await this._auth.setData(json); + await this._auth.setData({ + ...json, + code_verifier: this._codeVerifier, + }); this.emit(this.events.loginSuccess, response); @@ -399,13 +402,14 @@ export default class Platform extends EventEmitter { // Perform sanity checks if (!authData.refresh_token) throw new Error('Refresh token is missing'); if (!this._auth.refreshTokenValid()) throw new Error('Refresh token has expired'); - - const res = await this._tokenRequest(this._tokenEndpoint, { + const body: RefreshTokenBody = { grant_type: 'refresh_token', refresh_token: authData.refresh_token, - access_token_ttl: authData.expires_in + 1, - refresh_token_ttl: authData.refresh_token_expires_in + 1, - }); + access_token_ttl: parseInt(authData.expires_in), + refresh_token_ttl: parseInt(authData.refresh_token_expires_in), + }; + const skipAuthHeader = this._shouldSkipAuthHeader(authData); + const res = await this._tokenRequest(this._tokenEndpoint, body, skipAuthHeader); const json = await res.clone().json(); @@ -459,10 +463,15 @@ export default class Platform extends EventEmitter { let res = null; //FIXME https://developers.ringcentral.com/legacy-api-reference/index.html#!#RefRevokeToken.html requires secret - if (this._revokeEndpoint && this._clientSecret) { - res = await this._tokenRequest(this._revokeEndpoint, { - token: (await this._auth.data()).access_token, - }); + if (this._revokeEndpoint) { + const authData = await this._auth.data(); + const body: RevokeTokenBody = { + token: authData.access_token, + }; + const skipAuthHeader = this._shouldSkipAuthHeader(authData); + if (skipAuthHeader || this._clientSecret) { + res = await this._tokenRequest(this._revokeEndpoint, body, skipAuthHeader); + } } await this._cache.clean(); @@ -578,7 +587,9 @@ export default class Platform extends EventEmitter { let headers: TokenRequestHeaders = { 'Content-Type': Client._urlencodedContentType, }; - if (!skipAuthHeader) { + if (skipAuthHeader) { + body.client_id = this._clientId; + } else { headers.Authorization = this.basicAuthHeader(); } return this.send({ @@ -599,6 +610,10 @@ export default class Platform extends EventEmitter { const data = await this._auth.data(); return (data.token_type || 'Bearer') + (data.access_token ? ` ${data.access_token}` : ''); } + + private _shouldSkipAuthHeader(authData: AuthData) { + return !!(authData.code_verifier && authData.code_verifier.length > 0); + } } export interface PlatformOptions extends AuthOptions { @@ -709,3 +724,16 @@ export interface TokenRequestHeaders { 'Content-Type': string; Authorization?: string; } + +export interface RefreshTokenBody { + grant_type: 'refresh_token'; + refresh_token: string; + access_token_ttl: number; + refresh_token_ttl: number; + client_id?: string; +} + +export interface RevokeTokenBody { + token: string; + client_id?: string; +}