1
0
Fork 0
arangodb/js/server/modules/@arangodb/foxx/oauth1.js

221 lines
6.9 KiB
JavaScript

/* eslint camelcase: 0 */
'use strict';
// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// / http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
// / @author Alan Plum
// //////////////////////////////////////////////////////////////////////////////
const assert = require('assert');
const url = require('url');
const parseUrl = url.parse;
const formatUrl = url.format;
const parseQuery = require('querystring').parse;
const request = require('@arangodb/request');
const crypto = require('@arangodb/crypto');
function nonceAndTime () {
const oauth_timestamp = Math.floor(Date.now() / 1000);
const oauth_nonce = crypto.genRandomAlphaNumbers(32);
return {oauth_timestamp, oauth_nonce};
}
function normalizeUrl (requestUrl, parameters) {
const urlParts = url.parse(requestUrl);
const urlTokens = [urlParts.protocol, '//', urlParts.hostname];
if (urlParts.port && (
(urlParts.protocol === 'http:' && urlParts.port !== '80') ||
(urlParts.protocol === 'https:' && urlParts.port !== '443')
)) {
urlTokens.push(':', urlParts.port);
}
urlTokens.push(urlParts.pathname);
const params = [];
if (urlParts.query) {
const qs = parseQuery(urlParts.query);
parameters = Object.assign(qs, parameters);
}
for (const key of Object.keys(parameters)) {
const value = parameters[key] === undefined ? '' : String(parameters[key]);
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
}
return {
url: urlTokens.join(''),
params: params.sort().join('&')
};
}
function createSignatureBaseString (method, normalizedUrl, normalizedParams) {
return [
method.toUpperCase(),
encodeURIComponent(normalizedUrl),
encodeURIComponent(normalizedParams)
].join('&');
}
function parse (str) {
try {
return JSON.parse(str);
} catch (e) {
if (e instanceof SyntaxError) {
return parseQuery(str);
}
throw e;
}
}
module.exports = function oauth1 (cfg) {
if (!cfg) {
cfg = {};
}
assert(cfg.requestTokenEndpoint, 'No Request Token URL specified');
assert(cfg.authEndpoint, 'No User Authorization URL specified');
assert(cfg.accessTokenEndpoint, 'No Access Token URL specified');
assert(cfg.clientId, 'No Client ID specified');
assert(cfg.clientSecret, 'No Client Secret specified');
const oauth_consumer_key = cfg.clientId;
const oauth_signature_method = cfg.signatureMethod || 'HMAC-SHA1';
assert((
oauth_signature_method === 'HMAC-SHA1' ||
oauth_signature_method === 'PLAINTEXT'
), `Unsupported signature method: "${oauth_signature_method}"`);
function createSignature (signatureBaseString, token_secret) {
const key = `${cfg.clientSecret || ''}&${token_secret || ''}`;
if (oauth_signature_method === 'PLAINTEXT') {
return key;
}
const hex = crypto.hmac(key, signatureBaseString, 'sha1');
return new Buffer(hex, 'hex').toString('base64');
}
function createRequest (method, url, opts, token_secret) {
const normalized = normalizeUrl(
url,
Object.assign(
nonceAndTime(),
{
oauth_consumer_key,
oauth_signature_method,
oauth_version: '1.0'
},
opts
)
);
const signatureBaseString = createSignatureBaseString(
'POST',
normalized.url,
normalized.params
);
const signature = createSignature(signatureBaseString, token_secret);
normalized.params += `&oauth_signature=${encodeURIComponent(signature)}`;
const queryParams = [];
const authParams = [];
if (cfg.realm) {
authParams.push(`realm="${encodeURIComponent(cfg.realm)}"`);
}
for (const param of normalized.params.split('&')) {
if (param.startsWith('oauth_')) {
const [key, value] = param.split('=');
authParams.push(`${key}="${value}"`);
} else {
queryParams.push(param);
}
}
return {
url: normalized.url,
qs: queryParams.join('&'),
headers: {
accept: 'application/json',
authorization: `OAuth ${authParams.join(', ')}`
}
};
}
return {
fetchRequestToken (oauth_callback, opts) {
assert(oauth_callback, 'No Callback URL specified');
const req = createRequest(
'POST',
cfg.requestTokenEndpoint,
Object.assign({oauth_callback}, opts)
);
const res = request.post(req);
if (!res.body) {
throw new Error(`OAuth provider returned empty response with HTTP status ${res.status}`);
}
return parse(res.body);
},
getAuthUrl (oauth_token, opts) {
assert(oauth_token, 'No Request Token specified');
const endpoint = parseUrl(cfg.authEndpoint);
delete endpoint.search;
endpoint.query = Object.assign(
parseQuery(endpoint.query),
opts,
{oauth_token}
);
return formatUrl(endpoint);
},
exchangeRequestToken (oauth_token, oauth_verifier, opts) {
assert(oauth_token, 'No Request Token specified');
assert(oauth_verifier, 'No Verification Code specified');
const req = createRequest(
'POST',
cfg.accessTokenEndpoint,
Object.assign({oauth_token, oauth_verifier}, opts)
);
const res = request.post(req);
if (!res.body) {
throw new Error(`OAuth provider returned empty response with HTTP status ${res.status}`);
}
return parse(res.body);
},
fetchActiveUser (oauth_token, oauth_token_secret, opts) {
assert(oauth_token, 'No Access Token specified');
assert(oauth_token_secret, 'No Token Secret specified');
if (!cfg.activeUserEndpoint) {
return null;
}
const req = createRequest(
'GET',
cfg.activeUserEndpoint,
Object.assign({oauth_token}, opts),
oauth_token_secret
);
const res = request.get(req);
if (!res.body) {
throw new Error(`OAuth provider returned empty response with HTTP status ${res.status}`);
}
return parse(res.body);
},
createSignedRequest (method, url, parameters, oauth_token, oauth_token_secret) {
assert(oauth_token, 'No Access Token specified');
assert(oauth_token_secret, 'No Token Secret specified');
return createRequest(
method,
url,
Object.assign({oauth_token}, parameters),
oauth_token_secret
);
}
};
};