mirror of https://gitee.com/bigwinds/arangodb
221 lines
6.9 KiB
JavaScript
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
|
|
);
|
|
}
|
|
};
|
|
};
|