oAuth 1.0a Support (#571)

This commit is contained in:
Roman Besolov
2017-11-06 22:26:31 +03:00
committed by Gregory Schier
parent c0f4849697
commit d3e8a05fc7
8 changed files with 285 additions and 44 deletions

View File

@@ -1,10 +1,11 @@
// @flow
import type {BaseModel} from './index';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_NONE, AUTH_NTLM, AUTH_OAUTH_2, AUTH_HAWK, AUTH_AWS_IAM, AUTH_NETRC, CONTENT_TYPE_FILE, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_OTHER, getContentTypeFromHeaders, METHOD_GET, CONTENT_TYPE_GRAPHQL, CONTENT_TYPE_JSON, METHOD_POST, HAWK_ALGORITHM_SHA256} from '../common/constants';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_NONE, AUTH_NTLM, AUTH_OAUTH_1, AUTH_OAUTH_2, AUTH_HAWK, AUTH_AWS_IAM, AUTH_NETRC, CONTENT_TYPE_FILE, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_OTHER, getContentTypeFromHeaders, METHOD_GET, CONTENT_TYPE_GRAPHQL, CONTENT_TYPE_JSON, METHOD_POST, HAWK_ALGORITHM_SHA256} from '../common/constants';
import * as db from '../common/database';
import {getContentTypeHeader} from '../common/misc';
import {buildFromParams, deconstructToParams} from '../common/querystring';
import {GRANT_TYPE_AUTHORIZATION_CODE} from '../network/o-auth-2/constants';
import {SIGNATURE_METHOD_HMAC_SHA1} from '../network/o-auth-1/constants';
export const name = 'Request';
export const type = 'Request';
@@ -99,6 +100,14 @@ export function newAuth (type: string, oldAuth: RequestAuthentication = {}): Req
password: oldAuth.password || ''
};
case AUTH_OAUTH_1:
return {
type,
disabled: false,
signatureMethod: SIGNATURE_METHOD_HMAC_SHA1,
version: '1.0'
};
// OAuth 2.0
case AUTH_OAUTH_2:
return {type, grantType: GRANT_TYPE_AUTHORIZATION_CODE};

View File

@@ -1,6 +1,7 @@
import {AUTH_BASIC, AUTH_BEARER, AUTH_OAUTH_2, AUTH_HAWK} from '../common/constants';
import {AUTH_BASIC, AUTH_BEARER, AUTH_OAUTH_2, AUTH_OAUTH_1, AUTH_HAWK} from '../common/constants';
import {getBasicAuthHeader, getBearerAuthHeader} from '../common/misc';
import getOAuth2Token from './o-auth-2/get-token';
import getOAuth1Token from './o-auth-1/get-token';
import * as Hawk from 'hawk';
export async function getAuthHeader (requestId, url, method, authentication) {
@@ -28,6 +29,18 @@ export async function getAuthHeader (requestId, url, method, authentication) {
}
}
if (authentication.type === AUTH_OAUTH_1) {
const oAuth1Token = await getOAuth1Token(url, method, authentication);
if (oAuth1Token) {
return {
name: 'Authorization',
value: oAuth1Token.Authorization
};
} else {
return null;
}
}
if (authentication.type === AUTH_HAWK) {
const {id, key, algorithm} = authentication;

View File

@@ -0,0 +1,2 @@
export const SIGNATURE_METHOD_HMAC_SHA1 = 'HMAC-SHA1';
export const SIGNATURE_METHOD_PLAINTEXT = 'PLAINTEXT';

View File

@@ -0,0 +1,42 @@
/**
* Get an OAuth1Token object and also handle storing/saving/refreshing
* @returns {Promise.<void>}
*/
import crypto from 'crypto';
import OAuth1 from 'oauth-1.0a';
import { SIGNATURE_METHOD_HMAC_SHA1, SIGNATURE_METHOD_PLAINTEXT } from './constants';
function hashFunction (signatureMethod) {
if (signatureMethod === SIGNATURE_METHOD_HMAC_SHA1) {
return function (baseString, key) {
return crypto.createHmac('sha1', key).update(baseString).digest('base64');
};
}
if (signatureMethod === SIGNATURE_METHOD_PLAINTEXT) {
return function (baseString) {
return baseString;
};
}
return null;
}
export default async function (url, method, authentication) {
var oauth = OAuth1({
consumer: {
key: authentication.consumerKey,
secret: authentication.consumerSecret
},
signature_method: authentication.signatureMethod,
version: authentication.version,
hash_function: hashFunction(authentication.signatureMethod)
});
var requestData = {
url: url,
method: method
};
return oauth.toHeader(oauth.authorize(requestData));
}

View File

@@ -6,11 +6,11 @@ import DigestAuth from './digest-auth';
import BearerAuth from './bearer-auth';
import NTLMAuth from './ntlm-auth';
import OAuth2Auth from './o-auth-2-auth';
import OAuth1Auth from './o-auth-1-auth';
import HawkAuth from './hawk-auth';
import AWSAuth from './aws-auth';
import NetrcAuth from './netrc-auth';
import autobind from 'autobind-decorator';
import Link from '../../base/link';
@autobind
class AuthWrapper extends PureComponent {
@@ -65,17 +65,13 @@ class AuthWrapper extends PureComponent {
);
} else if (authentication.type === AUTH_OAUTH_1) {
return (
<div className="vertically-center text-center">
<div className="pad text-sm text-center">
<i className="fa fa-commenting super-faint" style={{fontSize: '8rem', opacity: 0.3}}/>
<p className="faint pad-top">
Want OAuth 1.0? Please upvote
the <Link href="https://github.com/getinsomnia/insomnia/issues/197">
Issue on GitHub
</Link>
</p>
</div>
</div>
<OAuth1Auth
request={request}
handleRender={handleRender}
handleGetRenderContext={handleGetRenderContext}
nunjucksPowerUserMode={nunjucksPowerUserMode}
onChange={onChange}
/>
);
} else if (authentication.type === AUTH_DIGEST) {
return (

View File

@@ -0,0 +1,173 @@
// @flow
import type {Request} from '../../../../models/request';
import * as React from 'react';
import autobind from 'autobind-decorator';
import OneLineEditor from '../../codemirror/one-line-editor';
import * as misc from '../../../../common/misc';
import HelpTooltip from '../../help-tooltip';
import { SIGNATURE_METHOD_HMAC_SHA1, SIGNATURE_METHOD_PLAINTEXT } from '../../../../network/o-auth-1/constants';
type Props = {
handleRender: Function,
handleGetRenderContext: Function,
nunjucksPowerUserMode: boolean,
onChange: Function,
request: Request
};
@autobind
class OAuth1Auth extends React.PureComponent<Props> {
_handleChangeProperty: Function;
constructor (props: any) {
super(props);
this._handleChangeProperty = misc.debounce(this._handleChangeProperty, 500);
}
_handleChangeProperty (property: string, value: string | boolean): void {
const {request} = this.props;
const authentication = Object.assign({}, request.authentication, {[property]: value});
this.props.onChange(authentication);
}
_handleChangeConsumerKey (value: string): void {
this._handleChangeProperty('consumerKey', value);
}
_handleChangeConsumerSecret (value: string): void {
this._handleChangeProperty('consumerSecret', value);
}
_handleChangeSignatureMethod (e: SyntheticEvent<HTMLInputElement>): void {
this._handleChangeProperty('signatureMethod', e.currentTarget.value);
}
_handleChangeVersion (value: string): void {
this._handleChangeProperty('version', value);
}
renderInputRow (
label: string,
property: string,
onChange: Function,
help: string | null = null,
handleAutocomplete: Function | null = null
): React.Element<*> {
const {handleRender, handleGetRenderContext, request, nunjucksPowerUserMode} = this.props;
const id = label.replace(/ /g, '-');
const type = !this.props.showPasswords && property === 'password' ? 'password' : 'text';
return (
<tr key={id}>
<td className="pad-right no-wrap valign-middle">
<label htmlFor={id} className="label--small no-pad">
{label}
{help && <HelpTooltip>{help}</HelpTooltip>}
</label>
</td>
<td className="wide">
<div className="form-control form-control--underlined no-margin">
<OneLineEditor
id={id}
type={type}
onChange={onChange}
defaultValue={request.authentication[property] || ''}
render={handleRender}
nunjucksPowerUserMode={nunjucksPowerUserMode}
getAutocompleteConstants={handleAutocomplete}
getRenderContext={handleGetRenderContext}
/>
</div>
</td>
</tr>
);
}
renderSelectRow (
label: string,
property: string,
options: Array<{name: string, value: string}>,
onChange: Function,
help: string | null = null
): React.Element<*> {
const {request} = this.props;
const id = label.replace(/ /g, '-');
const value = request.authentication.hasOwnProperty(property)
? request.authentication[property]
: options[0];
return (
<tr key={id}>
<td className="pad-right no-wrap valign-middle">
<label htmlFor={id} className="label--small no-pad">
{label}
{help && <HelpTooltip>{help}</HelpTooltip>}
</label>
</td>
<td className="wide">
<div className="form-control form-control--outlined no-margin">
<select id={id} onChange={onChange} value={value}>
{options.map(({name, value}) => (
<option key={value} value={value}>{name}</option>
))}
</select>
</div>
</td>
</tr>
);
}
renderFields (): Array<React.Element<*>> {
const consumerKey = this.renderInputRow(
'Consumer Key',
'consumerKey',
this._handleChangeConsumerKey
);
const consumerSecret = this.renderInputRow(
'Consumer Secret',
'consumerSecret',
this._handleChangeConsumerSecret
);
const signatureMethod = this.renderSelectRow(
'Signature Method',
'signatureMethod',
[
{name: 'HMAC-SHA1', value: SIGNATURE_METHOD_HMAC_SHA1},
{name: 'PLAINTEXT', value: SIGNATURE_METHOD_PLAINTEXT}
],
this._handleChangeSignatureMethod
);
const version = this.renderInputRow(
'Version',
'version',
this._handleChangeVersion
);
return [
consumerKey,
consumerSecret,
signatureMethod,
version
];
}
render () {
const fields = this.renderFields();
return (
<div className="pad">
<table>
<tbody>
{fields}
</tbody>
</table>
</div>
);
}
}
export default OAuth1Auth;

65
package-lock.json generated
View File

@@ -4,12 +4,6 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "7.0.46",
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.46.tgz",
"integrity": "sha512-u+JAi1KtmaUoU/EHJkxoiuvzyo91FCE41Z9TZWWcOUU3P8oUdlDLdrGzCGWySPgbRMD17B0B+1aaJLYI9egQ6A==",
"dev": true
},
"7zip": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/7zip/-/7zip-0.0.6.tgz",
@@ -31,6 +25,22 @@
"dev": true,
"optional": true
},
"@types/node": {
"version": "7.0.46",
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.46.tgz",
"integrity": "sha512-u+JAi1KtmaUoU/EHJkxoiuvzyo91FCE41Z9TZWWcOUU3P8oUdlDLdrGzCGWySPgbRMD17B0B+1aaJLYI9egQ6A==",
"dev": true
},
"JSONSelect": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz",
"integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40="
},
"JSV": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz",
"integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c="
},
"a-sync-waterfall": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.0.tgz",
@@ -5113,10 +5123,6 @@
}
}
},
"string_decoder": {
"version": "0.10.31",
"bundled": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
@@ -5126,6 +5132,10 @@
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "0.10.31",
"bundled": true
},
"stringstream": {
"version": "0.0.5",
"bundled": true,
@@ -6697,10 +6707,6 @@
}
}
},
"string_decoder": {
"version": "0.10.31",
"bundled": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
@@ -6710,6 +6716,10 @@
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "0.10.31",
"bundled": true
},
"stringstream": {
"version": "0.0.5",
"bundled": true
@@ -7590,12 +7600,12 @@
"resolved": "https://registry.npmjs.org/jison/-/jison-0.4.13.tgz",
"integrity": "sha1-kEFwfWIkE2f1iDRTK58ZwsNvrHg=",
"requires": {
"JSONSelect": "0.4.0",
"cjson": "0.2.1",
"ebnf-parser": "0.1.10",
"escodegen": "0.0.21",
"esprima": "1.0.4",
"jison-lex": "0.2.1",
"JSONSelect": "0.4.0",
"lex-parser": "0.1.4",
"nomnom": "1.5.2"
},
@@ -7961,11 +7971,6 @@
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk="
},
"JSONSelect": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz",
"integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40="
},
"jsprim": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
@@ -8009,11 +8014,6 @@
}
}
},
"JSV": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz",
"integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c="
},
"jsx-ast-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
@@ -9069,6 +9069,11 @@
"integrity": "sha1-tDiTYhcOfvl5jDx3FtgOvAEG/M8=",
"dev": true
},
"oauth-1.0a": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.2.tgz",
"integrity": "sha512-fHRvq3aHoYXq8rZrXOX80nYgMc0svd4a88R4W7QLltJ1Rl+GFjW6Eu3rgR1zWB5MbuyqsWojADsS4vbmDiho/w=="
},
"oauth-sign": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
@@ -11753,11 +11758,6 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"string-length": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
@@ -11783,6 +11783,11 @@
"integrity": "sha1-aybpvTr8qnvjtCabUm3huCAArHg=",
"dev": true
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",

View File

@@ -150,6 +150,7 @@
"nedb": "^1.8.0",
"node-forge": "^0.7.0",
"nunjucks": "^3.0.0",
"oauth-1.0a": "^2.2.2",
"pdfjs-dist": "^1.9.640",
"prop-types": "^15.5.10",
"react": "^16.0.0",