Skip to content

Commit

Permalink
♻️ Refactoring code
Browse files Browse the repository at this point in the history
  • Loading branch information
Liyas Thomas committed Jan 10, 2020
1 parent bf1a143 commit 57f7621
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 80 deletions.
150 changes: 80 additions & 70 deletions assets/js/oauth.js
Original file line number Diff line number Diff line change
@@ -1,175 +1,185 @@
const redirectUri = `${ window.location.origin }/`;
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 sendPostRequest = async (url, params) => {
let body = Object.keys(params)
.map(key => `${key}=${params[key]}`)
.join("&");
const options = {
method: 'post',
method: "post",
headers: {
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
"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);
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("=") );
if (string === "") {
return {};
}
let segments = string.split("&").map(s => s.split("="));
let queryString = {};
segments.forEach(s => queryString[s[0]] = s[1]);
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',
method: "GET",
headers: {
'Content-type': 'application/json'
"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);
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);
const array = new Uint32Array(28);
window.crypto.getRandomValues(array);
return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('');
}
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);
}
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.
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(/=+$/, '');
}
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) => {
const pkceChallengeFromVerifier = async v => {
let hashed = await sha256(v);
return base64urlencode(hashed);
}
};

//////////////////////////////////////////////////////////////////////
// OAUTH REQUEST

// Initiate PKCE Auth Code flow when requested
const tokenRequest = async({
const tokenRequest = async ({
oidcDiscoveryUrl,
grantType,
authUrl,
accessTokenUrl,
clientId,
scope
}) => {

// Check oauth configuration
if (oidcDiscoveryUrl !== '') {
const { authorization_endpoint, token_endpoint } = await getTokenConfiguration(oidcDiscoveryUrl);
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);
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);
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);
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'
;
}
const buildUrl = () =>
`${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 = '';
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 (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) {
if (q.code) {
// Verify state matches what we set at the beginning
if(localStorage.getItem('pkce_state') != q.state) {
alert('Invalid state');
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')
});
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);
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');
localStorage.removeItem("pkce_state");
localStorage.removeItem("pkce_code_verifier");
localStorage.removeItem("token_endpoint");
localStorage.removeItem("client_id");
return tokenResponse;
}
return tokenResponse;
}
};

export { tokenRequest, oauthRedirect }
export { tokenRequest, oauthRedirect };
4 changes: 2 additions & 2 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,8 @@ export default {
this.$store.state.postwoman.settings.THEME_CLASS || "";
// Load theme color data from settings, or use default color.
let color = this.$store.state.postwoman.settings.THEME_COLOR || "#50fa7b";
let vibrant = this.$store.state.postwoman.settings.THEME_COLOR_VIBRANT;
if (vibrant == null) vibrant = true;
let vibrant =
this.$store.state.postwoman.settings.THEME_COLOR_VIBRANT || true;
document.documentElement.style.setProperty("--ac-color", color);
document.documentElement.style.setProperty(
"--act-color",
Expand Down
30 changes: 23 additions & 7 deletions pages/graphql.vue
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,10 @@
</label>
<div v-if="queryFields.length > 0" class="tab">
<div v-for="field in queryFields" :key="field.name">
<gql-field :gqlField="field" :jumpTypeCallback="handleJumpToType" />
<gql-field
:gqlField="field"
:jumpTypeCallback="handleJumpToType"
/>
</div>
</div>

Expand All @@ -304,7 +307,10 @@
</label>
<div v-if="mutationFields.length > 0" class="tab">
<div v-for="field in mutationFields" :key="field.name">
<gql-field :gqlField="field" :jumpTypeCallback="handleJumpToType" />
<gql-field
:gqlField="field"
:jumpTypeCallback="handleJumpToType"
/>
</div>
</div>

Expand All @@ -320,7 +326,10 @@
</label>
<div v-if="subscriptionFields.length > 0" class="tab">
<div v-for="field in subscriptionFields" :key="field.name">
<gql-field :gqlField="field" :jumpTypeCallback="handleJumpToType" />
<gql-field
:gqlField="field"
:jumpTypeCallback="handleJumpToType"
/>
</div>
</div>

Expand All @@ -335,8 +344,15 @@
{{ $t("types") }}
</label>
<div v-if="gqlTypes.length > 0" class="tab">
<div v-for="type in gqlTypes" :key="type.name" :id="`type_${type.name}`">
<gql-type :gqlType="type" :jumpTypeCallback="handleJumpToType" />
<div
v-for="type in gqlTypes"
:key="type.name"
:id="`type_${type.name}`"
>
<gql-type
:gqlType="type"
:jumpTypeCallback="handleJumpToType"
/>
</div>
</div>
</section>
Expand Down Expand Up @@ -572,13 +588,13 @@ export default {
const target = document.getElementById(`type_${rootTypeName}`);
if (target) {
target.scrollIntoView({
behavior: 'smooth'
behavior: "smooth"
});
}
},
resolveRootType(type) {
let t = type;
while (t.ofType != null) t = t.ofType;
while (t.ofType !== null) t = t.ofType;
return t;
},
copySchema() {
Expand Down
2 changes: 1 addition & 1 deletion store/postwoman.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const state = () => ({
export const mutations = {
applySetting({ settings }, setting) {
if (
setting == null ||
setting === null ||
!(setting instanceof Array) ||
setting.length !== 2
) {
Expand Down

0 comments on commit 57f7621

Please sign in to comment.