Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .changeset/public-cougars-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@forgerock/oidc-client': minor
'@forgerock/storage': patch
'@forgerock/davinci-client': patch
'@forgerock/sdk-types': patch
---

Implement OIDC logout and user info request; includes type updates and global error type
4 changes: 3 additions & 1 deletion e2e/oidc-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
<link rel="stylesheet" href="/src/styles.css" />

<style>
#logout {
#logout,
#userinfo {
display: none;
}
</style>
Expand All @@ -19,6 +20,7 @@
<h1>Welcome</h1>
<button id="login">Login</button>
<button id="logout">Logout</button>
<button id="userinfo">User Info</button>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
12 changes: 12 additions & 0 deletions e2e/oidc-app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,22 @@ async function app() {
} else {
console.log('Token Exchange Response:', tokenResponse);
document.getElementById('logout')!.style.display = 'block';
document.getElementById('userinfo')!.style.display = 'block';
document.getElementById('login')!.style.display = 'none';
}
}
});

document.getElementById('userinfo')?.addEventListener('click', async () => {
const userInfo = await oidcClient.user.info();

if ('error' in userInfo) {
console.error('User Info Error:', userInfo);
} else {
console.log('User Info:', userInfo);
}
});

document.getElementById('logout')?.addEventListener('click', async () => {
const response = await oidcClient.user.logout();

Expand All @@ -67,6 +78,7 @@ async function app() {
} else {
console.log('Logout successful');
document.getElementById('logout')!.style.display = 'none';
document.getElementById('userinfo')!.style.display = 'none';
document.getElementById('login')!.style.display = 'block';
}
});
Expand Down
12 changes: 8 additions & 4 deletions packages/davinci-client/src/lib/client.types.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
*/
/* eslint-disable @typescript-eslint/no-unused-vars */
import { describe, expectTypeOf, it } from 'vitest';

import type { GenericError } from '@forgerock/sdk-types';

import type { InitFlow, InternalErrorResponse, Updater } from './client.types.js';
import type { GenericError } from './error.types.js';
import type { ErrorNode, FailureNode, ContinueNode, StartNode, SuccessNode } from './node.types.js';
import { PhoneNumberInputValue } from './collector.types.js';
import type { PhoneNumberInputValue } from './collector.types.js';

describe('Client Types', () => {
it('should allow function returning error', async () => {
Expand Down Expand Up @@ -189,8 +191,10 @@ describe('Updater', () => {

const withoutError: Updater = () => null;

expectTypeOf(withError).returns.toMatchTypeOf<{ error: GenericError } | null>();
expectTypeOf(withoutError).returns.toMatchTypeOf<{ error: GenericError } | null>();
expectTypeOf(withError).returns.toMatchTypeOf<{ error: Omit<GenericError, 'error'> } | null>();
expectTypeOf(withoutError).returns.toMatchTypeOf<{
error: Omit<GenericError, 'error'>;
} | null>();

// Test both are valid Updater types
expectTypeOf(withError).toMatchTypeOf<Updater>();
Expand Down
9 changes: 5 additions & 4 deletions packages/davinci-client/src/lib/client.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { PhoneNumberInputValue } from './collector.types.js';
import { GenericError } from './error.types.js';
import { ErrorNode, FailureNode, ContinueNode, StartNode, SuccessNode } from './node.types.js';
import type { GenericError } from '@forgerock/sdk-types';

import type { PhoneNumberInputValue } from './collector.types.js';
import type { ErrorNode, FailureNode, ContinueNode, StartNode, SuccessNode } from './node.types.js';

export type FlowNode = ContinueNode | ErrorNode | StartNode | SuccessNode | FailureNode;

export interface InternalErrorResponse {
error: GenericError;
error: Omit<GenericError, 'error'> & { message: string };
type: 'internal_error';
}

Expand Down
8 changes: 4 additions & 4 deletions packages/davinci-client/src/lib/error.types.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
*/
/* eslint-disable @typescript-eslint/no-unused-vars */
import { describe, expect, it } from 'vitest';
import type { GenericError } from './error.types.js';

import type { GenericError } from '@forgerock/sdk-types';

describe('GenericError type', () => {
it('should allow valid error objects', () => {
const validErrors: GenericError[] = [
const validErrors: Omit<GenericError, 'error'>[] = [
{
message: 'Something went wrong',
type: 'unknown_error',
Expand Down Expand Up @@ -48,8 +49,7 @@ describe('GenericError type', () => {

// This test is just for TypeScript compilation validation
it('should enforce required properties', () => {
// @ts-expect-error - message is required
const missingMessage: GenericError = {
const missingMessage: Omit<GenericError, 'error'> = {
type: 'unknown_error',
};

Expand Down
17 changes: 0 additions & 17 deletions packages/davinci-client/src/lib/error.types.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/davinci-client/src/lib/node.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { getCollectorErrors } from './node.utils.js';
* Import the types
*/
import type { Draft, PayloadAction } from '@reduxjs/toolkit';

import type { SubmitCollector } from './collector.types.js';
import type { GenericError } from './error.types.js';
import type {
DavinciErrorResponse,
DaVinciFailureResponse,
Expand Down Expand Up @@ -294,7 +294,7 @@ export const nodeSlice = createSlice({
code: 'unknown',
type: 'state_error',
message: `\`collectors\` are only available on nodes with \`status\` of ${CONTINUE_STATUS} or ${ERROR_STATUS}`,
} as GenericError,
} as const,
state: [],
};
},
Expand All @@ -310,7 +310,7 @@ export const nodeSlice = createSlice({
code: 'unknown',
type: 'state_error',
message: `\`collectors\` are only available on nodes with \`status\` of ${CONTINUE_STATUS} or ${ERROR_STATUS}`,
} as GenericError,
} as const,
state: null,
};
},
Expand All @@ -329,7 +329,7 @@ export const nodeSlice = createSlice({
code: 'unknown',
type: 'state_error',
message: `\`errorCollectors\` are only available on nodes with \`status\` of ${ERROR_STATUS}`,
} as GenericError,
} as const,
state: [],
};
},
Expand Down
6 changes: 4 additions & 2 deletions packages/davinci-client/src/lib/node.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { GenericError } from '@forgerock/sdk-types';

import type {
FlowCollector,
PasswordCollector,
Expand All @@ -23,7 +25,6 @@ import type {
UnknownCollector,
} from './collector.types.js';
import type { Links } from './davinci.types.js';
import { GenericError } from './error.types.js';

export type Collectors =
| FlowCollector
Expand Down Expand Up @@ -74,9 +75,10 @@ export interface ContinueNode {
status: 'continue';
}

export interface DaVinciError extends GenericError {
export interface DaVinciError extends Omit<GenericError, 'error'> {
collectors?: CollectorErrors[];
internalHttpStatus?: number;
message: string;
status: 'error' | 'failure' | 'unknown';
}

Expand Down
23 changes: 20 additions & 3 deletions packages/oidc-client/src/lib/authorize.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ import type { WellKnownResponse } from '@forgerock/sdk-types';
import type { OidcConfig } from './config.types.js';
import { AuthorizeErrorResponse, AuthorizeSuccessResponse } from './authorize.request.types.js';

/**
* @function authorizeµ
* @description Creates an authorization URL for the OIDC client.
* @param {WellKnownResponse} wellknown - The well-known configuration for the OIDC server.
* @param {OidcConfig} config - The OIDC client configuration.
* @param {CustomLogger} log - The logger instance for logging debug information.
* @param {GetAuthorizationUrlOptions} options - Optional parameters for the authorization request.
* @returns {Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse, never>} - A micro effect that resolves to the authorization response.
*/
export async function authorizeµ(
wellknown: WellKnownResponse,
config: OidcConfig,
Expand All @@ -37,13 +46,18 @@ export async function authorizeµ(
* If we support the pi.flow field, this means we are using a PingOne server.
* PingOne servers do not support redirection through iframes because they
* set iframe's to DENY.
*
* We do not use RTK Query for this because we don't want caching, or store
* updates, and want the request to be made similar to the iframe method below.
*
* This returns a Micro that resolves to the parsed response JSON.
*/
return authorizeFetchµ(url).pipe(
Micro.flatMap(
(response): Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse, never> => {
if ('authorizeResponse' in response) {
log.debug('Received authorize response', response.authorizeResponse);
return Micro.succeed(response.authorizeResponse);
if ('code' in response) {
log.debug('Received code in response', response);
return Micro.succeed(response);
}
log.error('Error in authorize response', response);
// For redirection, we need to remore `pi.flow` from the options
Expand All @@ -57,6 +71,9 @@ export async function authorizeµ(
/**
* If the response mode is not pi.flow, then we are likely using a traditional
* redirect based server supporting iframes. An example would be PingAM.
*
* This returns a Micro that's either the success URL parameters or error URL
* parameters.
*/
return authorizeIframeµ(url, config).pipe(
Micro.flatMap(
Expand Down
Loading
Loading