Skip to content

Commit

Permalink
feat: no client secret required in PKCE (get/refresh/revoke token) (r…
Browse files Browse the repository at this point in the history
…ingcentral#140)

* feat: no client secret required in pkce (get/refresh/revoke) token
  • Loading branch information
embbnux authored Jun 29, 2020
1 parent c6637b0 commit 2a70eaf
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 14 deletions.
3 changes: 3 additions & 0 deletions sdk/src/platform/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
46 changes: 45 additions & 1 deletion sdk/src/platform/Platform-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}),
);

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
}),
);
});
});
54 changes: 41 additions & 13 deletions sdk/src/platform/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);

Expand All @@ -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();

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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({
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}

0 comments on commit 2a70eaf

Please sign in to comment.