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
7 changes: 7 additions & 0 deletions .changeset/slow-teeth-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@forgerock/storage': minor
'@forgerock/oidc-client': minor
---

- Standardizes return types on storage client and updates tests
- Improves OIDC client where storage client methods are used
2 changes: 2 additions & 0 deletions packages/oidc-client/src/lib/client.store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { oidc } from './client.store.js';

import type { OidcConfig } from './config.types.js';

Object.defineProperty(global, 'localStorage', { value: null });

vi.stubGlobal(
'sessionStorage',
(() => {
Expand Down
8 changes: 2 additions & 6 deletions packages/oidc-client/src/lib/client.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,14 +372,10 @@ export async function oidc<ActionType extends ActionTypes = ActionTypes>({
// Delete local token and return combined results
Micro.flatMap((revokeResponse) =>
Micro.promise(() => storageClient.remove()).pipe(
Micro.flatMap((deleteRes) => {
const deleteResponse = typeof deleteRes === 'undefined' ? null : deleteRes;

Micro.flatMap((deleteResponse) => {
const isInnerRequestError =
(revokeResponse && 'error' in revokeResponse) ||
(deleteResponse &&
typeof deleteResponse === 'object' &&
'error' in deleteResponse);
(deleteResponse && 'error' in deleteResponse);

if (isInnerRequestError) {
const result: RevokeErrorResult = {
Expand Down
15 changes: 10 additions & 5 deletions packages/oidc-client/src/lib/client.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ export interface GetTokensOptions {
}

export type RevokeSuccessResult = {
revokeResponse: GenericError | null;
deleteResponse: GenericError | null;
revokeResponse: null;
deleteResponse: null;
};

export type RevokeErrorResult = RevokeSuccessResult & {
export type RevokeErrorResult = {
error: string;
revokeResponse: GenericError | null;
deleteResponse: GenericError | null;
};

export type LogoutSuccessResult = RevokeSuccessResult & {
sessionResponse: GenericError | null;
sessionResponse: null;
};

export type LogoutErrorResult = LogoutSuccessResult & {
export type LogoutErrorResult = {
error: string;
sessionResponse: GenericError | null;
revokeResponse: GenericError | null;
deleteResponse: GenericError | null;
};

export type UserInfoResponse = {
Expand Down
3 changes: 3 additions & 0 deletions packages/oidc-client/src/lib/logout.request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ const config: OidcConfig = {
responseType: 'code',
};

Object.defineProperty(global, 'sessionStorage', { value: null });
Object.defineProperty(global, 'localStorage', { value: null });
Comment on lines +69 to +70
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid redefining localStorage/sessionStorage with non-configurable stubs

storage.effects.test.ts already installs both globals via Object.defineProperty without configurable/writable, so this redefinition throws TypeError: Cannot redefine property: localStorage as soon as the suite loads. Please gate the override and only redefine when the descriptor is reconfigurable (or relax the original stub first) so the tests can execute.

-Object.defineProperty(global, 'sessionStorage', { value: null });
-Object.defineProperty(global, 'localStorage', { value: null });
+const disableBrowserStorage = (key: 'localStorage' | 'sessionStorage') => {
+  const descriptor = Object.getOwnPropertyDescriptor(globalThis, key);
+
+  if (descriptor && descriptor.configurable === false && descriptor.writable === false) {
+    return;
+  }
+
+  Object.defineProperty(globalThis, key, {
+    value: null,
+    configurable: true,
+    writable: true,
+  });
+};
+
+disableBrowserStorage('sessionStorage');
+disableBrowserStorage('localStorage');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Object.defineProperty(global, 'sessionStorage', { value: null });
Object.defineProperty(global, 'localStorage', { value: null });
const disableBrowserStorage = (key: 'localStorage' | 'sessionStorage') => {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, key);
// If the property is already non-configurable and non-writable, skip redefinition
if (descriptor && descriptor.configurable === false && descriptor.writable === false) {
return;
}
Object.defineProperty(globalThis, key, {
value: null,
configurable: true,
writable: true,
});
};
disableBrowserStorage('sessionStorage');
disableBrowserStorage('localStorage');


const customStorage: Record<string, string> = {};
const storageClient = createStorage<OauthTokens>({
type: 'custom',
Expand Down
16 changes: 7 additions & 9 deletions packages/oidc-client/src/lib/logout.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import { Micro } from 'effect';
import { oidcApi } from './oidc.api.js';
import { createClientStore, createLogoutError } from './client.store.utils.js';
import { OauthTokens, OidcConfig } from './config.types.js';
import { WellKnownResponse } from '@forgerock/sdk-types';
import { createStorage } from '@forgerock/storage';
import { LogoutErrorResult, LogoutSuccessResult } from './client.types.js';
import type { OauthTokens, OidcConfig } from './config.types.js';
import type { WellKnownResponse } from '@forgerock/sdk-types';
import type { StorageClient } from '@forgerock/storage';
import type { LogoutErrorResult, LogoutSuccessResult } from './client.types.js';

export function logoutµ({
tokens,
Expand All @@ -23,7 +23,7 @@ export function logoutµ({
config: OidcConfig;
wellknown: WellKnownResponse;
store: ReturnType<typeof createClientStore>;
storageClient: ReturnType<typeof createStorage<OauthTokens>>;
storageClient: StorageClient<OauthTokens>;
}) {
return Micro.zip(
// End session with the ID token
Expand All @@ -50,13 +50,11 @@ export function logoutµ({
// Delete local token and return combined results
Micro.flatMap(([sessionResponse, revokeResponse]) =>
Micro.promise(() => storageClient.remove()).pipe(
Micro.flatMap((deleteRes) => {
const deleteResponse = typeof deleteRes === 'undefined' ? null : deleteRes;

Micro.flatMap((deleteResponse) => {
const isInnerRequestError =
(sessionResponse && 'error' in sessionResponse) ||
(revokeResponse && 'error' in revokeResponse) ||
(deleteResponse && typeof deleteResponse === 'object' && 'error' in deleteResponse);
(deleteResponse && 'error' in deleteResponse);

if (isInnerRequestError) {
const result: LogoutErrorResult = {
Expand Down
Loading
Loading