Implement Hawk Authentication (#446)

* Implement Hawk Authentication

* fix the missing label of inputs

* Fix PR reviews
This commit is contained in:
Julien Giovaresco
2017-08-21 19:43:12 +02:00
committed by Gregory Schier
parent 0a796451d7
commit 2fcf98536f
10 changed files with 225 additions and 28 deletions

View File

@@ -139,6 +139,7 @@ export const AUTH_BASIC = 'basic';
export const AUTH_DIGEST = 'digest';
export const AUTH_BEARER = 'bearer';
export const AUTH_NTLM = 'ntlm';
export const AUTH_HAWK = 'hawk';
export const AUTH_AWS_IAM = 'iam';
export const AUTH_NETRC = 'netrc';
@@ -149,6 +150,7 @@ const authTypesMap = {
[AUTH_BEARER]: ['Bearer', 'Bearer Token'],
[AUTH_OAUTH_1]: ['OAuth 1', 'OAuth 1.0'],
[AUTH_OAUTH_2]: ['OAuth 2', 'OAuth 2.0'],
[AUTH_HAWK]: ['Hawk', 'Hawk'],
[AUTH_AWS_IAM]: ['AWS', 'AWS IAM v4'],
[AUTH_NETRC]: ['Netrc', 'Netrc']
};

View File

@@ -8,6 +8,8 @@ import {getAuthHeader} from '../network/authentication';
export async function exportHarWithRequest (renderedRequest, addContentLength = false) {
let postData = '';
const url = misc.prepareUrlForSending(renderedRequest.url);
if (renderedRequest.body.fileName) {
try {
postData = newBodyRaw(fs.readFileSync(renderedRequest.body.fileName, 'base64'));
@@ -36,6 +38,8 @@ export async function exportHarWithRequest (renderedRequest, addContentLength =
if (!misc.hasAuthHeader(renderedRequest.headers)) {
const header = await getAuthHeader(
renderedRequest._id,
url,
renderedRequest.method,
renderedRequest.authentication
);
header && renderedRequest.headers.push(header);
@@ -43,7 +47,7 @@ export async function exportHarWithRequest (renderedRequest, addContentLength =
return {
method: renderedRequest.method,
url: misc.prepareUrlForSending(renderedRequest.url),
url,
httpVersion: 'HTTP/1.1',
cookies: getCookies(renderedRequest),
headers: renderedRequest.headers,

View File

@@ -1,6 +1,6 @@
// @flow
import type {BaseModel} from './index';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_NONE, AUTH_NTLM, AUTH_OAUTH_2, 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} from '../common/constants';
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} from '../common/constants';
import * as db from '../common/database';
import {getContentTypeHeader} from '../common/misc';
import {buildFromParams, deconstructToParams} from '../common/querystring';
@@ -116,6 +116,10 @@ export function newAuth (type: string, oldAuth: RequestAuthentication = {}): Req
case AUTH_NETRC:
return {type};
// hawk
case AUTH_HAWK:
return {type, algorithm: 'sha256'};
// Types needing no defaults
default:
return {type};

View File

@@ -1,8 +1,9 @@
import {AUTH_BASIC, AUTH_BEARER, AUTH_OAUTH_2} from '../common/constants';
import {AUTH_BASIC, AUTH_BEARER, AUTH_OAUTH_2, AUTH_HAWK} from '../common/constants';
import {getBasicAuthHeader, getBearerAuthHeader} from '../common/misc';
import getOAuth2Token from './o-auth-2/get-token';
import * as Hawk from 'hawk';
export async function getAuthHeader (requestId, authentication) {
export async function getAuthHeader (requestId, url, method, authentication) {
if (authentication.disabled) {
return null;
}
@@ -27,6 +28,21 @@ export async function getAuthHeader (requestId, authentication) {
}
}
if (authentication.type === AUTH_HAWK) {
const {id, key, algorithm} = authentication;
const header = Hawk.client.header(
url,
method,
{credentials: {id, key, algorithm}}
);
return {
name: 'Authorization',
value: header.field
};
}
return null;
}

View File

@@ -473,6 +473,8 @@ export function _actuallySend (
} else {
const authHeader = await getAuthHeader(
renderedRequest._id,
finalUrl,
renderedRequest.method,
renderedRequest.authentication
);

View File

@@ -6,7 +6,7 @@ import {trackEvent} from '../../../analytics';
import {showModal} from '../modals';
import AlertModal from '../modals/alert-modal';
import * as models from '../../../models';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_BEARER, AUTH_NONE, AUTH_NTLM, AUTH_OAUTH_1, AUTH_OAUTH_2, AUTH_AWS_IAM, AUTH_NETRC, getAuthTypeName} from '../../../common/constants';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_BEARER, AUTH_NONE, AUTH_NTLM, AUTH_OAUTH_1, AUTH_OAUTH_2, AUTH_HAWK, AUTH_AWS_IAM, AUTH_NETRC, getAuthTypeName} from '../../../common/constants';
@autobind
class AuthDropdown extends PureComponent {
@@ -70,6 +70,7 @@ class AuthDropdown extends PureComponent {
{this.renderAuthType(AUTH_NTLM)}
{this.renderAuthType(AUTH_AWS_IAM)}
{this.renderAuthType(AUTH_NETRC)}
{this.renderAuthType(AUTH_HAWK)}
<DropdownDivider>Other</DropdownDivider>
{this.renderAuthType(AUTH_NONE, 'No Authentication')}
</Dropdown>

View File

@@ -1,11 +1,12 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_BEARER, AUTH_NTLM, AUTH_OAUTH_1, AUTH_OAUTH_2, AUTH_AWS_IAM, AUTH_NETRC} from '../../../../common/constants';
import {AUTH_BASIC, AUTH_DIGEST, AUTH_BEARER, AUTH_NTLM, AUTH_OAUTH_1, AUTH_OAUTH_2, AUTH_AWS_IAM, AUTH_HAWK, AUTH_NETRC} from '../../../../common/constants';
import BasicAuth from './basic-auth';
import DigestAuth from './digest-auth';
import BearerAuth from './bearer-auth';
import NTLMAuth from './ntlm-auth';
import OAuth2Auth from './o-auth-2-auth';
import HawkAuth from './hawk-auth';
import AWSAuth from './aws-auth';
import NetrcAuth from './netrc-auth';
import autobind from 'autobind-decorator';
@@ -49,6 +50,15 @@ class AuthWrapper extends PureComponent {
showPasswords={showPasswords}
/>
);
} else if (authentication.type === AUTH_HAWK) {
return (
<HawkAuth
request={request}
handleRender={handleRender}
handleGetRenderContext={handleGetRenderContext}
onChange={onChange}
/>
);
} else if (authentication.type === AUTH_OAUTH_1) {
return (
<div className="vertically-center text-center">

View File

@@ -0,0 +1,107 @@
// @flow
import type { Request } from '../../../../models/request';
import React from 'react';
import autobind from 'autobind-decorator';
import OneLineEditor from '../../codemirror/one-line-editor';
import * as misc from '../../../../common/misc';
@autobind
class HawkAuth extends React.PureComponent {
props: {
request: Request,
handleRender: Function,
handleGetRenderContext: Function,
onChange: Function
};
_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);
}
_handleChangeHawkAuthId (value: string): void {
this._handleChangeProperty('id', value);
}
_handleChangeHawkAuthKey (value: string): void {
this._handleChangeProperty('key', value);
}
_handleChangeAlgorithm (value: string): void {
this._handleChangeProperty('algorithm', value);
}
renderHawkAuthenticationFields (): Array<React.Element<*>> {
const hawkAuthId = this.renderInputRow(
'Hawk Auth ID',
'id',
this._handleChangeHawkAuthId
);
const hawkAuthKey = this.renderInputRow(
'Hawk Auth Key',
'key',
this._handleChangeHawkAuthKey
);
const algorithm = this.renderInputRow(
'Algorithm',
'algorithm',
this._handleChangeAlgorithm
);
return [hawkAuthId, hawkAuthKey, algorithm];
}
renderInputRow (label: string,
property: string,
onChange: Function): React.Element<*> {
const {handleRender, handleGetRenderContext, request} = this.props;
const id = label.replace(/ /g, '-');
return (
<tr key={id}>
<td className="pad-right no-wrap valign-middle">
<label htmlFor={id} className="label--small no-pad">
{label}
</label>
</td>
<td className="wide">
<div className="form-control form-control--underlined no-margin">
<OneLineEditor
id={id}
type="text"
onChange={onChange}
defaultValue={request.authentication[property] || ''}
render={handleRender}
getRenderContext={handleGetRenderContext}
/>
</div>
</td>
</tr>
);
}
render () {
const fields = this.renderHawkAuthenticationFields();
return (
<div className="pad">
<table>
<tbody>{fields}</tbody>
</table>
</div>
);
}
}
export default HawkAuth;

94
package-lock.json generated
View File

@@ -1499,11 +1499,11 @@
"dev": true
},
"boom": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"requires": {
"hoek": "2.16.3"
"hoek": "4.2.0"
}
},
"boxen": {
@@ -2478,11 +2478,21 @@
"integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8="
},
"cryptiles": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"requires": {
"boom": "2.10.1"
"boom": "5.2.0"
},
"dependencies": {
"boom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"requires": {
"hoek": "4.2.0"
}
}
}
},
"crypto-browserify": {
@@ -5610,14 +5620,14 @@
}
},
"hawk": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
"hoek": "2.16.3",
"sntp": "1.0.9"
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.0",
"sntp": "2.0.2"
}
},
"highlight.js": {
@@ -5642,9 +5652,9 @@
}
},
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
"integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ=="
},
"hoist-non-react-statics": {
"version": "1.2.0",
@@ -10811,6 +10821,22 @@
"uuid": "3.0.0"
},
"dependencies": {
"boom": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
"requires": {
"hoek": "2.16.3"
}
},
"cryptiles": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
"requires": {
"boom": "2.10.1"
}
},
"form-data": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
@@ -10829,6 +10855,30 @@
"ajv": "4.11.8",
"har-schema": "1.0.5"
}
},
"hawk": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
"hoek": "2.16.3",
"sntp": "1.0.9"
}
},
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
},
"sntp": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
"requires": {
"hoek": "2.16.3"
}
}
}
},
@@ -11247,11 +11297,11 @@
"dev": true
},
"sntp": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz",
"integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=",
"requires": {
"hoek": "2.16.3"
"hoek": "4.2.0"
}
},
"sockjs": {

View File

@@ -122,6 +122,7 @@
"electron-devtools-installer": "^2.2.0",
"electron-squirrel-startup": "^1.0.0",
"graphql": "^0.10.5",
"hawk": "^6.0.2",
"highlight.js": "^9.12.0",
"hkdf": "^0.0.2",
"html-entities": "^1.2.0",