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 <sheogorath@shivering-isles.com>
This commit is contained in:
Sheogorath 2018-05-16 01:19:44 +02:00
parent b8e7c4b97a
commit ad69c5017b
No known key found for this signature in database
GPG key ID: 1F05CC3635CDDFFD
11 changed files with 3 additions and 516 deletions

View file

@ -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

2
app.js
View file

@ -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
}

View file

@ -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

View file

@ -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 () {})
}
}
}())

View file

@ -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

View file

@ -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) {
$('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded')
.prop('async', true)
.prop('defer', true)
.appendTo('body')
} else {
ui.toolbar.import.googleDrive.hide()
ui.toolbar.export.googleDrive.hide()
}
function onGoogleAPILoaded () {
$('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded')
.prop('async', true)
.prop('defer', true)
.appendTo('body')
}
window.onGoogleAPILoaded = onGoogleAPILoaded
// button actions
// share
ui.toolbar.publish.attr('href', noteurl + '/publish')
@ -979,53 +954,6 @@ ui.toolbar.export.dropbox.click(function () {
}
Dropbox.save(options)
})
function uploadToGoogleDrive (accessToken) {
ui.spinner.show()
var filename = renderFilename(ui.area.markdown) + '.md'
var markdown = editor.getValue()
var blob = new Blob([markdown], {
type: 'text/markdown;charset=utf-8'
})
blob.name = filename
var uploader = new MediaUploader({
file: blob,
token: accessToken,
onComplete: function (data) {
data = JSON.parse(data)
showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true)
ui.spinner.hide()
},
onError: function (data) {
showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false)
ui.spinner.hide()
}
})
uploader.upload()
}
function googleApiAuth (immediate, callback) {
gapi.auth.authorize(
{
'client_id': GOOGLE_CLIENT_ID,
'scope': 'https://www.googleapis.com/auth/drive.file',
'immediate': immediate
}, callback || function () { })
}
function onGoogleClientLoaded () {
googleApiAuth(true)
buildImportFromGoogleDrive()
}
window.onGoogleClientLoaded = onGoogleClientLoaded
// export to google drive
ui.toolbar.export.googleDrive.click(function (e) {
var token = gapi.auth.getToken()
if (token) {
uploadToGoogleDrive(token.access_token)
} else {
googleApiAuth(false, function (result) {
uploadToGoogleDrive(result.access_token)
})
}
})
// export to gist
ui.toolbar.export.gist.attr('href', noteurl + '/gist')
// export to snippet
@ -1075,38 +1003,6 @@ ui.toolbar.import.dropbox.click(function () {
}
Dropbox.choose(options)
})
// import from google drive
function buildImportFromGoogleDrive () {
/* eslint-disable no-unused-vars */
let picker = new FilePicker({
apiKey: GOOGLE_API_KEY,
clientId: GOOGLE_CLIENT_ID,
buttonEl: ui.toolbar.import.googleDrive,
onSelect: function (file) {
if (file.downloadUrl) {
ui.spinner.show()
var accessToken = gapi.auth.getToken().access_token
$.ajax({
type: 'GET',
beforeSend: function (request) {
request.setRequestHeader('Authorization', 'Bearer ' + accessToken)
},
url: file.downloadUrl,
success: function (data) {
if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) }
},
error: function (data) {
showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false)
},
complete: function () {
ui.spinner.hide()
}
})
}
}
})
/* eslint-enable no-unused-vars */
}
// import from gist
ui.toolbar.import.gist.click(function () {
// na

View file

@ -5,6 +5,4 @@ window.version = '<%- version %>'
window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %>
window.GOOGLE_API_KEY = '<%- GOOGLE_API_KEY %>'
window.GOOGLE_CLIENT_ID = '<%- GOOGLE_CLIENT_ID %>'
window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>'

View file

@ -1,5 +1,3 @@
export const GOOGLE_API_KEY = window.GOOGLE_API_KEY || ''
export const GOOGLE_CLIENT_ID = window.GOOGLE_CLIENT_ID || ''
export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || ''
export const domain = window.domain || '' // domain name

View file

@ -22,13 +22,11 @@ export const getUIElements = () => ({
},
export: {
dropbox: $('.ui-save-dropbox'),
googleDrive: $('.ui-save-google-drive'),
gist: $('.ui-save-gist'),
snippet: $('.ui-save-snippet')
},
import: {
dropbox: $('.ui-import-dropbox'),
googleDrive: $('.ui-import-google-drive'),
gist: $('.ui-import-gist'),
snippet: $('.ui-import-snippet'),
clipboard: $('.ui-import-clipboard')

View file

@ -32,13 +32,11 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
</li>
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Export') %></li>
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
<li role="presentation"><a role="menuitem" class="ui-save-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-upload fa-fw"></i> Google Drive</a>
</li>
<% if(typeof github !== 'undefined' && github) { %>
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
@ -52,8 +50,6 @@
<li class="dropdown-header"><%= __('Import') %></li>
<li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
<li role="presentation"><a role="menuitem" class="ui-import-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-download fa-fw"></i> Google Drive</a>
</li>
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
@ -138,13 +134,11 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
</li>
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Export') %></li>
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
<li role="presentation"><a role="menuitem" class="ui-save-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-upload fa-fw"></i> Google Drive</a>
</li>
<% if(typeof github !== 'undefined' && github) { %>
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
@ -158,8 +152,6 @@
<li class="dropdown-header"><%= __('Import') %></li>
<li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
<li role="presentation"><a role="menuitem" class="ui-import-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-download fa-fw"></i> Google Drive</a>
</li>
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>

View file

@ -208,8 +208,6 @@ module.exports = {
'flowchart.js',
'js-sequence-diagrams',
'expose?RevealMarkdown!reveal-markdown',
path.join(__dirname, 'public/js/google-drive-upload.js'),
path.join(__dirname, 'public/js/google-drive-picker.js'),
path.join(__dirname, 'public/js/index.js')
],
'index-styles': [
@ -266,8 +264,6 @@ module.exports = {
'script!abcjs',
'expose?io!socket.io-client',
'expose?RevealMarkdown!reveal-markdown',
path.join(__dirname, 'public/js/google-drive-upload.js'),
path.join(__dirname, 'public/js/google-drive-picker.js'),
path.join(__dirname, 'public/js/index.js')
],
pretty: [