Skip to content
Snippets Groups Projects
Commit 8f081617 authored by Reinder Kraaij's avatar Reinder Kraaij :eye:
Browse files

Merge branch 'TMSS-2900-Front-End-Only-Login-Fix-Bundled' into 'master'

New Login Pattern

Closes TMSS-2900

See merge request !1299
parents f879c581 468f6778
No related branches found
No related tags found
1 merge request!1299New Login Pattern
......@@ -12,13 +12,85 @@ const Auth = {
* - If not then check the Keycloak authentication
* - Return false if no authentication found
*/
isDebugLoggin: true,
isTokenCheckInProgress: false,
prevalidatedToken: "abcd-abcd",
callCounter: 0,
validateExistingToken: async (user) => {
const loginType = localStorage.getItem("loginType");
if (Auth.isDebugLoggin) console.log(" \\ validateExistingToken:", user)
// Check if token validation is already in progress
if (Auth.isTokenCheckInProgress) {
// Another part of the application is already checking the token,
// so wait for it to complete before continuing.
await new Promise(resolve => {
const intervalId = setInterval(() => {
if (!Auth.isTokenCheckInProgress) {
clearInterval(intervalId);
resolve();
}
}, 100);
});
}
// another thread might have fixed this :)
if (Auth.prevalidatedToken === user.token) {
if (Auth.isDebugLoggin) {
console.log("// isAuthenticated : No Need anymore to Check Token !")
return true
}
}
if (loginType === "KeyCloak") {
if (Auth.isDebugLoggin) console.log(" - validateExistingToken: Checking for KeyCloak State", user?.token)
const currentAuthenticationState = await AuthService.getKeycloakAuthState();
if (currentAuthenticationState?.is_authenticated) {
if (Auth.isDebugLoggin) console.log(" // User was a valid KeyCloak user ", currentAuthenticationState)
Auth.prevalidatedToken = user.token
return true; // we are still having a valid session.
}
} else if (loginType === "Application") {
if (Auth.isDebugLoggin) console.log(" - validateExistingToken: Checking for Token State for Application ", user?.token)
let currentAuthenticationState = await AuthService.isValidAuthenticationState(user?.name)
if (currentAuthenticationState?.websocket_token_valid) {
Auth.prevalidatedToken = user.token
if (Auth.isDebugLoggin) console.log(" - validateExistingToken: Token Validated ", Auth.prevalidatedToken, currentAuthenticationState)
Auth.isTokenCheckInProgress = false
return true
}
}
if (Auth.isDebugLoggin) console.log(" // validateExistingToken: Failed to Validate Token ")
Auth.isTokenCheckInProgress = false
Auth.prevalidatedToken = "abcd-abcd"
return false
},
isAuthenticated: async () => {
let user = localStorage.getItem("user");
if (user) {
if (Auth.isDebugLoggin) console.log("\\ isAuthenticated User From localstorage:", user, Auth.prevalidatedToken, user?.token)
// by adding the user.authenticator, we reinforce all users to revalidate once, since it was missing in the previous version of the localstorage object
if (user) { // this is the cached user from the system, could be either LDAP or Keycloak.
user = JSON.parse(user);
return Auth.getUserPermission(user);
} else {
//Keycloak authentication
let authPermissions = await Auth.getUserPermission(user);
if (Auth.prevalidatedToken !== user?.token) { // let's check if this is still a valid token.
let isTokenValid = await Auth.validateExistingToken(user)
if (!isTokenValid) {
if (Auth.isDebugLoggin) console.log("// isAuthenticated : No Valid Token")
return false
} else {
if (Auth.isDebugLoggin) console.log("- isAuthenticated : Valid Existing Token")
}
} else {
if (Auth.isDebugLoggin) console.log(" - isAuthenticated : Cache token match", Auth.prevalidatedToken)
}
if (Auth.isDebugLoggin) console.log("/ isAuthenticated : Retrieved authPermissions:", authPermissions)
return authPermissions;
} else {
//Keycloak authentication, we relying on the cookie. LDAP User will get a False, we cannot authenticate them.
// but keycloak users might have a session
if (Auth.isDebugLoggin) console.log("- Check getKeycloakAuthState. ")
const res = await AuthService.getKeycloakAuthState();
if (res?.is_authenticated) {
localStorage.setItem("loginType", 'Keycloak');
......@@ -27,7 +99,7 @@ const Auth = {
prev[name] = value;
return prev;
}, {});
const userDetails = {'name':res.username, 'token': cookies.csrftoken || res.csrftoken, 'websocket_token': res.websocket_token};
const userDetails = { 'name': res.username, 'token': cookies.csrftoken || res.csrftoken, 'websocket_token': res.websocket_token };
localStorage.setItem("user", JSON.stringify(userDetails));
return Auth.getUserPermission(userDetails);
}
......@@ -35,12 +107,13 @@ const Auth = {
await PermissionStackUtil.getPermissions(false);
return false;
},
getUserPermission: async (user) => {
if (user.token) {
axios.defaults.headers.common['Authorization'] = `Token ${user.token}`;
axios.defaults.headers.common['X-CSRFTOKEN'] = `${user.token}`;
const permissions = await AuthStore.getState();
if(!permissions.userRolePermission.project) {
if (!permissions.userRolePermission.project) {
await PermissionStackUtil.getPermissions(true);
}
return true;
......@@ -51,22 +124,24 @@ const Auth = {
return JSON.parse(localStorage.getItem("user"));
},
/** Authenticate user from the backend and store user details in local storage */
login: async(user, pass) => {
login: async (user, pass) => {
const authData = await AuthService.authenticate(user, pass);
if (authData) {
localStorage.setItem("user", JSON.stringify({name:user, token: authData.token, websocket_token: authData.token},));
localStorage.setItem("user", JSON.stringify({ name: user, token: authData.token, websocket_token: authData.token },));
localStorage.setItem("loginType", 'Application');
await PermissionStackUtil.getPermissions(true);
return true;
} else {
} else {
return false;
}
},
/** Remove user details from localstorage on logout */
/** Remove user details from localstorage on logout and clear entire local storage*/
logout: () => {
AuthService.deAuthenticate();
PermissionStackUtil.deleteStore()
PermissionStackUtil.deleteStore();
Auth.prevaliDationUser = "";
localStorage.removeItem("user");
localStorage.clear();
}
}
......
......@@ -5,7 +5,7 @@ const AuthService = {
try {
delete axios.defaults.headers.common['Authorization'];
delete axios.defaults.headers.common['X-Csrftoken'];
const response = await axios.post("/api/token-auth/", {username: user, password: pass} );
const response = await axios.post("/api/token-auth/", {username: user, password: pass} , {headers: {'Content-Type': 'application/json'}});
axios.defaults.headers.common['Authorization'] = `Token ${response.data.token}`;
return response.data;
} catch(error) {
......@@ -58,14 +58,25 @@ const AuthService = {
}
axios.defaults.headers.common['Content-Type'] = contentType;
},
isValidToken: async(token) => {
isValidAuthenticationState: async(username) => {
let response
try {
axios.defaults.headers.common['Authorization'] = `Token ${token}`;
const response = await axios.get("/api/subtask_type/?limit=1&offset=1");
console.log(response);
console.log("\\ isValidAuthenticationState : requesting")
// the server will first attempt to see if the user is already authenticated (based upon cookies)
// and then if it fails, it will validate upon the token (present in the header) and the username
// This gives us a change to both validate keycloak and application tokens.
// when using keycloak, do not supply the username.
response = await axios.get("/api/authentication_state/?username="+username)
console.log("// isValidAuthenticationState: requested",response.data)
if (response?.status==200) {
return response.data
}
console.error("// isValidAuthenticationState retrieved response");
} catch(error) {
console.error(error);
console.error("// isValidAuthenticationState Requested. Got error", error, response);
}
return {}
},
getAccessControlMethod: async(subdomain, source) => {
let res = null;
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
......@@ -11,9 +11,12 @@ import { giveConsistentNodes } from '../utils/test.helper';
function MakePrimaryMock() {
let mock = new MockAdapter(axios);
mock.onGet("/api/authentication_state/").reply(200, { is_authenticated: true, username: "a username", csrftoken: "a csrftoken", websocket_token: "a websocket tokcen" }, {
'Set-Cookie': "srftoken=abcdefgh; expires=Sun, 22 Dec 2324 22:58:11 GMT; Path=/; SameSite=Lax"
mock.onGet("/api/authentication_state/").reply(200, { is_authenticated: true, username: "a username", csrftoken: "a csrftoken", websocket_token: "a websocket tokcen", websocket_token_valid:true }, {
'Set-Cookie': "srftoken=abcdefgh; expires=Sun, 22 Dec 2324 22:58:11 GMT; Path=/; SameSite=Lax"
});
mock.onGet("/api/authentication_state/?username=a%20username").reply(200, { is_authenticated: false, websocket_token_valid:true });
const urlPattern = /^\/api\/util\/lst.*$/;
mock.onGet(urlPattern).reply(200, { "UTC": "2023-12-24T00:00:00Z", "LST": { "CS002": "06:36:32" } });
mock.onGet("/api/util/utc").reply(200, "2023-12-24T22:58:22.413683");
......@@ -86,11 +89,14 @@ describe('App Search', () => {
let component
await act(async () => { component = render(<App />); });
await waitFor(() => { expect(component.queryAllByTitle('Logout a username')).toHaveLength(2) }, { timeout: 5000 });
expect(giveConsistentNodes(component.container.outerHTML)).toMatchSnapshot("Valid Credentials");
await waitFor(() => { expect(component.queryAllByTitle('Logout a username')).toHaveLength(2); }, { timeout: 5000 });
let navigationMenuItem = component.container.querySelector(".p-button.p-component.nav-btn.nav-btn-tooltip");
......@@ -104,8 +110,9 @@ describe('App Search', () => {
let logoutButton = component.queryAllByTitle("Logout a username")[0];
console.log(logoutButton);
await act(async () => {fireEvent.click(logoutButton);});
await waitFor(() => { expect(component.queryAllByTitle('Logout a username')).toHaveLength(1); }, { timeout: 5000 });
await waitFor(() => { expect(component.queryAllByTitle('Logout a username')).toHaveLength(0); }, { timeout: 5000 });
expect(giveConsistentNodes(component.container.outerHTML)).toMatchSnapshot("After Logout");
......
......@@ -310,6 +310,7 @@ const FakeDynamicSchedular = {
export function MakeWeekViewPrimaryMock(mock) {
if(!mock) mock = new MockAdapter(axios);
mock.onGet("/api/util/sun_rise_and_set?stations=CS002&timestamps=2023-12-03").reply(200, FakeSun);
mock.onGet("api/util/sun_rise_and_set?stations=CS002&timestamps=2023-08-06").reply(200, FakeSun);
mock.onGet("/api/project_state/").reply(200, FakeProjectState);
mock.onGet("/api/subsystem/scheduler").reply(200, FakeSchedular);
mock.onGet("/api/project/?fields=name,project_state_value&limit=150").reply(200, FakeProject);
......@@ -322,6 +323,8 @@ export function MakeWeekViewPrimaryMock(mock) {
mock.onOptions("/api/scheduling_unit_blueprint_extended/").reply(200, BluePrintExtended);
mock.onGet("/api/util/lst?timestamp=2023-12-03T00:00:00&stations=CS002").reply(200, { data: { "UTC": "2023-12-03T00:00:00Z", "LST": { "CS002": "05:13:44" } } });
mock.onGet("/api/util/sun_rise_and_set?stations=CS002&timestamps=2023-12-03,2023-12-04,2023-12-05,2023-12-06,2023-12-07,2023-12-08,2023-12-09").reply(200, combinedFakeSun);
mock.onGet("/api/util/sun_rise_and_set?stations=CS002&timestamps=2023-08-06,2023-08-07,2023-08-08,2023-08-09,2023-08-10,2023-08-11,2023-08-12").reply(200, combinedFakeSun);
return mock;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment