Skip to content

Commit

Permalink
Feature: added basic & bearer auth to Rest API (ToolJet#2742)
Browse files Browse the repository at this point in the history
* Added basic and bearer auth fields

* Implemented basic and bearer auth

* Changed token name

* Added encrpyted icon along side label

* resolved a basic-auth bug

* Refactored basic auth code
  • Loading branch information
shah21 authored Apr 22, 2022
1 parent 380ea73 commit b3ca2b6
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 118 deletions.
3 changes: 3 additions & 0 deletions frontend/src/_components/DynamicForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ const DynamicForm = ({
client_secret: options.client_secret?.value,
client_auth: options.client_auth?.value,
scopes: options.scopes?.value,
username: options.username?.value,
password: options.password?.value,
bearer_token: options.bearer_token?.value,
auth_url: options.auth_url?.value,
custom_auth_params: options.custom_auth_params?.value,
custom_query_params: options.custom_query_params?.value,
Expand Down
272 changes: 166 additions & 106 deletions frontend/src/_ui/OAuth/Authentication.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,128 +15,188 @@ const Authentication = ({
header_prefix,
grant_type,
scopes,
username,
bearer_token,
password,
auth_url,
optionchanged,
}) => {
if (auth_type !== 'oauth2') return null;

return (
<div>
<hr />
<h3 className="text-muted">Authentication</h3>
<div className="row mt-3">
<label className="form-label text-muted">Grant Type</label>
<Select
options={[{ name: 'Authorization Code', value: 'authorization_code' }]}
value={grant_type}
onChange={(value) => optionchanged('grant_type', value)}
width={'100%'}
useMenuPortal={false}
/>
<label className="form-label text-muted mt-3">Add Access Token To</label>
<Select
options={[{ name: 'Request Header', value: 'header' }]}
value={add_token_to}
onChange={(value) => optionchanged('add_token_to', value)}
width={'100%'}
useMenuPortal={false}
/>

{add_token_to === 'header' && (
<div className="col-md-12">
<label className="form-label text-muted mt-3">Header Prefix</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('header_prefix', e.target.value)}
value={header_prefix}
/>
</div>
)}
</div>
if (auth_type === 'oauth2') {
return (
<div>
<hr />
<h3 className="text-muted">Authentication</h3>
<div className="row mt-3">
<label className="form-label text-muted">Grant Type</label>
<Select
options={[{ name: 'Authorization Code', value: 'authorization_code' }]}
value={grant_type}
onChange={(value) => optionchanged('grant_type', value)}
width={'100%'}
useMenuPortal={false}
/>
<label className="form-label text-muted mt-3">Add Access Token To</label>
<Select
options={[{ name: 'Request Header', value: 'header' }]}
value={add_token_to}
onChange={(value) => optionchanged('add_token_to', value)}
width={'100%'}
useMenuPortal={false}
/>

<div className="col-md-12">
<label className="form-label text-muted mt-3">Access Token URL</label>
<Input
type="text"
placeholder="https://api.example.com/oauth/token"
className="form-control"
onChange={(e) => optionchanged('access_token_url', e.target.value)}
value={access_token_url}
/>
</div>
{add_token_to === 'header' && (
<div className="col-md-12">
<label className="form-label text-muted mt-3">Header Prefix</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('header_prefix', e.target.value)}
value={header_prefix}
/>
</div>
)}
</div>

<div className="col-md-12">
<label className="form-label text-muted mt-3">Client ID</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('client_id', e.target.value)}
value={client_id}
/>
</div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Access Token URL</label>
<Input
type="text"
placeholder="https://api.example.com/oauth/token"
className="form-control"
onChange={(e) => optionchanged('access_token_url', e.target.value)}
value={access_token_url}
/>
</div>

<div className="col-md-12">
<label className="form-label text-muted mt-3">Client Secret</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={client_secret}
/>
</div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Client ID</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('client_id', e.target.value)}
value={client_id}
/>
</div>

<div className="col-md-12">
<label className="form-label text-muted mt-3">Scope(s)</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('scopes', e.target.value)}
value={scopes}
/>
</div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">
Client Secret
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="/assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={client_secret}
/>
</div>

<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Custom Query Parameters</label>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Scope(s)</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('scopes', e.target.value)}
value={scopes}
/>
</div>
</div>
<Headers getter={'custom_query_params'} options={custom_query_params} optionchanged={optionchanged} />

{grant_type === 'authorization_code' && (
<div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Authorization URL</label>
<Input
type="text"
placeholder="https://api.example.com/oauth/authorize"
className="form-control"
onChange={(e) => optionchanged('auth_url', e.target.value)}
value={auth_url}
/>
<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Custom Query Parameters</label>
</div>
</div>
<Headers getter={'custom_query_params'} options={custom_query_params} optionchanged={optionchanged} />

<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Custom Authentication Parameters</label>
{grant_type === 'authorization_code' && (
<div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Authorization URL</label>
<Input
type="text"
placeholder="https://api.example.com/oauth/authorize"
className="form-control"
onChange={(e) => optionchanged('auth_url', e.target.value)}
value={auth_url}
/>
</div>

<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Custom Authentication Parameters</label>
</div>
</div>
<Headers getter={'custom_auth_params'} options={custom_auth_params} optionchanged={optionchanged} />
<label className="form-label text-muted mt-3">Client Authentication</label>
<Select
options={[
{ name: 'Send as Basic Auth header', value: 'header' },
{ name: 'Send client credentials in body ', value: 'body' },
]}
value={client_auth}
onChange={(value) => optionchanged('client_auth', value)}
width={'100%'}
useMenuPortal={false}
/>
</div>
<Headers getter={'custom_auth_params'} options={custom_auth_params} optionchanged={optionchanged} />
<label className="form-label text-muted mt-3">Client Authentication</label>
<Select
options={[
{ name: 'Send as Basic Auth header', value: 'header' },
{ name: 'Send client credentials in body ', value: 'body' },
]}
value={client_auth}
onChange={(value) => optionchanged('client_auth', value)}
width={'100%'}
useMenuPortal={false}
)}
</div>
);
} else if (auth_type === 'basic') {
return (
<div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Username</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('username', e.target.value)}
value={username}
/>
</div>
)}
</div>
);
<div className="col-md-12">
<label className="form-label text-muted mt-3">
Password
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="/assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('password', e.target.value)}
value={password}
/>
</div>
</div>
);
} else if (auth_type === 'bearer') {
return (
<div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">
Token
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="/assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('bearer_token', e.target.value)}
value={bearer_token}
/>
</div>
</div>
);
} else {
return null;
}
};

export default Authentication;
8 changes: 8 additions & 0 deletions frontend/src/_ui/OAuth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const OAuth = ({
custom_auth_params,
custom_query_params,
scopes,
username,
password,
bearer_token,
auth_url,
header_prefix,
add_token_to,
Expand All @@ -22,6 +25,8 @@ const OAuth = ({
<Select
options={[
{ name: 'None', value: 'none' },
{ name: 'Basic', value: 'basic' },
{ name: 'Bearer', value: 'bearer' },
{ name: 'OAuth 2.0', value: 'oauth2' },
]}
value={auth_type}
Expand All @@ -42,6 +47,9 @@ const OAuth = ({
client_secret={client_secret}
client_auth={client_auth}
scopes={scopes}
username={username}
password={password}
bearer_token={bearer_token}
auth_url={auth_url}
/>
</>
Expand Down
37 changes: 25 additions & 12 deletions plugins/packages/restapi/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const urrl = require('url');
import { readFileSync } from 'fs';
import * as tls from 'tls';
import { QueryError, QueryResult, QueryService } from '@tooljet-plugins/common';
import got, { Headers, HTTPError } from 'got';
import got, { Headers, HTTPError, OptionsOfTextResponseBody } from 'got';

function isEmpty(value: number | null | undefined | string) {
return (
Expand Down Expand Up @@ -79,7 +79,8 @@ export default class RestapiQueryService implements QueryService {
async run(sourceOptions: any, queryOptions: any, dataSourceId: string): Promise<RestAPIResult> {
/* REST API queries can be adhoc or associated with a REST API datasource */
const hasDataSource = dataSourceId !== undefined;
const requiresOauth = sourceOptions['auth_type'] === 'oauth2';
const authType = sourceOptions['auth_type'];
const requiresOauth = authType === 'oauth2';

const headers = this.headers(sourceOptions, queryOptions, hasDataSource);
const customQueryParams = sanitizeCustomParams(sourceOptions['custom_query_params']);
Expand Down Expand Up @@ -119,17 +120,29 @@ export default class RestapiQueryService implements QueryService {
const method = queryOptions['method'];
const json = method !== 'get' ? this.body(sourceOptions, queryOptions, hasDataSource) : undefined;
const paramsFromUrl = urrl.parse(url, true).query;

if (authType === 'bearer') {
headers['Authorization'] = `Bearer ${sourceOptions.bearer_token}`;
}

const requestOptions: OptionsOfTextResponseBody = {
method,
headers,
...this.fetchHttpsCertsForCustomCA(),
searchParams: {
...paramsFromUrl,
...this.searchParams(sourceOptions, queryOptions, hasDataSource),
},
json,
};

if (authType === 'basic') {
requestOptions.username = sourceOptions.username;
requestOptions.password = sourceOptions.password;
}

try {
const response = await got(url, {
method,
headers,
...this.fetchHttpsCertsForCustomCA(),
searchParams: {
...paramsFromUrl,
...this.searchParams(sourceOptions, queryOptions, hasDataSource),
},
json,
});
const response = await got(url, requestOptions);
result = this.isJson(response.body) ? JSON.parse(response.body) : response.body;
requestObject = {
requestUrl: response.request.requestUrl,
Expand Down
Loading

0 comments on commit b3ca2b6

Please sign in to comment.