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

STCOR-846 update session data with values from _self request #1466

Merged
merged 4 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Idle-session timeout and "Keep working?" modal. Refs STCOR-776.
* Implement password validation for Login Page. Refs STCOR-741.
* Avoid deprecated `defaultProps` for functional components. Refs STCOR-844..
* Update session data with values from `_self` request on reload. Refs STCOR-846.

## [10.1.0](https://github.com/folio-org/stripes-core/tree/v10.1.0) (2024-03-12)
[Full Changelog](https://github.com/folio-org/stripes-core/compare/v10.0.0...v10.1.0)
Expand Down
60 changes: 30 additions & 30 deletions src/loginServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,16 @@ function loadResources(okapiUrl, store, tenant, userId) {

/**
* spreadUserWithPerms
* return an object { user, perms } based on response from bl-users/self.
* Restructure the response from `bl-users/self?expandPermissions=true`
* to return an object shaped like
* {
* user: { id, username, ...personal }
* perms: { foo: true, bar: true, ... }
* }
*
* @param {object} userWithPerms
*
* @returns {object}
* @returns {object} { user, perms }
*/
export function spreadUserWithPerms(userWithPerms) {
const user = {
Expand All @@ -403,23 +408,16 @@ export function spreadUserWithPerms(userWithPerms) {
...userWithPerms?.user?.personal,
};

// remap data's array of permission-names to set with
// permission-names for keys and `true` for values.
//
// userWithPerms is shaped differently depending on whether
// it comes from a login call or a `.../_self` call, which
// is just totally totally awesome. :|
// we'll parse it differently depending on what it looks like.
let perms = {};
// remap userWithPerms.permissions.permissions from an array shaped like
// [{ "permissionName": "foo", ... }]
// to an object shaped like
// { foo: true, ...}
const perms = {};
const list = userWithPerms?.permissions?.permissions;
if (list && Array.isArray(list) && list.length > 0) {
// _self sends data like ["foo", "bar", "bat"]
// login sends data like [{ "permissionName": "foo" }]
if (typeof list[0] === 'string') {
perms = Object.assign({}, ...list.map(p => ({ [p]: true })));
} else {
perms = Object.assign({}, ...list.map(p => ({ [p.permissionName]: true })));
}
list.forEach(p => {
perms[p.permissionName] = true;
});
}

return { user, perms };
Expand Down Expand Up @@ -694,8 +692,8 @@ export function processOkapiSession(okapiUrl, store, tenant, resp, ssoToken) {
* @returns {Promise}
*/
export function validateUser(okapiUrl, store, tenant, session) {
const { token, user, perms, tenant: sessionTenant = tenant } = session;
return fetch(`${okapiUrl}/bl-users/_self`, {
const { token, tenant: sessionTenant = tenant } = session;
return fetch(`${okapiUrl}/bl-users/_self?expandPermissions=true`, {
headers: getHeaders(sessionTenant, token),
credentials: 'include',
mode: 'cors',
Expand All @@ -706,23 +704,25 @@ export function validateUser(okapiUrl, store, tenant, session) {
store.dispatch(setAuthError(null));
store.dispatch(setLoginData(data));

// If the request succeeded, we know the AT must be valid, but the
// response body from this endpoint doesn't include token-expiration
// data. So ... we set a near-future RT and an already-expired AT.
// On the next request, the expired AT will prompt an RTR cycle and
// we'll get real expiration values then.
const tokenExpiration = {
atExpires: -1,
rtExpires: Date.now() + (10 * 60 * 1000),
};

const { user, perms } = spreadUserWithPerms(data);
store.dispatch(setCurrentPerms(perms));

// update the session data with values from the response from _self
// in case they have changed since the previous login. this allows
// permissions changes to take effect immediately, without needing to
// re-authenticate.
//
// tenant and tokenExpiration data are still pulled from the session,
// tenant because the user may have switched the session-tenant to
// something other than their default and tokenExpiration because that
// data isn't provided by _self.
store.dispatch(setSessionData({
isAuthenticated: true,
user: data.user,
perms,
tenant: sessionTenant,
token,
tokenExpiration,
tokenExpiration: session.tokenExpiration
}));

return loadResources(okapiUrl, store, sessionTenant, user.id);
Expand Down
46 changes: 45 additions & 1 deletion src/loginServices.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
setIsAuthenticated,
setOkapiReady,
setServerDown,
// setSessionData,
setSessionData,
// setTokenExpiration,
setLoginData,
updateCurrentUser,
Expand Down Expand Up @@ -311,6 +311,50 @@ describe('validateUser', () => {
mockFetchCleanUp();
});

it('overwrites session data with new values from _self', async () => {
const store = {
dispatch: jest.fn(),
};

const tenant = 'tenant';
const sessionTenant = 'sessionTenant';
const data = {
user: {
id: 'ego',
username: 'superego',
},
permissions: {
permissions: [{ permissionName: 'ask' }, { permissionName: 'tell' }],
}
};

const session = {
user: { id: 'id', username: 'username' },
perms: { foo: true },
tenant: sessionTenant,
token: 'token',
};

mockFetchSuccess(data);

await validateUser('url', store, tenant, session);

const updatedSession = {
user: data.user,
isAuthenticated: true,
perms: { ask: true, tell: true },
tenant: session.tenant,
token: session.token,
};

expect(store.dispatch).toHaveBeenNthCalledWith(1, setAuthError(null));
expect(store.dispatch).toHaveBeenNthCalledWith(2, setLoginData(data));
expect(store.dispatch).toHaveBeenNthCalledWith(3, setCurrentPerms({ ask: true, tell: true }));
expect(store.dispatch).toHaveBeenNthCalledWith(4, setSessionData(updatedSession));

mockFetchCleanUp();
});

it('handles invalid user', async () => {
const store = {
dispatch: jest.fn(),
Expand Down
Loading