From ad69c5017b28f333bb5ed9a3ee20d95c92d7c046 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Wed, 16 May 2018 01:19:44 +0200 Subject: [PATCH] Removing google drive integration It's sad but it's not working. For multiple releases this should be already broken which shows how often it's used. As there is also a security issue related to that, it's better to remove the feature completely. Whoever wants to rewrite it, feel free to go. This commit removes the Google Drive integration from HackMD's Frontend editor and this way removes the need to provide any API key and Client ID in the frontend. Signed-off-by: Sheogorath --- README.md | 2 +- app.js | 2 - app.json | 4 - public/js/google-drive-picker.js | 118 ------------ public/js/google-drive-upload.js | 267 ---------------------------- public/js/index.js | 104 ----------- public/js/lib/common/constant.ejs | 2 - public/js/lib/config/index.js | 2 - public/js/lib/editor/ui-elements.js | 2 - public/views/hackmd/header.ejs | 12 +- webpackBaseConfig.js | 4 - 11 files changed, 3 insertions(+), 516 deletions(-) delete mode 100644 public/js/google-drive-picker.js delete mode 100644 public/js/google-drive-upload.js diff --git a/README.md b/README.md index 4f5a4b41..f302802c 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ There are some config settings you need to change in the files below. | ------- | --------- | ----------- | | facebook, twitter, github, gitlab, mattermost, dropbox, google, ldap, saml | environment variables or `config.json` | for signin | | imgur, s3, minio | environment variables or `config.json` | for image upload | -| google drive(`google/apiKey`, `google/clientID`), dropbox(`dropbox/appKey`) | `config.json` | for export and import | +| dropbox(`dropbox/appKey`) | `config.json` | for export and import | ## Third-party integration OAuth callback URLs diff --git a/app.js b/app.js index fcf905d5..8f9300f2 100644 --- a/app.js +++ b/app.js @@ -33,8 +33,6 @@ var data = { urlpath: config.urlPath, debug: config.debug, version: config.version, - GOOGLE_API_KEY: config.google.clientSecret, - GOOGLE_CLIENT_ID: config.google.clientID, DROPBOX_APP_KEY: config.dropbox.appKey, allowedUploadMimeTypes: config.allowedUploadMimeTypes } diff --git a/app.json b/app.json index 54ce160e..36877e68 100644 --- a/app.json +++ b/app.json @@ -136,10 +136,6 @@ "description": "Google API client secret", "required": false }, - "HMD_GOOGLE_API_KEY": { - "description": "Google API key (for import/export)", - "required": false - }, "HMD_IMGUR_CLIENTID": { "description": "Imgur API client id", "required": false diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js deleted file mode 100644 index 5006cd25..00000000 --- a/public/js/google-drive-picker.js +++ /dev/null @@ -1,118 +0,0 @@ -/** ! - * Google Drive File Picker Example - * By Daniel Lo Nigro (http://dan.cx/) - */ -(function () { - /** - * Initialise a Google Driver file picker - */ - var FilePicker = window.FilePicker = function (options) { - // Config - this.apiKey = options.apiKey - this.clientId = options.clientId - - // Elements - this.buttonEl = options.buttonEl - - // Events - this.onSelect = options.onSelect - this.buttonEl.on('click', this.open.bind(this)) - - // Disable the button until the API loads, as it won't work properly until then. - this.buttonEl.prop('disabled', true) - - // Load the drive API - window.gapi.client.setApiKey(this.apiKey) - window.gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this)) - window.google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) }) - } - - FilePicker.prototype = { - /** - * Open the file picker. - */ - open: function () { - // Check if the user has already authenticated - var token = window.gapi.auth.getToken() - if (token) { - this._showPicker() - } else { - // The user has not yet authenticated with Google - // We need to do the authentication before displaying the Drive picker. - this._doAuth(false, function () { this._showPicker() }.bind(this)) - } - }, - - /** - * Show the file picker once authentication has been done. - * @private - */ - _showPicker: function () { - var accessToken = window.gapi.auth.getToken().access_token - var view = new window.google.picker.DocsView() - view.setMimeTypes('text/markdown,text/html') - view.setIncludeFolders(true) - view.setOwnedByMe(true) - this.picker = new window.google.picker.PickerBuilder() - .enableFeature(window.google.picker.Feature.NAV_HIDDEN) - .addView(view) - .setAppId(this.clientId) - .setOAuthToken(accessToken) - .setCallback(this._pickerCallback.bind(this)) - .build() - .setVisible(true) - }, - - /** - * Called when a file has been selected in the Google Drive file picker. - * @private - */ - _pickerCallback: function (data) { - if (data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED) { - var file = data[window.google.picker.Response.DOCUMENTS][0] - var id = file[window.google.picker.Document.ID] - var request = window.gapi.client.drive.files.get({ - fileId: id - }) - request.execute(this._fileGetCallback.bind(this)) - } - }, - /** - * Called when file details have been retrieved from Google Drive. - * @private - */ - _fileGetCallback: function (file) { - if (this.onSelect) { - this.onSelect(file) - } - }, - - /** - * Called when the Google Drive file picker API has finished loading. - * @private - */ - _pickerApiLoaded: function () { - this.buttonEl.prop('disabled', false) - }, - - /** - * Called when the Google Drive API has finished loading. - * @private - */ - _driveApiLoaded: function () { - this._doAuth(true) - }, - - /** - * Authenticate with Google Drive via the Google JavaScript API. - * @private - */ - _doAuth: function (immediate, callback) { - window.gapi.auth.authorize({ - client_id: this.clientId, - scope: 'https://www.googleapis.com/auth/drive.readonly', - immediate: immediate - }, callback || function () {}) - } - } -}()) diff --git a/public/js/google-drive-upload.js b/public/js/google-drive-upload.js deleted file mode 100644 index 6c0e8a62..00000000 --- a/public/js/google-drive-upload.js +++ /dev/null @@ -1,267 +0,0 @@ -/* eslint-env browser, jquery */ -/** - * Helper for implementing retries with backoff. Initial retry - * delay is 1 second, increasing by 2x (+jitter) for subsequent retries - * - * @constructor - */ -var RetryHandler = function () { - this.interval = 1000 // Start at one second - this.maxInterval = 60 * 1000 // Don't wait longer than a minute -} - -/** - * Invoke the function after waiting - * - * @param {function} fn Function to invoke - */ -RetryHandler.prototype.retry = function (fn) { - setTimeout(fn, this.interval) - this.interval = this.nextInterval_() -} - -/** - * Reset the counter (e.g. after successful request.) - */ -RetryHandler.prototype.reset = function () { - this.interval = 1000 -} - -/** - * Calculate the next wait time. - * @return {number} Next wait interval, in milliseconds - * - * @private - */ -RetryHandler.prototype.nextInterval_ = function () { - var interval = this.interval * 2 + this.getRandomInt_(0, 1000) - return Math.min(interval, this.maxInterval) -} - -/** - * Get a random int in the range of min to max. Used to add jitter to wait times. - * - * @param {number} min Lower bounds - * @param {number} max Upper bounds - * @private - */ -RetryHandler.prototype.getRandomInt_ = function (min, max) { - return Math.floor(Math.random() * (max - min + 1) + min) -} - -/** - * Helper class for resumable uploads using XHR/CORS. Can upload any Blob-like item, whether - * files or in-memory constructs. - * - * @example - * var content = new Blob(["Hello world"], {"type": "text/plain"}); - * var uploader = new MediaUploader({ - * file: content, - * token: accessToken, - * onComplete: function(data) { ... } - * onError: function(data) { ... } - * }); - * uploader.upload(); - * - * @constructor - * @param {object} options Hash of options - * @param {string} options.token Access token - * @param {blob} options.file Blob-like item to upload - * @param {string} [options.fileId] ID of file if replacing - * @param {object} [options.params] Additional query parameters - * @param {string} [options.contentType] Content-type, if overriding the type of the blob. - * @param {object} [options.metadata] File metadata - * @param {function} [options.onComplete] Callback for when upload is complete - * @param {function} [options.onProgress] Callback for status for the in-progress upload - * @param {function} [options.onError] Callback if upload fails - */ -var MediaUploader = function (options) { - var noop = function () {} - this.file = options.file - this.contentType = options.contentType || this.file.type || 'application/octet-stream' - this.metadata = options.metadata || { - 'title': this.file.name, - 'mimeType': this.contentType - } - this.token = options.token - this.onComplete = options.onComplete || noop - this.onProgress = options.onProgress || noop - this.onError = options.onError || noop - this.offset = options.offset || 0 - this.chunkSize = options.chunkSize || 0 - this.retryHandler = new RetryHandler() - - this.url = options.url - if (!this.url) { - var params = options.params || {} - params.uploadType = 'resumable' - this.url = this.buildUrl_(options.fileId, params, options.baseUrl) - } - this.httpMethod = options.fileId ? 'PUT' : 'POST' -} - -/** - * Initiate the upload. - */ -MediaUploader.prototype.upload = function () { - var xhr = new XMLHttpRequest() - - xhr.open(this.httpMethod, this.url, true) - xhr.setRequestHeader('Authorization', 'Bearer ' + this.token) - xhr.setRequestHeader('Content-Type', 'application/json') - xhr.setRequestHeader('X-Upload-Content-Length', this.file.size) - xhr.setRequestHeader('X-Upload-Content-Type', this.contentType) - - xhr.onload = function (e) { - if (e.target.status < 400) { - var location = e.target.getResponseHeader('Location') - this.url = location - this.sendFile_() - } else { - this.onUploadError_(e) - } - }.bind(this) - xhr.onerror = this.onUploadError_.bind(this) - xhr.send(JSON.stringify(this.metadata)) -} - -/** - * Send the actual file content. - * - * @private - */ -MediaUploader.prototype.sendFile_ = function () { - var content = this.file - var end = this.file.size - - if (this.offset || this.chunkSize) { - // Only bother to slice the file if we're either resuming or uploading in chunks - if (this.chunkSize) { - end = Math.min(this.offset + this.chunkSize, this.file.size) - } - content = content.slice(this.offset, end) - } - - var xhr = new XMLHttpRequest() - xhr.open('PUT', this.url, true) - xhr.setRequestHeader('Content-Type', this.contentType) - xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size) - xhr.setRequestHeader('X-Upload-Content-Type', this.file.type) - if (xhr.upload) { - xhr.upload.addEventListener('progress', this.onProgress) - } - xhr.onload = this.onContentUploadSuccess_.bind(this) - xhr.onerror = this.onContentUploadError_.bind(this) - xhr.send(content) -} - -/** - * Query for the state of the file for resumption. - * - * @private - */ -MediaUploader.prototype.resume_ = function () { - var xhr = new XMLHttpRequest() - xhr.open('PUT', this.url, true) - xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size) - xhr.setRequestHeader('X-Upload-Content-Type', this.file.type) - if (xhr.upload) { - xhr.upload.addEventListener('progress', this.onProgress) - } - xhr.onload = this.onContentUploadSuccess_.bind(this) - xhr.onerror = this.onContentUploadError_.bind(this) - xhr.send() -} - -/** - * Extract the last saved range if available in the request. - * - * @param {XMLHttpRequest} xhr Request object - */ -MediaUploader.prototype.extractRange_ = function (xhr) { - var range = xhr.getResponseHeader('Range') - if (range) { - this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1 - } -} - -/** - * Handle successful responses for uploads. Depending on the context, - * may continue with uploading the next chunk of the file or, if complete, - * invokes the caller's callback. - * - * @private - * @param {object} e XHR event - */ -MediaUploader.prototype.onContentUploadSuccess_ = function (e) { - if (e.target.status === 200 || e.target.status === 201) { - this.onComplete(e.target.response) - } else if (e.target.status === 308) { - this.extractRange_(e.target) - this.retryHandler.reset() - this.sendFile_() - } else { - this.onContentUploadError_(e) - } -} - -/** - * Handles errors for uploads. Either retries or aborts depending - * on the error. - * - * @private - * @param {object} e XHR event - */ -MediaUploader.prototype.onContentUploadError_ = function (e) { - if (e.target.status && e.target.status < 500) { - this.onError(e.target.response) - } else { - this.retryHandler.retry(this.resume_.bind(this)) - } -} - -/** - * Handles errors for the initial request. - * - * @private - * @param {object} e XHR event - */ -MediaUploader.prototype.onUploadError_ = function (e) { - this.onError(e.target.response) // TODO - Retries for initial upload -} - -/** - * Construct a query string from a hash/object - * - * @private - * @param {object} [params] Key/value pairs for query string - * @return {string} query string - */ -MediaUploader.prototype.buildQuery_ = function (params) { - params = params || {} - return Object.keys(params).map(function (key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) - }).join('&') -} - -/** - * Build the drive upload URL - * - * @private - * @param {string} [id] File ID if replacing - * @param {object} [params] Query parameters - * @return {string} URL - */ -MediaUploader.prototype.buildUrl_ = function (id, params, baseUrl) { - var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/' - if (id) { - url += id - } - var query = this.buildQuery_(params) - if (query) { - url += '?' + query - } - return url -} - -window.MediaUploader = MediaUploader diff --git a/public/js/index.js b/public/js/index.js index d76a37fe..c6a4f770 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -30,8 +30,6 @@ import { import { debug, DROPBOX_APP_KEY, - GOOGLE_API_KEY, - GOOGLE_CLIENT_ID, noteid, noteurl, urlpath, @@ -908,29 +906,6 @@ if (DROPBOX_APP_KEY) { ui.toolbar.export.dropbox.hide() } -// check if google api key and client id are set and load scripts -if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) { - $('