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
34 changes: 33 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
{
"root": true,
"plugins": ["@typescript-eslint", "@nx"],
"plugins": ["@typescript-eslint", "@nx", "header"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"excludedFiles": [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.test.js",
"**/*.test.jsx",
"**/*.test.mjs",
"**/*.spec.mjs",
"**/vite*.config.ts",
"**/vitest.setup.ts",
"**/playwright.config.ts",
"**/_polyfills/**",
"tools/**"
],
"rules": {
"header/header": [
"warn",
"block",
[
{
"pattern": "[\\s\\S]*Copyright[\\s\\S]*Ping Identity[\\s\\S]*",
"template": "\n * Copyright (c) <current_year> Ping Identity Corporation. All rights reserved.\n * This software may be modified and distributed under the terms\n * of the MIT license. See the LICENSE file for details.\n "
}
]
]
}
},
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged && npx nx affected:lint && npx nx affected:build
node tools/copyright/sync-header-years.mjs && npx lint-staged && npx nx affected:lint && npx nx affected:build
85 changes: 60 additions & 25 deletions contributing_docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ We use changesets to handle publishing of all packages in the repository.
Please see the changesets repository for documentation on how to use
changesets. Below will be a brief summary.

## Table of Contents

- [Adding a changeset](#adding-a-changeset)
- [Versioning](#versioning)
- [Adding a package to the repository](#adding-a-package-to-the-repository)
- [Testing a package publish](#testing-a-package-publish)
- [First time releasing a package](#first-time-releasing-a-package)
- [Publishing a beta](#publishing-a-beta)

## Adding a changeset

You can run `pnpm changeset` in order to add a changeset. You then
Expand Down Expand Up @@ -55,46 +64,72 @@ This is common for `e2e` related applications. We don't version or care
about publishing them. You will see in the `.changesets/config.json` these are listed
in the `ignore` field, and they will all have `private:true` in the package.json

## Testing a package publish

In order to test a package publish, you should use `verdaccio`.

We provide verdaccio two ways:

1. `pnpm nx run local-registry`. This command will spawn a private npm registry. It also _should_ update your local `.npmrc` file to point here.

You can then publish your package like so:

```bash
pnpm changeset version
pnpm publish packages/{your_package} --dry-run --no-git-checks --registry=http://localhost:4873
```

**Notes**:

- The `changeset` command will version your packages before the test release. To version them as a beta add `--snapshot beta` to the changeset command
- I am including the `dry-run` flag here so if you copy paste it, you will "dry-run" the publish.
- I also like to add the `registry` flag, as a secondary check to make sure I publish to this registry.
- The `-r` flag is necessary if your package requires other workspace packages to be published. This command runs `publish` recursively via pnpm's topological graph. To publish all packages, include the `-r` flag and remove `packages/{yourpackage}` from the publish command.
- Include the `--no-git-checks` flag to ignore the changes made by the versioning command
- To test publish a beta, add `--tag beta`
- If you are publishing from a branch other than `main`, add `--publish-branch {branch-name}`

2. Publishing to a hosted private registry: Please message `@ryan.basmajian` on Slack.

## First time releasing a package

If your package is ready to be released, and has never been released before,
(the package.json `name` field does not exist on `npm`), then it is critical that
your `{packageRoot}/package.json` has the following:
If your package is ready to be released, and has never been released before, (the package.json `name` field does not exist on `npm`), then it is critical that the package be published manually as a beta first.

```
First ensure that the `{packageRoot}/package.json` has the following:

```json
"publishConfig": {
"access": true
"access": "public"
}
```

If your package does not contain this information, your package publishing **WILL**
break the publish pipeline.
When the package is officially ready for release, you should also delete the `private: true` from the `{projectRoot}/package.json`.

This is because all packages in this repository are published with `npm provenance`.
You can read about the requirements [here](https://docs.npmjs.com/generating-provenance-statements#prerequisites).
Then publish the package to npm:

## Testing a package publish

In order to test a package publish, you should use `verdaccio`.
```bash
# Version packages for beta
pnpm changeset version --snapshot beta
# Check that the beta tag is correct in a dry run
pnpm publish <package-name> --tag beta --no-git-checks --access public --dry-run
# Publish beta for the first time
pnpm publish <package-name> --tag beta --no-git-checks --access public
```

We provide verdaccio two ways:
If you do not do this, your package publishing **WILL** break the publish pipeline. Publishing manually first prevents the package being published as the default private.

- `pnpm nx run local-registry`. This command will spawn a private npm registry.
It also _should_ update your local `.npmrc` file to point here.
Next set up provenance and trusted publishing. With trusted publishing enabled, provenance attestations will be generated automatically. Learn more [here](https://docs.npmjs.com/trusted-publishers#automatic-provenance-generation).

You can then publish your package like so:
To set up trusted publishing, follow the instructions [here](https://docs.npmjs.com/trusted-publishers#for-github-actions). Configure the following fields:

```bash
pnpm publish packages/{your_package} --dry-run --registry=http://localhost:4873
```
- **Publisher**: GitHub Actions
- **Organization**: ForgeRock
- **Repository**: ping-javascript-sdk
- **Workflow filename**: publish.yml

Notes: - I am including the `dry-run` flag here so if you copy paste it,
you will "dry-run" the publish. - I also like to add the `registry` flag, as a secondary check to
make sure i publish to this registry. - The `-r` flag is necessary if your package requires other workspace packages
to be published. This command runs `publish` recursively via pnpm's
topological graph.
Additionally, set the publishing access to `Require two-factor authentication and disallow tokens`.

- Publishing to a hosted private registry: Please message @ryanbas21 on slack.
You should now be able to publish with provenance from GitHub Actions. To learn how to publish a beta from GitHub Actions see the next section [Publishing a beta](#publishing-a-beta) below.

## Publishing a beta

Expand Down
9 changes: 9 additions & 0 deletions e2e/autoscript-apps/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
/*
* @forgerock/javascript-sdk
*
* index.ts
*
* Copyright (c) 2021 - 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 'core-js/stable';
import 'regenerator-runtime';
5 changes: 5 additions & 0 deletions e2e/autoscript-suites/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright (c) 2021 - 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 { workspaceRoot } from '@nx/devkit';
import { PlaywrightTestConfig } from '@playwright/test';
import { baseConfig } from './playwright.config';
Expand Down
5 changes: 5 additions & 0 deletions e2e/mock-api/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright (c) 2021 - 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.
*/
export const environment = {
production: true,
};
5 changes: 5 additions & 0 deletions e2e/mock-api/src/environments/environment.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright (c) 2021 - 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.
*/
export const environment = {
AM_URL: 'https://openam-crbrl-01.forgeblocks.com/am/',
REALM_PATH: 'alpha',
Expand Down
5 changes: 5 additions & 0 deletions e2e/token-vault-suites/teardown.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright (c) 2021 - 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.
*/
export default () => {
console.log('tests finished');
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"ci:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm nx format:write --uncommitted",
"changeset": "changeset",
"commit": "git cz",
"copyright:check": "node ./tools/copyright/sync-header-years.mjs --check",
"copyright:sync": "node ./tools/copyright/sync-header-years.mjs",
"docs": "nx affected --target=typedoc",
"e2e": "CI=true nx affected:e2e",
"format:staged": "pretty-quick --staged",
Expand Down Expand Up @@ -83,6 +85,7 @@
"esbuild": "^0.19.2",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-playwright": "^1.5.1",
"eslint-plugin-prettier": "^5.1.3",
Expand Down
6 changes: 6 additions & 0 deletions packages/javascript-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 4.9.1

### Patch Changes

- [#587](https://github.com/ForgeRock/forgerock-javascript-sdk/pull/587) [`d14d301`](https://github.com/ForgeRock/forgerock-javascript-sdk/commit/d14d301349bb08040363be5dafc01e100fb5862d) Thanks [@ForgeRockEmma](https://github.com/ForgeRockEmma)! - fix: move getAuthenticationCredential back inside try/catch so that WebAuthn cancellation errors (e.g. NotAllowedError) are written to the HiddenValueCallback before re-throwing

## 4.9.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/javascript-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@forgerock/javascript-sdk",
"version": "4.9.0",
"version": "4.9.1",
"description": "ForgeRock JavaScript SDK",
"author": "ForgeRock",
"license": "MIT",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/*
* @forgerock/javascript-sdk
*
* msw-mock-data.ts
*
* 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 { GeneralResponse } from '../services/index.js';
import type {
OathResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* eslint-disable no-useless-escape */
/*
* @forgerock/javascript-sdk
*
* script-text.mock.data.ts
*
* Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2020 - 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.
*/
/* eslint-disable no-useless-escape */

import type { CallbackType } from '../auth/enums';

Expand Down
62 changes: 61 additions & 1 deletion packages/javascript-sdk/src/fr-webauthn/fr-webauthn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* of the MIT license. See the LICENSE file for details.
*/

import { WebAuthnStepType } from './enums';
import { WebAuthnOutcome, WebAuthnStepType } from './enums';
import FRWebAuthn from './index';
import {
webAuthnRegJSCallback653,
Expand All @@ -23,6 +23,7 @@ import {
webAuthnAuthMetaCallback70StoredUsername,
webAuthnAuthConditionalMetaCallback,
} from './fr-webauthn.mock.data';
import { CallbackType } from '../auth/enums';
import FRStep from '../fr-auth/fr-step';
import Config from '../config';

Expand Down Expand Up @@ -245,3 +246,62 @@ describe('Test FRWebAuthn class with Conditional UI', () => {
expect(Array.from(idArray)).toEqual([1, 2, 3, 4]);
});
});

describe('Test FRWebAuthn class with cancellation error handling', () => {
beforeEach(() => {
Object.defineProperty(global.navigator, 'credentials', {
value: {
get: vi.fn(),
create: vi.fn(),
},
writable: true,
});
Object.defineProperty(window, 'PublicKeyCredential', {
value: {
// Mocked as supported so conditional mediation checks pass through to the credential call
isConditionalMediationAvailable: vi.fn().mockResolvedValue(true),
},
writable: true,
});
});

afterEach(() => {
vi.restoreAllMocks();
});

it('should write NotAllowedError to HiddenValueCallback when user cancels conditional authentication', async () => {
const cancelError = new Error('The operation either timed out or was not allowed.');
cancelError.name = 'NotAllowedError';
vi.spyOn(navigator.credentials, 'get').mockRejectedValue(cancelError);

const step = new FRStep(webAuthnAuthConditionalMetaCallback as any);

await expect(FRWebAuthn.authenticate(step)).rejects.toMatchObject({
name: 'NotAllowedError',
});

const hiddenCallback = step.getCallbacksOfType(CallbackType.HiddenValueCallback)[0];
expect(hiddenCallback).toBeDefined();
expect(hiddenCallback.getInputValue()).toBe(
`${WebAuthnOutcome.Error}::NotAllowedError:The operation either timed out or was not allowed.`,
);
});

it('should write NotAllowedError to HiddenValueCallback when user cancels standard authentication', async () => {
const cancelError = new Error('The operation either timed out or was not allowed.');
cancelError.name = 'NotAllowedError';
vi.spyOn(navigator.credentials, 'get').mockRejectedValue(cancelError);

const step = new FRStep(webAuthnAuthMetaCallback70 as any);

await expect(FRWebAuthn.authenticate(step)).rejects.toMatchObject({
name: 'NotAllowedError',
});

const hiddenCallback = step.getCallbacksOfType(CallbackType.HiddenValueCallback)[0];
expect(hiddenCallback).toBeDefined();
expect(hiddenCallback.getInputValue()).toBe(
`${WebAuthnOutcome.Error}::NotAllowedError:The operation either timed out or was not allowed.`,
);
});
});
Loading