-
Notifications
You must be signed in to change notification settings - Fork 3
refactor: remove @forgerock/javascript-sdk dependency from device-client and e2e app #816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,66 @@ | ||
| /* | ||
| * | ||
| * Copyright © 2025 Ping Identity Corporation. All right reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| * | ||
| */ | ||
|
|
||
| import { deviceClient } from '@forgerock/device-client'; | ||
| import type { ConfigOptions, DeviceClient } from '@forgerock/device-client/types'; | ||
| import { | ||
| CallbackType, | ||
| Config, | ||
| FRAuth, | ||
| FRLoginFailure, | ||
| FRLoginSuccess, | ||
| FRStep, | ||
| callbackType, | ||
| journey, | ||
| NameCallback, | ||
| PasswordCallback, | ||
| SessionManager, | ||
| TokenManager, | ||
| UserManager, | ||
| } from '@forgerock/javascript-sdk'; | ||
| StepType, | ||
| } from '@forgerock/journey-client'; | ||
| import type { | ||
| JourneyClient, | ||
| JourneyClientConfig, | ||
| JourneyResult, | ||
| } from '@forgerock/journey-client/types'; | ||
| import { oidc } from '@forgerock/oidc-client'; | ||
| import type { OidcClient, OidcConfig } from '@forgerock/oidc-client/types'; | ||
| import { Console, Effect } from 'effect'; | ||
|
|
||
| const logout = Effect.ignore( | ||
| Effect.tryPromise({ | ||
| try: () => SessionManager.logout(), | ||
| catch: (err) => new Error(`Logout failed: ${err}`), | ||
| }), | ||
| ); | ||
| let cachedOidcClient: OidcClient | null = null; | ||
|
|
||
| const start = Effect.tryPromise({ | ||
| try: () => FRAuth.start(), | ||
| catch: (err) => new Error(`Authentication start failed: ${err}`), | ||
| }).pipe(Effect.tap((step) => Console.log('Called start', step))); | ||
| const oidcClientOrThrow = (): OidcClient => { | ||
| if (!cachedOidcClient) { | ||
| throw new Error('OIDC client not initialized'); | ||
| } | ||
| return cachedOidcClient; | ||
| }; | ||
|
|
||
| const checkFRStep = (step: FRStep | FRLoginFailure | FRLoginSuccess) => | ||
| const checkForStep = (step: JourneyResult) => | ||
| Effect.try({ | ||
| try: () => { | ||
| if (step.type == 'LoginSuccess' || step.type == 'LoginFailure') { | ||
| throw new Error(`Unexpected step type: ${step.type}`); | ||
| } else { | ||
| if (step && 'type' in step && step.type === StepType.Step) { | ||
| return step; | ||
| } | ||
| throw new Error(`Unexpected step type: ${JSON.stringify(step)}`); | ||
| }, | ||
| catch: (err) => new Error(`Failed to start authentication: ${err}`), | ||
| }); | ||
|
|
||
| const callNext = (step: FRStep) => | ||
| const callNext = (client: JourneyClient, step: JourneyResult) => | ||
| Effect.tryPromise({ | ||
| try: () => FRAuth.next(step), | ||
| try: () => client.next(step as Parameters<JourneyClient['next']>[0]), | ||
|
Comment on lines
+48
to
+50
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another nitpick but I don't think we need this cast either if you type the |
||
| catch: (err) => new Error(`Failed to proceed to next step: ${err}`), | ||
| }).pipe(Effect.tap((step) => Console.log('Got next step', step))); | ||
|
|
||
| const getTokens = Effect.tryPromise({ | ||
| try: () => TokenManager.getTokens(), | ||
| catch: (err) => new Error(`Failed to get tokens: ${err}`), | ||
| }).pipe(Effect.tap((tokens) => Console.log('Got Tokens', tokens))); | ||
| }).pipe(Effect.tap((next) => Console.log('Got next step', next))); | ||
|
|
||
| const checkForLoginSuccess = (step: FRStep | FRLoginSuccess | FRLoginFailure) => { | ||
| if (step.type === 'LoginSuccess') { | ||
| return Effect.succeed(step); | ||
| } else if (step.type === 'LoginFailure') { | ||
| const checkForLoginSuccess = (result: JourneyResult) => { | ||
| if (result && 'type' in result && result.type === StepType.LoginSuccess) { | ||
| return Effect.succeed(result); | ||
| } else if (result && 'type' in result && result.type === StepType.LoginFailure) { | ||
| return Effect.fail(new Error(`Login failed`)); | ||
| } else { | ||
| return Effect.fail( | ||
| new Error(`Unexpected step, expected to be in a LoginSuccess but got ${step.type}`), | ||
| new Error( | ||
| `Unexpected step, expected to be in a LoginSuccess but got ${JSON.stringify(result)}`, | ||
| ), | ||
| ); | ||
| } | ||
| }; | ||
|
|
@@ -66,7 +69,6 @@ export const LoginAndGetClient = Effect.gen(function* () { | |
| const url = new URL(window.location.href); | ||
| const amUrl = url.searchParams.get('amUrl') || 'https://openam-sdks.forgeblocks.com/am'; | ||
| const realmPath = url.searchParams.get('realmPath') || 'alpha'; | ||
| const platformHeader = url.searchParams.get('platformHeader') === 'true' ? true : false; | ||
| const tree = url.searchParams.get('tree') || 'selfservice'; | ||
|
|
||
| /** | ||
|
|
@@ -77,53 +79,92 @@ export const LoginAndGetClient = Effect.gen(function* () { | |
| const un = url.searchParams.get('un') || 'devicetestuser'; | ||
| const pw = url.searchParams.get('pw') || 'password'; | ||
|
|
||
| const config: ConfigOptions = { | ||
| const deviceConfig: ConfigOptions = { | ||
| realmPath, | ||
| serverConfig: { | ||
| baseUrl: amUrl, | ||
| timeout: 3000, | ||
| }, | ||
| }; | ||
|
|
||
| yield* Effect.try(() => | ||
| Config.set({ | ||
| platformHeader, | ||
| realmPath, | ||
| tree, | ||
| clientId: 'WebOAuthClient', | ||
| scope: 'profile email me.read openid', | ||
| redirectUri: `${window.location.origin}/src/_callback/index.html`, | ||
| serverConfig: { | ||
| baseUrl: amUrl, | ||
| timeout: 3000, | ||
| }, | ||
| }), | ||
| ); | ||
| yield* logout; | ||
| const realmSegment = realmPath ? `/realms/root/realms/${realmPath}` : ''; | ||
| const wellknown = `${amUrl.replace(/\/$/, '')}/oauth2${realmSegment}/.well-known/openid-configuration`; | ||
| const redirectUri = `${window.location.origin}/src/_callback/index.html`; | ||
|
|
||
| const journeyConfig: JourneyClientConfig = { | ||
| serverConfig: { | ||
| wellknown, | ||
| }, | ||
| }; | ||
|
|
||
| const oidcConfig: OidcConfig = { | ||
| clientId: 'WebOAuthClient', | ||
| scope: 'profile email me.read openid', | ||
| redirectUri, | ||
| serverConfig: { | ||
| wellknown, | ||
| }, | ||
| }; | ||
|
|
||
| const journeyClient = yield* Effect.tryPromise({ | ||
| try: () => journey({ config: journeyConfig }), | ||
| catch: (err) => new Error(`Failed to initialize journey client: ${err}`), | ||
| }); | ||
|
|
||
| const oidcClient = yield* Effect.tryPromise({ | ||
| try: () => oidc({ config: oidcConfig }), | ||
| catch: (err) => new Error(`Failed to initialize OIDC client: ${err}`), | ||
| }); | ||
|
|
||
| if ('error' in oidcClient) { | ||
| return yield* Effect.fail(new Error(`Failed to initialize OIDC client: ${oidcClient.error}`)); | ||
| } | ||
|
|
||
| cachedOidcClient = oidcClient; | ||
|
|
||
| yield* Effect.tryPromise({ | ||
| try: () => oidcClientOrThrow().user.logout(), | ||
| catch: (err) => new Error(`Logout failed: ${err}`), | ||
| }); | ||
|
Comment on lines
+125
to
+128
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this? If we are starting the app from a fresh state then won't this always fail preventing you from proceeding? |
||
|
|
||
| yield* start.pipe( | ||
| Effect.flatMap((step) => checkFRStep(step)), | ||
| yield* Effect.tryPromise({ | ||
| try: () => journeyClient.start({ journey: tree }), | ||
| catch: (err) => new Error(`Authentication start failed: ${err}`), | ||
| }).pipe( | ||
| Effect.tap((step) => Console.log('Called start', step)), | ||
| Effect.flatMap((step) => checkForStep(step)), | ||
| Effect.map((step) => { | ||
| step.getCallbackOfType<NameCallback>(CallbackType.NameCallback).setName(un); | ||
| step.getCallbackOfType<PasswordCallback>(CallbackType.PasswordCallback).setPassword(pw); | ||
| step.getCallbackOfType<NameCallback>(callbackType.NameCallback).setName(un); | ||
| step.getCallbackOfType<PasswordCallback>(callbackType.PasswordCallback).setPassword(pw); | ||
|
|
||
| return step; | ||
| }), | ||
| Effect.flatMap((step) => callNext(step)), | ||
| Effect.flatMap((step) => callNext(journeyClient, step)), | ||
| /** | ||
| * Don't explicitly need this but if the journey changes | ||
| * maybe we dont get a LoginSuccess | ||
| */ | ||
| Effect.flatMap((step) => checkForLoginSuccess(step)), | ||
| Effect.flatMap(() => getTokens), | ||
| Effect.flatMap(() => | ||
| Effect.tryPromise({ | ||
| try: () => oidcClientOrThrow().token.get({ backgroundRenew: true }), | ||
| catch: (err) => new Error(`Failed to get tokens: ${err}`), | ||
| }).pipe(Effect.tap((tokens) => Console.log('Got Tokens', tokens))), | ||
| ), | ||
| ); | ||
|
|
||
| const client: DeviceClient = deviceClient(config); | ||
| const client: DeviceClient = deviceClient(deviceConfig); | ||
| return client; | ||
| }); | ||
|
|
||
| export const getUser = Effect.tryPromise({ | ||
| try: () => UserManager.getCurrentUser() as Promise<Record<string, string>>, | ||
| try: async () => { | ||
| const response = await oidcClientOrThrow().user.info(); | ||
| if (response && 'error' in response) { | ||
| throw new Error(`Failed to get user info: ${response.error}`); | ||
| } | ||
| return response as unknown as Record<string, string>; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick but I don't think we need this cast. We have a better typed user response in the new oidc client. The device app only uses |
||
| }, | ||
| catch: (err) => new Error(`Failed to get current user: ${err}`), | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| /* | ||
| * Copyright (c) 2025 Ping Identity Corporation. All rights reserved. | ||
| * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ | ||
| import { type ConfigOptions } from '@forgerock/javascript-sdk'; | ||
| import { type ConfigOptions } from './types/index.js'; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a circular dependency? Should we import it from |
||
| import { configureStore } from '@reduxjs/toolkit'; | ||
| import { deviceService } from './services/index.js'; | ||
| import type { OathDevice, RetrieveOathQuery } from './types/oath.types.js'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * Copyright (c) 2025 Ping Identity Corporation. All rights reserved. | ||
| * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
|
|
@@ -9,7 +9,9 @@ import { deviceClient } from '../device.store.js'; | |
| export type DeviceClient = ReturnType<typeof deviceClient>; | ||
|
|
||
| // Re-export types from external dependencies that consumers need | ||
| export type { ConfigOptions } from '@forgerock/javascript-sdk'; | ||
| import type { LegacyConfigOptions } from '@forgerock/sdk-types'; | ||
|
|
||
| export type ConfigOptions = LegacyConfigOptions; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this technically a breaking change we need to be concerned about? The |
||
|
|
||
| // Re-export local types | ||
| export * from './oath.types.js'; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copyright should be 2025 - 2026