diff --git a/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx b/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx index 49a6352e..1bbeba52 100644 --- a/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx +++ b/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx @@ -1,5 +1,9 @@ import { type TokenProviderTokenApplicationKind } from '#providers/TokenProvider' -import { decodeExtras, getExtrasFromUrl } from '#providers/TokenProvider/extras' +import { + decodeExtras, + getExtrasFromUrl, + isValidUser +} from '#providers/TokenProvider/extras' import { extractDomainFromApiBaseEndpoint } from '#providers/TokenProvider/url' import { PageError } from '#ui/composite/PageError' import { PageSkeleton } from '#ui/composite/PageSkeleton' @@ -243,6 +247,11 @@ export const TokenProvider: React.FC = ({ removeAuthParamsFromUrl() + const userFromExtras = + extrasFromProp?.user != null && isValidUser(extrasFromProp?.user) + ? extrasFromProp.user + : null + dispatch({ type: 'validToken', payload: { @@ -258,7 +267,7 @@ export const TokenProvider: React.FC = ({ scopes: tokenInfo.scopes, extras }, - user: tokenInfo.user, + user: tokenInfo.user ?? userFromExtras, organization, rolePermissions: tokenInfo.permissions ?? {}, accessibleApps: tokenInfo.accessibleApps ?? [] diff --git a/packages/app-elements/src/providers/TokenProvider/extras.test.ts b/packages/app-elements/src/providers/TokenProvider/extras.test.ts index a19b3ad2..7f2ebee8 100644 --- a/packages/app-elements/src/providers/TokenProvider/extras.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/extras.test.ts @@ -1,3 +1,4 @@ +import { isValidUser } from '#providers/TokenProvider/extras' import { decodeExtras, encodeExtras, getExtrasFromUrl } from './extras' import type { TokenProviderExtras } from './types' @@ -116,3 +117,44 @@ describe('Encode object > Add in URL query string > Decode it from URL', () => { expect(decodeExtras(getExtrasFromUrl())).toEqual(undefined) }) }) + +describe('isValidUser', () => { + test('should return true if user is valid', () => { + const user = { + id: '1', + email: 'john@doe.com', + displayName: 'J.Doe', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + timezone: 'UTC' + } + expect(isValidUser(user)).toBe(true) + }) + + test('should return false if user is null', () => { + expect(isValidUser(null)).toBe(false) + }) + + test('should return false if user is undefined', () => { + expect(isValidUser(undefined)).toBe(false) + }) + + test('should return false if user is empty', () => { + // @ts-expect-error mismatching type for testing invalid user + expect(isValidUser({})).toBe(false) + }) + + test('should return false if user is missing keys', () => { + const user = { + id: '1', + email: 'john@doe.com', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + timezone: 'UTC' + } + // @ts-expect-error mismatching type for testing invalid user + expect(isValidUser(user)).toBe(false) + }) +}) diff --git a/packages/app-elements/src/providers/TokenProvider/extras.ts b/packages/app-elements/src/providers/TokenProvider/extras.ts index 1c4179c1..510e86e6 100644 --- a/packages/app-elements/src/providers/TokenProvider/extras.ts +++ b/packages/app-elements/src/providers/TokenProvider/extras.ts @@ -1,5 +1,5 @@ import isEmpty from 'lodash/isEmpty' -import type { TokenProviderExtras } from './types' +import type { TokenProviderAuthUser, TokenProviderExtras } from './types' /** * Encodes the given extras object into a Base64 string. @@ -72,3 +72,37 @@ const base64URLSafe = { return Buffer.from(encodedData, 'base64url').toString('binary') } } + +/** + * Validates if the user object received from `extras` is a valid one and can be added to the TokenProvider context. + */ +export function isValidUser( + user?: TokenProviderAuthUser | null +): user is TokenProviderAuthUser { + const compareKeys = Object.keys({ + id: '', + email: '', + firstName: '', + lastName: '', + displayName: '', + fullName: '', + timezone: '' + } satisfies TokenProviderAuthUser).sort() + + if (user == null || isEmpty(user)) { + return false + } + + if (isEmpty(user.email) || isEmpty(user.id)) { + return false + } + + if (isEmpty(user.firstName) && isEmpty(user.lastName)) { + // at least one of the them should not be empty string + return false + } + + return Object.keys(user) + .sort() + .every((key, index) => key === compareKeys[index]) +} diff --git a/packages/app-elements/src/providers/TokenProvider/types.ts b/packages/app-elements/src/providers/TokenProvider/types.ts index 1a3ab3cb..56e2f305 100644 --- a/packages/app-elements/src/providers/TokenProvider/types.ts +++ b/packages/app-elements/src/providers/TokenProvider/types.ts @@ -173,4 +173,6 @@ export interface TokenProviderExtras { organizations?: number skus?: number } + /** Allow to pass the current user in the app context when using an access token that does not contain an owner. */ + user?: TokenProviderAuthUser }