Skip to content

Commit

Permalink
STCOR-776 RTR adjustments for keycloak (#1490)
Browse files Browse the repository at this point in the history
There are many small differences in how keycloak and okapi respond to
authentication related requests.
* permissions are structured differently in Okapi between `login` and
  `_self` requests and depending on whether `expandPermissions=true` is
  present on the request; keycloak always responds with a flattened
  list.
* token expiration data is nested in the login-response in Okapi but is
  a root-level element in the `/authn/token` response from keycloak.

STCOR-776, STCOR-846
  • Loading branch information
zburke committed Jun 11, 2024
1 parent cc773c6 commit 2e162f6
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 12 deletions.
13 changes: 9 additions & 4 deletions src/components/Root/FFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,20 @@ export class FFetch {

// When starting a new session, the response from /bl-users/login-with-expiry
// will contain AT expiration info, but when restarting an existing session,
// the response from /bl-users/_self will NOT, although that information will
// the response from /bl-users/_self will NOT, although that information should
// have been cached in local-storage.
//
// This means there are many places we have to check to figure out when the
// AT is likely to expire, and thus when we want to rotate. First inspect
// the response, otherwise the session. Default to 10 seconds.
// the response, then the session, then default to 10 seconds.
if (res?.accessTokenExpiration) {
this.logger.log('rtr', 'rotation scheduled with login response data');
const rotationPromise = Promise.resolve((new Date(res.accessTokenExpiration).getTime() - Date.now()) * RTR_AT_TTL_FRACTION);

scheduleRotation(rotationPromise);
} else {
const rotationPromise = getTokenExpiry().then((expiry) => {
if (expiry.atExpires) {
if (expiry?.atExpires) {
this.logger.log('rtr', 'rotation scheduled with cached session data');
return (new Date(expiry.atExpires).getTime() - Date.now()) * RTR_AT_TTL_FRACTION;
}
Expand Down Expand Up @@ -189,7 +189,12 @@ export class FFetch {
if (clone.ok) {
this.logger.log('rtr', 'authn success!');
clone.json().then(json => {
this.rotateCallback(json.tokenExpiration);
// we want accessTokenExpiration. do we need to destructure?
// in community-folio, a /login-with-expiry response is shaped like
// { ..., tokenExpiration: { accessTokenExpiration, refreshTokenExpiration } }
// in eureka-folio, a /authn/token response is shaped like
// { accessTokenExpiration, refreshTokenExpiration }
this.rotateCallback(json.tokenExpiration ?? json);
});
}

Expand Down
31 changes: 23 additions & 8 deletions src/loginServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,31 @@ export function spreadUserWithPerms(userWithPerms) {
...userWithPerms?.user?.personal,
};

// remap userWithPerms.permissions.permissions from an array shaped like
// [{ "permissionName": "foo", ... }]
// to an object shaped like
// { foo: true, ...}
const perms = {};
// remap data's array of permission-names to set with
// permission-names for keys and `true` for values.
//
// userWithPerms is shaped differently depending on the API call
// that generated it.
// in community-folio, /login sends data like [{ "permissionName": "foo" }]
// and includes both directly and indirectly assigned permissions
// in community-folio, /_self sends data like ["foo", "bar", "bat"]
// but only includes directly assigned permissions
// in community-folio, /_self?expandPermissions=true sends data like [{ "permissionName": "foo" }]
// and includes both directly and indirectly assigned permissions
// in eureka-folio, /_self sends data like ["foo", "bar", "bat"]
// and includes both directly and indirectly assigned permissions
//
// we'll parse it differently depending on what it looks like.
let perms = {};
const list = userWithPerms?.permissions?.permissions;
if (list && Array.isArray(list) && list.length > 0) {
list.forEach(p => {
perms[p.permissionName] = true;
});
// shaped like this ["foo", "bar", "bat"] or
// shaped like that [{ "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 })));
}
}

return { user, perms };
Expand Down

0 comments on commit 2e162f6

Please sign in to comment.