Skip to content

Commit

Permalink
STCOR-776 RTR adjustments for keycloak
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 11b3618
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 11b3618

Please sign in to comment.