├── .travis.yml ├── .editorconfig ├── bin.js ├── package.json ├── license ├── .gitignore ├── readme.md └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | const {app} = require('electron'); 3 | const yargs = require('yargs'); 4 | const ego = require('./index'); 5 | 6 | const auth = ego(); 7 | const argv = yargs.argv; 8 | const preventQuit = e => e.preventDefault(); 9 | 10 | app.on('will-quit', preventQuit); 11 | 12 | app.on('ready', () => { 13 | auth.getAccessToken( 14 | argv.scopes, 15 | argv.clientId, 16 | argv.clientSecret, 17 | argv.redirectUri 18 | ) 19 | .then(token => { 20 | process.stdout.write(JSON.stringify(token, null, 2)); 21 | app.removeListener('will-quit', preventQuit); 22 | app.quit(); 23 | }) 24 | .catch(err => { 25 | process.stderr.write(err.message + '\n'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-google-oauth", 3 | "description": "Google api access token in electron", 4 | "keywords": [ 5 | "google api", 6 | "access token", 7 | "electron", 8 | "oauth", 9 | "oauth2" 10 | ], 11 | "repository": "parro-it/electron-google-oauth", 12 | "version": "2.0.0", 13 | "bin": { 14 | "ego": "bin.js" 15 | }, 16 | "dependencies": { 17 | "co": "^4.6.0", 18 | "googleapis": "^2.1.3", 19 | "node-fetch": "^1.3.2", 20 | "yargs": "^3.20.0" 21 | }, 22 | "files": ["bin.js", "index.js"], 23 | "author": "parro-it", 24 | "scripts": { 25 | "test": "xo", 26 | "start": "electron bin.js --scopes https://www.google.com/m8/feeds --clientId $CLIENTID --clientSecret $CLIENTSECRET" 27 | }, 28 | "devDependencies": { 29 | "electron-prebuilt": "^1.3.3", 30 | "xo": "^0.16.0" 31 | }, 32 | "license": "MIT" 33 | } 34 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Andrea Parodi 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== SublimeText ===##### 4 | # cache files for sublime text 5 | *.tmlanguage.cache 6 | *.tmPreferences.cache 7 | *.stTheme.cache 8 | 9 | # workspace files are user-specific 10 | *.sublime-workspace 11 | 12 | # project files should be checked into the repository, unless a significant 13 | # proportion of contributors will probably not be using SublimeText 14 | # *.sublime-project 15 | 16 | # sftp configuration file 17 | sftp-config.json 18 | 19 | #####=== Node ===##### 20 | 21 | # Logs 22 | logs 23 | *.log 24 | 25 | # Runtime data 26 | pids 27 | *.pid 28 | *.seed 29 | 30 | # Directory for instrumented libs generated by jscoverage/JSCover 31 | lib-cov 32 | 33 | # Coverage directory used by tools like istanbul 34 | coverage 35 | 36 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (http://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directory 46 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 47 | node_modules 48 | 49 | # Debug log from npm 50 | npm-debug.log 51 | private.sh 52 | 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # electron-google-oauth 2 | 3 | Get Google api access token using an electron window 4 | to let the user authorize the app. 5 | 6 | [![Travis Build Status](https://img.shields.io/travis/parro-it/electron-google-oauth/master.svg)](http://travis-ci.org/parro-it/electron-google-oauth) 7 | 8 | ## Installation 9 | 10 | ```bash 11 | npm install --save electron-google-oauth 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```javascript 17 | import electronGoogleOauth from 'electron-google-oauth'; 18 | 19 | const browserWindowParams = { 20 | 'use-content-size': true, 21 | center: true, 22 | show: false, 23 | resizable: false, 24 | 'always-on-top': true, 25 | 'standard-window': true, 26 | 'auto-hide-menu-bar': true, 27 | 'node-integration': false 28 | }; 29 | 30 | const googleOauth = electronGoogleOauth(browserWindowParams); 31 | 32 | ( async () => { 33 | 34 | // retrieve authorization code only 35 | const authCode = await googleOauth.getAuthorizationCode( 36 | ['https://www.google.com/m8/feeds'], 37 | 'your-client-id', 38 | 'your-client-secret', 39 | 'your-redirect-uri' 40 | ); 41 | console.dir(authCode); 42 | 43 | // retrieve access token and refresh token 44 | const result = await googleOauth.getAccessToken( 45 | ['https://www.google.com/m8/feeds'], 46 | 'your-client-id', 47 | 'your-client-secret', 48 | 'your-redirect-uri' 49 | ); 50 | console.dir(result); 51 | 52 | })(); 53 | 54 | ``` 55 | 56 | ## Testing 57 | 58 | To test the library, clone this git repo, then define CLIENTSECRET and CLIENTID environment variables holding your Google API details, then run: 59 | 60 | 61 | ```bash 62 | npm install 63 | npm start 64 | ``` 65 | 66 | ## License 67 | 68 | The MIT License (MIT) 69 | 70 | Copyright (c) 2015 Andrea Parodi 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const {stringify} = require('querystring'); 2 | const google = require('googleapis'); 3 | const co = require('co'); 4 | const fetch = require('node-fetch'); 5 | // eslint-disable-next-line import/no-extraneous-dependencies 6 | const {BrowserWindow} = require('electron'); 7 | 8 | const OAuth2 = google.auth.OAuth2; 9 | 10 | /* eslint-disable camelcase */ 11 | 12 | function getAuthenticationUrl(scopes, clientId, clientSecret, redirectUri = 'urn:ietf:wg:oauth:2.0:oob') { 13 | const oauth2Client = new OAuth2( 14 | clientId, 15 | clientSecret, 16 | redirectUri 17 | ); 18 | const url = oauth2Client.generateAuthUrl({ 19 | access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token) 20 | scope: scopes // If you only need one scope you can pass it as string 21 | }); 22 | return url; 23 | } 24 | 25 | function authorizeApp(url, browserWindowParams) { 26 | return new Promise((resolve, reject) => { 27 | const win = new BrowserWindow(browserWindowParams || {'use-content-size': true}); 28 | 29 | win.loadURL(url); 30 | 31 | win.on('closed', () => { 32 | reject(new Error('User closed the window')); 33 | }); 34 | 35 | win.on('page-title-updated', () => { 36 | setImmediate(() => { 37 | const title = win.getTitle(); 38 | if (title.startsWith('Denied')) { 39 | reject(new Error(title.split(/[ =]/)[2])); 40 | win.removeAllListeners('closed'); 41 | win.close(); 42 | } else if (title.startsWith('Success')) { 43 | resolve(title.split(/[ =]/)[2]); 44 | win.removeAllListeners('closed'); 45 | win.close(); 46 | } 47 | }); 48 | }); 49 | }); 50 | } 51 | 52 | module.exports = function electronGoogleOauth(browserWindowParams, httpAgent) { 53 | function getAuthorizationCode(scopes, clientId, clientSecret, redirectUri = 'urn:ietf:wg:oauth:2.0:oob') { 54 | const url = getAuthenticationUrl(scopes, clientId, clientSecret, redirectUri); 55 | return authorizeApp(url, browserWindowParams); 56 | } 57 | 58 | const getAccessToken = co.wrap(function * (scopes, clientId, clientSecret, redirectUri = 'urn:ietf:wg:oauth:2.0:oob') { 59 | const authorizationCode = yield getAuthorizationCode(scopes, clientId, clientSecret, redirectUri); 60 | 61 | const data = stringify({ 62 | code: authorizationCode, 63 | client_id: clientId, 64 | client_secret: clientSecret, 65 | grant_type: 'authorization_code', 66 | redirect_uri: redirectUri 67 | }); 68 | 69 | const res = yield fetch('https://accounts.google.com/o/oauth2/token', { 70 | method: 'post', 71 | headers: { 72 | 'Accept': 'application/json', 73 | 'Content-Type': 'application/x-www-form-urlencoded' 74 | }, 75 | body: data, 76 | agent: httpAgent 77 | }); 78 | return yield res.json(); 79 | }); 80 | 81 | return {getAuthorizationCode, getAccessToken}; 82 | }; 83 | --------------------------------------------------------------------------------