mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
[Feature] Enable response type selection for oauth2 implicit mode (#681)
* Enable response type selection for oauth2 implicit * Parse token even if callback server is unreachable * Fix style: add _ prefix to private method * Fix style: reorder private method declaration * Set OAuth 2.0 default responseType value to token * Add responseType to params * Fix response type constant value * Code styling * Fix authorization request parameters * Don't open dev tools
This commit is contained in:
parent
dd51487905
commit
3f46f5898c
@ -30,7 +30,8 @@ describe('implicit', () => {
|
|||||||
state: STATE,
|
state: STATE,
|
||||||
error: null,
|
error: null,
|
||||||
error_description: null,
|
error_description: null,
|
||||||
error_uri: null
|
error_uri: null,
|
||||||
|
nonce: null
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,13 +5,16 @@ export const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials';
|
|||||||
export const GRANT_TYPE_REFRESH = 'refresh_token';
|
export const GRANT_TYPE_REFRESH = 'refresh_token';
|
||||||
|
|
||||||
export const RESPONSE_TYPE_CODE = 'code';
|
export const RESPONSE_TYPE_CODE = 'code';
|
||||||
export const RESPONSE_TYPE_TOKEN = 'token';
|
export const RESPONSE_TYPE_ID = 'id_token';
|
||||||
|
export const RESPONSE_TYPE_ACCESS = 'token';
|
||||||
|
export const RESPONSE_TYPE_BOTH = 'id_token token';
|
||||||
|
|
||||||
export const P_ACCESS_TOKEN = 'access_token';
|
export const P_ACCESS_TOKEN = 'access_token';
|
||||||
export const P_CLIENT_ID = 'client_id';
|
export const P_CLIENT_ID = 'client_id';
|
||||||
export const P_CLIENT_SECRET = 'client_secret';
|
export const P_CLIENT_SECRET = 'client_secret';
|
||||||
export const P_AUDIENCE = 'audience';
|
export const P_AUDIENCE = 'audience';
|
||||||
export const P_CODE = 'code';
|
export const P_CODE = 'code';
|
||||||
|
export const P_NONCE = 'nonce';
|
||||||
export const P_ERROR = 'error';
|
export const P_ERROR = 'error';
|
||||||
export const P_ERROR_DESCRIPTION = 'error_description';
|
export const P_ERROR_DESCRIPTION = 'error_description';
|
||||||
export const P_ERROR_URI = 'error_uri';
|
export const P_ERROR_URI = 'error_uri';
|
||||||
|
@ -94,6 +94,7 @@ async function _getOAuth2ImplicitHeader (
|
|||||||
requestId,
|
requestId,
|
||||||
authentication.authorizationUrl,
|
authentication.authorizationUrl,
|
||||||
authentication.clientId,
|
authentication.clientId,
|
||||||
|
authentication.responseType,
|
||||||
authentication.redirectUrl,
|
authentication.redirectUrl,
|
||||||
authentication.scope,
|
authentication.scope,
|
||||||
authentication.state
|
authentication.state
|
||||||
|
@ -7,16 +7,21 @@ export default async function (
|
|||||||
requestId: string,
|
requestId: string,
|
||||||
authorizationUrl: string,
|
authorizationUrl: string,
|
||||||
clientId: string,
|
clientId: string,
|
||||||
|
responseType: string = c.RESPONSE_TYPE_ACCESS,
|
||||||
redirectUri: string = '',
|
redirectUri: string = '',
|
||||||
scope: string = '',
|
scope: string = '',
|
||||||
state: string = ''
|
state: string = ''
|
||||||
): Promise<Object> {
|
): Promise<Object> {
|
||||||
const params = [
|
const params = [
|
||||||
{name: c.P_RESPONSE_TYPE, value: c.RESPONSE_TYPE_TOKEN},
|
{name: c.P_RESPONSE_TYPE, value: responseType},
|
||||||
{name: c.P_CLIENT_ID, value: clientId}
|
{name: c.P_CLIENT_ID, value: clientId}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add optional params
|
// Add optional params
|
||||||
|
if (responseType === c.RESPONSE_TYPE_BOTH) {
|
||||||
|
const responseNonce: string = ((Math.floor(Math.random() * 9999999999999) + 1): any);
|
||||||
|
params.push({name: c.P_NONCE, value: responseNonce});
|
||||||
|
}
|
||||||
redirectUri && params.push({name: c.P_REDIRECT_URI, value: redirectUri});
|
redirectUri && params.push({name: c.P_REDIRECT_URI, value: redirectUri});
|
||||||
scope && params.push({name: c.P_SCOPE, value: scope});
|
scope && params.push({name: c.P_SCOPE, value: scope});
|
||||||
state && params.push({name: c.P_STATE, value: state});
|
state && params.push({name: c.P_STATE, value: state});
|
||||||
@ -25,7 +30,7 @@ export default async function (
|
|||||||
const qs = buildQueryStringFromParams(params);
|
const qs = buildQueryStringFromParams(params);
|
||||||
const finalUrl = joinUrlAndQueryString(authorizationUrl, qs);
|
const finalUrl = joinUrlAndQueryString(authorizationUrl, qs);
|
||||||
|
|
||||||
const redirectedTo = await authorizeUserInWindow(finalUrl, /(access_token=|error=)/);
|
const redirectedTo = await authorizeUserInWindow(finalUrl, /(access_token=)/, /(error=)/);
|
||||||
const fragment = redirectedTo.split('#')[1];
|
const fragment = redirectedTo.split('#')[1];
|
||||||
|
|
||||||
if (fragment) {
|
if (fragment) {
|
||||||
@ -33,6 +38,7 @@ export default async function (
|
|||||||
c.P_ACCESS_TOKEN,
|
c.P_ACCESS_TOKEN,
|
||||||
c.P_TOKEN_TYPE,
|
c.P_TOKEN_TYPE,
|
||||||
c.P_EXPIRES_IN,
|
c.P_EXPIRES_IN,
|
||||||
|
c.P_NONCE,
|
||||||
c.P_SCOPE,
|
c.P_SCOPE,
|
||||||
c.P_STATE,
|
c.P_STATE,
|
||||||
c.P_ERROR,
|
c.P_ERROR,
|
||||||
|
@ -27,9 +27,27 @@ export function responseToObject (body, keys) {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authorizeUserInWindow (url, urlRegex = /.*/) {
|
export function authorizeUserInWindow (url, urlSuccessRegex = /.*/, urlFailureRegex = /.*/) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let finalUrl = null;
|
let finalUrl = null;
|
||||||
|
let hasError = false;
|
||||||
|
|
||||||
|
function _parseUrl (currentUrl) {
|
||||||
|
if (currentUrl.match(urlSuccessRegex)) {
|
||||||
|
console.log(`[oauth2] Matched redirect to "${currentUrl}" with ${urlSuccessRegex.toString()}`);
|
||||||
|
finalUrl = currentUrl;
|
||||||
|
child.close();
|
||||||
|
} else if (currentUrl.match(urlFailureRegex)) {
|
||||||
|
console.log(`[oauth2] Matched redirect to "${currentUrl}" with ${urlFailureRegex.toString()}`);
|
||||||
|
hasError = true;
|
||||||
|
child.close();
|
||||||
|
} else if (currentUrl === url) {
|
||||||
|
// It's the first one, so it's not a redirect
|
||||||
|
console.log(`[oauth2] Loaded "${currentUrl}"`);
|
||||||
|
} else {
|
||||||
|
console.log(`[oauth2] Ignoring URL "${currentUrl}". Didn't match ${urlSuccessRegex.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a child window
|
// Create a child window
|
||||||
const child = new electron.remote.BrowserWindow({
|
const child = new electron.remote.BrowserWindow({
|
||||||
@ -45,7 +63,11 @@ export function authorizeUserInWindow (url, urlRegex = /.*/) {
|
|||||||
if (finalUrl) {
|
if (finalUrl) {
|
||||||
resolve(finalUrl);
|
resolve(finalUrl);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error('Authorization window closed'));
|
let errorDescription = 'Authorization window closed';
|
||||||
|
if (hasError) {
|
||||||
|
errorDescription += ' after oauth error';
|
||||||
|
}
|
||||||
|
reject(new Error(errorDescription));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,16 +75,12 @@ export function authorizeUserInWindow (url, urlRegex = /.*/) {
|
|||||||
child.webContents.on('did-navigate', () => {
|
child.webContents.on('did-navigate', () => {
|
||||||
// Be sure to resolve URL so that we can handle redirects with no host like /foo/bar
|
// Be sure to resolve URL so that we can handle redirects with no host like /foo/bar
|
||||||
const currentUrl = child.webContents.getURL();
|
const currentUrl = child.webContents.getURL();
|
||||||
if (currentUrl.match(urlRegex)) {
|
_parseUrl(currentUrl);
|
||||||
console.log(`[oauth2] Matched redirect to "${currentUrl}" with ${urlRegex.toString()}`);
|
});
|
||||||
finalUrl = currentUrl;
|
|
||||||
child.close();
|
child.webContents.on('did-fail-load', (e, errorCode, errorDescription, url) => {
|
||||||
} else if (currentUrl === url) {
|
// Listen for did-fail-load to be able to parse the URL even when the callback server is unreachable
|
||||||
// It's the first one, so it's not a redirect
|
_parseUrl(url);
|
||||||
console.log(`[oauth2] Loaded "${currentUrl}"`);
|
|
||||||
} else {
|
|
||||||
console.log(`[oauth2] Ignoring URL "${currentUrl}". Didn't match ${urlRegex.toString()}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show the window to the user after it loads
|
// Show the window to the user after it loads
|
||||||
|
@ -8,6 +8,7 @@ import autobind from 'autobind-decorator';
|
|||||||
import OneLineEditor from '../../codemirror/one-line-editor';
|
import OneLineEditor from '../../codemirror/one-line-editor';
|
||||||
import * as misc from '../../../../common/misc';
|
import * as misc from '../../../../common/misc';
|
||||||
import {GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_CLIENT_CREDENTIALS, GRANT_TYPE_IMPLICIT, GRANT_TYPE_PASSWORD} from '../../../../network/o-auth-2/constants';
|
import {GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_CLIENT_CREDENTIALS, GRANT_TYPE_IMPLICIT, GRANT_TYPE_PASSWORD} from '../../../../network/o-auth-2/constants';
|
||||||
|
import {RESPONSE_TYPE_ID, RESPONSE_TYPE_ACCESS, RESPONSE_TYPE_BOTH} from '../../../../network/o-auth-2/constants';
|
||||||
import authorizationUrls from '../../../../datasets/authorization-urls';
|
import authorizationUrls from '../../../../datasets/authorization-urls';
|
||||||
import accessTokenUrls from '../../../../datasets/access-token-urls';
|
import accessTokenUrls from '../../../../datasets/access-token-urls';
|
||||||
import getAccessToken from '../../../../network/o-auth-2/get-token';
|
import getAccessToken from '../../../../network/o-auth-2/get-token';
|
||||||
@ -115,6 +116,10 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
|||||||
this.props.onChange(authentication);
|
this.props.onChange(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handlerChangeResponseType (e: SyntheticEvent<HTMLInputElement>): void {
|
||||||
|
this._handleChangeProperty('responseType', e.currentTarget.value);
|
||||||
|
}
|
||||||
|
|
||||||
_handleChangeClientId (value: string): void {
|
_handleChangeClientId (value: string): void {
|
||||||
this._handleChangeProperty('clientId', value);
|
this._handleChangeProperty('clientId', value);
|
||||||
}
|
}
|
||||||
@ -310,6 +315,18 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
|||||||
'Change Authorization header prefix from Bearer to something else'
|
'Change Authorization header prefix from Bearer to something else'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const responseType = this.renderSelectRow(
|
||||||
|
'Response Type',
|
||||||
|
'responseType',
|
||||||
|
[
|
||||||
|
{name: 'Access Token', value: RESPONSE_TYPE_ACCESS},
|
||||||
|
{name: 'ID Token', value: RESPONSE_TYPE_ID},
|
||||||
|
{name: 'ID and Access Token', value: RESPONSE_TYPE_BOTH}
|
||||||
|
],
|
||||||
|
this._handlerChangeResponseType,
|
||||||
|
'Indicates the type of credentials returned in the response'
|
||||||
|
);
|
||||||
|
|
||||||
const audience = this.renderInputRow(
|
const audience = this.renderInputRow(
|
||||||
'Audience',
|
'Audience',
|
||||||
'audience',
|
'audience',
|
||||||
@ -378,6 +395,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
advancedFields = [
|
advancedFields = [
|
||||||
|
responseType,
|
||||||
scope,
|
scope,
|
||||||
state,
|
state,
|
||||||
tokenPrefix
|
tokenPrefix
|
||||||
|
Loading…
Reference in New Issue
Block a user