Skip to content

Instantly share code, notes, and snippets.

@loopmode
Last active August 17, 2018 05:43
Show Gist options
  • Save loopmode/9f72d22fcadc58c16addac52aaef4fac to your computer and use it in GitHub Desktop.
Save loopmode/9f72d22fcadc58c16addac52aaef4fac to your computer and use it in GitHub Desktop.
import * as spauth from 'node-sp-auth';
import getBaseUrl from '../utils/getBaseUrl';
export const AuthMethod = {
NONE: 'NONE',
NTLM: 'NTLM',
BASIC: 'BASIC'
};
export function getAuthMethod(authenticate) {
if (!authenticate) {
return AuthMethod.NONE;
}
if (authenticate.match(/NTLM/i)) {
return AuthMethod.NTLM;
}
if (authenticate.match(/Basic /i)) {
return AuthMethod.BASIC;
}
console.warn(`Unsupported authentication method: ${authenticate}`);
return undefined;
}
export function getAuthOptions(data) {
switch (getAuthMethod(data.authenticate)) {
case AuthMethod.BASIC:
return getAuthOptionsBasic(data);
case AuthMethod.NTLM:
return getAuthOptionsNTLM(data);
default:
return {};
}
}
export function getAuthOptionsBasic({ username, password }) {
return {
headers: {
Authorization: 'Basic ' + new Buffer(username + ':' + password).toString('base64')
}
};
}
export async function getAuthOptionsNTLM({ url, username, password, domain }) {
if (username.indexOf('\\') !== -1) {
const parts = username.split('\\');
domain = parts[0];
username = parts[1];
}
const data = await spauth.getAuth(getBaseUrl(url), {
username,
password,
domain
});
return {
...data.options,
headers: data.headers
};
}
import fs from 'fs-extra';
import path from 'path';
import crypto from 'crypto';
import * as request from 'request';
import config from '../config';
import ExternalAuthError from '../errors/ExternalAuthError';
import { getAuthOptions } from './Auth';
const tempDir = path.resolve(config.app.storage.attachments, '.temp');
/**
* Downloads a file from a URL to a (temp) location and saves it with a random filename.
* Returns a promise that is resolved with the full path to the downloaded file.
* Handles Basic and NTLM auth when given `user`, `password` and `authenticate`
*
* @param {String} url - the remote file to be downloaded
* @param {Object} [options] - An options object
* @param {String} [options.dir = tempDir] - Local path to a destination folder for the downloaded file.
* @param {String} [options.authenticate] - Type of authentication required by remote file. of `BASIC` or `NTLM`
* @param {String} [options.domain] - Domain for remote authentication. May be part of username as well.
* @param {String} [options.username] - Username for remote authentication. May contain a domain name (e.g. `domain\username`)
* @param {String} [options.password] - Password for remote authentication
*/
export default async function downloadToTemp(url, { dir = tempDir, authenticate, domain, username, password } = {}) {
const authOptions = await getAuthOptions({ url, username, password, domain, authenticate });
const requestOptions = { url, ...authOptions };
return new Promise((resolve, reject) => {
// start off with a paused stream - we need to check response headers first, then resume
// that's because the on('error') handler is not invoked on all errors - e.g. http error responses >=400 slip through
// see https://stackoverflow.com/questions/7222982/node-request-how-to-determine-if-an-error-occurred-during-the-request/26163128#26163128
const req = request.get(requestOptions);
req.pause();
// however, it is invoked in some error cases
req.on('error', function(error) {
reject(error);
});
req.on('response', async response => {
if (response.statusCode === 401) {
// Authentication required
reject(new ExternalAuthError(response));
} else if (response.statusCode >= 400) {
// Some other error
reject(new Error(response.statusCode));
} else {
// Download is available
try {
await fs.ensureDir(dir);
const id = crypto.randomBytes(20).toString('hex');
const filepath = path.resolve(dir, id);
req.pipe(fs.createWriteStream(filepath));
req.on('end', () => resolve(filepath));
req.resume();
} catch (error) {
reject(error);
}
}
});
});
}
/**
* Downloads a file from a URL and puts it in the user uploads folder.
* Creates a new Attachment node for that file in orientdb and connects it to the user node.
* Supports NTLM an basic auth (e.g. sharepoint, htaccess)
* Returns a promise.
*
* @param {String} url - The URL of the file
* @param {User|String} user - A user object or user ID
* @param {Object} [options] - An options object
* @param {String} [options.filename] - An optional name for the saved file. Defaults to the last segment of the URL
* @return {Object} - A `{ filename, filepath, hash }` object
*/
async function downloadForUser(url, userId, options = {}) {
const tempfilePath = await downloadToTemp(url, {
username: options.username,
password: options.password,
authenticate: options.authenticate
});
const hash = await getFileHash(tempfilePath);
const filename = options.filename || getFileName(url);
const dirpath = path.resolve(config.app.storage.attachments, userId, hash);
const filepath = path.resolve(dirpath, filename);
await fs.ensureDir(dirpath);
await fs.move(tempfilePath, filepath, { overwrite: true });
const fileInfo = { filename, filepath, hash };
const attachment = await this.getUserAttachment(fileInfo, userId);
if (attachment.vertex) {
const { vertex, edge } = attachment;
return { fileInfo, vertex, edge };
} else {
const { vertex, edge } = await Attachments.createUserAttachment(fileInfo, userId);
return { fileInfo, vertex, edge };
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment