Merge pull request #476 from reefqi037/feature/oauth-support
OAuth 2.0/OIDC Access Token Retrieval Support
This commit is contained in:
175
assets/js/oauth.js
Normal file
175
assets/js/oauth.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
const redirectUri = `${ window.location.origin }/`;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// GENERAL HELPER FUNCTIONS
|
||||||
|
|
||||||
|
// Make a POST request and parse the response as JSON
|
||||||
|
const sendPostRequest = async(url, params) => {
|
||||||
|
let body = Object.keys(params).map(key => key + '=' + params[key]).join('&');
|
||||||
|
const options = {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
|
},
|
||||||
|
body
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Request failed', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parse a query string into an object
|
||||||
|
const parseQueryString = string => {
|
||||||
|
if(string == "") { return {}; }
|
||||||
|
let segments = string.split("&").map(s => s.split("=") );
|
||||||
|
let queryString = {};
|
||||||
|
segments.forEach(s => queryString[s[0]] = s[1]);
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
// Get OAuth configuration from OpenID Discovery endpoint
|
||||||
|
const getTokenConfiguration = async endpoint => {
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(endpoint, options);
|
||||||
|
const config = await response.json();
|
||||||
|
return config;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Request failed', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// PKCE HELPER FUNCTIONS
|
||||||
|
|
||||||
|
// Generate a secure random string using the browser crypto functions
|
||||||
|
const generateRandomString = () => {
|
||||||
|
var array = new Uint32Array(28);
|
||||||
|
window.crypto.getRandomValues(array);
|
||||||
|
return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('');
|
||||||
|
}
|
||||||
|
// Calculate the SHA256 hash of the input text.
|
||||||
|
// Returns a promise that resolves to an ArrayBuffer
|
||||||
|
const sha256 = plain => {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(plain);
|
||||||
|
return window.crypto.subtle.digest('SHA-256', data);
|
||||||
|
}
|
||||||
|
// Base64-urlencodes the input string
|
||||||
|
const base64urlencode = str => {
|
||||||
|
// Convert the ArrayBuffer to string using Uint8 array to conver to what btoa accepts.
|
||||||
|
// btoa accepts chars only within ascii 0-255 and base64 encodes them.
|
||||||
|
// Then convert the base64 encoded to base64url encoded
|
||||||
|
// (replace + with -, replace / with _, trim trailing =)
|
||||||
|
return btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
|
||||||
|
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
}
|
||||||
|
// Return the base64-urlencoded sha256 hash for the PKCE challenge
|
||||||
|
const pkceChallengeFromVerifier = async(v) => {
|
||||||
|
let hashed = await sha256(v);
|
||||||
|
return base64urlencode(hashed);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// OAUTH REQUEST
|
||||||
|
|
||||||
|
// Initiate PKCE Auth Code flow when requested
|
||||||
|
const tokenRequest = async({
|
||||||
|
oidcDiscoveryUrl,
|
||||||
|
grantType,
|
||||||
|
authUrl,
|
||||||
|
accessTokenUrl,
|
||||||
|
clientId,
|
||||||
|
scope
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
// Check oauth configuration
|
||||||
|
if (oidcDiscoveryUrl !== '') {
|
||||||
|
const { authorization_endpoint, token_endpoint } = await getTokenConfiguration(oidcDiscoveryUrl);
|
||||||
|
authUrl = authorization_endpoint;
|
||||||
|
accessTokenUrl = token_endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store oauth information
|
||||||
|
localStorage.setItem('token_endpoint', accessTokenUrl);
|
||||||
|
localStorage.setItem('client_id', clientId);
|
||||||
|
|
||||||
|
// Create and store a random state value
|
||||||
|
const state = generateRandomString();
|
||||||
|
localStorage.setItem('pkce_state', state);
|
||||||
|
|
||||||
|
// Create and store a new PKCE code_verifier (the plaintext random secret)
|
||||||
|
const code_verifier = generateRandomString();
|
||||||
|
localStorage.setItem('pkce_code_verifier', code_verifier);
|
||||||
|
|
||||||
|
// Hash and base64-urlencode the secret to use as the challenge
|
||||||
|
const code_challenge = await pkceChallengeFromVerifier(code_verifier);
|
||||||
|
|
||||||
|
// Build the authorization URL
|
||||||
|
const buildUrl = () => {
|
||||||
|
return authUrl
|
||||||
|
+ `?response_type=${grantType}`
|
||||||
|
+ '&client_id='+encodeURIComponent(clientId)
|
||||||
|
+ '&state='+encodeURIComponent(state)
|
||||||
|
+ '&scope='+encodeURIComponent(scope)
|
||||||
|
+ '&redirect_uri='+encodeURIComponent(redirectUri)
|
||||||
|
+ '&code_challenge='+encodeURIComponent(code_challenge)
|
||||||
|
+ '&code_challenge_method=S256'
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to the authorization server
|
||||||
|
window.location = buildUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// OAUTH REDIRECT HANDLING
|
||||||
|
|
||||||
|
// Handle the redirect back from the authorization server and
|
||||||
|
// get an access token from the token endpoint
|
||||||
|
const oauthRedirect = async() => {
|
||||||
|
let tokenResponse = '';
|
||||||
|
let q = parseQueryString(window.location.search.substring(1));
|
||||||
|
// Check if the server returned an error string
|
||||||
|
if(q.error) {
|
||||||
|
alert('Error returned from authorization server: '+q.error);
|
||||||
|
}
|
||||||
|
// If the server returned an authorization code, attempt to exchange it for an access token
|
||||||
|
if(q.code) {
|
||||||
|
// Verify state matches what we set at the beginning
|
||||||
|
if(localStorage.getItem('pkce_state') != q.state) {
|
||||||
|
alert('Invalid state');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Exchange the authorization code for an access token
|
||||||
|
tokenResponse = await sendPostRequest(localStorage.getItem('token_endpoint'), {
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
code: q.code,
|
||||||
|
client_id: localStorage.getItem('client_id'),
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
code_verifier: localStorage.getItem('pkce_code_verifier')
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(error.error+'\n\n'+error.error_description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clean these up since we don't need them anymore
|
||||||
|
localStorage.removeItem('pkce_state');
|
||||||
|
localStorage.removeItem('pkce_code_verifier');
|
||||||
|
localStorage.removeItem('token_endpoint');
|
||||||
|
localStorage.removeItem('client_id');
|
||||||
|
return tokenResponse;
|
||||||
|
}
|
||||||
|
return tokenResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { tokenRequest, oauthRedirect }
|
||||||
@@ -86,5 +86,25 @@ export default {
|
|||||||
connect: "Connect",
|
connect: "Connect",
|
||||||
disconnect: "Disconnect",
|
disconnect: "Disconnect",
|
||||||
start: "Start",
|
start: "Start",
|
||||||
stop: "Stop"
|
stop: "Stop",
|
||||||
|
access_token: "Access Token",
|
||||||
|
token_list: "Token List",
|
||||||
|
get_token: "Get New Token",
|
||||||
|
manage_token: "Manage Access Token",
|
||||||
|
save_token: "Save Access Token",
|
||||||
|
use_token: "Use Saved Token",
|
||||||
|
request_token: "Request Token",
|
||||||
|
save_token_req: "Save Token Request",
|
||||||
|
manage_token_req: "Manage Token Request",
|
||||||
|
use_token_req: "Use Token Request",
|
||||||
|
token_req_name: "Request Name",
|
||||||
|
token_req_details: "Request Details",
|
||||||
|
token_name: "Token Name",
|
||||||
|
oidc_discovery_url: "OIDC Discovery URL",
|
||||||
|
auth_url: "Auth URL",
|
||||||
|
access_token_url: "Access Token URL",
|
||||||
|
client_id: "Client ID",
|
||||||
|
scope: "Scope",
|
||||||
|
state: "State",
|
||||||
|
token_req_list: "Token Request List"
|
||||||
};
|
};
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "postwoman",
|
"name": "postwoman",
|
||||||
"version": "1.0.0",
|
"version": "1.5.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
537
pages/index.vue
537
pages/index.vue
@@ -388,11 +388,29 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<ul v-if="auth === 'Bearer Token' || auth === 'OAuth 2.0'">
|
<ul v-if="auth === 'Bearer Token' || auth === 'OAuth 2.0'">
|
||||||
<li>
|
<li>
|
||||||
<input
|
<div class="flex-wrap">
|
||||||
placeholder="Token"
|
<input
|
||||||
name="bearer_token"
|
placeholder="Token"
|
||||||
v-model="bearerToken"
|
name="bearer_token"
|
||||||
/>
|
v-model="bearerToken"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="auth === 'OAuth 2.0'"
|
||||||
|
class="icon"
|
||||||
|
@click="showTokenList = !showTokenList"
|
||||||
|
v-tooltip.bottom="$t('use_token')"
|
||||||
|
>
|
||||||
|
<i class="material-icons">open_in_new</i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="auth === 'OAuth 2.0'"
|
||||||
|
class="icon"
|
||||||
|
@click="showTokenRequest = !showTokenRequest"
|
||||||
|
v-tooltip.bottom="$t('get_token')"
|
||||||
|
>
|
||||||
|
<i class="material-icons">vpn_key</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="flex-wrap">
|
<div class="flex-wrap">
|
||||||
@@ -404,6 +422,121 @@
|
|||||||
</pw-toggle>
|
</pw-toggle>
|
||||||
</div>
|
</div>
|
||||||
</pw-section>
|
</pw-section>
|
||||||
|
<pw-section
|
||||||
|
v-if="showTokenRequest"
|
||||||
|
class="red"
|
||||||
|
label="Access Token Request"
|
||||||
|
ref="accessTokenRequest"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<label for="token-name">{{ $t("token_name") }}</label>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
@click="showTokenRequestList = true"
|
||||||
|
v-tooltip.bottom="$t('manage_token_req')"
|
||||||
|
>
|
||||||
|
<i class="material-icons">library_add</i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
@click="clearContent('access_token', $event)"
|
||||||
|
v-tooltip.bottom="'Clear'"
|
||||||
|
>
|
||||||
|
<i class="material-icons">clear_all</i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
@click="showTokenRequest = false"
|
||||||
|
v-tooltip.bottom="'Close'"
|
||||||
|
>
|
||||||
|
<i class="material-icons">close</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="token-name"
|
||||||
|
placeholder="Enter a token name..."
|
||||||
|
name="token_name"
|
||||||
|
v-model="accessTokenName"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="oidc-discovery-url">{{ $t("oidc_discovery_url") }}</label>
|
||||||
|
<input
|
||||||
|
:disabled="this.authUrl !== '' || this.accessTokenUrl !== ''"
|
||||||
|
id="oidc-discovery-url"
|
||||||
|
name="oidc_discovery_url"
|
||||||
|
type="url"
|
||||||
|
v-model="oidcDiscoveryUrl"
|
||||||
|
placeholder="https://example.com/.well-known/openid-configuration"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="auth-url">{{ $t("auth_url") }}</label>
|
||||||
|
<input
|
||||||
|
:disabled="this.oidcDiscoveryUrl !== ''"
|
||||||
|
id="auth-url"
|
||||||
|
name="auth_url"
|
||||||
|
type="url"
|
||||||
|
v-model="authUrl"
|
||||||
|
placeholder="https://example.com/login/oauth/authorize"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="access-token-url">{{ $t("access_token_url") }}</label>
|
||||||
|
<input
|
||||||
|
:disabled="this.oidcDiscoveryUrl !== ''"
|
||||||
|
id="access-token-url"
|
||||||
|
name="access_token_url"
|
||||||
|
type="url"
|
||||||
|
v-model="accessTokenUrl"
|
||||||
|
placeholder="https://example.com/login/oauth/access_token"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="client-id">{{ $t("client_id") }}</label>
|
||||||
|
<input
|
||||||
|
id="client-id"
|
||||||
|
name="client_id"
|
||||||
|
type="text"
|
||||||
|
v-model="clientId"
|
||||||
|
placeholder="Client ID"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="scope">{{ $t("scope") }}</label>
|
||||||
|
<input
|
||||||
|
id="scope"
|
||||||
|
name="scope"
|
||||||
|
type="text"
|
||||||
|
v-model="scope"
|
||||||
|
placeholder="e.g. read:org"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<button class="icon" @click="handleAccessTokenRequest">
|
||||||
|
<i class="material-icons">vpn_key</i>
|
||||||
|
<span>{{ $t("request_token") }}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</pw-section>
|
||||||
</div>
|
</div>
|
||||||
<input id="tab-two" type="radio" name="options" />
|
<input id="tab-two" type="radio" name="options" />
|
||||||
<label for="tab-two">{{ $t("headers") }}</label>
|
<label for="tab-two">{{ $t("headers") }}</label>
|
||||||
@@ -805,6 +938,164 @@
|
|||||||
</div>
|
</div>
|
||||||
<div slot="footer"></div>
|
<div slot="footer"></div>
|
||||||
</pw-modal>
|
</pw-modal>
|
||||||
|
|
||||||
|
<pw-modal v-if="showTokenList" @close="showTokenList = false">
|
||||||
|
<div slot="header">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<h3 class="title">{{ $t("manage_token") }}</h3>
|
||||||
|
<div>
|
||||||
|
<button class="icon" @click="showTokenList = false">
|
||||||
|
<i class="material-icons">close</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div slot="body">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<label for="token-list">{{ $t("token_list") }}</label>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
@click="clearContent('tokens', $event)"
|
||||||
|
v-tooltip.bottom="'Clear'"
|
||||||
|
>
|
||||||
|
<i class="material-icons">clear_all</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul id="token-list" v-for="(token, index) in tokens" :key="index">
|
||||||
|
<li>
|
||||||
|
<input
|
||||||
|
:placeholder="'name ' + (index + 1)"
|
||||||
|
:value="token.name"
|
||||||
|
@change="
|
||||||
|
$store.commit('setOAuthTokenName', {
|
||||||
|
index,
|
||||||
|
value: $event.target.value
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input :value="token.value" readonly>
|
||||||
|
</li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
@click="useOAuthToken(token.value)"
|
||||||
|
v-tooltip.bottom="$t('use_token')"
|
||||||
|
>
|
||||||
|
<i class="material-icons">input</i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
@click="removeOAuthToken(index)"
|
||||||
|
v-tooltip.bottom="'Delete'"
|
||||||
|
>
|
||||||
|
<i class="material-icons">delete</i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div slot="footer"></div>
|
||||||
|
</pw-modal>
|
||||||
|
|
||||||
|
<pw-modal v-if="showTokenRequestList" @close="showTokenRequestList = false">
|
||||||
|
<div slot="header">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<h3 class="title">{{ $t("manage_token_req") }}</h3>
|
||||||
|
<div>
|
||||||
|
<button class="icon" @click="showTokenRequestList = false">
|
||||||
|
<i class="material-icons">close</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div slot="body">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<label for="token-req-list">{{ $t("token_req_list") }}</label>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
:disabled="this.tokenReqs.length === 0"
|
||||||
|
class="icon"
|
||||||
|
@click="showTokenRequestList = false"
|
||||||
|
v-tooltip.bottom="$t('use_token_req')"
|
||||||
|
>
|
||||||
|
<i class="material-icons">input</i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:disabled="this.tokenReqs.length === 0"
|
||||||
|
class="icon"
|
||||||
|
@click="removeOAuthTokenReq"
|
||||||
|
>
|
||||||
|
<i class="material-icons">delete</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="select-wrapper">
|
||||||
|
<select
|
||||||
|
id="token-req-list"
|
||||||
|
v-model="tokenReqSelect"
|
||||||
|
:disabled="this.tokenReqs.length === 0"
|
||||||
|
@change="tokenReqChange($event)">
|
||||||
|
<option
|
||||||
|
v-for="(req, index) in tokenReqs"
|
||||||
|
:key="index"
|
||||||
|
:value="req.name">
|
||||||
|
{{ req.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="token-req-name">{{ $t("token_req_name") }}</label>
|
||||||
|
<input v-model="tokenReqName">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="token-req-details">{{ $t("token_req_details") }}</label>
|
||||||
|
<textarea
|
||||||
|
id="token-req-details"
|
||||||
|
readonly
|
||||||
|
rows="7"
|
||||||
|
v-model="tokenReqDetails"
|
||||||
|
></textarea>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div slot="footer">
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<span></span>
|
||||||
|
<span>
|
||||||
|
<button class="icon primary" @click="addOAuthTokenReq">
|
||||||
|
{{ $t("save_token_req") }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</pw-modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -818,6 +1109,7 @@ import parseCurlCommand from "../assets/js/curlparser.js";
|
|||||||
import getEnvironmentVariablesFromScript from "../functions/preRequest";
|
import getEnvironmentVariablesFromScript from "../functions/preRequest";
|
||||||
import parseTemplateString from "../functions/templating";
|
import parseTemplateString from "../functions/templating";
|
||||||
import AceEditor from "../components/ace-editor";
|
import AceEditor from "../components/ace-editor";
|
||||||
|
import { tokenRequest, oauthRedirect } from "../assets/js/oauth";
|
||||||
|
|
||||||
const statusCategories = [
|
const statusCategories = [
|
||||||
{
|
{
|
||||||
@@ -901,6 +1193,9 @@ export default {
|
|||||||
previewEnabled: false,
|
previewEnabled: false,
|
||||||
paramsWatchEnabled: true,
|
paramsWatchEnabled: true,
|
||||||
expandResponse: false,
|
expandResponse: false,
|
||||||
|
showTokenList: false,
|
||||||
|
showTokenRequest: false,
|
||||||
|
showTokenRequestList: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are content types that can be automatically
|
* These are content types that can be automatically
|
||||||
@@ -1208,6 +1503,94 @@ export default {
|
|||||||
this.$store.commit("setState", { value, attribute: "bearerToken" });
|
this.$store.commit("setState", { value, attribute: "bearerToken" });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
tokens: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.tokens;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "tokens" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tokenReqs: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.tokenReqs;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "tokenReqs" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tokenReqSelect: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.tokenReqSelect;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "tokenReqSelect" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tokenReqName: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.tokenReqName;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "tokenReqName" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accessTokenName: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.accessTokenName;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "accessTokenName" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oidcDiscoveryUrl: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.oidcDiscoveryUrl;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "oidcDiscoveryUrl" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authUrl: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.authUrl;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "authUrl" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accessTokenUrl: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.accessTokenUrl;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "accessTokenUrl" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientId: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.clientId;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "clientId" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.scope;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "scope" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.oauth2.state;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("setOAuth2", { value, attribute: "state" });
|
||||||
|
}
|
||||||
|
},
|
||||||
headers: {
|
headers: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.state.request.headers;
|
return this.$store.state.request.headers;
|
||||||
@@ -1498,6 +1881,16 @@ export default {
|
|||||||
}
|
}
|
||||||
return requestString.join("").slice(0, -2);
|
return requestString.join("").slice(0, -2);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
tokenReqDetails() {
|
||||||
|
const details = {
|
||||||
|
oidcDiscoveryUrl: this.oidcDiscoveryUrl,
|
||||||
|
authUrl: this.authUrl,
|
||||||
|
accessTokenUrl: this.accessTokenUrl,
|
||||||
|
clientId: this.clientId,
|
||||||
|
scope: this.scope
|
||||||
|
};
|
||||||
|
return JSON.stringify(details, null, 2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -2047,6 +2440,9 @@ export default {
|
|||||||
this.httpUser = "";
|
this.httpUser = "";
|
||||||
this.httpPassword = "";
|
this.httpPassword = "";
|
||||||
this.bearerToken = "";
|
this.bearerToken = "";
|
||||||
|
this.showTokenRequest = false;
|
||||||
|
this.tokens = [];
|
||||||
|
this.tokenReqs = [];
|
||||||
break;
|
break;
|
||||||
case "headers":
|
case "headers":
|
||||||
this.headers = [];
|
this.headers = [];
|
||||||
@@ -2054,6 +2450,19 @@ export default {
|
|||||||
case "parameters":
|
case "parameters":
|
||||||
this.params = [];
|
this.params = [];
|
||||||
break;
|
break;
|
||||||
|
case "access_token":
|
||||||
|
this.accessTokenName = "";
|
||||||
|
this.oidcDiscoveryUrl = "";
|
||||||
|
this.authUrl = "";
|
||||||
|
this.accessTokenUrl = "";
|
||||||
|
this.clientId = "";
|
||||||
|
this.scope = "";
|
||||||
|
break;
|
||||||
|
case "tokens":
|
||||||
|
this.tokens = [];
|
||||||
|
break;
|
||||||
|
case "tokenReqs":
|
||||||
|
this.tokenReqs = [];
|
||||||
default:
|
default:
|
||||||
(this.label = ""),
|
(this.label = ""),
|
||||||
(this.method = "GET"),
|
(this.method = "GET"),
|
||||||
@@ -2068,6 +2477,15 @@ export default {
|
|||||||
this.params = [];
|
this.params = [];
|
||||||
this.bodyParams = [];
|
this.bodyParams = [];
|
||||||
this.rawParams = "";
|
this.rawParams = "";
|
||||||
|
this.showTokenRequest = false;
|
||||||
|
this.tokens = [];
|
||||||
|
this.tokenReqs = [];
|
||||||
|
this.accessTokenName = "";
|
||||||
|
this.oidcDiscoveryUrl = "";
|
||||||
|
this.authUrl = "";
|
||||||
|
this.accessTokenUrl = "";
|
||||||
|
this.clientId = "";
|
||||||
|
this.scope = "";
|
||||||
}
|
}
|
||||||
e.target.innerHTML = this.doneButton;
|
e.target.innerHTML = this.doneButton;
|
||||||
this.$toast.info("Cleared", {
|
this.$toast.info("Cleared", {
|
||||||
@@ -2152,9 +2570,115 @@ export default {
|
|||||||
icon: "attach_file"
|
icon: "attach_file"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async handleAccessTokenRequest() {
|
||||||
|
if (this.oidcDiscoveryUrl === "" && (this.authUrl === "" || this.accessTokenUrl === "")) {
|
||||||
|
this.$toast.error("Please complete configuration urls.", {
|
||||||
|
icon: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tokenReqParams = {
|
||||||
|
grantType: "code",
|
||||||
|
oidcDiscoveryUrl: this.oidcDiscoveryUrl,
|
||||||
|
authUrl: this.authUrl,
|
||||||
|
accessTokenUrl: this.accessTokenUrl,
|
||||||
|
clientId: this.clientId,
|
||||||
|
scope: this.scope
|
||||||
|
};
|
||||||
|
await tokenRequest(tokenReqParams);
|
||||||
|
} catch (e) {
|
||||||
|
this.$toast.error(e, {
|
||||||
|
icon: "code"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async oauthRedirectReq() {
|
||||||
|
let tokenInfo = await oauthRedirect();
|
||||||
|
if(tokenInfo.hasOwnProperty('access_token')) {
|
||||||
|
this.bearerToken = tokenInfo.access_token;
|
||||||
|
this.addOAuthToken({
|
||||||
|
name: this.accessTokenName,
|
||||||
|
value: tokenInfo.access_token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addOAuthToken({name, value}) {
|
||||||
|
this.$store.commit("addOAuthToken", {
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
removeOAuthToken(index) {
|
||||||
|
const oldTokens = this.tokens.slice();
|
||||||
|
this.$store.commit("removeOAuthToken", index);
|
||||||
|
this.$toast.error("Deleted", {
|
||||||
|
icon: "delete",
|
||||||
|
action: {
|
||||||
|
text: "Undo",
|
||||||
|
onClick: (e, toastObject) => {
|
||||||
|
this.tokens = oldTokens;
|
||||||
|
toastObject.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
useOAuthToken(value) {
|
||||||
|
this.bearerToken = value;
|
||||||
|
this.showTokenList = false;
|
||||||
|
},
|
||||||
|
addOAuthTokenReq() {
|
||||||
|
try {
|
||||||
|
const name = this.tokenReqName;
|
||||||
|
const details = JSON.parse(this.tokenReqDetails);
|
||||||
|
this.$store.commit("addOAuthTokenReq", {
|
||||||
|
name,
|
||||||
|
details
|
||||||
|
});
|
||||||
|
this.$toast.info("Token request saved");
|
||||||
|
this.showTokenRequestList = false;
|
||||||
|
} catch (e) {
|
||||||
|
this.$toast.error(e, {
|
||||||
|
icon: "code"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeOAuthTokenReq(index) {
|
||||||
|
const oldTokenReqs = this.tokenReqs.slice();
|
||||||
|
let targetReqIndex = this.tokenReqs.findIndex(tokenReq => tokenReq.name === this.tokenReqName);
|
||||||
|
if (targetReqIndex < 0) return;
|
||||||
|
this.$store.commit("removeOAuthTokenReq", targetReqIndex);
|
||||||
|
this.$toast.error("Deleted", {
|
||||||
|
icon: "delete",
|
||||||
|
action: {
|
||||||
|
text: "Undo",
|
||||||
|
onClick: (e, toastObject) => {
|
||||||
|
this.tokenReqs = oldTokenReqs;
|
||||||
|
toastObject.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tokenReqChange(event) {
|
||||||
|
let targetReq = this.tokenReqs.find(tokenReq => tokenReq.name === event.target.value);
|
||||||
|
let {
|
||||||
|
oidcDiscoveryUrl,
|
||||||
|
authUrl,
|
||||||
|
accessTokenUrl,
|
||||||
|
clientId,
|
||||||
|
scope
|
||||||
|
} = targetReq.details;
|
||||||
|
this.tokenReqName = targetReq.name;
|
||||||
|
this.oidcDiscoveryUrl = oidcDiscoveryUrl;
|
||||||
|
this.authUrl = authUrl;
|
||||||
|
this.accessTokenUrl = accessTokenUrl;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.scope = scope;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.observeRequestButton();
|
this.observeRequestButton();
|
||||||
this._keyListener = function(e) {
|
this._keyListener = function(e) {
|
||||||
if (e.key === "g" && (e.ctrlKey || e.metaKey)) {
|
if (e.key === "g" && (e.ctrlKey || e.metaKey)) {
|
||||||
@@ -2172,6 +2696,7 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener("keydown", this._keyListener.bind(this));
|
document.addEventListener("keydown", this._keyListener.bind(this));
|
||||||
|
await this.oauthRedirectReq();
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.urlExcludes = this.$store.state.postwoman.settings.URL_EXCLUDES || {
|
this.urlExcludes = this.$store.state.postwoman.settings.URL_EXCLUDES || {
|
||||||
|
|||||||
@@ -85,5 +85,29 @@ export default {
|
|||||||
|
|
||||||
setValueBodyParams({ request }, { index, value }) {
|
setValueBodyParams({ request }, { index, value }) {
|
||||||
request.bodyParams[index].value = value;
|
request.bodyParams[index].value = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
setOAuth2({ oauth2 }, { attribute, value }) {
|
||||||
|
oauth2[attribute] = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
addOAuthToken({ oauth2 }, value) {
|
||||||
|
oauth2.tokens.push(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeOAuthToken({ oauth2 }, index) {
|
||||||
|
oauth2.tokens.splice(index, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
setOAuthTokenName({ oauth2 }, { index, value }) {
|
||||||
|
oauth2.tokens[index].name = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
addOAuthTokenReq({ oauth2 }, value) {
|
||||||
|
oauth2.tokenReqs.push(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeOAuthTokenReq({ oauth2 }, index) {
|
||||||
|
oauth2.tokenReqs.splice(index, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,5 +22,17 @@ export default () => ({
|
|||||||
headers: [],
|
headers: [],
|
||||||
variables: [],
|
variables: [],
|
||||||
query: ""
|
query: ""
|
||||||
|
},
|
||||||
|
oauth2: {
|
||||||
|
tokens: [],
|
||||||
|
tokenReqs: [],
|
||||||
|
tokenReqSelect: "",
|
||||||
|
tokenReqName: "",
|
||||||
|
accessTokenName: "",
|
||||||
|
oidcDiscoveryUrl: "",
|
||||||
|
authUrl: "",
|
||||||
|
accessTokenUrl: "",
|
||||||
|
clientId: "",
|
||||||
|
scope: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user