Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oAuth 1.0a Support #571

Merged
merged 1 commit into from Nov 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/models/request.js
@@ -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';
Expand Down Expand Up @@ -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};
Expand Down
15 changes: 14 additions & 1 deletion app/network/authentication.js
@@ -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) {
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions app/network/o-auth-1/constants.js
@@ -0,0 +1,2 @@
export const SIGNATURE_METHOD_HMAC_SHA1 = 'HMAC-SHA1';
export const SIGNATURE_METHOD_PLAINTEXT = 'PLAINTEXT';
42 changes: 42 additions & 0 deletions app/network/o-auth-1/get-token.js
@@ -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));
}
20 changes: 8 additions & 12 deletions app/ui/components/editors/auth/auth-wrapper.js
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 (
Expand Down
173 changes: 173 additions & 0 deletions app/ui/components/editors/auth/o-auth-1-auth.js
@@ -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;