| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- /* global FIREFOX getActiveTab waitForTabUrl URLS */// toolbox.js
- /* global chromeLocal */// storage-util.js
- 'use strict';
- /* exported tokenMan */
- const tokenMan = (() => {
- const AUTH = {
- dropbox: {
- flow: 'token',
- clientId: 'zg52vphuapvpng9',
- authURL: 'https://www.dropbox.com/oauth2/authorize',
- tokenURL: 'https://api.dropboxapi.com/oauth2/token',
- revoke: token =>
- fetch('https://api.dropboxapi.com/2/auth/token/revoke', {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`,
- },
- }),
- },
- google: {
- flow: 'code',
- clientId: '283762574871-d4u58s4arra5jdan2gr00heasjlttt1e.apps.googleusercontent.com',
- clientSecret: 'J0nc5TlR_0V_ex9-sZk-5faf',
- authURL: 'https://accounts.google.com/o/oauth2/v2/auth',
- authQuery: {
- // NOTE: Google needs 'prompt' parameter to deliver multiple refresh
- // tokens for multiple machines.
- // https://stackoverflow.com/q/18519185
- access_type: 'offline',
- prompt: 'consent',
- },
- tokenURL: 'https://oauth2.googleapis.com/token',
- scopes: ['https://www.googleapis.com/auth/drive.appdata'],
- // FIXME: https://github.com/openstyles/stylus/issues/1248
- // revoke: token => {
- // const params = {token};
- // return postQuery(`https://accounts.google.com/o/oauth2/revoke?${new URLSearchParams(params)}`);
- // },
- },
- onedrive: {
- flow: 'code',
- clientId: '3864ce03-867c-4ad8-9856-371a097d47b1',
- clientSecret: '9Pj=TpsrStq8K@1BiwB9PIWLppM:@s=w',
- authURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
- tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
- redirect_uri: FIREFOX ?
- 'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/' :
- 'https://' + location.hostname + '.chromiumapp.org/',
- scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
- },
- userstylesworld: {
- flow: 'code',
- clientId: 'zeDmKhJIfJqULtcrGMsWaxRtWHEimKgS',
- clientSecret: 'wqHsvTuThQmXmDiVvOpZxPwSIbyycNFImpAOTxjaIRqDbsXcTOqrymMJKsOMuibFaij' +
- 'ZZAkVYTDbLkQuYFKqgpMsMlFlgwQOYHvHFbgxQHDTwwdOroYhOwFuekCwXUlk',
- authURL: URLS.usw + 'api/oauth/style/link',
- tokenURL: URLS.usw + 'api/oauth/token',
- redirect_uri: 'https://gusted.xyz/callback_helper/',
- },
- };
- const NETWORK_LATENCY = 30; // seconds
- let alwaysUseTab = FIREFOX ? false : null;
- class TokenError extends Error {
- constructor(provider, message) {
- super(`[${provider}] ${message}`);
- this.name = 'TokenError';
- this.provider = provider;
- if (Error.captureStackTrace) {
- Error.captureStackTrace(this, TokenError);
- }
- }
- }
- return {
- buildKeys(name, hooks) {
- const prefix = `secure/token/${hooks ? hooks.keyName(name) : name}/`;
- const k = {
- TOKEN: `${prefix}token`,
- EXPIRE: `${prefix}expire`,
- REFRESH: `${prefix}refresh`,
- };
- k.LIST = Object.values(k);
- return k;
- },
- getClientId(name) {
- return AUTH[name].clientId;
- },
- async getToken(name, interactive, hooks) {
- const k = tokenMan.buildKeys(name, hooks);
- const obj = await chromeLocal.get(k.LIST);
- if (obj[k.TOKEN]) {
- if (!obj[k.EXPIRE] || Date.now() < obj[k.EXPIRE]) {
- return obj[k.TOKEN];
- }
- if (obj[k.REFRESH]) {
- return refreshToken(name, k, obj);
- }
- }
- if (!interactive) {
- throw new TokenError(name, 'Token is missing');
- }
- return authUser(k, name, interactive, hooks);
- },
- async revokeToken(name, hooks) {
- const provider = AUTH[name];
- const k = tokenMan.buildKeys(name, hooks);
- if (provider.revoke) {
- try {
- const token = await chromeLocal.getValue(k.TOKEN);
- if (token) await provider.revoke(token);
- } catch (e) {
- console.error(e);
- }
- }
- await chromeLocal.remove(k.LIST);
- },
- };
- async function refreshToken(name, k, obj) {
- if (!obj[k.REFRESH]) {
- throw new TokenError(name, 'No refresh token');
- }
- const provider = AUTH[name];
- const body = {
- client_id: provider.clientId,
- refresh_token: obj[k.REFRESH],
- grant_type: 'refresh_token',
- scope: provider.scopes.join(' '),
- };
- if (provider.clientSecret) {
- body.client_secret = provider.clientSecret;
- }
- const result = await postQuery(provider.tokenURL, body);
- if (!result.refresh_token) {
- // reuse old refresh token
- result.refresh_token = obj[k.REFRESH];
- }
- return handleTokenResult(result, k);
- }
- async function authUser(keys, name, interactive = false, hooks = null) {
- await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow.min']);
- /* global webextLaunchWebAuthFlow */
- const provider = AUTH[name];
- const state = Math.random().toFixed(8).slice(2);
- const query = {
- response_type: provider.flow,
- client_id: provider.clientId,
- redirect_uri: provider.redirect_uri || chrome.identity.getRedirectURL(),
- state,
- };
- if (provider.scopes) {
- query.scope = provider.scopes.join(' ');
- }
- if (provider.authQuery) {
- Object.assign(query, provider.authQuery);
- }
- if (alwaysUseTab == null) {
- alwaysUseTab = await detectVivaldiWebRequestBug();
- }
- if (hooks) hooks.query(query);
- const url = `${provider.authURL}?${new URLSearchParams(query)}`;
- const width = Math.min(screen.availWidth - 100, 800);
- const height = Math.min(screen.availHeight - 100, 800);
- const wnd = await browser.windows.getLastFocused();
- const finalUrl = await webextLaunchWebAuthFlow({
- url,
- alwaysUseTab,
- interactive,
- redirect_uri: query.redirect_uri,
- windowOptions: Object.assign({
- state: 'normal',
- width,
- height,
- }, wnd.state !== 'minimized' && {
- // Center the popup to the current window
- top: Math.ceil(wnd.top + (wnd.height - width) / 2),
- left: Math.ceil(wnd.left + (wnd.width - width) / 2),
- }),
- });
- const params = new URLSearchParams(
- provider.flow === 'token' ?
- new URL(finalUrl).hash.slice(1) :
- new URL(finalUrl).search.slice(1)
- );
- if (params.get('state') !== state) {
- throw new TokenError(name, `Unexpected state: ${params.get('state')}, expected: ${state}`);
- }
- let result;
- if (provider.flow === 'token') {
- const obj = {};
- for (const [key, value] of params) {
- obj[key] = value;
- }
- result = obj;
- } else {
- const code = params.get('code');
- const body = {
- code,
- grant_type: 'authorization_code',
- client_id: provider.clientId,
- redirect_uri: query.redirect_uri,
- state,
- };
- if (provider.clientSecret) {
- body.client_secret = provider.clientSecret;
- }
- result = await postQuery(provider.tokenURL, body);
- }
- return handleTokenResult(result, keys);
- }
- async function handleTokenResult(result, k) {
- await chromeLocal.set({
- [k.TOKEN]: result.access_token,
- [k.EXPIRE]: result.expires_in
- ? Date.now() + (result.expires_in - NETWORK_LATENCY) * 1000
- : undefined,
- [k.REFRESH]: result.refresh_token,
- });
- return result.access_token;
- }
- async function postQuery(url, body) {
- const options = {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: body ? new URLSearchParams(body) : null,
- };
- const r = await fetch(url, options);
- if (r.ok) {
- return r.json();
- }
- const text = await r.text();
- const err = new Error(`Failed to fetch (${r.status}): ${text}`);
- err.code = r.status;
- throw err;
- }
- async function detectVivaldiWebRequestBug() {
- // Workaround for https://github.com/openstyles/stylus/issues/1182
- // Note that modern Vivaldi isn't exposed in `navigator.userAgent` but it adds `extData` to tabs
- const anyTab = await getActiveTab() || (await browser.tabs.query({}))[0];
- if (anyTab && !anyTab.extData) {
- return false;
- }
- let bugged = true;
- const TEST_URL = chrome.runtime.getURL('manifest.json');
- const check = ({url}) => {
- bugged = url !== TEST_URL;
- };
- chrome.webRequest.onBeforeRequest.addListener(check, {urls: [TEST_URL], types: ['main_frame']});
- const {tabs: [tab]} = await browser.windows.create({
- type: 'popup',
- state: 'minimized',
- url: TEST_URL,
- });
- await waitForTabUrl(tab);
- chrome.windows.remove(tab.windowId);
- chrome.webRequest.onBeforeRequest.removeListener(check);
- return bugged;
- }
- })();
|