Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-cows-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@forgerock/journey-client': minor
---

Implement well-known endpoint support for the journey-client package. Allow developers to target the wellknown endpoint to gather configuration data from their tenant to use for future requests.
14 changes: 7 additions & 7 deletions packages/davinci-client/src/lib/client.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { createClientStore, handleUpdateValidateError, RootState } from './clien
import { nodeSlice } from './node.slice.js';
import { davinciApi } from './davinci.api.js';
import { configSlice } from './config.slice.js';
import { wellknownApi } from './wellknown.api.js';
import { wellknownApi, createWellknownError } from './wellknown.api.js';

import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
/**
Expand Down Expand Up @@ -88,14 +88,14 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
throw error;
}

const { data: openIdResponse } = await store.dispatch(
wellknownApi.endpoints.wellknown.initiate(config.serverConfig.wellknown),
const { data: openIdResponse, error: fetchError } = await store.dispatch(
wellknownApi.endpoints.configuration.initiate(config.serverConfig.wellknown),
);

if (!openIdResponse) {
const error = new Error('error fetching `wellknown` response for OpenId Configuration');
log.error(error.message);
throw error;
if (fetchError || !openIdResponse) {
const genericError = createWellknownError(fetchError);
log.error(`${genericError.error}: ${genericError.message}`);
throw new Error(genericError.message);
}

store.dispatch(configSlice.actions.set({ ...config, wellknownResponse: openIdResponse }));
Expand Down
204 changes: 61 additions & 143 deletions packages/davinci-client/src/lib/config.types.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
*/
import { describe, expectTypeOf, it } from 'vitest';
import type { DaVinciConfig, InternalDaVinciConfig } from './config.types.js';
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
import type { WellknownResponse } from './wellknown.types.js';
import type { AsyncLegacyConfigOptions, WellKnownResponse } from '@forgerock/sdk-types';

describe('Config Types', () => {
describe('DaVinciConfig', () => {
Expand Down Expand Up @@ -51,30 +50,9 @@ describe('Config Types', () => {
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
userinfo_endpoint: 'https://example.com/userinfo',
jwks_uri: 'https://example.com/jwks',
revocation_endpoint: 'https://example.com/register',
end_session_endpoint: 'https://example.com/logout',
pushed_authorization_request_endpoint: '',
check_session_iframe: '',
introspection_endpoint: '',
device_authorization_endpoint: '',
claims_parameter_supported: '',
request_parameter_supported: '',
request_uri_parameter_supported: '',
require_pushed_authorization_requests: '',
scopes_supported: [],
response_types_supported: [],
response_modes_supported: [],
grant_types_supported: [],
subject_types_supported: [],
id_token_signing_alg_values_supported: [],
userinfo_signing_alg_values_supported: [],
request_object_signing_alg_values_supported: [],
token_endpoint_auth_methods_supported: [],
token_endpoint_auth_signing_alg_values_supported: [],
claim_types_supported: [],
claims_supported: [],
code_challenge_methods_supported: [],
introspection_endpoint: 'https://example.com/introspect',
revocation_endpoint: 'https://example.com/revoke',
},
responseType: 'code',
serverConfig: {},
Expand All @@ -100,174 +78,114 @@ describe('Config Types', () => {
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
userinfo_endpoint: 'https://example.com/userinfo',
jwks_uri: 'https://example.com/jwks',
revocation_endpoint: 'https://example.com/revoke',
end_session_endpoint: 'https://example.com/logout',
pushed_authorization_request_endpoint: '',
check_session_iframe: '',
introspection_endpoint: '',
device_authorization_endpoint: '',
claims_parameter_supported: '',
request_parameter_supported: '',
request_uri_parameter_supported: '',
require_pushed_authorization_requests: '',
scopes_supported: [],
response_types_supported: [],
response_modes_supported: [],
grant_types_supported: [],
subject_types_supported: [],
id_token_signing_alg_values_supported: [],
userinfo_signing_alg_values_supported: [],
request_object_signing_alg_values_supported: [],
token_endpoint_auth_methods_supported: [],
token_endpoint_auth_signing_alg_values_supported: [],
claim_types_supported: [],
claims_supported: [],
code_challenge_methods_supported: [],
introspection_endpoint: 'https://example.com/introspect',
revocation_endpoint: 'https://example.com/revoke',
// Optional properties
jwks_uri: 'https://example.com/jwks',
scopes_supported: ['openid', 'profile'],
},
};
expectTypeOf(config).toMatchTypeOf<InternalDaVinciConfig>();
});
});
});

describe('WellknownResponse', () => {
it('should have all required OIDC properties', () => {
const wellknown: WellknownResponse = {
/**
* WellKnownResponse type tests.
*
* Note: WellKnownResponse is now imported from @forgerock/sdk-types.
* The type correctly follows the OIDC Discovery spec where only
* issuer, authorization_endpoint, token_endpoint, and userinfo_endpoint
* are required. Other properties are optional.
*/
describe('WellKnownResponse', () => {
it('should have required OIDC properties', () => {
// Minimal wellknown response with only required properties
const wellknown: WellKnownResponse = {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
userinfo_endpoint: 'https://example.com/userinfo',
jwks_uri: 'https://example.com/jwks',
revocation_endpoint: 'https://example.com/revoke',
end_session_endpoint: 'https://example.com/logout',
pushed_authorization_request_endpoint: '',
check_session_iframe: '',
introspection_endpoint: '',
device_authorization_endpoint: '',
claims_parameter_supported: '',
request_parameter_supported: '',
request_uri_parameter_supported: '',
require_pushed_authorization_requests: '',
scopes_supported: [],
response_types_supported: [],
response_modes_supported: [],
grant_types_supported: [],
subject_types_supported: [],
id_token_signing_alg_values_supported: [],
userinfo_signing_alg_values_supported: [],
request_object_signing_alg_values_supported: [],
token_endpoint_auth_methods_supported: [],
token_endpoint_auth_signing_alg_values_supported: [],
claim_types_supported: [],
claims_supported: [],
code_challenge_methods_supported: [],
introspection_endpoint: 'https://example.com/introspect',
revocation_endpoint: 'https://example.com/revoke',
};

expectTypeOf<WellknownResponse>().toHaveProperty('issuer').toBeString();
expectTypeOf<WellknownResponse>().toHaveProperty('authorization_endpoint').toBeString();
expectTypeOf<WellknownResponse>().toHaveProperty('token_endpoint').toBeString();
expectTypeOf<WellknownResponse>().toHaveProperty('userinfo_endpoint').toBeString();
expectTypeOf<WellknownResponse>().toHaveProperty('jwks_uri').toBeString();
expectTypeOf<WellknownResponse>().toHaveProperty('revocation_endpoint').toBeString();
expectTypeOf<WellknownResponse>().toHaveProperty('end_session_endpoint').toBeString();
// Required properties should be strings
expectTypeOf<WellKnownResponse>().toHaveProperty('issuer').toBeString();
expectTypeOf<WellKnownResponse>().toHaveProperty('authorization_endpoint').toBeString();
expectTypeOf<WellKnownResponse>().toHaveProperty('token_endpoint').toBeString();
expectTypeOf<WellKnownResponse>().toHaveProperty('userinfo_endpoint').toBeString();
expectTypeOf<WellKnownResponse>().toHaveProperty('end_session_endpoint').toBeString();
expectTypeOf<WellKnownResponse>().toHaveProperty('introspection_endpoint').toBeString();
expectTypeOf<WellKnownResponse>().toHaveProperty('revocation_endpoint').toBeString();

expectTypeOf(wellknown).toMatchTypeOf<WellknownResponse>();
expectTypeOf(wellknown).toMatchTypeOf<WellKnownResponse>();
});

it('should allow optional OIDC properties', () => {
const wellknownWithOptionals: WellknownResponse = {
const wellknownWithOptionals: WellKnownResponse = {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
userinfo_endpoint: 'https://example.com/userinfo',
jwks_uri: 'https://example.com/jwks',
revocation_endpoint: 'https://example.com/revoke',
end_session_endpoint: 'https://example.com/logout',
introspection_endpoint: 'https://example.com/introspect',
revocation_endpoint: 'https://example.com/revoke',
// Optional properties
jwks_uri: 'https://example.com/jwks',
scopes_supported: ['openid', 'profile', 'email'],
response_types_supported: ['code', 'token'],
grant_types_supported: ['authorization_code', 'refresh_token'],
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
token_endpoint_auth_methods_supported: ['client_secret_basic'],
pushed_authorization_request_endpoint: '',
check_session_iframe: '',
introspection_endpoint: '',
device_authorization_endpoint: '',
claims_parameter_supported: '',
request_parameter_supported: '',
request_uri_parameter_supported: '',
require_pushed_authorization_requests: '',
response_modes_supported: [],
userinfo_signing_alg_values_supported: [],
request_object_signing_alg_values_supported: [],
token_endpoint_auth_signing_alg_values_supported: [],
claim_types_supported: [],
claims_supported: [],
code_challenge_methods_supported: [],
};

// Test optional properties are allowed but not required
expectTypeOf<WellknownResponse>().toHaveProperty('scopes_supported');
expectTypeOf<WellknownResponse>().toHaveProperty('response_types_supported');
expectTypeOf<WellknownResponse>().toHaveProperty('grant_types_supported');
expectTypeOf<WellKnownResponse>().toHaveProperty('scopes_supported');
expectTypeOf<WellKnownResponse>().toHaveProperty('response_types_supported');
expectTypeOf<WellKnownResponse>().toHaveProperty('grant_types_supported');
expectTypeOf<WellKnownResponse>().toHaveProperty('jwks_uri');

expectTypeOf(wellknownWithOptionals).toMatchTypeOf<WellknownResponse>();
expectTypeOf(wellknownWithOptionals).toMatchTypeOf<WellKnownResponse>();
});

it('should validate property types', () => {
// Test that array properties must contain strings
expectTypeOf<WellknownResponse['scopes_supported']>().toEqualTypeOf<string[]>();
expectTypeOf<WellknownResponse['response_types_supported']>().toEqualTypeOf<string[]>();
expectTypeOf<WellknownResponse['grant_types_supported']>().toEqualTypeOf<string[]>();
expectTypeOf<WellknownResponse['subject_types_supported']>().toEqualTypeOf<string[]>();
expectTypeOf<WellknownResponse['id_token_signing_alg_values_supported']>().toEqualTypeOf<
string[]
it('should validate optional array property types', () => {
// Test that optional array properties are string[] | undefined
expectTypeOf<WellKnownResponse['scopes_supported']>().toEqualTypeOf<string[] | undefined>();
expectTypeOf<WellKnownResponse['response_types_supported']>().toEqualTypeOf<
string[] | undefined
>();
expectTypeOf<WellknownResponse['token_endpoint_auth_methods_supported']>().toEqualTypeOf<
string[]
expectTypeOf<WellKnownResponse['grant_types_supported']>().toEqualTypeOf<
string[] | undefined
>();
expectTypeOf<WellKnownResponse['subject_types_supported']>().toEqualTypeOf<
string[] | undefined
>();
expectTypeOf<WellKnownResponse['id_token_signing_alg_values_supported']>().toEqualTypeOf<
string[] | undefined
>();
expectTypeOf<WellKnownResponse['token_endpoint_auth_methods_supported']>().toEqualTypeOf<
string[] | undefined
>();
});

it('should enforce URL format for endpoint properties', () => {
const wellknown: WellknownResponse = {
it('should enforce URL format for required endpoint properties', () => {
const wellknown: WellKnownResponse = {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
userinfo_endpoint: 'https://example.com/userinfo',
jwks_uri: 'https://example.com/jwks',
revocation_endpoint: 'https://example.com/register',
end_session_endpoint: 'https://example.com/logout',
pushed_authorization_request_endpoint: '',
check_session_iframe: '',
introspection_endpoint: '',
device_authorization_endpoint: '',
claims_parameter_supported: '',
request_parameter_supported: '',
request_uri_parameter_supported: '',
require_pushed_authorization_requests: '',
scopes_supported: [],
response_types_supported: [],
response_modes_supported: [],
grant_types_supported: [],
subject_types_supported: [],
id_token_signing_alg_values_supported: [],
userinfo_signing_alg_values_supported: [],
request_object_signing_alg_values_supported: [],
token_endpoint_auth_methods_supported: [],
token_endpoint_auth_signing_alg_values_supported: [],
claim_types_supported: [],
claims_supported: [],
code_challenge_methods_supported: [],
introspection_endpoint: 'https://example.com/introspect',
revocation_endpoint: 'https://example.com/revoke',
};

// Type assertion to ensure all endpoint properties are strings (URLs)
// Type assertion to ensure required endpoint properties are strings (URLs)
expectTypeOf(wellknown.authorization_endpoint).toBeString();
expectTypeOf(wellknown.token_endpoint).toBeString();
expectTypeOf(wellknown.userinfo_endpoint).toBeString();
expectTypeOf(wellknown.jwks_uri).toBeString();
expectTypeOf(wellknown.revocation_endpoint).toBeString();
expectTypeOf(wellknown.end_session_endpoint).toBeString();
});
});
9 changes: 3 additions & 6 deletions packages/davinci-client/src/lib/config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
/**
* Import LegacyConfigOptions type from the JavaScript SDK
*/
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
import { WellknownResponse } from './wellknown.types.js';

import type { AsyncLegacyConfigOptions, WellKnownResponse } from '@forgerock/sdk-types';

export interface DaVinciConfig extends AsyncLegacyConfigOptions {
responseType?: string;
}

export interface InternalDaVinciConfig extends DaVinciConfig {
wellknownResponse: WellknownResponse;
wellknownResponse: WellKnownResponse;
}
23 changes: 7 additions & 16 deletions packages/davinci-client/src/lib/wellknown.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,11 @@
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
import { WellknownResponse } from './wellknown.types.js';

export const wellknownApi = createApi({
reducerPath: 'wellknown',
baseQuery: fetchBaseQuery({
prepareHeaders: (headers) => {
headers.set('Accept', 'application/json');
return headers;
},
}),
endpoints: (builder) => ({
wellknown: builder.query<WellknownResponse, string>({
query: (endpoint: string) => ({ url: endpoint }),
}),
}),
});
/**
* Re-export the shared wellknown RTK Query API from @forgerock/sdk-oidc.
*
* The wellknown API provides OIDC endpoint discovery functionality via
* the `.well-known/openid-configuration` endpoint.
*/
export { wellknownApi, createWellknownSelector, createWellknownError } from '@forgerock/sdk-oidc';
Loading
Loading