diff --git a/src/main/__tests__/sessionDevices.spec.ts b/src/main/__tests__/sessionDevices.spec.ts new file mode 100644 index 0000000..f93abff --- /dev/null +++ b/src/main/__tests__/sessionDevices.spec.ts @@ -0,0 +1,68 @@ +import fetchMock from 'jest-fetch-mock' +import { defineWindowProperty, headers, mockWindowCrypto } from './helpers/testHelpers' +import { popNextRandomString } from './helpers/randomStringMock' +import { createDefaultTestClient } from './helpers/clientFactory' + +beforeAll(() => { + fetchMock.enableMocks() + defineWindowProperty('location') + defineWindowProperty('crypto', mockWindowCrypto) +}) + +beforeEach(() => { + document.body.innerHTML = '' + jest.resetAllMocks() + fetchMock.resetMocks() + popNextRandomString() +}) + +test('list session devices', async () => { + // Given + const { client, domain } = createDefaultTestClient() + const accessToken = '456' + + const listSessionDevicesCall = fetchMock.mockResponseOnce( + JSON.stringify({ + sessionDevices: [ + { + id: "UUID", + ip: "192.168.65.1", + operatingSystem: "Android", + userAgentName: "Chrome", + deviceClass: "Phone", + deviceName: "Google Nexus 6", + firstConnection: "date1", + lastConnection: "date2" + } + ] + }) + ) + + const result = await client.listSessionDevices(accessToken) + + expect(listSessionDevicesCall).toHaveBeenCalledWith( + `https://${domain}/identity/v1/session-devices`, + { + method: 'GET', + headers: expect.objectContaining({ + ...headers.accessToken(accessToken) + }) + } + ) + + expect(result).toEqual({ + sessionDevices: [ + { + id: "UUID", + ip: "192.168.65.1", + operatingSystem: "Android", + userAgentName: "Chrome", + deviceClass: "Phone", + deviceName: "Google Nexus 6", + firstConnection: "date1", + lastConnection: "date2" + } + ] + }) + +}) diff --git a/src/main/main.ts b/src/main/main.ts index 949b9d3..fd03469 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -6,7 +6,8 @@ import { PasswordlessResponse, MFA, OrchestrationToken, - PasswordStrength + PasswordStrength, + SessionDeviceListResponse } from './models' import OAuthClient, { LoginWithPasswordParams, @@ -96,6 +97,7 @@ export type Client = { getSignupData: (signupToken: string) => Promise getUser: (params: GetUserParams) => Promise listMfaCredentials: (accessToken: string) => Promise + listSessionDevices: (accessToken: string) => Promise listTrustedDevices: (accessToken: string) => Promise listWebAuthnDevices: (accessToken: string) => Promise loginFromSession: (options?: AuthOptions) => Promise @@ -264,6 +266,10 @@ export function createClient(creationConfig: Config): Client { return apiClients.then((clients) => clients.mfa.listMfaCredentials(accessToken)) } + function listSessionDevices(accessToken: string) { + return apiClients.then((clients) => clients.profile.listSessionDevices(accessToken)) + } + function listWebAuthnDevices(accessToken: string) { return apiClients.then((clients) => clients.webAuthn.listWebAuthnDevices(accessToken)) } @@ -450,6 +456,7 @@ export function createClient(creationConfig: Config): Client { getSignupData, getUser, listMfaCredentials, + listSessionDevices, listTrustedDevices, listWebAuthnDevices, loginFromSession, diff --git a/src/main/models.ts b/src/main/models.ts index 34195b5..a1602c9 100644 --- a/src/main/models.ts +++ b/src/main/models.ts @@ -396,6 +396,20 @@ export type Consent = { status: ConsentStatus } +export type SessionDevice = { + id: string, + ip?: string, + operatingSystem?: string, + userAgentName?: string, + deviceClass?: string, + deviceName?: string, + firstConnection: string, + lastConnection: string +} + +export type SessionDeviceListResponse = { + sessionDevices: SessionDevice[] +} /** * This type represents the settings of a ReachFive account's stored in the backend. */ diff --git a/src/main/profileClient.ts b/src/main/profileClient.ts index 26c7a4a..e047e01 100644 --- a/src/main/profileClient.ts +++ b/src/main/profileClient.ts @@ -1,7 +1,7 @@ import type { CaptchaParams } from './captcha' import type { HttpClient } from './httpClient' import type { IdentityEventManager } from './identityEventManager' -import type { ApiClientConfig } from './main' +import { ApiClientConfig, SessionDeviceListResponse } from './main' import type { OpenIdUser, Profile } from './models' export type UpdateEmailParams = { @@ -112,6 +112,7 @@ export default class ProfileClient { private sendEmailVerificationUrl: string private sendPhoneNumberVerificationUrl: string + private sessionDevicesUrl: string private signupDataUrl: string private unlinkUrl: string private updateEmailUrl: string @@ -129,6 +130,7 @@ export default class ProfileClient { this.sendEmailVerificationUrl = '/send-email-verification' this.sendPhoneNumberVerificationUrl = '/send-phone-number-verification' + this.sessionDevicesUrl = '/session-devices' this.signupDataUrl = '/signup/data' this.unlinkUrl = '/unlink' this.updateEmailUrl = '/update-email' @@ -140,6 +142,10 @@ export default class ProfileClient { this.verifyEmailUrl = '/verify-email' } + listSessionDevices(accessToken: string): Promise { + return this.http.get(this.sessionDevicesUrl, { accessToken }) + } + getSignupData(signupToken: string): Promise { return this.http.get(this.signupDataUrl, { query: {