Skip to content
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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,7 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk

# End of https://www.gitignore.io/api/go,vim,git,macos,linux,emacs,windows,eclipse,intellij+all,visualstudiocode
# End of https://www.gitignore.io/api/go,vim,git,macos,linux,emacs,windows,eclipse,intellij+all,visualstudiocode

# UI node-modules
node_modules
11 changes: 11 additions & 0 deletions pkg/assets/static/redirectpage.html
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>
189 changes: 189 additions & 0 deletions pkg/assets/static/redirectpage.js
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";
Copy link
Contributor

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:

  • rename consoleUrlMock to something like signupURL.
  • try to load it from configURL JSON as signup-url.
  • if there is no signup-url defined in the config payload JSON then fall back to the default value (which is just the base URL: /)
  • We, the dev sandbox team, will add https://console.redhat.com/openshift/sandbox to the config API for our production sandbox.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We, the dev sandbox team, will add https://console.redhat.com/openshift/sandbox to the config API for our production sandbox.

See #464

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I don't actually see in my browser any requests to "/api/v1/signup". This is what I see instead:

  • redirectpage.html redirects to sso (expected)
  • then it redirects to console.openshift.com/sandbox even if I already have an account in dev sandobx. So it doesn't seem to actually check /api/v1/signup unless I'm missing something. But anyway, no matter what I do I always end up in console.openshift.com


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();
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
And yes, in case of 404 we should redirect to the signup URL but we also need to redirect back to the redirectpage after user signs up successfully. See https://issues.redhat.com/browse/RHCLOUD-32115?focusedId=24554951&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-24554951 for details.
The redirect param is not currently supported in HCC but we can add this to our internal landing page for now and later add it to HCC too.

}
}
};
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();
27 changes: 27 additions & 0 deletions redirectpage-tests/README.adoc
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm...
What URL should I use if I want to get redirected to

{consoleURL}/catalog/ns/alexeykazakov-dev?category=databases&selectedId=8ef58d8f-edcc-4284-96bb-5006360b6e73

Where alexeykazakov-dev is my default namespace.
What should I use for keyword to get category=databases as the result?

Copy link
Author

@aneelac22 aneelac22 Sep 16, 2024

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

@alexeykazakov alexeykazakov Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you are rigth.
But could you please clarify what URL should be used to access

{consoleURL}/catalog/ns/alexeykazakov-dev?keyword=jboss&selectedId=openshift-helm-charts--https%3A%2F%2Fgithub.com%2Fopenshift-helm-charts%2Fcharts%2Freleases%2Fdownload%2Fredhat-eap8-1.1.2%2Feap8-1.1.2.tgz

?
Should we use

{baseURL}/redirectpage.html?link="catalog"&keyword=jboss&selectedId=openshift-helm-charts--https%3A%2F%2Fgithub.com%2Fopenshift-helm-charts%2Fcharts%2Freleases%2Fdownload%2Fredhat-eap8-1.1.2%2Feap8-1.1.2.tgz

?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

Copy link
Author

Choose a reason for hiding this comment

The 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/`

3 changes: 3 additions & 0 deletions redirectpage-tests/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['@babel/preset-env'],
};
6 changes: 6 additions & 0 deletions redirectpage-tests/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
testEnvironment: "jsdom",
transform: {
'^.+\\.jsx?$': 'babel-jest',
},
}
Loading
Loading