Skip to content

Commit 86a6886

Browse files
authored
support implicit flow auth (#613)
* support implicit flow auth * fix lint * upgrade commons
1 parent 92d3919 commit 86a6886

File tree

5 files changed

+164
-10
lines changed

5 files changed

+164
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"react-router-redux": "^4.0.7",
3939
"ringcentral": "^3.1.3",
4040
"ringcentral-client": "^1.0.0-rc1",
41-
"ringcentral-integration": "^0.7.35"
41+
"ringcentral-integration": "^0.7.37"
4242
},
4343
"devDependencies": {
4444
"alias-webpack-plugin": "^1.0.6",

src/lib/OAuthBase/index.js

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { prefixEnum } from 'ringcentral-integration/lib/Enum';
33
import { Module } from 'ringcentral-integration/lib/di';
44
import ensureExist from 'ringcentral-integration/lib/ensureExist';
55
import proxify from 'ringcentral-integration/lib/proxy/proxify';
6-
import parseCallbackUri from 'ringcentral-integration/lib/parseCallbackUri';
6+
77
import required from 'ringcentral-integration/lib/required';
88
import qs from 'qs';
99
import url from 'url';
10+
11+
import parseCallbackUri from '../parseCallbackUri';
1012
import baseActionTypes from './baseActionTypes';
1113
import getOAuthBaseReducer from './getOAuthBaseReducer';
1214
import oAuthMessages from './oAuthMessages';
@@ -72,16 +74,16 @@ export default class OAuthBase extends RcModule {
7274
}
7375

7476
@proxify
75-
async _handleCallbackUri(callbackUri) {
77+
async _handleCallbackUri(callbackUri, refresh = false) {
7678
try {
77-
const code = parseCallbackUri(callbackUri);
78-
if (code) {
79-
await this._auth.login({
80-
code,
81-
redirectUri: this.redirectUri,
82-
});
79+
const query = parseCallbackUri(callbackUri);
80+
if (refresh) {
81+
await this._refreshWithCallbackQuery(query);
82+
} else {
83+
await this._loginWithCallbackQuery(query);
8384
}
8485
} catch (error) {
86+
console.error('oauth error: ', error);
8587
let message;
8688
switch (error.message) {
8789
case 'invalid_request':
@@ -104,6 +106,32 @@ export default class OAuthBase extends RcModule {
104106
}
105107
}
106108

109+
async _loginWithCallbackQuery(query) {
110+
if (!(query.code || query.access_token)) {
111+
return;
112+
}
113+
await this._auth.login({
114+
code: query.code,
115+
accessToken: query.access_token,
116+
expiresIn: query.expires_in,
117+
endpointId: query.endpoint_id,
118+
redirectUri: this.redirectUri,
119+
tokenType: query.token_type,
120+
});
121+
}
122+
123+
async _refreshWithCallbackQuery(query) {
124+
if (!query.access_token) {
125+
return;
126+
}
127+
await this._auth.refreshImplicitToken({
128+
tokenType: query.token_type,
129+
accessToken: query.access_token,
130+
expiresIn: query.expires_in,
131+
endpointId: query.endpoint_id,
132+
});
133+
}
134+
107135
@required
108136
async prepareOAuth() {}
109137

@@ -125,9 +153,21 @@ export default class OAuthBase extends RcModule {
125153
brandId: this._brand.id,
126154
state: btoa(Date.now()),
127155
display: 'page',
156+
implicit: this._auth.isImplicit,
128157
})}&${extendedQuery}`;
129158
}
130159

160+
get implictRefreshOAuthUri() {
161+
return `${this._auth.getLoginUrl({
162+
redirectUri: this.redirectUri,
163+
brandId: this._brand.id,
164+
state: btoa(Date.now()),
165+
display: 'page',
166+
prompt: 'none',
167+
implicit: this._auth.isImplicit,
168+
})}`;
169+
}
170+
131171
get status() {
132172
return this.state.status;
133173
}

src/lib/parseCallbackUri.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import url from 'url';
2+
import qs from 'qs';
3+
4+
/**
5+
* @function
6+
* @param {String} callbackUri
7+
* @return {Object}
8+
*/
9+
export default function parseCallbackUri(callbackUri) {
10+
const { query, hash } = url.parse(callbackUri, true);
11+
const hashObject = hash ? qs.parse(hash.replace(/^#/, '')) : {};
12+
if (query.error || hashObject.error) {
13+
const error = new Error(query.error || hashObject.error);
14+
for (const key in query) {
15+
if (query::Object.prototype.hasOwnProperty(key)) {
16+
error[key] = query[key];
17+
}
18+
if (hashObject::Object.prototype.hasOwnProperty(key)) {
19+
error[key] = query[key];
20+
}
21+
}
22+
throw error;
23+
}
24+
25+
return {
26+
...query,
27+
...hashObject,
28+
};
29+
}

src/modules/ProxyFrameOAuth/index.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@ export default class ProxyFrameOAuth extends OAuthBase {
3333
this._defaultProxyRetry = defaultProxyRetry;
3434

3535
this._reducer = getProxyFrameOAuthReducer(this.actionTypes);
36+
37+
this._loggedIn = false;
38+
}
39+
40+
_onStateChange() {
41+
super._onStateChange();
42+
if (this._auth.loggedIn === this._loggedIn) {
43+
return;
44+
}
45+
this._loggedIn = this._auth.loggedIn;
46+
if (this._loggedIn && this._auth.isImplicit) {
47+
console.log('new login, start refresh token timeout');
48+
this._createImplicitRefreshTimeout();
49+
}
50+
if (!this._loggedIn && this._auth.isImplicit) {
51+
this._clearImplicitRefreshIframe();
52+
if (this._implicitRefreshTimeoutId) {
53+
clearTimeout(this._implicitRefreshTimeoutId);
54+
}
55+
}
56+
}
57+
58+
async _handleCallbackUri(options) {
59+
await super._handleCallbackUri(options);
60+
if (this._auth.isImplicit && this._auth.loggedIn) {
61+
this._createImplicitRefreshTimeout();
62+
}
3663
}
3764

3865
get name() {
@@ -137,4 +164,62 @@ export default class ProxyFrameOAuth extends OAuthBase {
137164
}, '*');
138165
}
139166
}
167+
168+
_createImplicitRefreshIframe() {
169+
this._clearImplicitRefreshIframe();
170+
this._implicitRefreshFrame = document.createElement('iframe');
171+
this._implicitRefreshFrame.src = this.implictRefreshOAuthUri;
172+
this._implicitRefreshFrame.style.display = 'none';
173+
document.body.appendChild(this._implicitRefreshFrame);
174+
// eslint-disable-next-line
175+
this._implictitRefreshCallBack = ({ origin, data }) => {
176+
const { refreshCallbackUri } = data;
177+
if (refreshCallbackUri && this._auth.loggedIn) {
178+
this._handleCallbackUri(refreshCallbackUri, true);
179+
this._clearImplicitRefreshIframe();
180+
}
181+
};
182+
window.addEventListener('message', this._implictitRefreshCallBack);
183+
}
184+
185+
_clearImplicitRefreshIframe() {
186+
if (this._implicitRefreshFrame) {
187+
document.body.removeChild(this._implicitRefreshFrame);
188+
this._implicitRefreshFrame = null;
189+
window.removeEventListener('message', this._implictitRefreshCallBack);
190+
this._callbackHandler = null;
191+
}
192+
}
193+
194+
// create a time out to refresh implicit flow token
195+
_createImplicitRefreshTimeout() {
196+
if (this._implicitRefreshTimeoutId) {
197+
clearTimeout(this._implicitRefreshTimeoutId);
198+
}
199+
const authData = this._auth.token;
200+
const refreshTokenExpiresIn = authData.expiresIn;
201+
const { expireTime } = authData;
202+
if (!refreshTokenExpiresIn || !expireTime) {
203+
return;
204+
}
205+
// set refresh time to (token exposre time) / 3
206+
let refreshTokenTimeoutTime = (parseInt(refreshTokenExpiresIn, 10) * 1000) / 3;
207+
if (refreshTokenTimeoutTime + Date.now() > expireTime) {
208+
refreshTokenTimeoutTime = expireTime - Date.now() - 5000;
209+
if (refreshTokenTimeoutTime < 0) {
210+
return;
211+
}
212+
}
213+
this._implicitRefreshTimeoutId = setTimeout(() => {
214+
if (!this._auth.loggedIn) {
215+
return;
216+
}
217+
if (this._tabManager && !this._tabManager.active) {
218+
this._createImplicitRefreshTimeout();
219+
return;
220+
}
221+
this._createImplicitRefreshIframe();
222+
this._implicitRefreshTimeoutId = null;
223+
}, refreshTokenTimeoutTime);
224+
}
140225
}

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7164,7 +7164,7 @@ ringcentral-client@^1.0.0-rc1:
71647164

71657165
"ringcentral-integration@https://github.com/ringcentral/ringcentral-js-integration-commons#latest":
71667166
version "0.1.0"
7167-
resolved "https://github.com/ringcentral/ringcentral-js-integration-commons#38b7ee200d14a517f1a94013acba725f3c972ceb"
7167+
resolved "https://github.com/ringcentral/ringcentral-js-integration-commons#e46d8e78ab3926f487bac270a3f0b6acaf27bb62"
71687168
dependencies:
71697169
file-loader "^0.11.2"
71707170
json-mask "^0.3.8"

0 commit comments

Comments
 (0)