mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-24 07:39:10 -04:00
Expose identity token for oauth2 (#3211)
This commit is contained in:
1 parent
3b2306adb2
commit
351afde43f
15 files changed
+133
-14
No files matched your search
@@ -1,6 +1,12 @@
|
||||
import * as misc from '../misc';
|
||||
import { globalBeforeEach } from '../../__jest__/before-each';
|
||||
import { diffPatchObj, isNotNullOrUndefined, pluralize, snapNumberToLimits } from '../misc';
|
||||
import {
|
||||
diffPatchObj,
|
||||
isNotNullOrUndefined,
|
||||
pluralize,
|
||||
snapNumberToLimits,
|
||||
convertEpochToMilliseconds,
|
||||
} from '../misc';
|
||||
|
||||
describe('hasAuthHeader()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
@@ -248,6 +254,20 @@ describe('diffPatchObj()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertEpochToMilliseconds()', () => {
|
||||
it('should convert microseconds to milliseconds', () => {
|
||||
expect(convertEpochToMilliseconds(1617616858412123)).toBe(1617616858412);
|
||||
});
|
||||
|
||||
it('should convert seconds to milliseconds', () => {
|
||||
expect(convertEpochToMilliseconds(1617617010)).toBe(1617617010000);
|
||||
});
|
||||
|
||||
it('should output same if value already in milliseconds', () => {
|
||||
expect(convertEpochToMilliseconds(1617617141412)).toBe(1617617141412);
|
||||
});
|
||||
});
|
||||
|
||||
describe('snapNumberToLimits()', () => {
|
||||
it('should return value', () => {
|
||||
expect(snapNumberToLimits(2)).toBe(2);
|
||||
|
||||
@@ -441,6 +441,16 @@ export function isObject(obj: any) {
|
||||
return obj !== null && typeof obj === 'object';
|
||||
}
|
||||
|
||||
export function convertEpochToMilliseconds(epoch: number) {
|
||||
/*
|
||||
Finds epoch's digit count and converts it to make it exactly 13 digits.
|
||||
Which is the epoch millisecond represntation.
|
||||
*/
|
||||
const expDigitCount = epoch.toString().length;
|
||||
const convertedEpoch = parseInt(epoch * 10 ** (13 - expDigitCount));
|
||||
return convertedEpoch;
|
||||
}
|
||||
|
||||
export function snapNumberToLimits(value: number, min?: number, max?: number): number {
|
||||
const moreThanMax = max && !Number.isNaN(max) && value > max;
|
||||
const lessThanMin = min && !Number.isNaN(min) && value < min;
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as db from '../common/database';
|
||||
type BaseOAuth2Token = {
|
||||
refreshToken: string,
|
||||
accessToken: string,
|
||||
identityToken: string,
|
||||
expiresAt: number | null, // Should be Date.now() if valid
|
||||
|
||||
// Debug
|
||||
@@ -30,6 +31,7 @@ export function init(): BaseOAuth2Token {
|
||||
return {
|
||||
refreshToken: '',
|
||||
accessToken: '',
|
||||
identityToken: '',
|
||||
expiresAt: null, // Should be Date.now() if valid
|
||||
|
||||
// Debug
|
||||
|
||||
@@ -95,6 +95,7 @@ describe('authorization_code', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
refresh_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
@@ -182,6 +183,7 @@ describe('authorization_code', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
refresh_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
@@ -241,6 +243,7 @@ describe('authorization_code', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
refresh_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
|
||||
@@ -85,6 +85,7 @@ describe('client_credentials', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
scope: SCOPE,
|
||||
@@ -165,6 +166,7 @@ describe('client_credentials', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
scope: SCOPE,
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('implicit', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
token_type: null,
|
||||
expires_in: null,
|
||||
scope: null,
|
||||
|
||||
@@ -87,6 +87,7 @@ describe('password', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
refresh_token: null,
|
||||
@@ -168,6 +169,7 @@ describe('password', () => {
|
||||
// Check the expected value
|
||||
expect(result).toEqual({
|
||||
access_token: 'token_123',
|
||||
id_token: null,
|
||||
expires_in: null,
|
||||
token_type: 'token_type',
|
||||
refresh_token: null,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
P_ERROR,
|
||||
P_EXPIRES_IN,
|
||||
P_REFRESH_TOKEN,
|
||||
P_ID_TOKEN,
|
||||
X_RESPONSE_ID,
|
||||
X_ERROR,
|
||||
} from './constants';
|
||||
@@ -92,7 +93,6 @@ async function _getOAuth2ClientCredentialsHeader(
|
||||
authentication.audience,
|
||||
authentication.resource,
|
||||
);
|
||||
|
||||
return _updateOAuth2Token(requestId, results);
|
||||
}
|
||||
|
||||
@@ -218,6 +218,7 @@ async function _updateOAuth2Token(requestId: string, authResults: Object): Promi
|
||||
expiresAt,
|
||||
refreshToken: authResults[P_REFRESH_TOKEN] || null,
|
||||
accessToken: authResults[P_ACCESS_TOKEN] || null,
|
||||
identityToken: authResults[P_ID_TOKEN] || null,
|
||||
error: authResults[P_ERROR] || null,
|
||||
errorDescription: authResults[P_ERROR_DESCRIPTION] || null,
|
||||
errorUri: authResults[P_ERROR_URI] || null,
|
||||
|
||||
@@ -192,6 +192,7 @@ async function _getToken(
|
||||
|
||||
const results = responseToObject(bodyBuffer.toString('utf8'), [
|
||||
c.P_ACCESS_TOKEN,
|
||||
c.P_ID_TOKEN,
|
||||
c.P_REFRESH_TOKEN,
|
||||
c.P_EXPIRES_IN,
|
||||
c.P_TOKEN_TYPE,
|
||||
|
||||
@@ -67,6 +67,7 @@ export default async function(
|
||||
|
||||
const results = responseToObject(bodyBuffer.toString('utf8'), [
|
||||
c.P_ACCESS_TOKEN,
|
||||
c.P_ID_TOKEN,
|
||||
c.P_TOKEN_TYPE,
|
||||
c.P_EXPIRES_IN,
|
||||
c.P_SCOPE,
|
||||
|
||||
@@ -56,7 +56,6 @@ export default async function(
|
||||
c.P_ERROR_URI,
|
||||
]);
|
||||
results[c.P_ACCESS_TOKEN] = results[c.P_ACCESS_TOKEN] || results[c.P_ID_TOKEN];
|
||||
delete results[c.P_ID_TOKEN];
|
||||
return results;
|
||||
} else {
|
||||
// Bad redirect
|
||||
|
||||
@@ -71,6 +71,7 @@ export default async function(
|
||||
|
||||
const results = responseToObject(bodyBuffer.toString(), [
|
||||
c.P_ACCESS_TOKEN,
|
||||
c.P_ID_TOKEN,
|
||||
c.P_TOKEN_TYPE,
|
||||
c.P_EXPIRES_IN,
|
||||
c.P_REFRESH_TOKEN,
|
||||
|
||||
@@ -80,6 +80,7 @@ export default async function(
|
||||
bodyBuffer.toString(),
|
||||
[
|
||||
c.P_ACCESS_TOKEN,
|
||||
c.P_ID_TOKEN,
|
||||
c.P_REFRESH_TOKEN,
|
||||
c.P_EXPIRES_IN,
|
||||
c.P_TOKEN_TYPE,
|
||||
|
||||
@@ -29,6 +29,7 @@ import { showModal } from '../../modals';
|
||||
import ResponseDebugModal from '../../modals/response-debug-modal';
|
||||
import type { Settings } from '../../../../models/settings';
|
||||
import { initNewOAuthSession } from '../../../../network/o-auth-2/misc';
|
||||
import { convertEpochToMilliseconds } from '../../../../common/misc';
|
||||
|
||||
type Props = {
|
||||
handleRender: Function,
|
||||
@@ -87,6 +88,20 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
async _handleUpdateIdentityToken(e: SyntheticEvent<HTMLInputElement>): Promise<void> {
|
||||
const { oAuth2Token } = this.props;
|
||||
const identityToken = e.currentTarget.value;
|
||||
|
||||
if (oAuth2Token) {
|
||||
await models.oAuth2Token.update(oAuth2Token, { identityToken });
|
||||
} else {
|
||||
await models.oAuth2Token.create({
|
||||
identityToken,
|
||||
parentId: this.props.request._id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async _handleUpdateRefreshToken(e: SyntheticEvent<HTMLInputElement>): Promise<void> {
|
||||
const { oAuth2Token } = this.props;
|
||||
const refreshToken = e.currentTarget.value;
|
||||
@@ -483,7 +498,31 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
||||
return { basic: basicFields, advanced: advancedFields };
|
||||
}
|
||||
|
||||
static renderExpireAt(token: ?OAuth2Token): React.Element<*> | string | null {
|
||||
static renderIdentityTokenExpiry(token: ?OAuth2Token): React.Element<*> | string | null {
|
||||
if (!token || !token.identityToken) {
|
||||
return null;
|
||||
}
|
||||
const base64Url = token.identityToken.split('.')[1];
|
||||
let decodedString = '';
|
||||
try {
|
||||
decodedString = window.atob(base64Url);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
const { exp } = JSON.parse(decodedString);
|
||||
if (!exp) {
|
||||
return '(never expires)';
|
||||
}
|
||||
const convertedExp = convertEpochToMilliseconds(exp);
|
||||
return (
|
||||
<span>
|
||||
(expires <TimeFromNow timestamp={convertedExp} />
|
||||
)
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
static renderAccessTokenExpiry(token: ?OAuth2Token): React.Element<*> | string | null {
|
||||
if (!token || !token.accessToken) {
|
||||
return null;
|
||||
}
|
||||
@@ -491,7 +530,6 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
||||
if (!token.expiresAt) {
|
||||
return '(never expires)';
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
(expires <TimeFromNow timestamp={token.expiresAt} />
|
||||
@@ -541,7 +579,8 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
||||
const { request, oAuth2Token: tok } = this.props;
|
||||
const { loading, error, showAdvanced } = this.state;
|
||||
|
||||
const expireLabel = OAuth2Auth.renderExpireAt(tok);
|
||||
const accessExpireLabel = OAuth2Auth.renderAccessTokenExpiry(tok);
|
||||
const identityExpireLabel = OAuth2Auth.renderIdentityTokenExpiry(tok);
|
||||
const fields = this.renderGrantTypeFields(request.authentication.grantType);
|
||||
|
||||
return (
|
||||
@@ -608,7 +647,17 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
<small>Access Token {tok ? <em>{expireLabel}</em> : null}</small>
|
||||
<small>Identity Token {tok ? <em>{identityExpireLabel}</em> : null}</small>
|
||||
<input
|
||||
value={(tok && tok.identityToken) || ''}
|
||||
placeholder="n/a"
|
||||
onChange={this._handleUpdateIdentityToken}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
<small>Access Token {tok ? <em>{accessExpireLabel}</em> : null}</small>
|
||||
<input
|
||||
value={(tok && tok.accessToken) || ''}
|
||||
placeholder="n/a"
|
||||
|
||||
@@ -46,15 +46,29 @@ module.exports.templateTags = [
|
||||
description: 'cookie value by name',
|
||||
},
|
||||
{
|
||||
displayName: 'OAuth 2.0 Token',
|
||||
displayName: 'OAuth 2.0 Access Token',
|
||||
value: 'oauth2',
|
||||
description: 'access token',
|
||||
/*
|
||||
This value is left as is and not renamed to 'oauth2-access' so as to not
|
||||
break the current release's usage of `oauth2`.
|
||||
*/
|
||||
},
|
||||
{
|
||||
displayName: 'OAuth 2.0 Identity Token',
|
||||
value: 'oauth2-identity',
|
||||
},
|
||||
{
|
||||
displayName: 'OAuth 2.0 Refresh Token',
|
||||
value: 'oauth2-refresh',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
hide: args => ['url', 'oauth2', 'name', 'folder'].includes(args[0].value),
|
||||
hide: args =>
|
||||
['url', 'oauth2', 'oauth2-identity', 'oauth2-refresh', 'name', 'folder'].includes(
|
||||
args[0].value,
|
||||
),
|
||||
displayName: args => {
|
||||
switch (args[0].value) {
|
||||
case 'cookie':
|
||||
@@ -150,11 +164,23 @@ module.exports.templateTags = [
|
||||
const headerNamesStr = headerNames.map(n => `"${n}"`).join(',\n\t');
|
||||
throw new Error(`No header with name "${name}".\nChoices are [\n\t${headerNamesStr}\n]`);
|
||||
case 'oauth2':
|
||||
const token = await context.util.models.oAuth2Token.getByRequestId(request._id);
|
||||
if (!token) {
|
||||
throw new Error('No OAuth 2.0 tokens found for request');
|
||||
const access = await context.util.models.oAuth2Token.getByRequestId(request._id);
|
||||
if (!access || !access.accessToken) {
|
||||
throw new Error('No OAuth 2.0 access tokens found for request');
|
||||
}
|
||||
return token.accessToken;
|
||||
return access.accessToken;
|
||||
case 'oauth2-identity':
|
||||
const identity = await context.util.models.oAuth2Token.getByRequestId(request._id);
|
||||
if (!identity || !identity.identityToken) {
|
||||
throw new Error('No OAuth 2.0 identity tokens found for request');
|
||||
}
|
||||
return identity.identityToken;
|
||||
case 'oauth2-refresh':
|
||||
const refresh = await context.util.models.oAuth2Token.getByRequestId(request._id);
|
||||
if (!refresh || !refresh.refreshToken) {
|
||||
throw new Error('No OAuth 2.0 refresh tokens found for request');
|
||||
}
|
||||
return refresh.refreshToken;
|
||||
case 'name':
|
||||
return request.name;
|
||||
case 'folder':
|
||||
|
||||
Reference in new issue
Block a user