2019-09-16 22:57:56 +02:00
|
|
|
import { Events } from 'framework/events';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { Links } from 'const/links';
|
|
|
|
import { AppSettingsModel } from 'models/app-settings-model';
|
|
|
|
import { RuntimeDataModel } from 'models/runtime-data-model';
|
|
|
|
import { Logger } from 'util/logger';
|
2020-03-17 19:45:38 +01:00
|
|
|
import { StorageOAuthListener } from 'storage/storage-oauth-listener';
|
|
|
|
import { UrlFormat } from 'util/formatting/url-format';
|
|
|
|
import { Launcher } from 'comp/launcher';
|
|
|
|
import { omitEmpty } from 'util/fn';
|
|
|
|
import { Timeouts } from 'const/timeouts';
|
2020-04-17 19:42:35 +02:00
|
|
|
import { Features } from 'util/features';
|
|
|
|
import { createOAuthSession } from 'storage/pkce';
|
2016-03-27 09:06:23 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const MaxRequestRetries = 3;
|
2016-03-27 15:18:05 +02:00
|
|
|
|
2019-09-18 21:26:43 +02:00
|
|
|
class StorageBase {
|
|
|
|
name = null;
|
|
|
|
icon = null;
|
|
|
|
iconSvg = null;
|
|
|
|
enabled = false;
|
|
|
|
system = false;
|
|
|
|
uipos = null;
|
2016-03-27 09:06:23 +02:00
|
|
|
|
2019-09-18 21:26:43 +02:00
|
|
|
logger = null;
|
|
|
|
appSettings = AppSettingsModel;
|
|
|
|
runtimeData = RuntimeDataModel;
|
2016-03-27 09:06:23 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
init() {
|
2016-03-27 09:06:23 +02:00
|
|
|
if (!this.name) {
|
|
|
|
throw 'Failed to init provider: no name';
|
|
|
|
}
|
|
|
|
if (!this.system) {
|
2019-09-17 19:50:42 +02:00
|
|
|
const enabled = this.appSettings[this.name];
|
2016-03-27 09:06:23 +02:00
|
|
|
if (typeof enabled === 'boolean') {
|
|
|
|
this.enabled = enabled;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.logger = new Logger('storage-' + this.name);
|
2016-03-27 09:46:43 +02:00
|
|
|
return this;
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-03-27 09:42:48 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setEnabled(enabled) {
|
2020-04-17 21:36:56 +02:00
|
|
|
if (!enabled) {
|
|
|
|
this.logout();
|
|
|
|
}
|
2016-06-04 17:08:50 +02:00
|
|
|
this.enabled = enabled;
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-06-04 17:08:50 +02:00
|
|
|
|
2020-04-17 21:36:56 +02:00
|
|
|
get loggedIn() {
|
|
|
|
return !!this.runtimeData[this.name + 'OAuthToken'];
|
|
|
|
}
|
|
|
|
|
|
|
|
logout() {}
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
_xhr(config) {
|
2020-03-22 20:58:29 +01:00
|
|
|
this.logger.info('HTTP request', config.method || 'GET', config.url);
|
2020-03-17 19:45:38 +01:00
|
|
|
if (config.data) {
|
|
|
|
if (!config.dataType) {
|
|
|
|
config.dataType = 'application/octet-stream';
|
|
|
|
}
|
|
|
|
config.headers = {
|
|
|
|
...config.headers,
|
|
|
|
'Content-Type': config.dataType
|
|
|
|
};
|
2016-03-27 09:42:48 +02:00
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
if (this._oauthToken && !config.skipAuth) {
|
|
|
|
config.headers = {
|
|
|
|
...config.headers,
|
|
|
|
'Authorization': 'Bearer ' + this._oauthToken.accessToken
|
|
|
|
};
|
|
|
|
}
|
|
|
|
this._httpRequest(config, response => {
|
2020-03-22 20:58:29 +01:00
|
|
|
this.logger.info('HTTP response', response.status);
|
2020-03-17 19:45:38 +01:00
|
|
|
const statuses = config.statuses || [200];
|
|
|
|
if (statuses.indexOf(response.status) >= 0) {
|
|
|
|
return config.success && config.success(response.response, response);
|
2016-03-27 15:18:05 +02:00
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
if (response.status === 401 && this._oauthToken) {
|
|
|
|
this._oauthGetNewToken(err => {
|
2016-03-27 15:18:05 +02:00
|
|
|
if (err) {
|
2020-03-17 19:45:38 +01:00
|
|
|
return config.error && config.error('unauthorized', response);
|
2016-03-27 15:18:05 +02:00
|
|
|
} else {
|
|
|
|
config.tryNum = (config.tryNum || 0) + 1;
|
|
|
|
if (config.tryNum >= MaxRequestRetries) {
|
2019-08-18 08:05:38 +02:00
|
|
|
this.logger.info(
|
|
|
|
'Too many authorize attempts, fail request',
|
|
|
|
config.url
|
|
|
|
);
|
2020-03-17 19:45:38 +01:00
|
|
|
return config.error && config.error('unauthorized', response);
|
2016-03-27 15:18:05 +02:00
|
|
|
}
|
2016-07-17 13:30:38 +02:00
|
|
|
this.logger.info('Repeat request, try #' + config.tryNum, config.url);
|
|
|
|
this._xhr(config);
|
2016-03-27 15:18:05 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2020-03-17 19:45:38 +01:00
|
|
|
return config.error && config.error('http status ' + response.status, response);
|
2016-03-27 09:42:48 +02:00
|
|
|
}
|
|
|
|
});
|
2020-03-17 19:45:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_httpRequest(config, onLoad) {
|
2020-04-17 19:42:35 +02:00
|
|
|
const httpRequest = Features.isDesktop ? this._httpRequestLauncher : this._httpRequestWeb;
|
|
|
|
httpRequest.call(this, config, onLoad);
|
2020-03-17 19:45:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_httpRequestWeb(config, onLoad) {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
if (config.responseType) {
|
|
|
|
xhr.responseType = config.responseType;
|
|
|
|
}
|
|
|
|
xhr.addEventListener('load', () => {
|
|
|
|
onLoad({
|
|
|
|
status: xhr.status,
|
|
|
|
response: xhr.response,
|
|
|
|
getResponseHeader: name => xhr.getResponseHeader(name)
|
|
|
|
});
|
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('error', () => {
|
2016-03-27 15:18:05 +02:00
|
|
|
return config.error && config.error('network error', xhr);
|
2016-03-27 09:42:48 +02:00
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('timeout', () => {
|
2016-03-27 15:18:05 +02:00
|
|
|
return config.error && config.error('timeout', xhr);
|
2016-03-27 09:42:48 +02:00
|
|
|
});
|
|
|
|
xhr.open(config.method || 'GET', config.url);
|
2019-09-29 11:32:03 +02:00
|
|
|
if (config.headers) {
|
|
|
|
for (const [key, value] of Object.entries(config.headers)) {
|
|
|
|
xhr.setRequestHeader(key, value);
|
|
|
|
}
|
2019-09-17 22:17:40 +02:00
|
|
|
}
|
2019-09-14 22:21:21 +02:00
|
|
|
let data = config.data;
|
2020-03-17 19:45:38 +01:00
|
|
|
if (data) {
|
|
|
|
if (!config.dataIsMultipart) {
|
|
|
|
data = [data];
|
|
|
|
}
|
|
|
|
data = new Blob(data, { type: config.dataType });
|
2019-09-14 22:21:21 +02:00
|
|
|
}
|
|
|
|
xhr.send(data);
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2020-03-17 19:45:38 +01:00
|
|
|
_httpRequestLauncher(config, onLoad) {
|
2020-04-30 17:59:22 +02:00
|
|
|
const net = Launcher.remReq('electron').net;
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2020-04-30 17:59:22 +02:00
|
|
|
const opts = Launcher.req('url').parse(config.url);
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2020-04-30 17:59:22 +02:00
|
|
|
opts.method = config.method || 'GET';
|
|
|
|
opts.headers = {
|
|
|
|
'User-Agent': navigator.userAgent,
|
|
|
|
...config.headers
|
|
|
|
};
|
|
|
|
opts.timeout = Timeouts.DefaultHttpRequest;
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2020-04-30 17:59:22 +02:00
|
|
|
let data;
|
|
|
|
if (config.data) {
|
|
|
|
if (config.dataIsMultipart) {
|
|
|
|
data = Buffer.concat(config.data.map(chunk => Buffer.from(chunk)));
|
|
|
|
} else {
|
|
|
|
data = Buffer.from(config.data);
|
2020-04-17 19:42:35 +02:00
|
|
|
}
|
2020-04-30 17:59:22 +02:00
|
|
|
// Electron's API doesn't like that, while node.js needs it
|
|
|
|
// opts.headers['Content-Length'] = data.byteLength;
|
|
|
|
}
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2020-04-30 17:59:22 +02:00
|
|
|
const req = net.request(opts);
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2020-04-30 17:59:22 +02:00
|
|
|
let closed = false;
|
|
|
|
req.on('close', () => {
|
|
|
|
closed = true;
|
|
|
|
});
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2020-04-30 17:59:22 +02:00
|
|
|
req.on('response', res => {
|
|
|
|
const chunks = [];
|
|
|
|
const onClose = () => {
|
|
|
|
this.logger.debug(
|
|
|
|
'HTTP response',
|
|
|
|
opts.method,
|
|
|
|
config.url,
|
|
|
|
res.statusCode,
|
|
|
|
res.headers
|
|
|
|
);
|
|
|
|
|
|
|
|
let response = Buffer.concat(chunks);
|
|
|
|
if (config.responseType === 'json') {
|
|
|
|
try {
|
|
|
|
response = JSON.parse(response.toString('utf8'));
|
|
|
|
} catch (e) {
|
|
|
|
return config.error && config.error('json parse error');
|
2020-04-17 19:42:35 +02:00
|
|
|
}
|
2020-04-30 17:59:22 +02:00
|
|
|
} else {
|
|
|
|
response = response.buffer.slice(
|
|
|
|
response.byteOffset,
|
|
|
|
response.byteOffset + response.length
|
|
|
|
);
|
|
|
|
}
|
|
|
|
onLoad({
|
|
|
|
status: res.statusCode,
|
|
|
|
response,
|
|
|
|
getResponseHeader: name => res.headers[name.toLowerCase()]
|
2020-03-17 19:45:38 +01:00
|
|
|
});
|
2020-04-30 17:59:22 +02:00
|
|
|
};
|
|
|
|
res.on('data', chunk => {
|
|
|
|
chunks.push(chunk);
|
|
|
|
if (closed && !res.readable) {
|
|
|
|
// sometimes 'close' event arrives faster in Electron
|
|
|
|
onClose();
|
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
});
|
2020-04-30 17:59:22 +02:00
|
|
|
// in Electron it's not res.on('end'), like in node.js, which is a bit weird
|
|
|
|
req.on('close', onClose);
|
|
|
|
});
|
|
|
|
req.on('error', e => {
|
|
|
|
this.logger.error('HTTP error', opts.method, config.url, e);
|
|
|
|
return config.error && config.error('network error', {});
|
|
|
|
});
|
|
|
|
req.on('timeout', () => {
|
|
|
|
req.abort();
|
|
|
|
return config.error && config.error('timeout', {});
|
2020-04-17 19:42:35 +02:00
|
|
|
});
|
2020-04-30 17:59:22 +02:00
|
|
|
if (data) {
|
|
|
|
req.write(data);
|
|
|
|
}
|
|
|
|
req.end();
|
2020-03-17 19:45:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_openPopup(url, title, width, height, extras) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
|
|
|
|
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2019-08-16 23:05:39 +02:00
|
|
|
const winWidth = window.innerWidth
|
|
|
|
? window.innerWidth
|
|
|
|
: document.documentElement.clientWidth
|
|
|
|
? document.documentElement.clientWidth
|
|
|
|
: screen.width;
|
|
|
|
const winHeight = window.innerHeight
|
|
|
|
? window.innerHeight
|
|
|
|
: document.documentElement.clientHeight
|
|
|
|
? document.documentElement.clientHeight
|
|
|
|
: screen.height;
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2019-08-16 23:05:39 +02:00
|
|
|
const left = winWidth / 2 - width / 2 + dualScreenLeft;
|
|
|
|
const top = winHeight / 2 - height / 2 + dualScreenTop;
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
let settings = {
|
2019-08-18 10:17:09 +02:00
|
|
|
width,
|
|
|
|
height,
|
|
|
|
left,
|
|
|
|
top,
|
2016-03-27 13:54:35 +02:00
|
|
|
dialog: 'yes',
|
|
|
|
dependent: 'yes',
|
|
|
|
scrollbars: 'yes',
|
|
|
|
location: 'yes'
|
|
|
|
};
|
2019-08-16 23:05:39 +02:00
|
|
|
settings = Object.keys(settings)
|
|
|
|
.map(key => key + '=' + settings[key])
|
|
|
|
.join(',');
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2020-03-17 19:45:38 +01:00
|
|
|
return window.open(url, title, settings, extras);
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-03-27 14:57:22 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
_getOauthRedirectUrl() {
|
2017-01-31 07:50:28 +01:00
|
|
|
let redirectUrl = window.location.href;
|
2016-04-03 16:33:34 +02:00
|
|
|
if (redirectUrl.lastIndexOf('file:', 0) === 0) {
|
|
|
|
redirectUrl = Links.WebApp;
|
|
|
|
}
|
2018-03-08 19:37:52 +01:00
|
|
|
redirectUrl = redirectUrl.split('?')[0];
|
2016-04-03 16:33:34 +02:00
|
|
|
return redirectUrl;
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-04-03 16:33:34 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
_oauthAuthorize(callback) {
|
2017-11-27 19:00:39 +01:00
|
|
|
if (this._tokenIsValid(this._oauthToken)) {
|
2016-03-27 15:18:05 +02:00
|
|
|
return callback();
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const opts = this._getOAuthConfig();
|
2019-09-17 21:50:46 +02:00
|
|
|
const oldToken = this.runtimeData[this.name + 'OAuthToken'];
|
2017-11-27 19:00:39 +01:00
|
|
|
if (this._tokenIsValid(oldToken)) {
|
2016-07-17 13:30:38 +02:00
|
|
|
this._oauthToken = oldToken;
|
2017-11-27 19:00:39 +01:00
|
|
|
return callback();
|
2016-03-27 14:57:22 +02:00
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
|
|
|
|
if (oldToken && oldToken.refreshToken) {
|
|
|
|
return this._oauthExchangeRefreshToken(callback);
|
|
|
|
}
|
|
|
|
|
2020-04-17 19:42:35 +02:00
|
|
|
const session = createOAuthSession();
|
|
|
|
|
|
|
|
let listener;
|
|
|
|
if (Features.isDesktop) {
|
|
|
|
listener = StorageOAuthListener.listen();
|
|
|
|
session.redirectUri = listener.redirectUri;
|
|
|
|
} else {
|
|
|
|
session.redirectUri = this._getOauthRedirectUrl();
|
2020-03-17 19:45:38 +01:00
|
|
|
}
|
|
|
|
|
2020-04-17 19:42:35 +02:00
|
|
|
const pkceParams = opts.pkce
|
|
|
|
? {
|
|
|
|
'code_challenge': session.codeChallenge,
|
|
|
|
'code_challenge_method': 'S256'
|
|
|
|
}
|
|
|
|
: undefined;
|
|
|
|
|
2020-03-17 19:45:38 +01:00
|
|
|
const url = UrlFormat.makeUrl(opts.url, {
|
|
|
|
'client_id': opts.clientId,
|
|
|
|
'scope': opts.scope,
|
2020-04-17 19:42:35 +02:00
|
|
|
'state': session.state,
|
|
|
|
'redirect_uri': session.redirectUri,
|
|
|
|
'response_type': 'code',
|
|
|
|
...pkceParams
|
2020-03-17 19:45:38 +01:00
|
|
|
});
|
|
|
|
|
2020-04-17 19:42:35 +02:00
|
|
|
if (listener) {
|
|
|
|
listener.on('ready', () => {
|
|
|
|
Launcher.openLink(url);
|
|
|
|
callback('browser-auth-started');
|
|
|
|
});
|
|
|
|
listener.on('error', err => callback(err));
|
|
|
|
listener.on('result', result => this._oauthCodeReceived(result, session));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-25 18:58:23 +01:00
|
|
|
const popupWindow = this._openPopup(url, 'OAuth', opts.width, opts.height);
|
|
|
|
if (!popupWindow) {
|
2017-11-27 19:00:39 +01:00
|
|
|
return callback('OAuth: cannot open popup');
|
2016-03-27 15:36:07 +02:00
|
|
|
}
|
2020-04-17 19:42:35 +02:00
|
|
|
|
|
|
|
this.logger.debug('OAuth: popup opened');
|
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const popupClosed = () => {
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.off('popup-closed', popupClosed);
|
2016-03-27 14:57:22 +02:00
|
|
|
window.removeEventListener('message', windowMessage);
|
2016-07-17 13:30:38 +02:00
|
|
|
this.logger.error('OAuth error', 'popup closed');
|
2017-06-11 19:41:56 +02:00
|
|
|
callback('OAuth: popup closed');
|
2016-03-27 14:57:22 +02:00
|
|
|
};
|
2020-04-17 19:42:35 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const windowMessage = e => {
|
2020-04-17 20:53:40 +02:00
|
|
|
if (e.origin !== location.origin) {
|
|
|
|
return;
|
|
|
|
}
|
2020-04-17 19:42:35 +02:00
|
|
|
if (e.data && e.data.error) {
|
|
|
|
this.logger.error('OAuth error', e.data.error, e.data.error_description);
|
|
|
|
callback('OAuth: ' + e.data.error);
|
|
|
|
} else if (e.data && e.data.code) {
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.off('popup-closed', popupClosed);
|
2017-12-09 21:46:08 +01:00
|
|
|
window.removeEventListener('message', windowMessage);
|
2020-04-17 19:42:35 +02:00
|
|
|
this._oauthCodeReceived(e.data, session, callback);
|
2017-12-09 21:46:08 +01:00
|
|
|
} else {
|
|
|
|
this.logger.debug('Skipped OAuth message', e.data);
|
|
|
|
}
|
2016-03-27 14:57:22 +02:00
|
|
|
};
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.on('popup-closed', popupClosed);
|
2016-03-27 14:57:22 +02:00
|
|
|
window.addEventListener('message', windowMessage);
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-03-27 14:57:22 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
_oauthProcessReturn(message) {
|
2017-04-16 18:05:58 +02:00
|
|
|
const token = this._oauthMsgToToken(message);
|
2017-12-09 21:46:08 +01:00
|
|
|
if (token && !token.error) {
|
2017-04-16 18:05:58 +02:00
|
|
|
this._oauthToken = token;
|
2019-09-17 21:50:46 +02:00
|
|
|
this.runtimeData[this.name + 'OAuthToken'] = token;
|
2017-04-16 18:05:58 +02:00
|
|
|
this.logger.debug('OAuth token received');
|
|
|
|
}
|
2017-12-09 21:46:08 +01:00
|
|
|
return token;
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2017-04-16 18:05:58 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
_oauthMsgToToken(data) {
|
2017-12-09 21:46:08 +01:00
|
|
|
if (!data.token_type) {
|
|
|
|
if (data.error) {
|
2019-08-16 23:05:39 +02:00
|
|
|
return { error: data.error, errorDescription: data.error_description };
|
2017-12-09 21:46:08 +01:00
|
|
|
} else {
|
|
|
|
return undefined;
|
2017-12-09 12:39:18 +01:00
|
|
|
}
|
2016-03-27 14:57:22 +02:00
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
return omitEmpty({
|
2017-11-27 19:00:39 +01:00
|
|
|
dt: Date.now() - 60 * 1000,
|
2016-03-27 14:57:22 +02:00
|
|
|
tokenType: data.token_type,
|
|
|
|
accessToken: data.access_token,
|
2020-03-17 19:45:38 +01:00
|
|
|
refreshToken: data.refresh_token,
|
2016-03-27 14:57:22 +02:00
|
|
|
authenticationToken: data.authentication_token,
|
2017-11-27 19:00:39 +01:00
|
|
|
expiresIn: +data.expires_in,
|
2016-03-27 14:57:22 +02:00
|
|
|
scope: data.scope,
|
|
|
|
userId: data.user_id
|
2020-03-17 19:45:38 +01:00
|
|
|
});
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-03-27 15:18:05 +02:00
|
|
|
|
2020-03-17 19:45:38 +01:00
|
|
|
_oauthGetNewToken(callback) {
|
2016-03-27 15:18:05 +02:00
|
|
|
this._oauthToken.expired = true;
|
2019-09-17 21:50:46 +02:00
|
|
|
this.runtimeData[this.name + 'OAuthToken'] = this._oauthToken;
|
2020-03-17 19:45:38 +01:00
|
|
|
if (this._oauthToken.refreshToken) {
|
|
|
|
this._oauthExchangeRefreshToken(callback);
|
|
|
|
} else {
|
|
|
|
this._oauthAuthorize(callback);
|
|
|
|
}
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-06-04 17:08:50 +02:00
|
|
|
|
2020-04-17 21:36:56 +02:00
|
|
|
_oauthRevokeToken(url, requestOptions) {
|
2019-09-17 21:50:46 +02:00
|
|
|
const token = this.runtimeData[this.name + 'OAuthToken'];
|
2016-06-04 17:08:50 +02:00
|
|
|
if (token) {
|
2017-04-16 17:00:35 +02:00
|
|
|
if (url) {
|
|
|
|
this._xhr({
|
|
|
|
url: url.replace('{token}', token.accessToken),
|
2020-04-17 21:36:56 +02:00
|
|
|
statuses: [200, 401],
|
|
|
|
...requestOptions
|
2017-04-16 17:00:35 +02:00
|
|
|
});
|
|
|
|
}
|
2019-09-17 21:50:46 +02:00
|
|
|
delete this.runtimeData[this.name + 'OAuthToken'];
|
2016-06-04 17:08:50 +02:00
|
|
|
this._oauthToken = null;
|
|
|
|
}
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2017-11-27 19:00:39 +01:00
|
|
|
|
|
|
|
_tokenIsValid(token) {
|
|
|
|
if (!token || token.expired) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (token.dt && token.expiresIn && token.dt + token.expiresIn * 1000 < Date.now()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2016-03-27 09:06:23 +02:00
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
|
2020-04-17 19:42:35 +02:00
|
|
|
_oauthCodeReceived(result, session, callback) {
|
|
|
|
if (!result.state) {
|
|
|
|
this.logger.info('OAuth result has no state');
|
|
|
|
return callback && callback('OAuth result has no state');
|
|
|
|
}
|
|
|
|
if (result.state !== session.state) {
|
|
|
|
this.logger.info('OAuth result has bad state');
|
|
|
|
return callback && callback('OAuth result has bad state');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!result.code) {
|
|
|
|
this.logger.info('OAuth result has no code');
|
|
|
|
return callback && callback('OAuth result has no code');
|
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
|
|
|
|
this.logger.debug('OAuth code received');
|
2020-04-17 19:42:35 +02:00
|
|
|
|
|
|
|
if (Features.isDesktop) {
|
|
|
|
Launcher.showMainWindow();
|
|
|
|
}
|
2020-03-17 19:45:38 +01:00
|
|
|
const config = this._getOAuthConfig();
|
2020-04-17 19:42:35 +02:00
|
|
|
const pkceParams = config.pkce ? { 'code_verifier': session.codeVerifier } : undefined;
|
|
|
|
|
2020-03-17 19:45:38 +01:00
|
|
|
this._xhr({
|
|
|
|
url: config.tokenUrl,
|
|
|
|
method: 'POST',
|
|
|
|
responseType: 'json',
|
|
|
|
skipAuth: true,
|
2020-04-17 19:42:35 +02:00
|
|
|
data: UrlFormat.buildFormData({
|
2020-03-17 19:45:38 +01:00
|
|
|
'client_id': config.clientId,
|
|
|
|
'client_secret': config.clientSecret,
|
|
|
|
'grant_type': 'authorization_code',
|
2020-04-17 19:42:35 +02:00
|
|
|
'code': result.code,
|
|
|
|
'redirect_uri': session.redirectUri,
|
|
|
|
...pkceParams
|
2020-03-17 19:45:38 +01:00
|
|
|
}),
|
2020-04-17 19:42:35 +02:00
|
|
|
dataType: 'application/x-www-form-urlencoded',
|
2020-03-17 19:45:38 +01:00
|
|
|
success: response => {
|
2020-04-17 19:42:35 +02:00
|
|
|
this.logger.debug('OAuth code exchanged', response);
|
|
|
|
const token = this._oauthProcessReturn(response);
|
|
|
|
if (token && token.error) {
|
|
|
|
return callback && callback('OAuth code exchange error: ' + token.error);
|
|
|
|
}
|
|
|
|
callback && callback();
|
2020-03-17 19:45:38 +01:00
|
|
|
},
|
|
|
|
error: err => {
|
|
|
|
this.logger.error('Error exchanging OAuth code', err);
|
2020-04-17 19:42:35 +02:00
|
|
|
callback && callback('OAuth code exchange error: ' + err);
|
2020-03-17 19:45:38 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_oauthExchangeRefreshToken(callback) {
|
|
|
|
this.logger.debug('Exchanging refresh token');
|
|
|
|
const { refreshToken } = this.runtimeData[this.name + 'OAuthToken'];
|
|
|
|
const config = this._getOAuthConfig();
|
|
|
|
this._xhr({
|
|
|
|
url: config.tokenUrl,
|
|
|
|
method: 'POST',
|
|
|
|
responseType: 'json',
|
|
|
|
skipAuth: true,
|
2020-04-17 19:42:35 +02:00
|
|
|
data: UrlFormat.buildFormData({
|
2020-03-17 19:45:38 +01:00
|
|
|
'client_id': config.clientId,
|
|
|
|
'client_secret': config.clientSecret,
|
|
|
|
'grant_type': 'refresh_token',
|
|
|
|
'refresh_token': refreshToken
|
|
|
|
}),
|
2020-04-17 19:42:35 +02:00
|
|
|
dataType: 'application/x-www-form-urlencoded',
|
2020-03-17 19:45:38 +01:00
|
|
|
success: response => {
|
|
|
|
this.logger.debug('Refresh token exchanged');
|
|
|
|
this._oauthProcessReturn({
|
|
|
|
'refresh_token': refreshToken,
|
|
|
|
...response
|
|
|
|
});
|
|
|
|
callback();
|
|
|
|
},
|
|
|
|
error: (err, xhr) => {
|
|
|
|
if (xhr.status === 400) {
|
|
|
|
delete this.runtimeData[this.name + 'OAuthToken'];
|
|
|
|
this._oauthToken = null;
|
|
|
|
}
|
|
|
|
this.logger.error('Error exchanging refresh token', err);
|
|
|
|
callback && callback('Error exchanging refresh token');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-09-18 21:26:43 +02:00
|
|
|
}
|
2016-03-27 09:06:23 +02:00
|
|
|
|
2019-09-15 14:16:32 +02:00
|
|
|
export { StorageBase };
|