diff --git a/CliClient/app/main.js b/CliClient/app/main.js index 1a6b64a82..526de9fb7 100644 --- a/CliClient/app/main.js +++ b/CliClient/app/main.js @@ -677,27 +677,28 @@ async function synchronizer(syncTarget) { let fileApi = null; if (syncTarget == 'onedrive') { - const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b'; - const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5'; + let oneDriveApi = oneDriveApi.instance(); + // const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b'; + // const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5'; - let driver = new FileApiDriverOneDrive(CLIENT_ID, CLIENT_SECRET); + //let driver = new FileApiDriverOneDrive(CLIENT_ID, CLIENT_SECRET); + let driver = new FileApiDriverOneDrive(oneDriveApi); let auth = Setting.value('sync.onedrive.auth'); if (auth) { auth = JSON.parse(auth); } else { - //auth = await driver.api().oauthDance(vorpal); - const oneDriveApiUtils = new OneDriveApiNodeUtils(driver.api()); + const oneDriveApiUtils = new OneDriveApiNodeUtils(oneDriveApi); auth = await oneDriveApiUtils.oauthDance(vorpal); Setting.setValue('sync.onedrive.auth', JSON.stringify(auth)); } - driver.api().setAuth(auth); - driver.api().on('authRefreshed', (a) => { + //oneDriveApi.setAuth(auth); + oneDriveApi.on('authRefreshed', (a) => { Setting.setValue('sync.onedrive.auth', JSON.stringify(a)); }); - let appDir = await driver.api().appDirectory(); + let appDir = await oneDriveApi.appDirectory(); logger.info('App dir: ' + appDir); fileApi = new FileApi(appDir, driver); fileApi.setLogger(logger); diff --git a/CliClient/app/onedrive-api-node-utils.js b/CliClient/app/onedrive-api-node-utils.js index 1ec7c4b3c..fe6bb6b16 100644 --- a/CliClient/app/onedrive-api-node-utils.js +++ b/CliClient/app/onedrive-api-node-utils.js @@ -53,35 +53,49 @@ class OneDriveApiNodeUtils { if (!query.code) return writeResponse(400, '"code" query parameter is missing'); - let body = new FormData(); - body.append('client_id', this.api().clientId()); - body.append('client_secret', this.api().clientSecret()); - body.append('code', query.code ? query.code : ''); - body.append('redirect_uri', 'http://localhost:' + port.toString()); - body.append('grant_type', 'authorization_code'); + // let body = new FormData(); + // body.append('client_id', this.api().clientId()); + // body.append('client_secret', this.api().clientSecret()); + // body.append('code', query.code ? query.code : ''); + // body.append('redirect_uri', 'http://localhost:' + port.toString()); + // body.append('grant_type', 'authorization_code'); - let options = { - method: 'POST', - body: body, - }; + // let options = { + // method: 'POST', + // body: body, + // }; - fetch(this.api().tokenBaseUrl(), options).then((r) => { - if (!r.ok) { - errorMessage = 'Could not retrieve auth code: ' + r.status + ': ' + r.statusText; - writeResponse(400, errorMessage); - targetConsole.log(''); - targetConsole.log(errorMessage); - server.destroy(); - return; - } + // fetch(this.api().tokenBaseUrl(), options).then((r) => { - return r.json().then((json) => { - this.api().setAuth(json); - writeResponse(200, 'The application has been authorised - you may now close this browser tab.'); - targetConsole.log(''); - targetConsole.log('The application has been successfully authorised.'); - server.destroy(); - }); + // this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then((r) => { + // if (!r.ok) { + // errorMessage = 'Could not retrieve auth code: ' + r.status + ': ' + r.statusText; + // writeResponse(400, errorMessage); + // targetConsole.log(''); + // targetConsole.log(errorMessage); + // server.destroy(); + // return; + // } + + // return r.json().then((json) => { + // this.api().setAuth(json); + // writeResponse(200, 'The application has been authorised - you may now close this browser tab.'); + // targetConsole.log(''); + // targetConsole.log('The application has been successfully authorised.'); + // server.destroy(); + // }); + // }); + + this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then(() => { + writeResponse(200, 'The application has been authorised - you may now close this browser tab.'); + targetConsole.log(''); + targetConsole.log('The application has been successfully authorised.'); + server.destroy(); + }).catch((error) => { + writeResponse(400, error.message); + targetConsole.log(''); + targetConsole.log(error.message); + server.destroy(); }); }); diff --git a/ReactNativeClient/lib/components/screen-header.js b/ReactNativeClient/lib/components/screen-header.js index 50bbb755b..dc3bcd230 100644 --- a/ReactNativeClient/lib/components/screen-header.js +++ b/ReactNativeClient/lib/components/screen-header.js @@ -41,6 +41,11 @@ class ScreenHeaderComponent extends Component { } menu_synchronize() { + this.props.dispatch({ + type: 'Navigation/NAVIGATE', + routeName: 'OneDriveLogin', + }); + // const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b'; // const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5'; diff --git a/ReactNativeClient/lib/components/screens/onedrive-login.js b/ReactNativeClient/lib/components/screens/onedrive-login.js new file mode 100644 index 000000000..f192c1f9c --- /dev/null +++ b/ReactNativeClient/lib/components/screens/onedrive-login.js @@ -0,0 +1,90 @@ +import React, { Component } from 'react'; +import { View } from 'react-native'; +import { WebView, Button } from 'react-native'; +import { connect } from 'react-redux' +import { Log } from 'lib/log.js' +import { ScreenHeader } from 'lib/components/screen-header.js'; +import { OneDriveApi } from 'lib/onedrive-api.js'; +import { _ } from 'lib/locale.js'; + +class OneDriveLoginScreenComponent extends React.Component { + + static navigationOptions(options) { + return { header: null }; + } + + constructor() { + super(); + this.state = { webviewUrl: '' }; + this.authCode_ = null; + } + + componentWillMount() { + this.setState({ + webviewUrl: this.api().authCodeUrl(this.redirectUrl()), + }); + } + + api() { + return OneDriveApi.instance(); + + redirectUrl() { + return 'https://login.microsoftonline.com/common/oauth2/nativeclient'; + } + + async webview_load(noIdeaWhatThisIs) { + // This is deprecated according to the doc but since the non-deprecated property (source) + // doesn't exist, use this for now. The whole component is completely undocumented + // at the moment so it's likely to change. + const url = noIdeaWhatThisIs.url; + + console.info('URL: ' + url); + + if (!this.authCode_) { + if (url.indexOf(this.redirectUrl() + '?code=') === 0) { + let code = url.split('?code='); + this.authCode_ = code[1]; + + await this.api().execTokenRequest(this.authCode_, this.redirectUrl(), true); + Setting.setValue('sync.onedrive.auth', JSON.stringify(this.api().auth())); + oneDriveApi.on('authRefreshed', (a) => { + Setting.setValue('sync.onedrive.auth', JSON.stringify(a)); + }); + + let appDir = await this.api().appDirectory(); + + Log.info('APP DIR: ' + appDir); + // fileApi = new FileApi(appDir, driver); + // fileApi.setLogger(logger); + } + } + } + + render() { + const source = { + uri: this.state.webviewUrl, + } + + // + + return ( + + + { this.webview_load(o); }} + /> + + ); + } + +} + +const OneDriveLoginScreen = connect( + (state) => { + return {}; + } +)(OneDriveLoginScreenComponent) + +export { OneDriveLoginScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/file-api-driver-onedrive.js b/ReactNativeClient/lib/file-api-driver-onedrive.js index 5ea93c705..669db2299 100644 --- a/ReactNativeClient/lib/file-api-driver-onedrive.js +++ b/ReactNativeClient/lib/file-api-driver-onedrive.js @@ -5,8 +5,10 @@ import { OneDriveApi } from 'lib/onedrive-api.js'; class FileApiDriverOneDrive { - constructor(clientId, clientSecret) { - this.api_ = new OneDriveApi(clientId, clientSecret); + //constructor(clientId, clientSecret) { + constructor(api) { + this.api_ = api; + //this.api_ = new OneDriveApi(clientId, clientSecret); } api() { diff --git a/ReactNativeClient/lib/onedrive-api.js b/ReactNativeClient/lib/onedrive-api.js index 0fe16f032..c582bbc56 100644 --- a/ReactNativeClient/lib/onedrive-api.js +++ b/ReactNativeClient/lib/onedrive-api.js @@ -12,6 +12,15 @@ class OneDriveApi { }; } + static instance() { + if (this.instance_) return this.instance_; + + const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b'; + const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5'; + this.instance_ = new OneDriveApi(CLIENT_ID, CLIENT_SECRET); + return this.instance_; + } + dispatch(eventName, param) { let ls = this.listeners_[eventName]; for (let i = 0; i < ls.length; i++) { @@ -47,10 +56,6 @@ class OneDriveApi { return this.clientSecret_; } - // possibleOAuthDancePorts() { - // return [1917, 9917, 8917]; - // } - async appDirectory() { let r = await this.execJson('GET', '/drive/special/approot'); return r.parentReference.path + '/' + r.name; @@ -66,6 +71,34 @@ class OneDriveApi { return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + stringify(query); } + async execTokenRequest(code, redirectUri, isPublic = false) { + let body = new shim.FormData(); + body.append('client_id', this.clientId()); + if (!isPublic) body.append('client_secret', this.clientSecret()); + body.append('code', code); + body.append('redirect_uri', redirectUri); + body.append('grant_type', 'authorization_code'); + + const r = await shim.fetch(this.tokenBaseUrl(), { + method: 'POST', + body: body, + }) + + if (!r.ok) { + const text = await r.text(); + throw new Error('Could not retrieve auth code: ' + r.status + ': ' + r.statusText + ': ' + text); + } + + try { + const json = await r.json(); + this.setAuth(json); + } catch (error) { + const text = await r.text(); + error.message += ': ' + text; + throw error; + } + } + oneDriveErrorResponseToError(errorResponse) { if (!errorResponse) return new Error('Undefined error'); @@ -181,90 +214,6 @@ class OneDriveApi { this.dispatch('authRefreshed', this.auth_); } - // async oauthDance(targetConsole = null) { - // if (targetConsole === null) targetConsole = console; - - // this.auth_ = null; - - // let ports = this.possibleOAuthDancePorts(); - // let port = null; - // for (let i = 0; i < ports.length; i++) { - // let inUse = await tcpPortUsed.check(ports[i]); - // if (!inUse) { - // port = ports[i]; - // break; - // } - // } - - // if (!port) throw new Error('All potential ports are in use - please report the issue at https://github.com/laurent22/joplin'); - - // let authCodeUrl = this.authCodeUrl('http://localhost:' + port); - - // return new Promise((resolve, reject) => { - // let server = http.createServer(); - // let errorMessage = null; - - // server.on('request', (request, response) => { - // const query = urlParser.parse(request.url, true).query; - - // function writeResponse(code, message) { - // response.writeHead(code, {"Content-Type": "text/html"}); - // response.write(message); - // response.end(); - // } - - // if (!query.code) return writeResponse(400, '"code" query parameter is missing'); - - // let body = new shim.FormData(); - // body.append('client_id', this.clientId()); - // body.append('client_secret', this.clientSecret()); - // body.append('code', query.code ? query.code : ''); - // body.append('redirect_uri', 'http://localhost:' + port.toString()); - // body.append('grant_type', 'authorization_code'); - - // let options = { - // method: 'POST', - // body: body, - // }; - - // fetch(this.tokenBaseUrl(), options).then((r) => { - // if (!r.ok) { - // errorMessage = 'Could not retrieve auth code: ' + r.status + ': ' + r.statusText; - // writeResponse(400, errorMessage); - // targetConsole.log(''); - // targetConsole.log(errorMessage); - // server.destroy(); - // return; - // } - - // return r.json().then((json) => { - // this.auth_ = json; - // writeResponse(200, 'The application has been authorised - you may now close this browser tab.'); - // targetConsole.log(''); - // targetConsole.log('The application has been successfully authorised.'); - // server.destroy(); - // }); - // }); - // }); - - // server.on('close', () => { - // if (errorMessage) { - // reject(new Error(errorMessage)); - // } else { - // resolve(this.auth_); - // } - // }); - - // server.listen(port); - - // enableServerDestroy(server); - - // targetConsole.log('Please open this URL in your browser to authentify the application:'); - // targetConsole.log(''); - // targetConsole.log(authCodeUrl); - // }); - // } - } export { OneDriveApi }; \ No newline at end of file diff --git a/ReactNativeClient/lib/shim.js b/ReactNativeClient/lib/shim.js index 874f369c4..7084166b5 100644 --- a/ReactNativeClient/lib/shim.js +++ b/ReactNativeClient/lib/shim.js @@ -1,6 +1,7 @@ let shim = {}; shim.fetch = typeof fetch !== 'undefined' ? fetch : null; +shim.FormData = typeof FormData !== 'undefined' ? FormData : null; if (!shim.fetch) { let moduleName = 'node-fetch'; diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index 9172b6420..b6788bc1f 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -19,6 +19,7 @@ import { FolderScreen } from 'lib/components/screens/folder.js' import { FoldersScreen } from 'lib/components/screens/folders.js' import { LoginScreen } from 'lib/components/screens/login.js' import { LoadingScreen } from 'lib/components/screens/loading.js' +import { OneDriveLoginScreen } from 'lib/components/screens/onedrive-login.js' import { Setting } from 'lib/models/setting.js' import { Synchronizer } from 'lib/synchronizer.js' import { MenuContext } from 'react-native-popup-menu'; @@ -184,6 +185,7 @@ const AppNavigator = StackNavigator({ Folders: { screen: FoldersScreen }, Login: { screen: LoginScreen }, Loading: { screen: LoadingScreen }, + OneDriveLogin: { screen: OneDriveLoginScreen }, }); class AppComponent extends React.Component {