-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add static redirect page to sso and then to requested page #438
base: master
Are you sure you want to change the base?
Changes from 5 commits
37075cd
7ce0f44
9d16ff1
5359ec4
c00ec62
c2e35fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<!DOCTYPE html> | ||
<html lang="en" class="pf-m-redhat-font"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<title>OpenShift Developer Sandbox</title> | ||
</head> | ||
<body> | ||
</body> | ||
<script type="module" src="redirectpage.js"></script> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
const configURL = "/api/v1/authconfig"; // URL to get config | ||
const queryString = window.location.search; | ||
const urlParams = new URLSearchParams(queryString); | ||
const link = urlParams.get("link"); | ||
const keyword = urlParams.get("keyword"); | ||
const selectedId = urlParams.get("selectedId"); | ||
const consoleUrlMock = "https://console.redhat.com/openshift/sandbox"; | ||
const registrationURL = "/api/v1/signup"; //Url to get User info needed for redirect | ||
|
||
const params = { | ||
keyword, | ||
selectedId, | ||
}; | ||
|
||
let idToken; | ||
let keycloak; | ||
let intervalRefRefresh; | ||
|
||
function handleSuccess(data) { | ||
if (!data?.consoleURL || !data?.defaultUserNamespace) { | ||
window.location.href = consoleUrlMock; | ||
return; | ||
} | ||
|
||
const baseUrl = `${data.consoleURL}/`; | ||
const appendedUrl = `${link}/ns/${data.defaultUserNamespace}`; | ||
const redirectUrl = new URL(baseUrl + appendedUrl); | ||
let resultUrl; | ||
if (data.status != "ready") { | ||
window.location.url = consoleUrlMock; | ||
} | ||
|
||
if (link === "notebookController") { | ||
resultUrl = `${baseUrl}notebookController/spawner`; | ||
} else if (link === "dashboard") { | ||
resultUrl = `${baseUrl}dashboard`; | ||
} else { | ||
resultUrl = redirectUrl.toString(); | ||
} | ||
const url = new URL(resultUrl); | ||
Object.keys(params).forEach((key) => { | ||
if (params[key]) { | ||
url.searchParams.append(key, params[key]); | ||
} | ||
}); | ||
window.location.href = url.toString(); | ||
} | ||
|
||
function handleError(error) { | ||
window.location.href = consoleUrlMock; | ||
} | ||
|
||
function handleUnauthenticated() { | ||
idToken = null; | ||
window.location.href = consoleUrlMock; | ||
} | ||
|
||
function handleUserInfo() { | ||
idToken = keycloak.idToken; | ||
} | ||
|
||
function refreshToken() { | ||
keycloak.updateToken(30).catch((error) => { | ||
console.error("Failed to refresh token:", error); | ||
handleUnauthenticated(); | ||
}); | ||
} | ||
|
||
function initializeKeycloak(clientConfig) { | ||
keycloak = new Keycloak(clientConfig); | ||
keycloak | ||
.init({ | ||
onLoad: "check-sso", | ||
silentCheckSsoRedirectUri: | ||
window.location.origin + "/silent-check-sso.html", | ||
}) | ||
.then((authenticated) => { | ||
if (authenticated) { | ||
console.log("User is authenticated"); | ||
intervalRefRefresh = setInterval(refreshToken, 15000); //start 15s interval token refresh | ||
keycloak | ||
.loadUserInfo() | ||
.then(handleUserInfo) | ||
.catch(() => handleError("failed to pull in user data.")); | ||
} else { | ||
console.log("user not authenticated"); | ||
handleUnauthenticated(); | ||
} | ||
}) | ||
.catch(() => handleError("Failed to initialize authorization")); | ||
} | ||
|
||
// General function to fetch JSON data | ||
function getJSON( | ||
method, | ||
url, | ||
token, | ||
callback, | ||
body = null, | ||
headers = {} | ||
) { | ||
let xhr = new XMLHttpRequest(); | ||
xhr.open(method, url, true); | ||
if (token != null) xhr.setRequestHeader("Authorization", "Bearer " + token); | ||
|
||
Object.entries(headers).forEach(([key, value]) => { | ||
xhr.setRequestHeader(key, value); | ||
}); | ||
|
||
xhr.responseType = "json"; | ||
xhr.onload = () => { | ||
let status = xhr.status; | ||
if (status >= 200 && status < 300) { | ||
callback(null, xhr.response); | ||
} else { | ||
callback(status, xhr.response); | ||
} | ||
}; | ||
xhr.send(body ? JSON.stringify(body) : null); | ||
} | ||
|
||
function loadAuthLibrary(url, cbSuccess, cbError) { | ||
const script = document.createElement("script"); | ||
script.setAttribute("src", url); | ||
script.setAttribute("type", "text/javascript"); | ||
let loaded = false; | ||
function loadFunction() { | ||
if (loaded) return; | ||
loaded = true; | ||
cbSuccess(); | ||
} | ||
function errorFunction(error) { | ||
if (loaded) return; | ||
cbError(error); | ||
} | ||
script.onerror = errorFunction; | ||
script.onload = loadFunction; | ||
script.onreadystatechange = loadFunction; | ||
document.head.appendChild(script); | ||
} | ||
|
||
async function getRedirectData() { | ||
const xhr = new XMLHttpRequest(); | ||
|
||
xhr.open("GET", registrationURL, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... I don't actually see in my browser any requests to
|
||
|
||
xhr.setRequestHeader("Authorization", `Bearer ${idToken}`); | ||
xhr.onreadystatechange = function () { | ||
if (xhr.readyState === XMLHttpRequest.DONE) { | ||
if (xhr.status >= 200 && xhr.status < 300) { | ||
try { | ||
const data = JSON.parse(xhr.responseText); | ||
handleSuccess(data); | ||
} catch (error) { | ||
handleError(); | ||
} | ||
} else { | ||
handleError(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the signup api returns 404 it's not an error actually. It just means that user has to sign up first. So I wouldn't mix it with other cases when something went wrong. |
||
} | ||
} | ||
}; | ||
xhr.onerror = function () { | ||
handleError(); | ||
}; | ||
xhr.send(); | ||
} | ||
|
||
function redirectUser() { | ||
if (!idToken) { | ||
getJSON("GET", configURL, null, (err, data) => { | ||
if (err) { | ||
console.error("Error loading client config: ", err); | ||
} else { | ||
loadAuthLibrary( | ||
data["auth-client-library-url"], | ||
() => { | ||
const clientConfig = JSON.parse(data["auth-client-config"]); | ||
initializeKeycloak(clientConfig); | ||
}, | ||
() => handleError() | ||
); | ||
} | ||
}); | ||
} else { | ||
getRedirectData(); | ||
} | ||
} | ||
|
||
redirectUser(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
|
||
The redirectPage.js implements the redirect flow of pages from sandbox developer catalog. | ||
|
||
== Test | ||
|
||
1. In the assets static folder install node_modules by running | ||
``` | ||
npm i | ||
``` | ||
2. To run tests, execute | ||
``` | ||
npm run test | ||
``` | ||
|
||
== Implementation guidelines | ||
|
||
1. When user clicks on a catalog item on developers.redhat.com it redirects to this url format | ||
`${baseUrl}?link="catalogName"&keyword="keyword"&selectedId="selectedId"` | ||
2. If the user is not authenticated, it redirects to sso page for user to login | ||
3. Once the user is authenticated it redirects to the page user wants to access which is in the format | ||
`${consoleUrl}/${link}/ns/${defaultUserNamespace}?keyword="keyword"&selectedId="selectedId"` with | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm...
Where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the google doc there is no URL with category as a param, Am I missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you are rigth.
?
? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is correct |
||
most of the info coming from `api/v1/signup` and the original url | ||
|
||
The below are exceptions to above mentioned redirect Url's format | ||
For `Jupyter Notebooks` the url format is `${consoleUrl}/notebookController/spawner` | ||
For `Red Hat Openshift DevSpaces` the url format is `${consoleUrl}/dashboard/` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
presets: ['@babel/preset-env'], | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
testEnvironment: "jsdom", | ||
transform: { | ||
'^.+\\.jsx?$': 'babel-jest', | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of hardcoding this consleURL (and why it's called consoleUrlMock btw?) let's load it from the config (
/api/v1/authconfig
)So let's maybe do the following:
consoleUrlMock
to something likesignupURL
.configURL
JSON assignup-url
.signup-url
defined in the config payload JSON then fall back to the default value (which is just the base URL:/
)https://console.redhat.com/openshift/sandbox
to the config API for our production sandbox.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #464