diff --git a/packages/commerce-sdk-react/src/auth/index.test.ts b/packages/commerce-sdk-react/src/auth/index.test.ts index b16faf33a1..cdfce86201 100644 --- a/packages/commerce-sdk-react/src/auth/index.test.ts +++ b/packages/commerce-sdk-react/src/auth/index.test.ts @@ -190,11 +190,52 @@ describe('Auth', () => { expect(auth.ready()).resolves.toEqual(result) }) test('ready - use `fetchedToken` and short circuit network request', async () => { - const auth = new Auth({...config, fetchedToken: 'fake-token'}) + const fetchedToken = jwt.sign( + { + sub: `cc-slas::zzrf_001::scid:xxxxxx::usid:usid`, + isb: `uido:ecom::upn:test@gmail.com::uidn:firstname lastname::gcid:guestuserid::rcid:rcid::chid:siteId` + }, + 'secret' + ) + const auth = new Auth({...config, fetchedToken}) jest.spyOn(auth, 'queueRequest') - await auth.ready().then(() => { - expect(auth.queueRequest).not.toHaveBeenCalled() - }) + await auth.ready() + expect(auth.queueRequest).not.toHaveBeenCalled() + }) + test('ready - use `fetchedToken` and auth data is populated for registered user', async () => { + const usid = 'usidddddd' + const customerId = 'customerIddddddd' + const fetchedToken = jwt.sign( + { + sub: `cc-slas::zzrf_001::scid:xxxxxx::usid:${usid}`, + isb: `uido:ecom::upn:test@gmail.com::uidn:firstname lastname::gcid:guestuserid::rcid:${customerId}::chid:siteId` + }, + 'secret' + ) + const auth = new Auth({...config, fetchedToken}) + await auth.ready() + expect(auth.get('access_token')).toBe(fetchedToken) + expect(auth.get('customer_id')).toBe(customerId) + expect(auth.get('usid')).toBe(usid) + expect(auth.get('customer_type')).toBe('registered') + }) + test('ready - use `fetchedToken` and auth data is populated for guest user', async () => { + // isb: `uido:slas::upn:Guest::uidn:Guest User::gcid:bclrdGlbIZlHaRxHsZlWYYxHwZ::chid: ` + const usid = 'usidddddd' + const customerId = 'customerIddddddd' + const fetchedToken = jwt.sign( + { + sub: `cc-slas::zzrf_001::scid:xxxxxx::usid:${usid}`, + isb: `uido:ecom::upn:Guest::uidn:firstname lastname::gcid:${customerId}::rcid:registeredCid::chid:siteId` + }, + 'secret' + ) + const auth = new Auth({...config, fetchedToken}) + await auth.ready() + expect(auth.get('access_token')).toBe(fetchedToken) + expect(auth.get('customer_id')).toBe(customerId) + expect(auth.get('usid')).toBe(usid) + expect(auth.get('customer_type')).toBe('guest') }) test('ready - use refresh token when access token is expired', async () => { const auth = new Auth(config) @@ -220,34 +261,29 @@ describe('Auth', () => { auth.set(key, data[key]) }) - await auth.ready().then(() => { - expect(helpers.refreshAccessToken).toBeCalled() - }) + await auth.ready() + expect(helpers.refreshAccessToken).toBeCalled() }) test('ready - PKCE flow', async () => { const auth = new Auth(config) - await auth.ready().then(() => { - expect(helpers.loginGuestUser).toBeCalled() - }) + await auth.ready() + expect(helpers.loginGuestUser).toBeCalled() }) test('loginGuestUser', async () => { const auth = new Auth(config) - await auth.loginGuestUser().then(() => { - expect(helpers.loginGuestUser).toBeCalled() - }) + await auth.loginGuestUser() + expect(helpers.loginGuestUser).toBeCalled() }) test('loginRegisteredUserB2C', async () => { const auth = new Auth(config) - await auth.loginRegisteredUserB2C({username: 'test', password: 'test'}).then(() => { - expect(helpers.loginRegisteredUserB2C).toBeCalled() - }) + await auth.loginRegisteredUserB2C({username: 'test', password: 'test'}) + expect(helpers.loginRegisteredUserB2C).toBeCalled() }) test('logout', async () => { const auth = new Auth(config) - await auth.logout().then(() => { - expect(helpers.loginGuestUser).toBeCalled() - }) + await auth.logout() + expect(helpers.loginGuestUser).toBeCalled() }) test('running on the server uses a shared context memory store', async () => { const refreshTokenGuest = 'guest' diff --git a/packages/commerce-sdk-react/src/auth/index.ts b/packages/commerce-sdk-react/src/auth/index.ts index ad38c783a7..1cf1719751 100644 --- a/packages/commerce-sdk-react/src/auth/index.ts +++ b/packages/commerce-sdk-react/src/auth/index.ts @@ -11,7 +11,7 @@ import { ShopperLoginTypes, ShopperCustomersTypes } from 'commerce-sdk-isomorphic' -import jwtDecode from 'jwt-decode' +import jwtDecode, {JwtPayload} from 'jwt-decode' import {ApiClientConfigParams, Prettify, RemoveStringIndex} from '../hooks/types' import {BaseStorage, LocalStorage, CookieStorage, MemoryStorage, StorageType} from './storage' import {CustomerType} from '../hooks/useCustomerType' @@ -31,6 +31,11 @@ interface JWTHeaders { iat: number } +interface SlasJwtPayload extends JwtPayload { + sub: string + isb: string +} + /** * The extended field is not from api response, we manually store the auth type, * so we don't need to make another API call when we already have the data. @@ -288,7 +293,12 @@ class Auth { */ async ready() { if (this.fetchedToken && this.fetchedToken !== '') { - this.pendingToken = Promise.resolve({...this.data, access_token: this.fetchedToken}) + const {isGuest, customerId, usid} = this.parseSlasJWT(this.fetchedToken) + this.set('access_token', this.fetchedToken) + this.set('customer_id', customerId) + this.set('usid', usid) + this.set('customer_type', isGuest ? 'guest' : 'registered') + this.pendingToken = Promise.resolve(this.data) return this.pendingToken } if (this.pendingToken) { @@ -405,6 +415,30 @@ class Auth { this.clearStorage() return this.loginGuestUser() } + + /** + * Decode SLAS JWT and extract information such as customer id, usid, etc. + * + */ + parseSlasJWT(jwt: string) { + const payload = jwtDecode(jwt) as SlasJwtPayload + const {sub, isb} = payload + // ISB format + // 'uido:ecom::upn:Guest||xxxEmailxxx::uidn:FirstName LastName::gcid:xxxGuestCustomerIdxxx::rcid:xxxRegisteredCustomerIdxxx::chid:xxxSiteIdxxx', + const isbParts = isb.split('::') + const isGuest = isbParts[1] === 'upn:Guest' + const customerId = isGuest + ? isbParts[3].replace('gcid:', '') + : isbParts[4].replace('rcid:', '') + // SUB format + // cc-slas::zzrf_001::scid:c9c45bfd-0ed3-4aa2-xxxx-40f88962b836::usid:b4865233-de92-4039-xxxx-aa2dfc8c1ea5 + const usid = sub.split('::')[3].replace('usid:', '') + return { + isGuest, + customerId, + usid + } + } } export default Auth